github-security-report 0.21.0__tar.gz → 0.22.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (120) hide show
  1. {github_security_report-0.21.0 → github_security_report-0.22.0}/PKG-INFO +26 -4
  2. {github_security_report-0.21.0 → github_security_report-0.22.0}/README.md +23 -1
  3. {github_security_report-0.21.0 → github_security_report-0.22.0}/pyproject.toml +4 -4
  4. {github_security_report-0.21.0 → github_security_report-0.22.0}/src/github_security_report/_version.py +2 -2
  5. {github_security_report-0.21.0 → github_security_report-0.22.0}/src/github_security_report/collect/workflow_runs.py +1 -0
  6. {github_security_report-0.21.0 → github_security_report-0.22.0}/src/github_security_report/config/blocks.py +1 -0
  7. {github_security_report-0.21.0 → github_security_report-0.22.0}/src/github_security_report/config/models.py +3 -0
  8. {github_security_report-0.21.0 → github_security_report-0.22.0}/src/github_security_report/config/schema.py +5 -0
  9. {github_security_report-0.21.0 → github_security_report-0.22.0}/src/github_security_report/malware.py +2 -2
  10. {github_security_report-0.21.0 → github_security_report-0.22.0}/src/github_security_report/workflow_runs/tables.py +11 -2
  11. {github_security_report-0.21.0 → github_security_report-0.22.0}/.gitignore +0 -0
  12. {github_security_report-0.21.0 → github_security_report-0.22.0}/LICENSE +0 -0
  13. {github_security_report-0.21.0 → github_security_report-0.22.0}/LICENSES/Apache-2.0.txt +0 -0
  14. {github_security_report-0.21.0 → github_security_report-0.22.0}/scripts/README.md +0 -0
  15. {github_security_report-0.21.0 → github_security_report-0.22.0}/src/github_security_report/__init__.py +0 -0
  16. {github_security_report-0.21.0 → github_security_report-0.22.0}/src/github_security_report/authors.py +0 -0
  17. {github_security_report-0.21.0 → github_security_report-0.22.0}/src/github_security_report/categories/__init__.py +0 -0
  18. {github_security_report-0.21.0 → github_security_report-0.22.0}/src/github_security_report/categories/keys.py +0 -0
  19. {github_security_report-0.21.0 → github_security_report-0.22.0}/src/github_security_report/categories/signals.py +0 -0
  20. {github_security_report-0.21.0 → github_security_report-0.22.0}/src/github_security_report/categories/tables.py +0 -0
  21. {github_security_report-0.21.0 → github_security_report-0.22.0}/src/github_security_report/classify.py +0 -0
  22. {github_security_report-0.21.0 → github_security_report-0.22.0}/src/github_security_report/cli/__init__.py +0 -0
  23. {github_security_report-0.21.0 → github_security_report-0.22.0}/src/github_security_report/cli/__main__.py +0 -0
  24. {github_security_report-0.21.0 → github_security_report-0.22.0}/src/github_security_report/cli/app.py +0 -0
  25. {github_security_report-0.21.0 → github_security_report-0.22.0}/src/github_security_report/cli/boundary.py +0 -0
  26. {github_security_report-0.21.0 → github_security_report-0.22.0}/src/github_security_report/cli/modes.py +0 -0
  27. {github_security_report-0.21.0 → github_security_report-0.22.0}/src/github_security_report/cli/options.py +0 -0
  28. {github_security_report-0.21.0 → github_security_report-0.22.0}/src/github_security_report/cli/outputs.py +0 -0
  29. {github_security_report-0.21.0 → github_security_report-0.22.0}/src/github_security_report/cli/progress.py +0 -0
  30. {github_security_report-0.21.0 → github_security_report-0.22.0}/src/github_security_report/cli/publish.py +0 -0
  31. {github_security_report-0.21.0 → github_security_report-0.22.0}/src/github_security_report/cli/remediation.py +0 -0
  32. {github_security_report-0.21.0 → github_security_report-0.22.0}/src/github_security_report/cli/serialise.py +0 -0
  33. {github_security_report-0.21.0 → github_security_report-0.22.0}/src/github_security_report/cli/summary.py +0 -0
  34. {github_security_report-0.21.0 → github_security_report-0.22.0}/src/github_security_report/client/__init__.py +0 -0
  35. {github_security_report-0.21.0 → github_security_report-0.22.0}/src/github_security_report/client/accounting.py +0 -0
  36. {github_security_report-0.21.0 → github_security_report-0.22.0}/src/github_security_report/client/alerts.py +0 -0
  37. {github_security_report-0.21.0 → github_security_report-0.22.0}/src/github_security_report/client/analyses.py +0 -0
  38. {github_security_report-0.21.0 → github_security_report-0.22.0}/src/github_security_report/client/batch_errors.py +0 -0
  39. {github_security_report-0.21.0 → github_security_report-0.22.0}/src/github_security_report/client/codeql_parsers.py +0 -0
  40. {github_security_report-0.21.0 → github_security_report-0.22.0}/src/github_security_report/client/copilot.py +0 -0
  41. {github_security_report-0.21.0 → github_security_report-0.22.0}/src/github_security_report/client/endpoints.py +0 -0
  42. {github_security_report-0.21.0 → github_security_report-0.22.0}/src/github_security_report/client/errors.py +0 -0
  43. {github_security_report-0.21.0 → github_security_report-0.22.0}/src/github_security_report/client/events.py +0 -0
  44. {github_security_report-0.21.0 → github_security_report-0.22.0}/src/github_security_report/client/failures.py +0 -0
  45. {github_security_report-0.21.0 → github_security_report-0.22.0}/src/github_security_report/client/org_reads.py +0 -0
  46. {github_security_report-0.21.0 → github_security_report-0.22.0}/src/github_security_report/client/paging.py +0 -0
  47. {github_security_report-0.21.0 → github_security_report-0.22.0}/src/github_security_report/client/parsers.py +0 -0
  48. {github_security_report-0.21.0 → github_security_report-0.22.0}/src/github_security_report/client/queries.py +0 -0
  49. {github_security_report-0.21.0 → github_security_report-0.22.0}/src/github_security_report/client/reads.py +0 -0
  50. {github_security_report-0.21.0 → github_security_report-0.22.0}/src/github_security_report/client/repo_parser.py +0 -0
  51. {github_security_report-0.21.0 → github_security_report-0.22.0}/src/github_security_report/client/reviews.py +0 -0
  52. {github_security_report-0.21.0 → github_security_report-0.22.0}/src/github_security_report/client/transport.py +0 -0
  53. {github_security_report-0.21.0 → github_security_report-0.22.0}/src/github_security_report/client/workflow_runs.py +0 -0
  54. {github_security_report-0.21.0 → github_security_report-0.22.0}/src/github_security_report/client/writes.py +0 -0
  55. {github_security_report-0.21.0 → github_security_report-0.22.0}/src/github_security_report/code_quality.py +0 -0
  56. {github_security_report-0.21.0 → github_security_report-0.22.0}/src/github_security_report/codeql/__init__.py +0 -0
  57. {github_security_report-0.21.0 → github_security_report-0.22.0}/src/github_security_report/codeql/cleanup.py +0 -0
  58. {github_security_report-0.21.0 → github_security_report-0.22.0}/src/github_security_report/codeql/facts.py +0 -0
  59. {github_security_report-0.21.0 → github_security_report-0.22.0}/src/github_security_report/codeql/tables.py +0 -0
  60. {github_security_report-0.21.0 → github_security_report-0.22.0}/src/github_security_report/collect/__init__.py +0 -0
  61. {github_security_report-0.21.0 → github_security_report-0.22.0}/src/github_security_report/collect/codeql.py +0 -0
  62. {github_security_report-0.21.0 → github_security_report-0.22.0}/src/github_security_report/collect/context.py +0 -0
  63. {github_security_report-0.21.0 → github_security_report-0.22.0}/src/github_security_report/collect/extras.py +0 -0
  64. {github_security_report-0.21.0 → github_security_report-0.22.0}/src/github_security_report/collect/graph.py +0 -0
  65. {github_security_report-0.21.0 → github_security_report-0.22.0}/src/github_security_report/collect/org.py +0 -0
  66. {github_security_report-0.21.0 → github_security_report-0.22.0}/src/github_security_report/collect/protocols.py +0 -0
  67. {github_security_report-0.21.0 → github_security_report-0.22.0}/src/github_security_report/collect/repo.py +0 -0
  68. {github_security_report-0.21.0 → github_security_report-0.22.0}/src/github_security_report/collect/run_sync.py +0 -0
  69. {github_security_report-0.21.0 → github_security_report-0.22.0}/src/github_security_report/collect/scoping.py +0 -0
  70. {github_security_report-0.21.0 → github_security_report-0.22.0}/src/github_security_report/config/__init__.py +0 -0
  71. {github_security_report-0.21.0 → github_security_report-0.22.0}/src/github_security_report/config/loader.py +0 -0
  72. {github_security_report-0.21.0 → github_security_report-0.22.0}/src/github_security_report/config/order.py +0 -0
  73. {github_security_report-0.21.0 → github_security_report-0.22.0}/src/github_security_report/gating.py +0 -0
  74. {github_security_report-0.21.0 → github_security_report-0.22.0}/src/github_security_report/gitctx.py +0 -0
  75. {github_security_report-0.21.0 → github_security_report-0.22.0}/src/github_security_report/issue_presentation.py +0 -0
  76. {github_security_report-0.21.0 → github_security_report-0.22.0}/src/github_security_report/issues.py +0 -0
  77. {github_security_report-0.21.0 → github_security_report-0.22.0}/src/github_security_report/layout.py +0 -0
  78. {github_security_report-0.21.0 → github_security_report-0.22.0}/src/github_security_report/models.py +0 -0
  79. {github_security_report-0.21.0 → github_security_report-0.22.0}/src/github_security_report/ordering.py +0 -0
  80. {github_security_report-0.21.0 → github_security_report-0.22.0}/src/github_security_report/posture/__init__.py +0 -0
  81. {github_security_report-0.21.0 → github_security_report-0.22.0}/src/github_security_report/posture/enablement.py +0 -0
  82. {github_security_report-0.21.0 → github_security_report-0.22.0}/src/github_security_report/posture/facts.py +0 -0
  83. {github_security_report-0.21.0 → github_security_report-0.22.0}/src/github_security_report/posture/quality.py +0 -0
  84. {github_security_report-0.21.0 → github_security_report-0.22.0}/src/github_security_report/posture/releases.py +0 -0
  85. {github_security_report-0.21.0 → github_security_report-0.22.0}/src/github_security_report/pulls/__init__.py +0 -0
  86. {github_security_report-0.21.0 → github_security_report-0.22.0}/src/github_security_report/pulls/columns.py +0 -0
  87. {github_security_report-0.21.0 → github_security_report-0.22.0}/src/github_security_report/pulls/counting.py +0 -0
  88. {github_security_report-0.21.0 → github_security_report-0.22.0}/src/github_security_report/pulls/presentation.py +0 -0
  89. {github_security_report-0.21.0 → github_security_report-0.22.0}/src/github_security_report/pulls/table.py +0 -0
  90. {github_security_report-0.21.0 → github_security_report-0.22.0}/src/github_security_report/py.typed +0 -0
  91. {github_security_report-0.21.0 → github_security_report-0.22.0}/src/github_security_report/ranking.py +0 -0
  92. {github_security_report-0.21.0 → github_security_report-0.22.0}/src/github_security_report/remediate/__init__.py +0 -0
  93. {github_security_report-0.21.0 → github_security_report-0.22.0}/src/github_security_report/remediate/codeql.py +0 -0
  94. {github_security_report-0.21.0 → github_security_report-0.22.0}/src/github_security_report/remediate/model.py +0 -0
  95. {github_security_report-0.21.0 → github_security_report-0.22.0}/src/github_security_report/render/__init__.py +0 -0
  96. {github_security_report-0.21.0 → github_security_report-0.22.0}/src/github_security_report/render/html.py +0 -0
  97. {github_security_report-0.21.0 → github_security_report-0.22.0}/src/github_security_report/render/markdown.py +0 -0
  98. {github_security_report-0.21.0 → github_security_report-0.22.0}/src/github_security_report/render/slack.py +0 -0
  99. {github_security_report-0.21.0 → github_security_report-0.22.0}/src/github_security_report/render/slack_limits.py +0 -0
  100. {github_security_report-0.21.0 → github_security_report-0.22.0}/src/github_security_report/render/slack_text.py +0 -0
  101. {github_security_report-0.21.0 → github_security_report-0.22.0}/src/github_security_report/render/terminal.py +0 -0
  102. {github_security_report-0.21.0 → github_security_report-0.22.0}/src/github_security_report/render/terminal_notices.py +0 -0
  103. {github_security_report-0.21.0 → github_security_report-0.22.0}/src/github_security_report/report/__init__.py +0 -0
  104. {github_security_report-0.21.0 → github_security_report-0.22.0}/src/github_security_report/report/aggregate.py +0 -0
  105. {github_security_report-0.21.0 → github_security_report-0.22.0}/src/github_security_report/report/display.py +0 -0
  106. {github_security_report-0.21.0 → github_security_report-0.22.0}/src/github_security_report/report/signals.py +0 -0
  107. {github_security_report-0.21.0 → github_security_report-0.22.0}/src/github_security_report/report/tables.py +0 -0
  108. {github_security_report-0.21.0 → github_security_report-0.22.0}/src/github_security_report/rulesets.py +0 -0
  109. {github_security_report-0.21.0 → github_security_report-0.22.0}/src/github_security_report/runner.py +0 -0
  110. {github_security_report-0.21.0 → github_security_report-0.22.0}/src/github_security_report/scope.py +0 -0
  111. {github_security_report-0.21.0 → github_security_report-0.22.0}/src/github_security_report/secret_patterns.py +0 -0
  112. {github_security_report-0.21.0 → github_security_report-0.22.0}/src/github_security_report/severity.py +0 -0
  113. {github_security_report-0.21.0 → github_security_report-0.22.0}/src/github_security_report/summary.py +0 -0
  114. {github_security_report-0.21.0 → github_security_report-0.22.0}/src/github_security_report/templates/index.html.j2 +0 -0
  115. {github_security_report-0.21.0 → github_security_report-0.22.0}/src/github_security_report/templates/report.html.j2 +0 -0
  116. {github_security_report-0.21.0 → github_security_report-0.22.0}/src/github_security_report/workflow_runs/__init__.py +0 -0
  117. {github_security_report-0.21.0 → github_security_report-0.22.0}/src/github_security_report/workflow_runs/aggregate.py +0 -0
  118. {github_security_report-0.21.0 → github_security_report-0.22.0}/src/github_security_report/workflow_runs/models.py +0 -0
  119. {github_security_report-0.21.0 → github_security_report-0.22.0}/src/github_security_report/workflow_runs/store.py +0 -0
  120. {github_security_report-0.21.0 → github_security_report-0.22.0}/src/github_security_report/workflow_runs/store_codec.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: github-security-report
