Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions .github/workflows/build-tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
name: Build Tests

on:
pull_request:
branches: [dev, master, main]
workflow_dispatch:

jobs:
build:
uses: OpenVoiceOS/gh-automations/.github/workflows/build-tests.yml@dev

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n .github/workflows/build-tests.yml | head -20

Repository: TigreGotico/ovoscope

Length of output: 524


Pin reusable workflow to an immutable ref.

Using @dev is mutable and can change behavior unexpectedly across runs. Pin to a commit SHA for reproducibility and supply-chain safety.

Suggested fix
-    uses: OpenVoiceOS/gh-automations/.github/workflows/build-tests.yml@dev
+    uses: OpenVoiceOS/gh-automations/.github/workflows/build-tests.yml@<commit-sha>
🧰 Tools
🪛 GitHub Check: CodeQL

[warning] 10-15: Workflow does not contain permissions
Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {{}}

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/build-tests.yml at line 10, The workflow currently
references the reusable workflow using a mutable ref
"OpenVoiceOS/gh-automations/.github/workflows/build-tests.yml@dev"; update that
to an immutable commit SHA (replace "@dev" with the specific commit hash) so the
workflow is pinned and reproducible. Locate the line that calls the reusable
workflow (the "uses:
OpenVoiceOS/gh-automations/.github/workflows/build-tests.yml@dev" entry) and
replace the ref with the full commit SHA from the target repository, commit the
change, and verify the workflow still executes as expected.

secrets: inherit
with:
python_versions: '["3.10", "3.11", "3.12", "3.13", "3.14"]'
install_extras: 'audio,pydantic'
test_path: 'test/unittests/'
Comment on lines +10 to +15

Check warning

Code scanning / CodeQL

Workflow does not contain permissions Medium

Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {}

Copilot Autofix

AI 4 months ago

In general, the fix is to add an explicit permissions: block to the workflow (either at the root or under the affected job) that grants only the minimal scopes required. For a typical build-and-test workflow that just checks out code and runs tests, contents: read is sufficient; if the reusable workflow needs more (e.g., to write checks or pull request comments), additional scopes could be added, but we cannot infer that from the snippet, so we start with a minimal, safe configuration.

The best, least intrusive fix here is to add a top-level permissions: block (aligned with on: and jobs:) in .github/workflows/build-tests.yml, setting contents: read. This applies to all jobs that don’t override permissions, including the build job using the reusable workflow, and does not change any other behavior of the workflow. Concretely, we should insert:

permissions:
  contents: read

after the on: block (e.g., after line 6) or before jobs:. No new imports or additional definitions are required because this is just YAML configuration for GitHub Actions.

Suggested changeset 1
.github/workflows/build-tests.yml

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/.github/workflows/build-tests.yml b/.github/workflows/build-tests.yml
--- a/.github/workflows/build-tests.yml
+++ b/.github/workflows/build-tests.yml
@@ -5,6 +5,9 @@
     branches: [dev, master, main]
   workflow_dispatch:
 
+permissions:
+  contents: read
+
 jobs:
   build:
     uses: OpenVoiceOS/gh-automations/.github/workflows/build-tests.yml@dev
EOF
@@ -5,6 +5,9 @@
branches: [dev, master, main]
workflow_dispatch:

permissions:
contents: read

jobs:
build:
uses: OpenVoiceOS/gh-automations/.github/workflows/build-tests.yml@dev
Copilot is powered by AI and may make mistakes. Always verify output.
Comment on lines +3 to +15

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Add explicit GITHUB_TOKEN permissions.

This workflow currently relies on default token scopes. Please define least-privilege permissions explicitly to reduce token exposure.

Suggested fix
 on:
   pull_request:
     branches: [dev, master, main]
   workflow_dispatch:

+permissions:
+  contents: read
+
 jobs:
   build:
🧰 Tools
🪛 GitHub Check: CodeQL

[warning] 10-15: Workflow does not contain permissions
Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {{}}

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/build-tests.yml around lines 3 - 15, Add an explicit
top-level permissions block for the GITHUB_TOKEN to avoid relying on default
scopes; because this workflow runs on pull_request and delegates to the reusable
job referenced by uses:
OpenVoiceOS/gh-automations/.github/workflows/build-tests.yml@dev (job: build),
add a permissions: mapping that grants only the least-privilege scopes required
(for example, contents: read and any other specific scopes you need such as
pull-requests: write or id-token: write) instead of the defaults so the token
exposure is minimized.

18 changes: 0 additions & 18 deletions .github/workflows/build_tests.yml

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@ jobs:
label:
runs-on: ubuntu-latest
steps:
- uses: bcoe/conventional-release-labels@v1
- uses: bcoe/conventional-release-labels@v1
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
name: Run Tests
name: Coverage Pages
on:
pull_request:
push:
branches:
- dev
workflow_dispatch:

permissions:
pull-requests: write
contents: read
contents: write

jobs:
unit_tests:
coverage_pages:
uses: OpenVoiceOS/gh-automations/.github/workflows/coverage.yml@dev

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

GitHub Actions reusable workflow pinning commit SHA vs branch reference best practices

💡 Result:

Best practice: prefer immutable refs (commit SHA) over mutable refs (branches)

1) If the reusable workflow is in a different repo

  • Security-first default: pin the called workflow to a full commit SHA (immutable) to reduce supply-chain risk from a moved tag/branch or repo compromise. This is the same guidance GitHub gives for actions generally, and org policies can enforce SHA pinning. [2], [3]
  • Avoid @main, @master, or any branch ref for “trusted” behavior—branches are mutable. [2]

