import assert from 'node:assert/strict'; import { readFile } from 'node:fs/promises'; const workflowPath = new URL('../.github/workflows/poll-nvd-cves.yml', import.meta.url); const workflow = await readFile(workflowPath, 'utf8'); const ciWorkflowPath = new URL('../.github/workflows/ci.yml', import.meta.url); const ciWorkflow = await readFile(ciWorkflowPath, 'utf8'); function requiredIndex(snippet, message) { const index = workflow.indexOf(snippet); assert.notEqual(index, -1, message); return index; } assert.match( workflow, /GHSA_FEED_PATH:\s+advisories\/ghsa-without-cve\.json/, 'NVD workflow must write the provisional GHSA source feed', ); assert.match( workflow, /GHSA_FEED_SIG_PATH:\s+advisories\/ghsa-without-cve\.json\.sig/, 'NVD workflow must sign the provisional GHSA source feed', ); assert.match( workflow, /node scripts\/ghsa-without-cve-feed\.mjs[\s\S]*--output "\$GHSA_FEED_PATH"[\s\S]*--consolidated-feed "\$FEED_PATH"[\s\S]*--existing-feed "\$GHSA_FEED_PATH"[\s\S]*--nvd-feed "\$FEED_PATH"/, 'NVD workflow must merge GHSA advisories into the signed agent feed', ); assert.match( workflow, /id: feed_changes[\s\S]*ghsa_changed=\$GHSA_CHANGED[\s\S]*agent_changed=\$AGENT_CHANGED[\s\S]*changed=true/, 'NVD workflow must detect GHSA and consolidated agent feed changes separately', ); assert.match( workflow, /if: steps\.feed_changes\.outputs\.ghsa_changed == 'true'[\s\S]*input_file: \$\{\{ env\.GHSA_FEED_PATH \}\}[\s\S]*signature_file: \$\{\{ env\.GHSA_FEED_SIG_PATH \}\}/, 'NVD workflow must sign the provisional GHSA feed when it changes', ); assert.match( workflow, /if: steps\.feed_changes\.outputs\.agent_changed == 'true'[\s\S]*input_file: \$\{\{ env\.FEED_PATH \}\}[\s\S]*signature_file: \$\{\{ env\.FEED_SIG_PATH \}\}/, 'NVD workflow must sign the consolidated agent feed when it changes', ); assert.match( workflow, /git add "\$FEED_PATH" "\$FEED_SIG_PATH" "\$GHSA_FEED_PATH" "\$GHSA_FEED_SIG_PATH" "\$SKILL_FEED_PATH" "\$SKILL_FEED_SIG_PATH"/, 'NVD workflow PR must include both NVD and GHSA feed artifacts', ); assert.match( ciWorkflow, /name: NVD \+ GHSA Pipeline Dry Run[\s\S]*node scripts\/test-nvd-ghsa-pipeline-dry-run\.mjs/, 'CI must run the deterministic NVD + GHSA pipeline dry run before merge', ); const updateFeedIndex = requiredIndex('name: Update feed.json', 'NVD workflow must update the CVE feed first'); const pollGhsaIndex = requiredIndex( 'name: Poll GHSA without CVE and consolidate feed', 'NVD workflow must poll GHSA before signing', ); const detectChangesIndex = requiredIndex( 'name: Detect advisory feed changes', 'NVD workflow must detect combined feed changes before signing', ); const signGhsaIndex = requiredIndex( 'name: Sign GHSA feed and verify', 'NVD workflow must sign the GHSA source feed', ); const signAgentIndex = requiredIndex( 'name: Sign advisory feed and verify', 'NVD workflow must sign the consolidated agent feed', ); const upsertPrIndex = requiredIndex( 'name: Upsert NVD advisory PR', 'NVD workflow must upsert a PR for any feed change', ); assert.ok( updateFeedIndex < pollGhsaIndex, 'GHSA consolidation must run after the NVD update step so matured advisories can reconcile against new CVEs', ); assert.ok( pollGhsaIndex < detectChangesIndex, 'Combined feed change detection must run after GHSA consolidation', ); assert.ok(detectChangesIndex < signGhsaIndex, 'GHSA signing must run after change detection'); assert.ok(detectChangesIndex < signAgentIndex, 'Agent feed signing must run after change detection'); assert.ok(signAgentIndex < upsertPrIndex, 'The PR must be created after feed signing');