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' diff --git a/.github/workflows/inbox-steward.yml b/.github/workflows/inbox-steward.yml index e72246c3..679adc36 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: @@ -430,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 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/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"]; 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