Example:

jobs:
  call:
    uses: org/central-workflows/.github/workflows/ci.yml@<FULL_COMMIT_SHA>

2) If the reusable workflow is in the same repo

  • You can call it without owner/repo@ref; GitHub resolves it from the same commit as the caller workflow, which is effectively immutable for that run and avoids ref-drift. [1]

Example:

jobs:
  call:
    uses: ./.github/workflows/ci.yml

3) When a branch/tag ref can be acceptable (trade-off)

  • If you need easier updates across many repos, use a versioned tag or release branch (e.g., v1 / v1.2.3) only if you also put governance around it (protected tags/branches, required reviews, restricted push) and accept the residual risk that the ref can still move unless you enforce immutability. GitHub’s security guidance still prefers SHA pinning for strongest protection. [2]

4) Organization enforcement

  • GitHub Actions policies can require full commit SHAs for dependencies (including reusable workflow references), failing workflows that aren’t pinned. [3]

Rule of thumb:

  • External or cross-repo reuse: pin to full SHA. [2], [3]
  • Same-repo reuse: omit @ref and rely on same-commit resolution. [1]

Sources: GitHub Docs on reusable workflows/ref resolution [1]; GitHub Well-Architected Actions security recommendation on immutable pinning [2]; GitHub Changelog on SHA-pinning policy enforcement [3].


Pin reusable workflow to a full commit SHA.

@dev is a mutable branch reference that can change unexpectedly, introducing supply-chain risk. GitHub's security guidance recommends pinning external reusable workflows to a specific commit SHA for deterministic CI behavior.

