@@ -36,6 +36,24 @@ const __dirname = path.dirname(__filename);
3636
3737const WORKFLOWS_DIR = path . join ( __dirname , '..' , '.github' , 'workflows' ) ;
3838const SCRIPTS_DIR = path . join ( __dirname , '..' , 'scripts' ) ;
39+ /**
40+ * Shared composite action that contains the canonical pre-warm + pre-flight
41+ * diagnostics for every `news-*.md` agentic workflow. Introduced in PR #2008
42+ * to deduplicate ~80 lines of identical YAML across 11 workflows. The
43+ * frontmatter `steps:` block in each workflow references this action via
44+ * `uses: ./.github/actions/news-prewarm`, so the diagnostics literals
45+ * (e.g. "Pre-flight external endpoint reachability check", "DNS Resolution
46+ * Tests") now live in the action file rather than each workflow.
47+ */
48+ const NEWS_PREWARM_ACTION = path . join (
49+ __dirname ,
50+ '..' ,
51+ '.github' ,
52+ 'actions' ,
53+ 'news-prewarm' ,
54+ 'action.yml'
55+ ) ;
56+ const NEWS_PREWARM_USES_REF = './.github/actions/news-prewarm' ;
3957
4058// ---------------------------------------------------------------------------
4159// Constants
@@ -390,45 +408,73 @@ describe('Network Diagnostics Configuration', () => {
390408 } ) ;
391409
392410 describe ( 'Pre-flight External Reachability Check (runs before MCP Gateway)' , ( ) => {
393- it ( 'all workflows should label pre-flight step correctly' , ( ) => {
394- // The pre-flight step tests DIRECT external HTTPS — not gateway routing.
395- // It MUST be clearly named to avoid false confidence (see PR #1711).
411+ // The pre-flight diagnostics were extracted in PR #2008 into the shared
412+ // composite action `.github/actions/news-prewarm/action.yml` and every
413+ // news workflow now references it via `uses: ./.github/actions/news-prewarm`.
414+ // We therefore validate (a) the composite action carries the canonical
415+ // diagnostics content, and (b) every workflow wires the action into its
416+ // frontmatter `steps:` block — instead of grepping each workflow file for
417+ // the inline YAML literals (which no longer exist).
418+
419+ it ( 'shared news-prewarm composite action exists' , ( ) => {
420+ expect (
421+ fs . existsSync ( NEWS_PREWARM_ACTION ) ,
422+ `Missing shared pre-warm action at ${ NEWS_PREWARM_ACTION } `
423+ ) . toBe ( true ) ;
424+ } ) ;
425+
426+ it ( 'shared news-prewarm action labels pre-flight step correctly' , ( ) => {
427+ const action = fs . readFileSync ( NEWS_PREWARM_ACTION , 'utf-8' ) ;
428+
429+ // Must NOT use the misleading old name from PR #1711.
430+ expect (
431+ action ,
432+ 'news-prewarm still uses misleading "Network and MCP diagnostics" step name'
433+ ) . not . toContain ( '- name: Network and MCP diagnostics' ) ;
434+
435+ // Must use the clarified name.
436+ expect (
437+ action ,
438+ 'news-prewarm missing pre-flight step with clarified name'
439+ ) . toContain ( 'Pre-flight external endpoint reachability check' ) ;
440+ } ) ;
441+
442+ it ( 'all workflows reference the shared news-prewarm composite action' , ( ) => {
396443 ALL_NEWS_WORKFLOWS . forEach ( workflow => {
397444 const filepath = path . join ( WORKFLOWS_DIR , workflow ) ;
398445 const content = fs . readFileSync ( filepath , 'utf-8' ) ;
399446 const fm = extractFrontmatter ( content ) ;
400447
401- // Must NOT use the misleading old name
448+ // Each workflow must wire the shared composite action so the
449+ // pre-flight + pre-warm diagnostics run before the MCP Gateway.
402450 expect (
403451 fm ,
404- `${ workflow } still uses misleading "Network and MCP diagnostics" step name `
405- ) . not . toContain ( '- name: Network and MCP diagnostics' ) ;
452+ `${ workflow } does not reference shared composite action ${ NEWS_PREWARM_USES_REF } `
453+ ) . toContain ( `uses: ${ NEWS_PREWARM_USES_REF } ` ) ;
406454
407- // Must use the clarified name
455+ // And it must NOT have re-introduced the misleading inline name.
408456 expect (
409457 fm ,
410- `${ workflow } missing pre-flight step with clarified name `
411- ) . toContain ( 'Pre-flight external endpoint reachability check ' ) ;
458+ `${ workflow } still uses misleading "Network and MCP diagnostics" inline step `
459+ ) . not . toContain ( '- name: Network and MCP diagnostics ' ) ;
412460 } ) ;
413461 } ) ;
414462
415- it ( 'news-propositions.md should have canonical diagnostics content' , ( ) => {
416- const filepath = path . join ( WORKFLOWS_DIR , 'news-propositions.md' ) ;
417- const content = fs . readFileSync ( filepath , 'utf-8' ) ;
463+ it ( 'shared news-prewarm action has canonical diagnostics content' , ( ) => {
464+ const action = fs . readFileSync ( NEWS_PREWARM_ACTION , 'utf-8' ) ;
418465
419- expect ( content ) . toContain ( 'DNS Resolution Tests' ) ;
420- expect ( content ) . toContain ( 'HTTPS Connectivity Tests' ) ;
421- expect ( content ) . toContain ( 'MCP Server Tool Count' ) ;
466+ expect ( action ) . toContain ( 'DNS Resolution Tests' ) ;
467+ expect ( action ) . toContain ( 'HTTPS Connectivity Tests' ) ;
468+ expect ( action ) . toContain ( 'MCP Server Tool Count' ) ;
422469 } ) ;
423470
424- it ( 'diagnostics block should test all required domains' , ( ) => {
425- const filepath = path . join ( WORKFLOWS_DIR , 'news-propositions.md' ) ;
426- const content = fs . readFileSync ( filepath , 'utf-8' ) ;
471+ it ( 'shared news-prewarm action probes all required MCP/data domains' , ( ) => {
472+ const action = fs . readFileSync ( NEWS_PREWARM_ACTION , 'utf-8' ) ;
427473
428474 REQUIRED_MCP_DOMAINS . forEach ( domain => {
429475 expect (
430- content ,
431- `Diagnostics block missing domain check for: ${ domain } `
476+ action ,
477+ `news-prewarm action missing domain check for: ${ domain } `
432478 ) . toContain ( domain ) ;
433479 } ) ;
434480 } ) ;
@@ -438,10 +484,12 @@ describe('Network Diagnostics Configuration', () => {
438484 // The dedicated "MCP Quick Diagnostic" in-prompt block that existed in
439485 // the pre-modularisation architecture is now replaced by the health gate
440486 // in `../prompts/02-mcp-access.md` (3× `get_sync_status` at workflow
441- // start, then proceed). The CI `steps:` block handles external DNS /
442- // HTTPS pre-flight checks, and the MCP-unreachable no-op policy lives
443- // in `../prompts/07-commit-and-pr.md`. We therefore verify the effective
444- // prompt exposes the health gate rather than a specific legacy heading.
487+ // start, then proceed). The frontmatter `steps:` block references the
488+ // shared composite action `./.github/actions/news-prewarm` (PR #2008)
489+ // which handles external DNS / HTTPS pre-flight checks; the
490+ // MCP-unreachable no-op policy lives in `../prompts/07-commit-and-pr.md`.
491+ // We therefore verify the effective prompt exposes the health gate and
492+ // that the workflow wires in the shared pre-flight composite action.
445493 const CONTENT_GENERATION_WORKFLOWS = ALL_NEWS_WORKFLOWS . filter ( w => w !== 'news-translate.md' ) ;
446494
447495 CONTENT_GENERATION_WORKFLOWS . forEach ( workflow => {
@@ -462,61 +510,75 @@ describe('Network Diagnostics Configuration', () => {
462510 ) . toContain ( 'safeoutputs___noop' ) ;
463511 } ) ;
464512
465- it ( `${ workflow } should test external MCP reachability in frontmatter pre-flight step ` , ( ) => {
513+ it ( `${ workflow } should wire shared pre-flight composite action ` , ( ) => {
466514 const filepath = path . join ( WORKFLOWS_DIR , workflow ) ;
467515 const content = fs . readFileSync ( filepath , 'utf-8' ) ;
468516 const fm = extractFrontmatter ( content ) ;
469517
470518 // External HTTPS reachability to the MCP server is verified by the
471- // frontmatter pre-flight step, not by an in-prompt diagnostic block.
519+ // frontmatter `steps:` block, which references the shared composite
520+ // action `./.github/actions/news-prewarm` rather than inlining the
521+ // pre-flight YAML in every workflow (PR #2008 deduplication).
472522 expect (
473523 fm ,
474- `${ workflow } missing pre-flight external reachability check`
475- ) . toContain ( 'Pre-flight external endpoint reachability check' ) ;
476- expect (
477- fm ,
478- `${ workflow } pre-flight step should probe the Render MCP endpoint`
479- ) . toContain ( 'riksdag-regering-ai.onrender.com' ) ;
524+ `${ workflow } missing reference to shared pre-flight action ${ NEWS_PREWARM_USES_REF } `
525+ ) . toContain ( `uses: ${ NEWS_PREWARM_USES_REF } ` ) ;
480526 } ) ;
481527 } ) ;
528+
529+ it ( 'shared news-prewarm action probes the Render MCP endpoint' , ( ) => {
530+ // The shared composite action is the single place where the external
531+ // HTTPS reachability check runs, so the Render MCP endpoint probe must
532+ // live there (either as a literal default or via the `mcp-url` input).
533+ const action = fs . readFileSync ( NEWS_PREWARM_ACTION , 'utf-8' ) ;
534+ expect (
535+ action ,
536+ 'news-prewarm action does not probe riksdag-regering-ai.onrender.com'
537+ ) . toContain ( 'riksdag-regering-ai.onrender.com' ) ;
538+ } ) ;
482539 } ) ;
483540
484541 describe ( 'Pre-warm and Keep-alive Patterns' , ( ) => {
485- it ( 'news-propositions.md should have MCP pre-warm step' , ( ) => {
486- const filepath = path . join ( WORKFLOWS_DIR , 'news-propositions.md' ) ;
487- const content = fs . readFileSync ( filepath , 'utf-8' ) ;
488-
489- // The single `curl`-based pre-warm `steps:` block is canonical
490- // (see `../prompts/02-mcp-access.md` §"Pre-warm step"). We no longer
491- // keep long-running keep-alive pingers — the `safeoutputs` session is
492- // kept alive by completing work inside its ~30-minute idle window.
493- expect ( content ) . toContain ( 'Pre-warm MCP server' ) ;
494- expect ( content ) . toContain ( 'tools/list' ) ;
542+ it ( 'shared news-prewarm action contains MCP pre-warm step' , ( ) => {
543+ // The single `curl`-based pre-warm `steps:` block is canonical and now
544+ // lives in the shared composite action `.github/actions/news-prewarm/`
545+ // (see `../prompts/02-mcp-access.md` §"Pre-warm step" and PR #2008).
546+ // We no longer keep long-running keep-alive pingers — the `safeoutputs`
547+ // session is kept alive by completing work inside its ~30-minute idle
548+ // window.
549+ const action = fs . readFileSync ( NEWS_PREWARM_ACTION , 'utf-8' ) ;
550+ expect ( action ) . toContain ( 'Pre-warm MCP server' ) ;
551+ expect ( action ) . toContain ( 'tools/list' ) ;
495552 } ) ;
496553
497554 ALL_NEWS_WORKFLOWS . forEach ( workflow => {
498555 it ( `${ workflow } should reference MCP pre-warm or health check` , ( ) => {
499556 const filepath = path . join ( WORKFLOWS_DIR , workflow ) ;
500557 const content = readWorkflowWithImports ( filepath ) ;
558+ const fm = extractFrontmatter ( fs . readFileSync ( filepath , 'utf-8' ) ) ;
501559
502- const hasPreWarm = content . includes ( 'Pre-warm' ) || content . includes ( 'pre-warm' ) ;
560+ const hasPreWarmReference =
561+ content . includes ( 'Pre-warm' ) ||
562+ content . includes ( 'pre-warm' ) ||
563+ fm . includes ( NEWS_PREWARM_USES_REF ) ;
503564 const hasHealthGate = content . includes ( 'get_sync_status' ) ;
504565 const hasToolsList = content . includes ( 'tools/list' ) ;
505566
506567 expect (
507- hasPreWarm || hasHealthGate || hasToolsList ,
568+ hasPreWarmReference || hasHealthGate || hasToolsList ,
508569 `${ workflow } has no MCP warm-up or health check mechanism`
509570 ) . toBe ( true ) ;
510571 } ) ;
511572 } ) ;
512573 } ) ;
513574
514575 describe ( 'Step Ordering Awareness' , ( ) => {
515- it ( 'pre-flight steps should be in frontmatter, health gate in prompt body' , ( ) => {
576+ it ( 'pre-flight steps should be in frontmatter (via shared action) , health gate in prompt body' , ( ) => {
516577 // Validates the architectural split:
517- // - Pre-flight external reachability checks (frontmatter `steps:`) run
518- // BEFORE the agent starts, proving DNS + HTTPS to the Render MCP
519- // endpoint work from the runner.
578+ // - Pre-flight external reachability checks run BEFORE the agent starts
579+ // via the shared composite action `./.github/actions/news-prewarm`
580+ // wired into the frontmatter `steps:` block (PR #2008). This proves
581+ // DNS + HTTPS to the Render MCP endpoint work from the runner.
520582 // - The in-prompt MCP health gate (`get_sync_status` + noop fallback)
521583 // runs INSIDE the agent, proving the MCP Gateway routes tool calls
522584 // correctly. That rule lives in `../prompts/02-mcp-access.md`.
@@ -527,11 +589,12 @@ describe('Network Diagnostics Configuration', () => {
527589 const fm = extractFrontmatter ( content ) ;
528590 const effective = readWorkflowWithImports ( filepath ) ;
529591
530- // Pre-flight reachability should be in frontmatter steps.
592+ // Pre-flight reachability is wired via the shared composite action
593+ // referenced from the frontmatter `steps:` block.
531594 expect (
532595 fm ,
533- `${ workflow } missing pre-flight check in frontmatter steps `
534- ) . toContain ( 'Pre-flight external endpoint reachability' ) ;
596+ `${ workflow } missing shared pre-flight composite action ${ NEWS_PREWARM_USES_REF } `
597+ ) . toContain ( `uses: ${ NEWS_PREWARM_USES_REF } ` ) ;
535598
536599 // Health gate should be reachable from the effective prompt surface
537600 // (workflow body + imported modules).
0 commit comments