From 09ae8fd9213c07423cb365b5debcc12f7ba553ac Mon Sep 17 00:00:00 2001 From: "Jonathan D.A. Jewell" <6759885+hyperpolymath@users.noreply.github.com> Date: Wed, 29 Jul 2026 00:50:45 +0100 Subject: [PATCH 1/5] fix(ci): disarm the Inbox Steward notification amplifier check_suite:[completed] and workflow_run:[completed] made this workflow fire once per CI completion on every watched workflow -- a multiplier on every unit of estate CI activity, and the mechanism behind the notification burst of 2026-07-20/21 (measured: 3,041 CheckSuite notifications inside a ~19-hour window, and none since). The workflow is currently disabled_manually, which is why the burst stopped. That makes the risk latent rather than resolved: these triggers plus contents:write / pull-requests:write mean re-enabling it would restart the multiplier. Removing them so re-enabling is safe. pull_request, pull_request_review and workflow_dispatch are retained -- the monitoring path the workflow was neutered down to in 2026-06 is untouched. Verified: YAML parses and the trigger map is now exactly pull_request, pull_request_review, workflow_dispatch. Co-Authored-By: Claude Opus 5 --- .github/workflows/inbox-steward.yml | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/.github/workflows/inbox-steward.yml b/.github/workflows/inbox-steward.yml index e72246c3..688a8252 100644 --- a/.github/workflows/inbox-steward.yml +++ b/.github/workflows/inbox-steward.yml @@ -21,15 +21,12 @@ on: types: [opened, synchronize, ready_for_review, converted_to_draft, review_requested] pull_request_review: types: [submitted, dismissed] - check_suite: - types: [completed] - workflow_run: - workflows: - - Dogfood Gate - - Scorecard Enforcer - - Hypatia Security Scan - - Static Analysis Gate - types: [completed] + # AMPLIFIER REMOVED 2026-07-29. check_suite:[completed] and + # workflow_run:[completed] made this fire once per CI completion on every + # watched workflow -- a multiplier on every unit of estate CI activity, and + # the mechanism behind the 3,041-notification burst of 2026-07-20/21. + # The workflow is currently disabled_manually; these triggers are removed so + # that RE-ENABLING it is safe. PR/review events and workflow_dispatch remain. # Manual trigger workflow_dispatch: inputs: From d5676248fb43d312b0f0056b9ce1fdb09cfd8cd2 Mon Sep 17 00:00:00 2001 From: "Jonathan D.A. Jewell" <6759885+hyperpolymath@users.noreply.github.com> Date: Wed, 29 Jul 2026 00:55:56 +0100 Subject: [PATCH 2/5] fix(ci): repoint the two DELETED validate-actions at the live ecosystem repos dogfood-gate.yml pinned hyperpolymath/a2ml-validate-action and hyperpolymath/k9-validate-action. Both repositories are DELETED (verified 404). An unresolvable uses: ref produces NO check run at all -- not a red one -- so 'Validate A2ML manifests' and 'Validate K9 contracts' could never report, and any ruleset requiring them was unsatisfiable. That is what blocked this very PR. The actions were not lost, they MOVED: hyperpolymath/a2ml-ecosystem/validate-action (action.yml verified at the pin) hyperpolymath/k9-ecosystem/validate-action (action.yml verified at the pin) Repointed rather than vendored. The estate handover proposed vendoring the scripts into ~272 consumer repos; that would create 272 copies to drift, which the same document names as this estate's most recurring failure mode. Repointing is one line per file and keeps a single source. SHAs are the ones already proven green in the-nash-equilibrium#83; both verified to resolve (2026-07-25) and to contain validate-action/action.yml at that exact ref. Co-Authored-By: Claude Opus 5 --- .github/workflows/dogfood-gate.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/dogfood-gate.yml b/.github/workflows/dogfood-gate.yml index 652bb867..827ae1a3 100644 --- a/.github/workflows/dogfood-gate.yml +++ b/.github/workflows/dogfood-gate.yml @@ -43,7 +43,7 @@ jobs: - name: Validate A2ML manifests if: steps.detect.outputs.count > 0 - uses: hyperpolymath/a2ml-validate-action@05bcb78917c09702e90ed18004298a6728753914 # main + uses: hyperpolymath/a2ml-ecosystem/validate-action@aa4b836bd969df2bc58128cb8e3d20bbc88d5e79 # main with: path: '.' strict: 'false' @@ -105,7 +105,7 @@ jobs: - name: Validate K9 contracts if: steps.detect.outputs.k9_count > 0 - uses: hyperpolymath/k9-validate-action@bddcd9109ee96f9ea3fdb4bf51084fe9cd0909ce # main + uses: hyperpolymath/k9-ecosystem/validate-action@89f3c2702f4f650a92aa7411502f38da06abd562 # main with: path: '.' strict: 'false' From 21610ed46011cf564146660e09f4ac5b12bd12e6 Mon Sep 17 00:00:00 2001 From: "Jonathan D.A. Jewell" <6759885+hyperpolymath@users.noreply.github.com> Date: Wed, 29 Jul 2026 01:31:55 +0100 Subject: [PATCH 3/5] fix(a2ml): move the six 6a2ml manifests to the canonical .machine_readable/6a2/ Hypatia SD004, severity critical, 6 occurrences: '6a2ml file outside canonical location -- must be in .machine_readable/6a2/'. Verified as a REAL finding rather than taken on trust: both standards and hypatia keep exactly these six files -- AGENTIC, ECOSYSTEM, META, NEUROSYM, PLAYBOOK, STATE -- under .machine_readable/6a2/. gitbot-fleet was the outlier. The other .a2ml files at that level (ANCHOR, CICD-PATTERNS, CLADE, SESSION-*) are NOT flagged and are correctly left where they are. Moving them breaks a consumer: scripts/enroll-hypatia-fleet.sh probed the flat paths for META/STATE. It now accepts either location. Deliberately BOTH, not just the canonical one -- that script scans other repos, most of which have not migrated, and narrowing it would silently report them as lacking a manifest they actually have. Verified: bash -n passes, shellcheck -S error clean. Co-Authored-By: Claude Opus 5 --- .machine_readable/{ => 6a2}/AGENTIC.a2ml | 0 .machine_readable/{ => 6a2}/ECOSYSTEM.a2ml | 0 .machine_readable/{ => 6a2}/META.a2ml | 0 .machine_readable/{ => 6a2}/NEUROSYM.a2ml | 0 .machine_readable/{ => 6a2}/PLAYBOOK.a2ml | 0 .machine_readable/{ => 6a2}/STATE.a2ml | 0 scripts/enroll-hypatia-fleet.sh | 8 ++++++-- 7 files changed, 6 insertions(+), 2 deletions(-) rename .machine_readable/{ => 6a2}/AGENTIC.a2ml (100%) rename .machine_readable/{ => 6a2}/ECOSYSTEM.a2ml (100%) rename .machine_readable/{ => 6a2}/META.a2ml (100%) rename .machine_readable/{ => 6a2}/NEUROSYM.a2ml (100%) rename .machine_readable/{ => 6a2}/PLAYBOOK.a2ml (100%) rename .machine_readable/{ => 6a2}/STATE.a2ml (100%) diff --git a/.machine_readable/AGENTIC.a2ml b/.machine_readable/6a2/AGENTIC.a2ml similarity index 100% rename from .machine_readable/AGENTIC.a2ml rename to .machine_readable/6a2/AGENTIC.a2ml diff --git a/.machine_readable/ECOSYSTEM.a2ml b/.machine_readable/6a2/ECOSYSTEM.a2ml similarity index 100% rename from .machine_readable/ECOSYSTEM.a2ml rename to .machine_readable/6a2/ECOSYSTEM.a2ml diff --git a/.machine_readable/META.a2ml b/.machine_readable/6a2/META.a2ml similarity index 100% rename from .machine_readable/META.a2ml rename to .machine_readable/6a2/META.a2ml diff --git a/.machine_readable/NEUROSYM.a2ml b/.machine_readable/6a2/NEUROSYM.a2ml similarity index 100% rename from .machine_readable/NEUROSYM.a2ml rename to .machine_readable/6a2/NEUROSYM.a2ml diff --git a/.machine_readable/PLAYBOOK.a2ml b/.machine_readable/6a2/PLAYBOOK.a2ml similarity index 100% rename from .machine_readable/PLAYBOOK.a2ml rename to .machine_readable/6a2/PLAYBOOK.a2ml diff --git a/.machine_readable/STATE.a2ml b/.machine_readable/6a2/STATE.a2ml similarity index 100% rename from .machine_readable/STATE.a2ml rename to .machine_readable/6a2/STATE.a2ml diff --git a/scripts/enroll-hypatia-fleet.sh b/scripts/enroll-hypatia-fleet.sh index e3e52f7b..bbe2c290 100755 --- a/scripts/enroll-hypatia-fleet.sh +++ b/scripts/enroll-hypatia-fleet.sh @@ -94,8 +94,12 @@ while IFS= read -r repo_path; do [[ -d "$repo_path/.machine_readable" ]] && has_machine_readable=true [[ -d "$repo_path/.machine_readable/bot_directives" ]] && has_bot_directives=true - [[ -f "$repo_path/.machine_readable/META.a2ml" ]] && has_a2ml_meta=true - [[ -f "$repo_path/.machine_readable/STATE.a2ml" ]] && has_a2ml_state=true + # Canonical location is .machine_readable/6a2/ (SD004). Legacy flat layout is + # still accepted here because this script scans OTHER repos, many of which + # have not migrated yet -- narrowing to 6a2/ alone would silently mark them + # as lacking a manifest they actually have. + [[ -f "$repo_path/.machine_readable/6a2/META.a2ml" || -f "$repo_path/.machine_readable/META.a2ml" ]] && has_a2ml_meta=true + [[ -f "$repo_path/.machine_readable/6a2/STATE.a2ml" || -f "$repo_path/.machine_readable/STATE.a2ml" ]] && has_a2ml_state=true if [[ -x "$repo_path/scripts/maintenance/run-maintenance.sh" || -x "$repo_path/run-maintenance.sh" ]]; then has_maint_script=true fi From dbc4c09524d43da5a61a6970971dccd65dd2e22f Mon Sep 17 00:00:00 2001 From: "Jonathan D.A. Jewell" <6759885+hyperpolymath@users.noreply.github.com> Date: Wed, 29 Jul 2026 01:35:09 +0100 Subject: [PATCH 4/5] perf(bots): hoist per-call regex compilation into LazyLock statics Hypatia expect_in_hot_path. Regex::new().expect(..) was being called inside function bodies, so the regex recompiled on every invocation of an analyzer that runs per file. Hoisted to module-level LazyLock statics: compiled once, on first use. Touched 6 files across 3 crates: accessibilitybot contrast.rs (6), css.rs (4->3), keyboard.rs (2) finishingbot claims.rs (2), license.rs (1) glambot accessibility.rs (3) glambot additionally needed use regex::Regex; at module scope -- it was function-scoped, which no longer reaches the hoisted statics. The now-redundant function-local imports were removed. Deliberately NOT touched: the 11 files whose expect() already sits inside a LazyLock/OnceLock initialiser (they compile once already -- the rule is wrong about those), and the expect() calls that are panic-on-error rather than per-call compilation (a semaphore acquire, a post-insert lookup, benchmark setup). Those need error handling or a rule fix, not caching. Verified: cargo check exit 0 on all three crates. Co-Authored-By: Claude Opus 5 --- .../src/analyzers/contrast.rs | 31 +++++++++++++------ bots/accessibilitybot/src/analyzers/css.rs | 17 +++++++--- .../src/analyzers/keyboard.rs | 11 +++++-- bots/finishingbot/src/analyzers/claims.rs | 13 +++++--- bots/finishingbot/src/analyzers/license.rs | 8 +++-- bots/glambot/src/analyzers/accessibility.rs | 21 ++++++++----- 6 files changed, 71 insertions(+), 30 deletions(-) diff --git a/bots/accessibilitybot/src/analyzers/contrast.rs b/bots/accessibilitybot/src/analyzers/contrast.rs index 042d10bb..3ccef948 100644 --- a/bots/accessibilitybot/src/analyzers/contrast.rs +++ b/bots/accessibilitybot/src/analyzers/contrast.rs @@ -10,6 +10,21 @@ use crate::analyzers::Analyzer; use crate::fleet::{Finding, ImpactAssessment, Severity, WcagLevel}; use regex::Regex; use std::path::Path; +use std::sync::LazyLock; + +// Compiled once. These were rebuilt on every call (hypatia expect_in_hot_path). +static COLOR_RE: LazyLock = + LazyLock::new(|| Regex::new(r"(?i)(?:^|;|\{)\s*color\s*:\s*([^;}\n]+)").expect("valid regex")); +static BG_RE: LazyLock = + LazyLock::new(|| Regex::new(r"(?i)background(?:-color)?\s*:\s*([^;}\n]+)").expect("valid regex")); +static BLOCK_RE: LazyLock = + LazyLock::new(|| Regex::new(r"([^{]+)\{([^}]+)\}").expect("valid regex")); +static STYLE_RE: LazyLock = + LazyLock::new(|| Regex::new(r#"style\s*=\s*"([^"]+)""#).expect("valid regex")); +static COLOR_RE_4: LazyLock = + LazyLock::new(|| Regex::new(r"(?i)(?:^|;)\s*color\s*:\s*([^;]+)").expect("valid regex")); +static BG_RE_5: LazyLock = + LazyLock::new(|| Regex::new(r"(?i)background(?:-color)?\s*:\s*([^;]+)").expect("valid regex")); /// Contrast analyzer for CSS color pairs pub struct ContrastAnalyzer; @@ -133,15 +148,11 @@ pub fn contrast_ratio(fg: (u8, u8, u8), bg: (u8, u8, u8)) -> f64 { /// Analyze CSS content for contrast issues fn analyze_css(path: &Path, content: &str) -> Vec { let mut findings = Vec::new(); - let color_re = Regex::new( - r"(?i)(?:^|;|\{)\s*color\s*:\s*([^;}\n]+)" - ).expect("valid regex"); - let bg_re = Regex::new( - r"(?i)background(?:-color)?\s*:\s*([^;}\n]+)" - ).expect("valid regex"); + let color_re = &*COLOR_RE; + let bg_re = &*BG_RE; // Extract color/background-color pairs within CSS rule blocks - let block_re = Regex::new(r"([^{]+)\{([^}]+)\}").expect("valid regex"); + let block_re = &*BLOCK_RE; for caps in block_re.captures_iter(content) { let selector = caps[1].trim(); @@ -211,9 +222,9 @@ fn analyze_css(path: &Path, content: &str) -> Vec { /// Analyze inline styles in HTML for contrast issues fn analyze_inline_styles(path: &Path, content: &str) -> Vec { let mut findings = Vec::new(); - let style_re = Regex::new(r#"style\s*=\s*"([^"]+)""#).expect("valid regex"); - let color_re = Regex::new(r"(?i)(?:^|;)\s*color\s*:\s*([^;]+)").expect("valid regex"); - let bg_re = Regex::new(r"(?i)background(?:-color)?\s*:\s*([^;]+)").expect("valid regex"); + let style_re = &*STYLE_RE; + let color_re = &*COLOR_RE_4; + let bg_re = &*BG_RE_5; for (line_num, line) in content.lines().enumerate() { if let Some(style_caps) = style_re.captures(line) { diff --git a/bots/accessibilitybot/src/analyzers/css.rs b/bots/accessibilitybot/src/analyzers/css.rs index cfb943fe..d843e16d 100644 --- a/bots/accessibilitybot/src/analyzers/css.rs +++ b/bots/accessibilitybot/src/analyzers/css.rs @@ -16,6 +16,15 @@ use crate::analyzers::Analyzer; use crate::fleet::{Finding, ImpactAssessment, Severity, WcagLevel}; use regex::Regex; use std::path::Path; +use std::sync::LazyLock; + +// Compiled once. These were rebuilt on every call (hypatia expect_in_hot_path). +static FONT_SIZE_PX_RE: LazyLock = + LazyLock::new(|| Regex::new(r"(?i)font-size\s*:\s*(\d+)px").expect("valid regex")); +static LINE_HEIGHT_RE: LazyLock = + LazyLock::new(|| Regex::new(r"(?i)line-height\s*:\s*([\d.]+)").expect("valid regex")); +static BLOCK_RE: LazyLock = + LazyLock::new(|| Regex::new(r"([^{]+)\{([^}]+)\}").expect("valid regex")); /// CSS accessibility analyzer pub struct CssAnalyzer; @@ -53,7 +62,7 @@ impl Analyzer for CssAnalyzer { /// Check for font sizes using px instead of rem/em fn check_px_font_sizes(path: &Path, content: &str, findings: &mut Vec) { - let font_size_px_re = Regex::new(r"(?i)font-size\s*:\s*(\d+)px").expect("valid regex"); + let font_size_px_re = &*FONT_SIZE_PX_RE; for (line_num, line) in content.lines().enumerate() { if let Some(caps) = font_size_px_re.captures(line) { @@ -83,8 +92,8 @@ fn check_px_font_sizes(path: &Path, content: &str, findings: &mut Vec) /// Check line-height is at least 1.5 for body text fn check_line_height(path: &Path, content: &str, findings: &mut Vec) { - let line_height_re = Regex::new(r"(?i)line-height\s*:\s*([\d.]+)").expect("valid regex"); - let block_re = Regex::new(r"([^{]+)\{([^}]+)\}").expect("valid regex"); + let line_height_re = &*LINE_HEIGHT_RE; + let block_re = &*BLOCK_RE; for caps in block_re.captures_iter(content) { let selector = caps[1].trim(); @@ -203,7 +212,7 @@ fn check_contrast_preference(path: &Path, content: &str, findings: &mut Vec) { - let block_re = Regex::new(r"([^{]+)\{([^}]+)\}").expect("valid regex"); + let block_re = &*BLOCK_RE; for caps in block_re.captures_iter(content) { let selector = caps[1].trim(); diff --git a/bots/accessibilitybot/src/analyzers/keyboard.rs b/bots/accessibilitybot/src/analyzers/keyboard.rs index a13c5335..680b5517 100644 --- a/bots/accessibilitybot/src/analyzers/keyboard.rs +++ b/bots/accessibilitybot/src/analyzers/keyboard.rs @@ -13,6 +13,13 @@ use crate::fleet::{Finding, ImpactAssessment, Severity, WcagLevel}; use regex::Regex; use scraper::{Html, Selector}; use std::path::Path; +use std::sync::LazyLock; + +// Compiled once. These were rebuilt on every call (hypatia expect_in_hot_path). +static OUTLINE_NONE_RE: LazyLock = + LazyLock::new(|| Regex::new(r"(?i)outline\s*:\s*(none|0)\s*[;}\n]").expect("valid regex")); +static STYLE_RE: LazyLock = + LazyLock::new(|| Regex::new(r#"style\s*=\s*"[^"]*outline\s*:\s*(none|0)[^"]*""#).expect("valid regex")); /// Keyboard navigation analyzer pub struct KeyboardAnalyzer; @@ -185,7 +192,7 @@ fn check_onclick_no_keyboard( /// Check for outline: none in CSS (suppresses focus indicator) fn check_focus_styles_css(path: &Path, content: &str) -> Vec { let mut findings = Vec::new(); - let outline_none_re = Regex::new(r"(?i)outline\s*:\s*(none|0)\s*[;}\n]").expect("valid regex"); + let outline_none_re = &*OUTLINE_NONE_RE; for (line_num, line) in content.lines().enumerate() { if outline_none_re.is_match(line) { @@ -228,7 +235,7 @@ fn check_inline_focus_suppression( content: &str, findings: &mut Vec, ) { - let style_re = Regex::new(r#"style\s*=\s*"[^"]*outline\s*:\s*(none|0)[^"]*""#).expect("valid regex"); + let style_re = &*STYLE_RE; for (line_num, line) in content.lines().enumerate() { if style_re.is_match(line) { diff --git a/bots/finishingbot/src/analyzers/claims.rs b/bots/finishingbot/src/analyzers/claims.rs index 966f7a68..5c20e1ac 100644 --- a/bots/finishingbot/src/analyzers/claims.rs +++ b/bots/finishingbot/src/analyzers/claims.rs @@ -10,6 +10,13 @@ use regex::Regex; use std::path::Path; use tracing::debug; use walkdir::WalkDir; +use std::sync::LazyLock; + +// Compiled once. These were rebuilt on every call (hypatia expect_in_hot_path). +static MEMBER_RE: LazyLock = + LazyLock::new(|| Regex::new(r#"members\s*=\s*\[([\s\S]*?)\]"#).expect("Cargo workspace members regex is a valid constant pattern")); +static PATH_RE: LazyLock = + LazyLock::new(|| Regex::new(r#""([^"]+)""#).expect("Cargo workspace member-path regex is a valid constant pattern")); /// Claim verification analyzer pub struct ClaimsAnalyzer; @@ -442,12 +449,10 @@ impl ClaimsAnalyzer { if let Ok(content) = std::fs::read_to_string(cargo_path) { // Simple check: look for workspace members that don't exist if content.contains("[workspace]") { - let member_re = Regex::new(r#"members\s*=\s*\[([\s\S]*?)\]"#) - .expect("Cargo workspace members regex is a valid constant pattern"); + let member_re = &*MEMBER_RE; if let Some(caps) = member_re.captures(&content) { let members_str = &caps[1]; - let path_re = Regex::new(r#""([^"]+)""#) - .expect("Cargo workspace member-path regex is a valid constant pattern"); + let path_re = &*PATH_RE; let cargo_dir = cargo_path.parent().unwrap_or_else(|| Path::new(".")); for cap in path_re.captures_iter(members_str) { let member_path = cargo_dir.join(&cap[1]); diff --git a/bots/finishingbot/src/analyzers/license.rs b/bots/finishingbot/src/analyzers/license.rs index 23d47cbd..65cb6c7e 100644 --- a/bots/finishingbot/src/analyzers/license.rs +++ b/bots/finishingbot/src/analyzers/license.rs @@ -9,6 +9,11 @@ use regex::Regex; use std::path::Path; use tracing::debug; use walkdir::WalkDir; +use std::sync::LazyLock; + +// Compiled once. These were rebuilt on every call (hypatia expect_in_hot_path). +static SPDX_PATTERN: LazyLock = + LazyLock::new(|| Regex::new(r"(?i)SPDX-License-Identifier:\s*[A-Za-z0-9.-]+").expect("SPDX header regex is a valid constant pattern")); /// Repositories that deliberately use AGPL-3.0-or-later instead of PMPL. /// These are co-developed projects with the user's son and use AGPL by deliberate choice. @@ -222,8 +227,7 @@ impl LicenseAnalyzer { /// Check for SPDX headers in source files fn check_spdx_headers(&self, path: &Path, config: &Config, result: &mut AnalysisResult) { - let spdx_pattern = Regex::new(r"(?i)SPDX-License-Identifier:\s*[A-Za-z0-9.-]+") - .expect("SPDX header regex is a valid constant pattern"); + let spdx_pattern = &*SPDX_PATTERN; for entry in WalkDir::new(path) .follow_links(false) diff --git a/bots/glambot/src/analyzers/accessibility.rs b/bots/glambot/src/analyzers/accessibility.rs index 0e670d0a..29c1e1f6 100644 --- a/bots/glambot/src/analyzers/accessibility.rs +++ b/bots/glambot/src/analyzers/accessibility.rs @@ -7,6 +7,16 @@ use crate::config::Config; use crate::error::Result; use std::path::Path; use walkdir::WalkDir; +use std::sync::LazyLock; +use regex::Regex; + +// Compiled once. These were rebuilt on every call (hypatia expect_in_hot_path). +static MD_IMAGE_RE: LazyLock = + LazyLock::new(|| Regex::new(r"!\[(.*?)\]\((.*?)\)").expect("static markdown image regex literal is valid")); +static ADOC_IMAGE_RE: LazyLock = + LazyLock::new(|| Regex::new(r"image::(.*?)\[(.*?)\]").expect("static asciidoc image regex literal is valid")); +static MD_LINK_RE: LazyLock = + LazyLock::new(|| Regex::new(r"\[(.*?)\]\((.*?)\)").expect("static markdown link regex literal is valid")); /// Accessibility analyzer pub struct AccessibilityAnalyzer; @@ -121,12 +131,9 @@ impl Analyzer for AccessibilityAnalyzer { impl AccessibilityAnalyzer { fn check_image_alt_text(&self, path: &Path, config: &Config, result: &mut AnalysisResult) { - use regex::Regex; - let md_image_re = Regex::new(r"!\[(.*?)\]\((.*?)\)") - .expect("static markdown image regex literal is valid"); - let adoc_image_re = Regex::new(r"image::(.*?)\[(.*?)\]") - .expect("static asciidoc image regex literal is valid"); + let md_image_re = &*MD_IMAGE_RE; + let adoc_image_re = &*ADOC_IMAGE_RE; for entry in WalkDir::new(path) .into_iter() @@ -239,10 +246,8 @@ impl AccessibilityAnalyzer { } fn check_link_text(&self, path: &Path, config: &Config, result: &mut AnalysisResult) { - use regex::Regex; - let md_link_re = Regex::new(r"\[(.*?)\]\((.*?)\)") - .expect("static markdown link regex literal is valid"); + let md_link_re = &*MD_LINK_RE; let non_descriptive = ["click here", "here", "read more", "link", "more"]; From 779f65ec36dd2ebc5d9264f1bee42af5a4a97910 Mon Sep 17 00:00:00 2001 From: "Jonathan D.A. Jewell" <6759885+hyperpolymath@users.noreply.github.com> Date: Wed, 29 Jul 2026 01:42:53 +0100 Subject: [PATCH 5/5] fix(ci): add the two genuinely missing timeout-minutes Hypatia workflow_audit missing_timeout_minutes, 2 occurrences. Both verified as REAL by parsing the YAML per job rather than grepping the file: inbox-steward.yml 4 of 5 jobs had one; 'summary' did not -> 5 push-email-notify.yml its single 'notify' job had none -> 10 A file-level grep would have cleared inbox-steward.yml, which contains four timeout-minutes lines already. The gap was one job. Verified: both files still parse; every job now declares a timeout. Co-Authored-By: Claude Opus 5 --- .github/workflows/inbox-steward.yml | 1 + .github/workflows/push-email-notify.yml | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/workflows/inbox-steward.yml b/.github/workflows/inbox-steward.yml index 688a8252..679adc36 100644 --- a/.github/workflows/inbox-steward.yml +++ b/.github/workflows/inbox-steward.yml @@ -427,6 +427,7 @@ jobs: summary: name: Generate stewardship summary runs-on: ubuntu-latest + timeout-minutes: 5 needs: [identify-passed-prs, validate-prs, auto-merge-prs] if: always() diff --git a/.github/workflows/push-email-notify.yml b/.github/workflows/push-email-notify.yml index 08167719..9fd52cec 100644 --- a/.github/workflows/push-email-notify.yml +++ b/.github/workflows/push-email-notify.yml @@ -13,6 +13,7 @@ jobs: name: Email on push if: ${{ vars.PUSH_EMAIL_ENABLED == 'true' }} runs-on: ubuntu-latest + timeout-minutes: 10 steps: - name: Send push notification email uses: dawidd6/action-send-mail@c50dc4cc848ade21f848990889906d804fae78c5 # pinned