3
- Version: 0.21.0
3
+ Version: 0.22.0
4
4
  Summary: Security and quality reporting across GitHub organisations
5
5
  Project-URL: Homepage, https://github.com/lfreleng-actions/github-security-report-action
6
6
  Project-URL: Repository, https://github.com/lfreleng-actions/github-security-report-action
@@ -32,11 +32,11 @@ Requires-Dist: rich==15.0.0
32
32
  Requires-Dist: typer==0.27.3
33
33
  Provides-Extra: dev
34
34
  Requires-Dist: mypy==2.4.0; extra == 'dev'
35
- Requires-Dist: pytest-asyncio==1.4.0; extra == 'dev'
35
+ Requires-Dist: pytest-asyncio==1.4.1; extra == 'dev'
36
36
  Requires-Dist: pytest-cov==7.1.0; extra == 'dev'
37
37
  Requires-Dist: pytest==9.1.1; extra == 'dev'
38
38
  Requires-Dist: respx==0.23.1; extra == 'dev'
39
- Requires-Dist: ruff==0.17.1; extra == 'dev'
39
+ Requires-Dist: ruff==0.17.3; extra == 'dev'
40
40
  Requires-Dist: syrupy==6.1.1; extra == 'dev'
41
41
  Requires-Dist: types-jsonschema==4.26.0.20261006; extra == 'dev'