Suggested fix
-    uses: OpenVoiceOS/gh-automations/.github/workflows/coverage.yml@dev
+    uses: OpenVoiceOS/gh-automations/.github/workflows/coverage.yml@<commit-sha>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/coverage-pages.yml at line 13, The workflow references a
mutable branch tag in the reusable workflow declaration ("uses:
OpenVoiceOS/gh-automations/.github/workflows/coverage.yml@dev"); replace the
branch ref with a specific commit SHA to pin the reusable workflow. Locate the
"uses: OpenVoiceOS/gh-automations/.github/workflows/coverage.yml@dev" line and
replace "@dev" with the full commit SHA (e.g., "@<full-commit-sha>") from the
OpenVoiceOS/gh-automations repo (obtain via git ls-remote or the repo's commit
history) so the workflow is deterministic and not tied to a moving branch.

secrets: inherit
with:
python_version: "3.12"
install_extras: "audio"
test_path: "test/unittests/"
coverage_source: "ovoscope"
install_extras: "audio,pydantic"
deploy_pages: true
16 changes: 16 additions & 0 deletions .github/workflows/coverage.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
name: Code Coverage

on:
pull_request:
branches: [dev]
workflow_dispatch:

jobs:
coverage:
uses: OpenVoiceOS/gh-automations/.github/workflows/coverage.yml@dev

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# Check if the file exists and examine its content around line 10
if [ -f ".github/workflows/coverage.yml" ]; then
  echo "=== File exists. Content around line 10: ==="
  cat -n .github/workflows/coverage.yml | sed -n '1,20p'
else
  echo "File not found"
fi

Repository: TigreGotico/ovoscope

Length of output: 590


🏁 Script executed:

# Check if there are other workflow files using similar patterns
echo "=== Searching for other workflow references to gh-automations ==="
rg "gh-automations" .github/workflows/ -A 1 -B 1

Repository: TigreGotico/ovoscope

Length of output: 2985


Pin reusable workflow to a commit SHA.

@dev is a mutable branch reference; pinning to a commit SHA ensures deterministic CI behavior and prevents unexpected upstream changes.

Suggested fix
-    uses: OpenVoiceOS/gh-automations/.github/workflows/coverage.yml@dev
+    uses: OpenVoiceOS/gh-automations/.github/workflows/coverage.yml@<commit-sha>
🧰 Tools
🪛 GitHub Check: CodeQL

[warning] 10-17: Workflow does not contain permissions
Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {{}}

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/coverage.yml at line 10, Replace the mutable branch
reference in the reusable workflow invocation "uses:
OpenVoiceOS/gh-automations/.github/workflows/coverage.yml@dev" with a specific
commit SHA (e.g., @<full-40-char-commit-sha>) so the workflow is pinned to an
immutable commit; update the "uses" value to use that exact SHA and commit the
change.

secrets: inherit
with:
python_version: '3.11'
coverage_source: 'ovoscope'
test_path: 'test/unittests/'
min_coverage: 0
Comment on lines +10 to +16

Check warning

Code scanning / CodeQL

Workflow does not contain permissions Medium

Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {}

Copilot Autofix

AI 4 months ago

In general, to fix this class of issue you add a permissions block either at the top level of the workflow (so it applies to all jobs without their own permissions) or within the specific job. The block should grant only the minimum access needed, typically contents: read for jobs that just check out and read code or run tests.

For this specific workflow, the best fix with minimal behavioral change is to define explicit read-only permissions at the workflow level, directly under name: or on:. Since this workflow delegates to a reusable workflow to run coverage, and there is no evidence that it needs any write access (no steps are shown that push commits, create releases, comment on PRs, etc.), we can safely restrict permissions to contents: read. If the reusable workflow needs additional scopes, they can be added later in that reusable workflow, but here we should at least limit the default token. Concretely, edit .github/workflows/coverage.yml to insert:

permissions:
  contents: read

near the top (e.g., after name: Code Coverage), leaving the rest of the workflow untouched.

Suggested changeset 1
.github/workflows/coverage.yml

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml
--- a/.github/workflows/coverage.yml
+++ b/.github/workflows/coverage.yml
@@ -1,5 +1,8 @@
 name: Code Coverage
 
+permissions:
+  contents: read
+
 on:
   pull_request:
     branches: [dev]
EOF
@@ -1,5 +1,8 @@
name: Code Coverage

permissions:
contents: read

on:
pull_request:
branches: [dev]
Copilot is powered by AI and may make mistakes. Always verify output.
Comment on lines +3 to +16

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Define explicit workflow permissions.

This workflow should declare least-privilege permissions instead of relying on defaults.

Suggested fix
 on:
   pull_request:
     branches: [dev]
   workflow_dispatch:

+permissions:
+  contents: read
+
 jobs:
   coverage:
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
on:
pull_request:
branches: [dev]
workflow_dispatch:
jobs:
coverage:
uses: OpenVoiceOS/gh-automations/.github/workflows/coverage.yml@dev
secrets: inherit
with:
python_version: '3.11'
coverage_source: 'ovoscope'
test_path: 'test/unittests/'
install_extras: 'audio,pydantic'
min_coverage: 0
on:
pull_request:
branches: [dev]
workflow_dispatch:
permissions:
contents: read
jobs:
coverage:
uses: OpenVoiceOS/gh-automations/.github/workflows/coverage.yml@dev
secrets: inherit
with:
python_version: '3.11'
coverage_source: 'ovoscope'
test_path: 'test/unittests/'
install_extras: 'audio,pydantic'
min_coverage: 0
🧰 Tools
🪛 GitHub Check: CodeQL

[warning] 10-17: Workflow does not contain permissions
Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {{}}

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/coverage.yml around lines 3 - 17, Add an explicit
top-level permissions mapping to this workflow (near the top alongside the
existing on: block) to enforce least-privilege instead of using defaults; for
example, add a permissions: block granting only the minimum scopes your reusable
workflow needs (e.g., contents: read and any other specific scopes required by
the reused workflow) so the coverage job (the coverage reusable workflow invoked
with uses: OpenVoiceOS/gh-automations/.github/workflows/coverage.yml@dev) does
not inherit broad default permissions.

18 changes: 0 additions & 18 deletions .github/workflows/coverage_pages.yml

This file was deleted.

14 changes: 14 additions & 0 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
name: Lint

on:
pull_request:
branches: [dev, master, main]
workflow_dispatch:

jobs:
lint:
uses: OpenVoiceOS/gh-automations/.github/workflows/lint.yml@dev

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, check the structure of the repository
git ls-files | head -20

Repository: TigreGotico/ovoscope

Length of output: 596


🏁 Script executed:

# Read the specific file mentioned in the review
cat -n .github/workflows/lint.yml

Repository: TigreGotico/ovoscope

Length of output: 450


🏁 Script executed:

# Search for other reusable workflow references in the codebase
find .github/workflows -type f -name "*.yml" -o -name "*.yaml" | xargs grep -l "uses:" | head -10

Repository: TigreGotico/ovoscope

Length of output: 415


🏁 Script executed:

# Check all uses of reusable workflows to see the pattern
find .github/workflows -type f \( -name "*.yml" -o -name "*.yaml" \) | xargs grep "uses:" | grep -E "@(dev|main|v[0-9]|[a-f0-9]{40})"

Repository: TigreGotico/ovoscope

Length of output: 1478


🌐 Web query:

GitHub Actions best practices reusable workflows mutable branch references commit SHA

💡 Result:

Best practices: reusable workflows + mutable refs (branch/tag) vs commit SHAs

  • Prefer pinning reusable workflows to a full commit SHA to make the called workflow immutable and reproducible:
    uses: org/repo/.github/workflows/ci.yml@<full-sha>
    This ensures every caller runs the exact same YAML, even if the default branch changes. [1]

  • Treat branch names (e.g., @main) and most tags (e.g., @v1) as mutable and therefore higher risk:

    • They can move without notice (breaking builds) or be changed in a compromise (supply-chain risk).
    • GitHub’s guidance for securing Actions emphasizes that full-length commit SHA pinning is the only immutable way to reference an action, and GitHub provides org/repo policies to enforce this. [2]
  • If you need “easy updates” (a moving target), use a tag/branch intentionally—then add controls:

    • Use protected branches, required reviews, and restricted write access on the workflow “source” repo.
    • Prefer a release tag you control over a branch, and establish a documented release process. (Note: tags can still be mutable unless you have controls/immutability.) [1][3]
  • Enforce and automate pinning:

    • GitHub Actions policies can require full commit SHAs (workflows fail if not pinned) and can help standardize this across an org. [3]
    • Use automation (e.g., Dependabot or internal tooling) to periodically update pins to newer reviewed SHAs.
  • Avoid user-controlled refs in sensitive contexts

    • Don’t rely on untrusted branch/ref values when it affects what code runs; guidance commonly recommends using commit SHAs (e.g., prefer head.sha over head.ref where applicable) to avoid ref manipulation. [4]

Sources

[1] GitHub Docs — Reusing workflows (reusable workflows can be referenced by SHA, tag, or branch; SHA ensures same YAML)
[2] GitHub Docs — Using GitHub’s security features to secure your use of GitHub Actions (full-length SHA pinning as immutable; enforcement policies)
[3] GitHub Changelog (2025-08-15) — GitHub Actions policy now supports blocking and SHA pinning actions
[4] GitHub Well-Architected — Securing GitHub Actions Workflows (recommend SHA over user-provided refs; pin to immutable references)


Use an immutable pin for the reusable lint workflow.

@dev is mutable and can introduce non-deterministic CI behavior and supply-chain risks. Pin to a full commit SHA instead.

Suggested fix
-    uses: OpenVoiceOS/gh-automations/.github/workflows/lint.yml@dev
+    uses: OpenVoiceOS/gh-automations/.github/workflows/lint.yml@<commit-sha>

Note: This pattern appears across multiple workflows (build-tests, coverage, downstream-check, license-check, pip-audit, publish-stable, release-preview, release-workflow, and repo-health). Consider updating all references to commit SHAs for consistency.

🧰 Tools
🪛 GitHub Check: CodeQL

[warning] 10-14: Workflow does not contain permissions
Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {{}}

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/lint.yml at line 10, The workflow is using a mutable ref
("uses: OpenVoiceOS/gh-automations/.github/workflows/lint.yml@dev") which should
be pinned to an immutable commit SHA; update the `uses` entry in this workflow
(and the other workflows referenced: build-tests, coverage, downstream-check,
license-check, pip-audit, publish-stable, release-preview, release-workflow,
repo-health) to point to a specific commit SHA of OpenVoiceOS/gh-automations
(replace the `@dev` suffix with `@<full-commit-sha>`), ensuring each reusable
workflow reference uses the exact same or appropriate commit SHA for
reproducible CI.

secrets: inherit
with:
ruff: true
pre_commit: false # set true if .pre-commit-config.yaml exists
Comment on lines +10 to +14

Check warning

Code scanning / CodeQL

Workflow does not contain permissions Medium

Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {}

Copilot Autofix

AI 4 months ago

In general, the fix is to explicitly define a permissions block that grants the minimal GITHUB_TOKEN scopes required for this lint job, either at the workflow root (for all jobs) or under the specific job (lint). Since this workflow only delegates to a reusable lint workflow, the host workflow itself likely only needs read access to the repository contents.

The least intrusive and clearest fix is to add a permissions block at the job level for lint, directly under the lint: job configuration, alongside uses, secrets, and with. For a linting workflow that merely reads code, contents: read is usually sufficient; if the reusable workflow needs additional scopes (e.g., to write PR comments), those can be added there as well, but given the information provided, we will use a conservative contents: read. Concretely, in .github/workflows/lint.yml, after line 9 (lint:) and before line 10 (uses: OpenVoiceOS/...), insert:

    permissions:
      contents: read

This ensures the lint job (and thus the GITHUB_TOKEN available to the reusable workflow) is limited to read-only access to repository contents, satisfying the CodeQL recommendation without changing the existing behavior of the linting logic.

Suggested changeset 1
.github/workflows/lint.yml

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
--- a/.github/workflows/lint.yml
+++ b/.github/workflows/lint.yml
@@ -7,6 +7,8 @@
 
 jobs:
   lint:
+    permissions:
+      contents: read
     uses: OpenVoiceOS/gh-automations/.github/workflows/lint.yml@dev
     secrets: inherit
     with:
EOF
@@ -7,6 +7,8 @@

jobs:
lint:
permissions:
contents: read
uses: OpenVoiceOS/gh-automations/.github/workflows/lint.yml@dev
secrets: inherit
with:
Copilot is powered by AI and may make mistakes. Always verify output.
Comment on lines +3 to +14

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Add explicit least-privilege permissions.

Please define permissions explicitly to avoid broad default token scopes.

Suggested fix
 on:
   pull_request:
     branches: [dev, master, main]
   workflow_dispatch:

+permissions:
+  contents: read
+
 jobs:
   lint:
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
on:
pull_request:
branches: [dev, master, main]
workflow_dispatch:
jobs:
lint:
uses: OpenVoiceOS/gh-automations/.github/workflows/lint.yml@dev
secrets: inherit
with:
ruff: true
pre_commit: false # set true if .pre-commit-config.yaml exists
on:
pull_request:
branches: [dev, master, main]
workflow_dispatch:
permissions:
contents: read
jobs:
lint:
uses: OpenVoiceOS/gh-automations/.github/workflows/lint.yml@dev
secrets: inherit
with:
ruff: true
pre_commit: false # set true if .pre-commit-config.yaml exists
🧰 Tools
🪛 GitHub Check: CodeQL

[warning] 10-14: Workflow does not contain permissions
Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {{}}

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/lint.yml around lines 3 - 14, Add an explicit
least-privilege permissions block to the workflow so the "lint" job doesn't rely
on broad default token scopes: update the top-level workflow to include a
permissions mapping (e.g., set contents: read, checks: write or as minimal
needed for the OpenVoiceOS/gh-automations action) and ensure the "lint" job
still passes secrets: inherit and with inputs (ruff, pre_commit) to the
referenced reusable workflow; reference the "lint" job and the existing uses:
OpenVoiceOS/gh-automations/.github/workflows/lint.yml@dev when adding the
permissions block.

File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
name: Release Preview

on:
pull_request:
branches: [dev]
Expand All @@ -9,5 +10,5 @@ jobs:
uses: OpenVoiceOS/gh-automations/.github/workflows/release-preview.yml@dev
secrets: inherit
with:
package_name: "ovoscope"
version_file: "ovoscope/version.py"
package_name: 'ovoscope'
version_file: 'ovoscope/version.py'
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
name: Repo Health

on:
pull_request:
branches: [dev]
branches: [dev, master, main]
workflow_dispatch:

jobs:
repo_health:
uses: OpenVoiceOS/gh-automations/.github/workflows/repo-health.yml@dev
secrets: inherit
with:
version_file: 'ovoscope/version.py'
126 changes: 126 additions & 0 deletions AUDIT.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,129 @@ builds non-reproducible if the upstream action changes.
## Next Steps (Priority Order)
1. Address CaptureSession.capture() return value — usability
2. Pin CI action refs — reproducibility

---

## Audit [2026-03-12] — Correctness Bugs, Coverage Gaps, Docs

### ~~[CRITICAL] `pipeline.py` race condition in `match()`~~ ✅ FIXED

**Evidence**: `ovoscope/pipeline.py:159–181` — a single `threading.Event` was
shared for both success (`intent.service.skills.activated`) and failure
(`intent_failure`, `mycroft.skill.handler.start`) handlers. If `intent_failure`
fired first, `captured[0]` would be a failure message returned as success.
Additionally, `done.wait()` return value was not checked; a timeout silently
returned `captured[0]` (or raised `IndexError` on empty list).

**Fix**: Separate `_matched` and `_failed` events; only the success handler
populates `captured`. Timeout and failure both return `None`.
Source: `ovoscope/pipeline.py:149`.

### ~~[MAJOR] `diff.py` — subset comparison silently ignores extra keys~~ ✅ FIXED

**Evidence**: `ovoscope/diff.py:121–138` — `_dict_diff()` only iterated keys
in `expected`, so unexpected keys in `actual` were never flagged.

**Fix**: Added `strict: bool = False` parameter to `_dict_diff()` and
`diff_fixtures()`. When `strict=True`, extra keys in `actual` are included in
the diff detail. Default `False` preserves existing behaviour.
Source: `ovoscope/diff.py:121`.

### ~~[MINOR] `bus_coverage.py` — dead method `_skill_id_for_handler()`~~ ✅ FIXED

**Evidence**: `ovoscope/bus_coverage.py:745–763` — `_skill_id_for_handler()`
was never called anywhere in the codebase (verified by grep). Its logic is
a subset of `_skill_id_for_closure()` which is the method actually used.

**Fix**: Method deleted.
Source: formerly `ovoscope/bus_coverage.py:745`.

### ~~[MAJOR] No unit tests for `media.py` (`MockOCPBackend`, `OCPCaptureSession`, `OCPPlayerHarness`)~~ ✅ FIXED

**Evidence**: `ovoscope/media.py` had no corresponding test file.

**Fix**: Created `test/unittests/test_media.py` — 20 tests covering
`MockOCPBackend` state transitions, `OCPCaptureSession` message accumulation,
and assertion helpers.

### ~~[MAJOR] No unit tests for `remote_recorder.py`~~ ✅ FIXED

**Evidence**: `ovoscope/remote_recorder.py` had no corresponding test file.

**Fix**: Created `test/unittests/test_remote_recorder.py` — 15 tests covering
constructor defaults, `_parse_url`, connect/disconnect lifecycle, `record()` with
mocked bus client, timeout handling, and fixture serialization.

### ~~[MINOR] Deprecated `ovos_utils.messagebus.Message` import in `test_phal.py`~~ ✅ FIXED

**Evidence**: `test/unittests/test_phal.py:23` — imported `Message` from
deprecated `ovos_utils.messagebus` instead of canonical `ovos_bus_client.message`.

**Fix**: Import changed to `from ovos_bus_client.message import Message`.
Source: `test/unittests/test_phal.py:23`.

### ~~[MINOR] `SUGGESTIONS.md` missing~~ ✅ FIXED

**Evidence**: `SUGGESTIONS.md` was absent despite being required by `AGENTS.md`.

**Fix**: `SUGGESTIONS.md` created with 10 structured proposals.

---
## Bus Coverage Module — Full Audit [2026-03-12]

### ~~[CRITICAL] async_responses excluded from emitter coverage~~ ✅ FIXED
`execute()` now passes `messages + list(capture.async_responses)` to `record_session()`. Source: `ovoscope/__init__.py:666`.

### ~~[CRITICAL] Unattributed expected_messages silently disappear~~ ✅ FIXED
Both observed and expected messages with no `skill_id` in context now fall back to the `"__core__"` sentinel bucket. Source: `BusCoverageTracker.record_session` — `ovoscope/bus_coverage.py:510`.

### [CRITICAL] Registration-time handlers always show NOT TESTED — misleading
**Evidence**: `ovoscope/bus_coverage.py:368` / `ovoscope/__init__.py:647` — `snapshot_listeners()` is called after `MiniCroft` reaches READY. Handlers invoked *during* skill loading were called *before* the snapshot and will always show 0 invocations.
**Impact**: Users see `register_intent: NOT TESTED` and conclude their intent registration failed.
**Status**: Documented in `docs/bus-coverage.md` Limitations as a known structural constraint. A `LOAD_TIME` tag or pre-READY tracking remains a future enhancement.

### ~~[CRITICAL] Non-skill messages silently excluded from emitter coverage~~ ✅ FIXED
Messages with no `skill_id` in context are now attributed to the `"__core__"` bucket in `record_session()`. Source: `ovoscope/bus_coverage.py:510`.

### ~~[MAJOR] Unused `skill_map` variable in `record_session()`~~ ✅ FIXED
Dead `skill_map = self._skill_instance_map()` call removed from `record_session()`.

### ~~[MAJOR] `_get_bus_events()` called three times in `snapshot_listeners()`~~ ✅ FIXED
`bus_events = self._get_bus_events()` is now called once and reused across all three passes. Source: `ovoscope/bus_coverage.py:432`.

### ~~[MAJOR] Double-stop risk in `cmd_bus_coverage()`~~ ✅ FIXED
`test.managed = False` is now set explicitly before `test.execute()`. The `finally` block is the sole owner of `mc.stop()`. The redundant `mc.stop()` in the `except Exception` branch was also removed. Source: `ovoscope/cli.py:349`.

### ~~[MAJOR] `pytest_terminal_summary` hook uses private pytest internals~~ ✅ FIXED
`bus_coverage_session` fixture now stores merged reports on `request.config._bus_coverage_reports` in its teardown. `pytest_terminal_summary` reads that list — no private attrs. Source: `ovoscope/pytest_plugin.py:191`.

### [MAJOR] `once()` handlers invisible after firing
**Evidence**: `bus_coverage.py:368` — one-shot handlers fired during skill loading are de-registered before the snapshot.
**Status**: Documented in `docs/bus-coverage.md` Limitations. Pre-READY tracking is a future enhancement.

### [MAJOR] `ignore_messages` list silently excludes messages from emitter coverage
**Evidence**: `ovoscope/__init__.py:504-505` — messages in `ignore_messages` never reach `responses`.
**Status**: Documented in `docs/bus-coverage.md` Limitations. Passing ignored messages as a separate bucket is a future enhancement.

### ~~[MAJOR] No JSON schema version field~~ ✅ FIXED
`to_json()` now includes `"schema_version": "1"` as the first key. Source: `ovoscope/bus_coverage.py:297`.

### ~~[MINOR] Column widths hardcoded in `print_report()`~~ ✅ FIXED
`col_w = max(len(s.skill_id) for s in self.skills) + 2` now drives column width dynamically. Source: `ovoscope/bus_coverage.py:237`.

### ~~[MINOR] Coverage summary printed before test assertions~~ ✅ FIXED
`print_bus_coverage` block moved to after all assertions, just before `managed` teardown. Source: `ovoscope/__init__.py:795`.

### ~~[MINOR] `bus_coverage_report` type hint uses `Optional[Any]`~~ ✅ FIXED
Type hint changed to `Optional["BusCoverageReport"]`. Source: `ovoscope/__init__.py:589`.

### ~~[NITPICK] `_SKIP` set has fragile `"type"` entry~~ ✅ FIXED
Pass 2 now uses `isinstance(owner, type)` check to skip class objects, and the `"type"` string entry removed from `_SKIP`. Source: `ovoscope/bus_coverage.py:450`.

### ~~Docs gaps (bus-coverage.md)~~ ✅ FIXED
All five missing Limitations items added to `docs/bus-coverage.md`:
- Registration-time handlers always show NOT TESTED
- Pipeline matching is not bus-driven
- `async_responses` now included (fix note)
- `ignore_messages` types excluded
- Core services use `__core__` bucket (not 0/0 — fix note)
51 changes: 51 additions & 0 deletions FAQ.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,46 @@
# FAQ — `ovoscope`
## How do I measure which bus message handlers my tests actually exercise?

Use bus coverage: set `track_bus_coverage=True` on `End2EndTest`. After
`execute()`, `test.bus_coverage_report` contains a `BusCoverageReport` with
per-skill listener coverage (which `bus.on()` registrations were triggered)
and emitter coverage (which message types were observed / asserted).

```python
test = End2EndTest(
skill_ids=["my-skill.author"],
source_message=message,
expected_messages=[...],
track_bus_coverage=True,
print_bus_coverage=True, # print inline summary
)
test.execute()
print(test.bus_coverage_report.to_json())
```

See [docs/bus-coverage.md](docs/bus-coverage.md) for the full reference.
`BusCoverageTracker` — `ovoscope/bus_coverage.py:242`.

## How do I get an aggregate bus coverage report across an entire test suite?

Use the `bus_coverage_session` pytest fixture. Each test calls
`bus_coverage_session.add(test.bus_coverage_report)` after `execute()`. A
merged table is printed automatically at session end. See
[docs/bus-coverage.md](docs/bus-coverage.md).

## How do I run bus coverage from the command line without writing pytest tests?

Use the `ovoscope bus-coverage` subcommand:

```bash
ovoscope bus-coverage path/to/fixtures/ # table report
ovoscope bus-coverage path/to/fixtures/ --format json
ovoscope bus-coverage path/to/fixtures/ --verbose # per-msg detail
```

`cmd_bus_coverage` — `ovoscope/cli.py`.


## How do I test AudioService or PlaybackService without real audio hardware?
Use `AudioServiceHarness` or `PlaybackServiceHarness` from `ovoscope.audio`. Both run on a
`FakeBus` with `MockAudioBackend`/`MockTTS` respectively — no real audio device, TTS engine,
Expand Down Expand Up @@ -356,6 +398,15 @@ with GUICaptureSession(mc.bus) as gui:
gui.assert_page_shown("my_skill", "main.qml")
```

### How do I assert that a skill set a specific session data key in a namespace?
Use `assert_namespace_has_key()`:
```python
with GUICaptureSession(mc.bus) as gui:
# ... trigger interaction ...
gui.assert_namespace_has_key("my_skill", "temperature")
```
This checks that a `mycroft.session.set` message was captured containing the given key in the specified namespace. See [docs/gui-testing.md](docs/gui-testing.md).

---

## Coverage Scanner
Expand Down
Loading
Loading