SKILL.md (3767B)
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