42
42
  Requires-Dist: types-pyyaml==6.0.12.20250915; extra == 'dev'
@@ -209,7 +209,9 @@ tables (org mode):
209
209
  pull request could have caught them. Each row gives **Failed** (runs that
210
210
  concluded `failure`, `startup_failure`, `timed_out` or `action_required`),
211
211
  **Runs** (every concluded run of every workflow in the repository),
212
- **Fail %** (failed over failed *plus passed*, so skips never dilute it),
212
+ **Fail %** (failed over failed *plus passed*, so skips never dilute it;
213
+ on the terminal, red above `workflow_runs.fail_rate_error_threshold`, 5%
214
+ by default, and yellow for any lower non-zero rate),
213
215
  **Skipped** (skipped, cancelled, neutral and stale runs — a cancelled run
214
216
  reached no verdict, and a bulk-merge round cancels hundreds of superseded
215
217
  ones), **Retried** (runs that passed on a later attempt, a direct sign of
@@ -415,6 +417,18 @@ that only shape an organisation-wide run — `output_dir`, `pages_url`,
415
417
  misconfigured workflow says so instead of quietly producing a report that
416
418
  ignores them.
417
419
 
420
+ ### Runtime isolation
421
+
422
+ The action installs its own uv-managed Python and runs the tool from an
423
+ action-owned virtual environment with `python -I`, so interpreter settings an
424
+ earlier step of the caller's job exports (`UV_PYTHON`, `VIRTUAL_ENV`,
425
+ `CONDA_PREFIX`, `PYTHONPATH` and similar) cannot choose what runs.
426
+
427
+ The action trusts the caller's `PATH`. The runner resolves `bash` for every
428
+ `shell: bash` step on it, and setup-uv runs helpers such as `tar` from it, so
429
+ no composite action can defend against a caller that has put a directory from
430
+ its checkout on `PATH`.
431
+
418
432
  ## Configuration
