| 1 | --- |
| 2 | name: "ci-validation-gates" |
| 3 | description: "Defensive CI/CD patterns: semver validation, token checks, retry logic, draft detection — earned from v0.8.22" |
| 4 | domain: "ci-cd" |
| 5 | confidence: "high" |
| 6 | source: "extracted from Drucker and Trejo charters — earned knowledge from v0.8.22 release incident" |
| 7 | --- |
| 8 | |
| 9 | ## Context |
| 10 | |
| 11 | CI workflows must be defensive. These patterns were learned from the v0.8.22 release disaster where invalid semver, wrong token types, missing retry logic, and draft releases caused a multi-hour outage. Both Drucker (CI/CD) and Trejo (Release Manager) carried this knowledge in their charters — now centralized here. |
| 12 | |
| 13 | ## Patterns |
| 14 | |
| 15 | ### Semver Validation Gate |
| 16 | Every publish workflow MUST validate version format before `npm publish`. 4-part versions (e.g., 0.8.21.4) are NOT valid semver — npm mangles them. |
| 17 | |
| 18 | ```yaml |
| 19 | - name: Validate semver |
| 20 | run: | |
| 21 | VERSION="${{ github.event.release.tag_name }}" |
| 22 | VERSION="${VERSION#v}" |
| 23 | if ! npx semver "$VERSION" > /dev/null 2>&1; then |
| 24 | echo "❌ Invalid semver: $VERSION" |
| 25 | echo "Only 3-part versions (X.Y.Z) or prerelease (X.Y.Z-tag.N) are valid." |
| 26 | exit 1 |
| 27 | fi |
| 28 | echo "✅ Valid semver: $VERSION" |
| 29 | ``` |
| 30 | |
| 31 | ### NPM Token Type Verification |
| 32 | NPM_TOKEN MUST be an Automation token, not a User token with 2FA: |
| 33 | - User tokens require OTP — CI can't provide it → EOTP error |
| 34 | - Create Automation tokens at npmjs.com → Settings → Access Tokens → Automation |
| 35 | - Verify before first publish in any workflow |
| 36 | |
| 37 | ### Retry Logic for npm Registry Propagation |
| 38 | npm registry uses eventual consistency. After `npm publish` succeeds, the package may not be immediately queryable. |
| 39 | - Propagation: typically 5-30s, up to 2min in rare cases |
| 40 | - All verify steps: 5 attempts, 15-second intervals |
| 41 | - Log each attempt: "Attempt 1/5: Checking package..." |
| 42 | - Exit loop on success, fail after max attempts |
| 43 | |
| 44 | ```yaml |
| 45 | - name: Verify package (with retry) |
| 46 | run: | |
| 47 | MAX_ATTEMPTS=5 |
| 48 | WAIT_SECONDS=15 |
| 49 | for attempt in $(seq 1 $MAX_ATTEMPTS); do |
| 50 | echo "Attempt $attempt/$MAX_ATTEMPTS: Checking $PACKAGE@$VERSION..." |
| 51 | if npm view "$PACKAGE@$VERSION" version > /dev/null 2>&1; then |
| 52 | echo "✅ Package verified" |
| 53 | exit 0 |
| 54 | fi |
| 55 | [ $attempt -lt $MAX_ATTEMPTS ] && sleep $WAIT_SECONDS |
| 56 | done |
| 57 | echo "❌ Failed to verify after $MAX_ATTEMPTS attempts" |
| 58 | exit 1 |
| 59 | ``` |
| 60 | |
| 61 | ### Draft Release Detection |
| 62 | Draft releases don't emit `release: published` event. Workflows MUST: |
| 63 | - Trigger on `release: published` (NOT `created`) |
| 64 | - If using workflow_dispatch: verify release is published via GitHub API before proceeding |
| 65 | |
| 66 | ### Build Script Protection |
| 67 | Set `SKIP_BUILD_BUMP=1` (or `$env:SKIP_BUILD_BUMP = "1"` on Windows) before ANY release build. bump-build.mjs is for dev builds ONLY — it silently mutates versions. |
| 68 | |
| 69 | ## Known Failure Modes (v0.8.22 Incident) |
| 70 | |
| 71 | | # | What Happened | Root Cause | Prevention | |
| 72 | |---|---------------|-----------|------------| |
| 73 | | 1 | 4-part version published, npm mangled it | No semver validation gate | `npx semver` check before every publish | |
| 74 | | 2 | CI failed 5+ times with EOTP | User token with 2FA | Automation token only | |
| 75 | | 3 | Verify returned false 404 | No retry logic for propagation | 5 attempts, 15s intervals | |
| 76 | | 4 | Workflow never triggered | Draft release doesn't emit event | Never create draft releases | |
| 77 | | 5 | Version mutated during release | bump-build.mjs ran in release | SKIP_BUILD_BUMP=1 | |
| 78 | |
| 79 | ## Anti-Patterns |
| 80 | - ❌ Publishing without semver validation gate |
| 81 | - ❌ Single-shot verification without retry |
| 82 | - ❌ Hard-coded secrets in workflows |
| 83 | - ❌ Silent CI failures — every error needs actionable output with remediation |
| 84 | - ❌ Assuming npm publish is instantly queryable |