419
433
 
420
434
  Configuration is JSON, supplied as a plain `vars.` entry or base64-encoded in a
@@ -1606,6 +1620,7 @@ like every other `report` key. Every key with its default:
1606
1620
  "collapse_matrix": true,
1607
1621
  "persistent_threshold": 0.9,
1608
1622
  "min_runs": 3,
1623
+ "fail_rate_error_threshold": 0.05,
1609
1624
  "cache": true
1610
1625
  }
1611
1626
  },
@@ -1647,6 +1662,13 @@ fewer than `min_runs` runs that reached a verdict *insufficient* to say. Both
1647
1662
  judge the same pass-or-fail population that Fail % does, so skips count
1648
1663
  towards neither.
1649
1664
 
1665
+ `fail_rate_error_threshold` colours the per-repository table's **Fail %**
1666
+ on the terminal: a repository failing more than that fraction of its
1667
+ pass-or-fail runs shows red, and any lower non-zero rate shows yellow.
1668
+ Markdown, HTML and Slack carry no cell colour, so the setting leaves them
1669
+ unchanged. It is independent of `persistent_threshold`, which judges
1670
+ single workflows.
1671
+
1650
1672
  `cache` and `cache_dir` govern the **run store**, a SQLite file per
1651
1673
  organisation at
1652
1674
 
@@ -165,7 +165,9 @@ tables (org mode):
165
165
  pull request could have caught them. Each row gives **Failed** (runs that
166
166
  concluded `failure`, `startup_failure`, `timed_out` or `action_required`),
167
167
  **Runs** (every concluded run of every workflow in the repository),
168
- **Fail %** (failed over failed *plus passed*, so skips never dilute it),
168
+ **Fail %** (failed over failed *plus passed*, so skips never dilute it;
169
+ on the terminal, red above `workflow_runs.fail_rate_error_threshold`, 5%
170
+ by default, and yellow for any lower non-zero rate),
169
171
  **Skipped** (skipped, cancelled, neutral and stale runs — a cancelled run
170
172
  reached no verdict, and a bulk-merge round cancels hundreds of superseded
171
173
  ones), **Retried** (runs that passed on a later attempt, a direct sign of
@@ -371,6 +373,18 @@ that only shape an organisation-wide run — `output_dir`, `pages_url`,
371
373
  misconfigured workflow says so instead of quietly producing a report that
372
374
  ignores them.
373
375
 
376
+ ### Runtime isolation
377
+
378
+ The action installs its own uv-managed Python and runs the tool from an
379
+ action-owned virtual environment with `python -I`, so interpreter settings an
380
+ earlier step of the caller's job exports (`UV_PYTHON`, `VIRTUAL_ENV`,
381
+ `CONDA_PREFIX`, `PYTHONPATH` and similar) cannot choose what runs.
382
+
383
+ The action trusts the caller's `PATH`. The runner resolves `bash` for every
384
+ `shell: bash` step on it, and setup-uv runs helpers such as `tar` from it, so
385
+ no composite action can defend against a caller that has put a directory from
386
+ its checkout on `PATH`.
387
+
374
388
  ## Configuration
375
389
 
376
390
  Configuration is JSON, supplied as a plain `vars.` entry or base64-encoded in a
@@ -1562,6 +1576,7 @@ like every other `report` key. Every key with its default:
1562
1576
  "collapse_matrix": true,
1563
1577
  "persistent_threshold": 0.9,
1564
1578
  "min_runs": 3,
1579
+ "fail_rate_error_threshold": 0.05,
1565
1580
  "cache": true
1566
1581
  }
1567
1582
  },
@@ -1603,6 +1618,13 @@ fewer than `min_runs` runs that reached a verdict *insufficient* to say. Both
1603
1618
  judge the same pass-or-fail population that Fail % does, so skips count
1604
1619
  towards neither.
1605
1620
 
1621
+ `fail_rate_error_threshold` colours the per-repository table's **Fail %**
1622
+ on the terminal: a repository failing more than that fraction of its
1623
+ pass-or-fail runs shows red, and any lower non-zero rate shows yellow.
1624
+ Markdown, HTML and Slack carry no cell colour, so the setting leaves them
1625
+ unchanged. It is independent of `persistent_threshold`, which judges
1626
+ single workflows.
1627
+
1606
1628
  `cache` and `cache_dir` govern the **run store**, a SQLite file per
1607
1629
  organisation at
1608
1630
 
@@ -60,12 +60,12 @@ Issues = "https://github.com/lfreleng-actions/github-security-report-action/issu
60
60
  [project.optional-dependencies]
61
61
  dev = [
62
62
  "pytest==9.1.1",
63
- "pytest-asyncio==1.4.0",
63
+ "pytest-asyncio==1.4.1",
64
64
  "pytest-cov==7.1.0",
65
65
  "respx==0.23.1",
66
66
  "syrupy==6.1.1",
67
67
  "mypy==2.4.0",
68
- "ruff==0.17.1",
68
+ "ruff==0.17.3",
69
69
  "types-jsonschema==4.26.0.20261006",
70
70
  "types-PyYAML==6.0.12.20250915",
71
71
  ]
@@ -92,12 +92,12 @@ include = ["src", "README.md", "LICENSE*", "pyproject.toml"]
92
92
  [dependency-groups]
93
93
  dev = [
94
94
  "pytest==9.1.1",
95
- "pytest-asyncio==1.4.0",
95
+ "pytest-asyncio==1.4.1",
96
96
  "pytest-cov==7.1.0",
97
97
  "respx==0.23.1",
98
98
  "syrupy==6.1.1",
99
99
  "mypy==2.4.0",
100
- "ruff==0.17.1",
100
+ "ruff==0.17.3",
101
101
  "types-jsonschema==4.26.0.20261006",
102
102
  "types-PyYAML==6.0.12.20250915",
103
103
  ]
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
18
18
  commit_id: str | None
19
19
  __commit_id__: str | None
20
20
 
21
- __version__ = version = '0.21.0'
22
- __version_tuple__ = version_tuple = (0, 21, 0)
21
+ __version__ = version = '0.22.0'
22
+ __version_tuple__ = version_tuple = (0, 22, 0)
23
23
 
24
24
  __commit_id__ = commit_id = None
@@ -316,5 +316,6 @@ async def attach_workflow_failures(
316
316
  ),
317
317
  unknown=result.unknown,
318
318
  oldest=result.oldest,
319
+ fail_rate_error_threshold=cfg.fail_rate_error_threshold,
319
320
  )
320
321
  )
@@ -178,6 +178,7 @@ def _workflow_runs_from(data: dict, base: WorkflowRunsConfig) -> WorkflowRunsCon
178
178
  "collapse_matrix",
179
179
  "persistent_threshold",
180
180
  "min_runs",
181
+ "fail_rate_error_threshold",
181
182
  "cache",
182
183
  "cache_dir",
183
184
  }
@@ -188,6 +188,9 @@ class WorkflowRunsConfig:
188
188
  # intermittent. Fewer than ``min_runs`` concluded runs is too few to say.
189
189
  persistent_threshold: float = 0.9
190
190
  min_runs: int = 3
191
+ # A repository failing more than this share of its runs has its Fail %
192
+ # shown as an error; any lower non-zero rate is a warning.
193
+ fail_rate_error_threshold: float = 0.05
191
194
  cache: bool = True
192
195
  cache_dir: str | None = None
193
196
 
@@ -274,6 +274,11 @@ CONFIG_SCHEMA: dict = {
274
274
  "maximum": 1,
275
275
  },
276
276
  "min_runs": {"type": "integer", "minimum": 1},
277
+ "fail_rate_error_threshold": {
278
+ "type": "number",
279
+ "minimum": 0,
280
+ "maximum": 1,
281
+ },
277
282
  "cache": {"type": "boolean"},
278
283
  "cache_dir": {"type": "string", "minLength": 1},
279
284
  },
@@ -29,8 +29,8 @@ _NUMERIC_COLUMNS = frozenset({4, 5})
29
29
  # A headline and its detail, split by a newline: each surface sets the detail
30
30
  # on a line of its own under the headline (see the summary renderers).
31
31
  COVERAGE_NOTE = (
32
- "Malware alert enablement/status NOT currently available through APIs\n"
33
- "Repositories without findings may have the feature off so coverage unknown"
32
+ "Enablement status of malware detection NOT currently available via APIs\n"
33
+ "Coverage unknown; repositories without findings may have the feature off"
34
34
  )
35
35
 
36
36
 
@@ -84,8 +84,13 @@ def _percent(rate: float) -> str:
84
84
  return f"{round(rate * 100)}%"
85
85
 
86
86
 
87
+ # The repository failure rate above which Fail % reads as an error.
88
+ DEFAULT_FAIL_RATE_ERROR_THRESHOLD = 0.05
89
+
90
+
87
91
  def _rate_level(rate: float, threshold: float) -> str | None:
88
- if rate >= threshold:
92
+ """Error strictly above ``threshold``, warning for any lower failure."""
93
+ if rate > threshold:
89
94
  return CELL_BAD
90
95
  return CELL_WARN if rate > 0 else None
91
96
 
@@ -256,6 +261,7 @@ def build_workflow_failure_tables(
256
261
  options: WindowOptions,
257
262
  unknown: frozenset[str] = frozenset(),
258
263
  oldest: Mapping[str, dt.datetime | None] | None = None,
264
+ fail_rate_error_threshold: float = DEFAULT_FAIL_RATE_ERROR_THRESHOLD,
259
265
  ) -> tuple[tuple[TableSection, ...], tuple[TableSection, ...]]:
260
266
  """Both tables for every window, shortest window first.
261
267
 
@@ -264,6 +270,9 @@ def build_workflow_failure_tables(
264
270
  as unreadable rather than as failure-free on every window; ``oldest``
265
271
  says how far back each repository's stored history reaches, so a window
266
272
  reaching further is noted as truncated in the footer.
273
+ ``fail_rate_error_threshold`` is the repository failure rate above which
274
+ the Fail % cell reads as an error rather than a warning; the persistent
275
+ threshold in ``options`` judges only the detail table's Pattern.
267
276
  """
268
277
  by_name = {repo.name: repo for repo in in_scope}
269
278
  held = list(records)
@@ -305,7 +314,7 @@ def build_workflow_failure_tables(
305
314
  category=category_meta(CategoryKey.WORKFLOW_FAILURES),
306
315
  columns=REPO_COLUMNS,
307
316
  rows=[
308
- _repo_row(by_name[s.repo], s, options.persistent_threshold)
317
+ _repo_row(by_name[s.repo], s, fail_rate_error_threshold)
309
318
  for s in failing
310
319
  ],
311
320
  sum_columns=frozenset({1, 2, 4, 5, 6}),