Skip to content
Closed
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
1 change: 1 addition & 0 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ Last updated: 2026-03-11
- The desktop app is scaffolded as `Tauri + Vite + React`, but initial verification keeps Rust packaging out of the default quickcheck path.
- The desktop shell uses an explicit Tauri CSP that only allows self-hosted assets, inline styles, Tauri IPC, and loopback development traffic.
- Mechanical gates focus on lint, typecheck, unit tests, coverage for Python, and documentation presence.
- Python quality gates also require 100% docstring coverage via `package.json` script `check:python-docstrings`, enforced with Ruff rules `D100` through `D107` across tracked packages, modules, classes, nested classes, functions, methods (including `__init__`), `services/analysis-engine` tests, and repo-owned Python scripts.
- Mechanical gates also enforce security document presence, plan `Security Notes`, and basic forbidden-pattern checks.
- Security context is part of architecture, not just implementation detail; docs and plans must record the trust boundary touched by risky changes.
- Supply-chain controls are part of the bootstrap architecture, not a release-afterthought.
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,10 @@
"check:security-gates": "python3 scripts/checks/security_gates.py",
"check:supply-chain": "python3 scripts/checks/verify_supply_chain.py",
"check:github-bootstrap": "python3 scripts/checks/verify_github_bootstrap_policy.py",
"check:python-docstrings": "sh -c 'cd services/analysis-engine && uv run ruff check src tests ../../scripts --select D100,D101,D102,D103,D104,D105,D106,D107'",
"ruff:check": "sh -c 'cd services/analysis-engine && uv run ruff check src tests'",
"ruff:format:check": "sh -c 'cd services/analysis-engine && uv run ruff format --check src tests'",
"lint": "npm run lint:workspaces && npm run check:docs && npm run check:security-notes && npm run check:security-gates && npm run check:supply-chain && npm run check:github-bootstrap && npm run ruff:check && npm run ruff:format:check",
"lint": "npm run lint:workspaces && npm run check:docs && npm run check:security-notes && npm run check:security-gates && npm run check:supply-chain && npm run check:github-bootstrap && npm run check:python-docstrings && npm run ruff:check && npm run ruff:format:check",
"typecheck": "npm run typecheck --workspaces --if-present && sh -c 'cd services/analysis-engine && uv run mypy src'",
"test": "npm run test --workspaces --if-present && sh -c 'cd services/analysis-engine && uv run pytest tests --cov=src/bandscope_analysis --cov-report=term-missing --cov-fail-under=100'",
"build": "npm run build --workspaces --if-present",
Expand Down
4 changes: 4 additions & 0 deletions scripts/checks/security_gates.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
"""Scan tracked source files for disallowed security patterns."""

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

โš ๏ธ Potential issue | ๐ŸŸก Minor

tracked๋ผ๋Š” ํ‘œํ˜„์ด ํ˜„์žฌ ๋™์ž‘๊ณผ ๋งž์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

์ด ์Šคํฌ๋ฆฝํŠธ๋Š” Line 47์˜ Path(".").rglob("*")๋กœ ์ž‘์—… ํŠธ๋ฆฌ ์ „์ฒด๋ฅผ ํ›‘๊ธฐ ๋•Œ๋ฌธ์—, ํ˜„์žฌ docstring์ฒ˜๋Ÿผ ์ถ”์ ๋œ ํŒŒ์ผ๋งŒ ๊ฒ€์‚ฌํ•˜๋Š” ๋™์ž‘์€ ์•„๋‹™๋‹ˆ๋‹ค. ๋กœ์ปฌ quickcheck๊ฐ€ ์ž„์‹œ/๋ฏธ์ถ”์  ํŒŒ์ผ์— ์˜ํ•ด ํ”๋“ค๋ฆด ์ˆ˜ ์žˆ์œผ๋‹ˆ, ์„ค๋ช…์„ ์‹ค์ œ ๋™์ž‘์— ๋งž์ถ”๊ฑฐ๋‚˜ ํŒŒ์ผ ์ˆ˜์ง‘ ๋กœ์ง์„ ์ถ”์  ํŒŒ์ผ ๊ธฐ์ค€์œผ๋กœ ๋งž์ถฐ ์ฃผ์„ธ์š”.

์˜ˆ์‹œ ์ˆ˜์ •์•ˆ
-"""Scan tracked source files for disallowed security patterns."""
+"""Scan repository files for disallowed security patterns."""
๐Ÿ“ 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
"""Scan tracked source files for disallowed security patterns."""
"""Scan repository files for disallowed security patterns."""
๐Ÿค– Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/checks/security_gates.py` at line 1, The module docstring claims it
scans "tracked" files but the implementation uses Path(".").rglob("*") (see
Path(".").rglob) which iterates the whole working tree; update the code to make
behavior and docs consistent: either change the top-level docstring to state it
scans the entire workspace, or change the file-collection logic to only include
tracked files (e.g., replace Path(".").rglob("*") usage with a git-tracked file
list via subprocess/gitpython or filter by tracked paths returned from `git
ls-files`) and ensure functions that rely on that list (the file iteration loop)
use the new tracked-only list.


from pathlib import Path
import re
import sys
Expand Down Expand Up @@ -32,12 +34,14 @@


def should_scan(path: Path) -> bool:
"""Return whether a path should be scanned for security-pattern violations."""
return path.suffix in TARGET_EXTENSIONS and not any(
part in EXCLUDED_PARTS for part in path.parts
)


def main() -> int:
"""Return a failing exit code when a forbidden security pattern is found."""
violations: list[str] = []

for path in Path(".").rglob("*"):
Expand Down
3 changes: 3 additions & 0 deletions scripts/checks/verify_docs.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
"""Verify that required repository documentation files and references exist."""

from pathlib import Path
import sys

Expand Down Expand Up @@ -62,6 +64,7 @@


def main() -> int:
"""Return a failing exit code when required docs or references are missing."""
missing = [str(path) for path in REQUIRED_PATHS if not path.exists()]
if missing:
print("Missing required docs:")
Expand Down
3 changes: 3 additions & 0 deletions scripts/checks/verify_github_bootstrap_policy.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
"""Verify that GitHub bootstrap policy docs are present and referenced."""

from pathlib import Path


Expand All @@ -16,6 +18,7 @@


def main() -> int:
"""Return a failing exit code when bootstrap policy docs drift out of sync."""
if not REQUIRED_PATH.exists():
print(f"Missing GitHub bootstrap policy: {REQUIRED_PATH}")
return 1
Expand Down
4 changes: 4 additions & 0 deletions scripts/checks/verify_security_notes.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
"""Verify that design-plan documents include a complete Security Notes section."""

from pathlib import Path
import sys

Expand All @@ -15,6 +17,7 @@


def security_notes_section(content: str) -> str:
"""Extract the lowercased Security Notes section from a plan document."""
lowered = content.lower()
marker = SECURITY_NOTES_TEXT.lower()
start = lowered.find(marker)
Expand All @@ -34,6 +37,7 @@ def security_notes_section(content: str) -> str:


def main() -> int:
"""Return a failing exit code when plan files are missing security notes."""
Comment on lines 39 to +40

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

๐Ÿงน Nitpick | ๐Ÿ”ต Trivial

main() docstring์ด ์‹ค์ œ ์‹คํŒจ ์กฐ๊ฑด์„ ๋œ ์„ค๋ช…ํ•ฉ๋‹ˆ๋‹ค.

์ด ํ•จ์ˆ˜๋Š” Security Notes ์„น์…˜ ์ž์ฒด๊ฐ€ ์—†์„ ๋•Œ๋งŒ ์•„๋‹ˆ๋ผ, Line 48-50์ฒ˜๋Ÿผ ํ•„์ˆ˜ ํ•˜์œ„ ์„น์…˜์ด ๋น ์ ธ๋„ ์‹คํŒจ๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. ํ˜„์žฌ ๋ฌธ๊ตฌ๋ฉด ์ฒดํฌ ๋ฒ”์œ„๋ฅผ ์˜คํ•ดํ•˜๊ธฐ ์‰ฌ์šฐ๋‹ˆ ์„ค๋ช…์„ ๋„“ํ˜€ ๋‘๋Š” ํŽธ์ด ์ข‹๊ฒ ์Šต๋‹ˆ๋‹ค.

์˜ˆ์‹œ ์ˆ˜์ •์•ˆ
 def main() -> int:
-    """Return a failing exit code when plan files are missing security notes."""
+    """Return a failing exit code when plan files are missing Security Notes or required subsections."""
๐Ÿ“ 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
def main() -> int:
"""Return a failing exit code when plan files are missing security notes."""
def main() -> int:
"""Return a failing exit code when plan files are missing Security Notes or required subsections."""
๐Ÿค– Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/checks/verify_security_notes.py` around lines 39 - 40, Update the
main() docstring to accurately reflect that the function returns a failing exit
code not only when the "Security Notes" section itself is missing, but also when
required subsections inside that section are absent (i.e., the presence and
completeness of mandatory sub-sections checked later in the function such as
those validated in the block around lines checking for required sub-sections).
Mention the specific behavior: it fails for a missing Security Notes section or
for missing required subsections, so readers of the code understand the full
validation performed by main().

missing: list[str] = []
for path in sorted(PLAN_DIR.glob("*.md")):
content = path.read_text(encoding="utf-8")
Expand Down
8 changes: 8 additions & 0 deletions scripts/checks/verify_supply_chain.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
"""Verify that repository-controlled supply-chain controls stay in place."""

from pathlib import Path
import re

Expand Down Expand Up @@ -28,10 +30,12 @@


def verify_required_files() -> list[str]:
"""Return missing files required by the supply-chain baseline."""
return [str(path) for path in REQUIRED_FILES if not path.exists()]


def verify_pinned_actions() -> list[str]:
"""Return workflow actions that are not pinned to immutable SHAs."""
violations: list[str] = []
workflow_paths = sorted(Path(".github/workflows").glob("*.yml")) + sorted(
Path(".github/workflows").glob("*.yaml")
Expand All @@ -53,6 +57,7 @@ def verify_pinned_actions() -> list[str]:


def verify_dependabot_coverage() -> list[str]:
"""Return missing Dependabot ecosystems from the repo configuration."""
path = Path(".github/dependabot.yml")
if not path.exists():
return [f"missing file: {path}"]
Expand All @@ -65,13 +70,15 @@ def verify_dependabot_coverage() -> list[str]:


def read_workflow(path: Path, label: str, missing: list[str]) -> str:
"""Read a workflow file, recording a missing-file violation when absent."""
if not path.exists():
missing.append(f"missing file: {path}")
return ""
return path.read_text(encoding="utf-8")


def verify_workflow_coverage() -> list[str]:
"""Return workflow trigger and artifact coverage violations."""
missing: list[str] = []
ci = read_workflow(Path(".github/workflows/ci.yml"), "ci", missing)
for token in ["develop", "main", "pull_request", "push", "ci / build-and-test"]:
Expand Down Expand Up @@ -153,6 +160,7 @@ def verify_workflow_coverage() -> list[str]:


def main() -> int:
"""Return a failing exit code when supply-chain controls are incomplete."""
violations: list[str] = []
violations.extend(f"missing file: {item}" for item in verify_required_files())
violations.extend(verify_pinned_actions())
Expand Down
30 changes: 28 additions & 2 deletions scripts/release/package_desktop_artifact.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
"""Package desktop build outputs into traceable release artifacts."""

from __future__ import annotations

from pathlib import Path
Expand All @@ -8,6 +10,7 @@


def sha256_file(path: Path) -> str:
"""Return the SHA-256 digest for a file."""
digest = hashlib.sha256()
with path.open("rb") as handle:
for chunk in iter(lambda: handle.read(1024 * 1024), b""):
Expand All @@ -16,9 +19,16 @@ def sha256_file(path: Path) -> str:


def normalized_platform() -> str:
"""Return the normalized artifact platform label for the current environment."""
if artifact_platform := os.environ.get("BANDSCOPE_ARTIFACT_OS"):
return artifact_platform

target_triple = os.environ.get("BANDSCOPE_TARGET_TRIPLE", "")
if "windows" in target_triple:
return "windows"
if "apple-darwin" in target_triple:
return "macos"

system = platform.system().lower()
if system == "darwin":
return "macos"
Expand All @@ -27,9 +37,16 @@ def normalized_platform() -> str:


def normalized_architecture() -> str:
"""Return the normalized artifact architecture label for the current environment."""
if artifact_arch := os.environ.get("BANDSCOPE_ARTIFACT_ARCH"):
return artifact_arch

target_triple = os.environ.get("BANDSCOPE_TARGET_TRIPLE", "")
if target_triple.startswith(("x86_64", "amd64")):
return "amd64"
if target_triple.startswith(("aarch64", "arm64")):
return "arm64"

machine = platform.machine().lower()
if machine in {"x86_64", "amd64"}:
return "amd64"
Expand All @@ -40,6 +57,7 @@ def normalized_architecture() -> str:


def artifact_identity() -> dict[str, str]:
"""Build the archive and manifest names for the current artifact target."""
git_sha = os.environ.get("GITHUB_SHA", "local")[:12]
target_platform = normalized_platform()
target_arch = normalized_architecture()
Expand All @@ -53,17 +71,25 @@ def artifact_identity() -> dict[str, str]:


def expected_binary_path(repo_root: Path) -> Path:
system = normalized_platform()
"""Return the expected desktop binary path for the selected target triple."""
target_triple = os.environ.get("BANDSCOPE_TARGET_TRIPLE")
if target_triple and "windows" in target_triple:
system = "windows"
elif target_triple and "apple-darwin" in target_triple:
system = "macos"
else:
system = normalized_platform()
binary_name = (
"bandscope-desktop.exe" if system == "windows" else "bandscope-desktop"
)
target_root = repo_root / "apps" / "desktop" / "src-tauri" / "target"
if target_triple := os.environ.get("BANDSCOPE_TARGET_TRIPLE"):
if target_triple:
target_root = target_root / target_triple
return target_root / "release" / binary_name
Comment on lines 73 to 88

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

โš ๏ธ Potential issue | ๐Ÿ”ด Critical

๐Ÿงฉ Analysis chain

๐Ÿ Script executed:

#!/bin/bash
# Verify whether package_desktop_artifact.py is ever invoked with
# BANDSCOPE_TARGET_TRIPLE but without BANDSCOPE_ARTIFACT_OS / BANDSCOPE_ARTIFACT_ARCH.

rg -n -C3 'package_desktop_artifact\.py|BANDSCOPE_TARGET_TRIPLE|BANDSCOPE_ARTIFACT_OS|BANDSCOPE_ARTIFACT_ARCH'

Repository: seonghobae/bandscope

Length of output: 9738


๐Ÿ Script executed:

#!/bin/bash
# Read the test that checks Windows target on non-Windows host (lines 56-76)
sed -n '56,76p' services/analysis-engine/tests/test_release_packaging.py

Repository: seonghobae/bandscope

Length of output: 784


๐Ÿ Script executed:

#!/bin/bash
# Read the full main() function to see how binary path and artifact identity are used together
sed -n '107,150p' scripts/release/package_desktop_artifact.py

Repository: seonghobae/bandscope

Length of output: 1415


๐Ÿ Script executed:

#!/bin/bash
# Check if there's any validation logic around BANDSCOPE_TARGET_TRIPLE and artifact overrides
grep -n "raise\|ValueError\|AssertionError\|if.*BANDSCOPE_TARGET_TRIPLE" scripts/release/package_desktop_artifact.py

Repository: seonghobae/bandscope

Length of output: 346


ํƒ€๊นƒ ํŠธ๋ฆฌํ”Œ๊ณผ ์•„ํ‹ฐํŒฉํŠธ ์‹๋ณ„์ž๊ฐ€ ๋ถ„๋ฆฌ๋œ ์ƒํƒœ์—์„œ ๊ต์ฐจ ๋นŒ๋“œ ์‹œ ์•„์นด์ด๋ธŒ ์ด๋ฆ„ ๋ถˆ์ผ์น˜ ์œ„ํ—˜

expected_binary_path()๋Š” BANDSCOPE_TARGET_TRIPLE๋กœ ์˜ฌ๋ฐ”๋ฅธ ๋ฐ”์ด๋„ˆ๋ฆฌ ์ด๋ฆ„(.exe ๋“ฑ)์„ ์„ ํƒํ•˜์ง€๋งŒ, artifact_identity()๋Š” ๊ณ„์† ํ˜ธ์ŠคํŠธ ํ”Œ๋žซํผ ๊ฐ’์œผ๋กœ archive_name์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. BANDSCOPE_ARTIFACT_OS์™€ BANDSCOPE_ARTIFACT_ARCH๋ฅผ ์„ค์ •ํ•˜์ง€ ์•Š์€ ์ฑ„๋กœ Windows ํƒ€๊นƒ ํŠธ๋ฆฌํ”Œ๋กœ macOS ํ˜ธ์ŠคํŠธ์—์„œ ํŒจํ‚ค์ง•ํ•˜๋ฉด, ์•„์นด์ด๋ธŒ ํŒŒ์ผ๋ช…์€ "macos"๋ฅผ ํฌํ•จํ•˜์ง€๋งŒ ๋‚ด์šฉ์€ Windows ๋ฐ”์ด๋„ˆ๋ฆฌ๊ฐ€ ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํƒ€๊นƒ ํŠธ๋ฆฌํ”Œ์—์„œ ์‹๋ณ„์ž๋ฅผ ํ•จ๊ป˜ ์œ ๋„ํ•˜๊ฑฐ๋‚˜, ๊ต์ฐจ ๋นŒ๋“œ ์‹œ ๋‘ ํ™˜๊ฒฝ๋ณ€์ˆ˜ ๋ˆ„๋ฝ์„ ๋ช…์‹œ์ ์œผ๋กœ ์‹คํŒจ ์ฒ˜๋ฆฌํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

๐Ÿค– Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/release/package_desktop_artifact.py` around lines 61 - 76,
expected_binary_path() currently uses BANDSCOPE_TARGET_TRIPLE to pick the binary
name, but artifact_identity() still uses the host platform to form the archive
name, causing mismatched archive names during cross-builds; update
artifact_identity() to derive BANDSCOPE_ARTIFACT_OS and BANDSCOPE_ARTIFACT_ARCH
from BANDSCOPE_TARGET_TRIPLE when that env var is present (use the same mapping
logic used in expected_binary_path() to map "windows"/"apple-darwin"/others to
artifact OS and arch), and if BANDSCOPE_TARGET_TRIPLE is not set and
BANDSCOPE_ARTIFACT_OS/ARCH are missing, raise an explicit error so packaging
fails fast rather than producing a misleading archive name; reference
expected_binary_path, artifact_identity, BANDSCOPE_TARGET_TRIPLE,
BANDSCOPE_ARTIFACT_OS, and BANDSCOPE_ARTIFACT_ARCH when making the change.



def main() -> int:
"""Package the desktop binary, frontend assets, and metadata into a zip archive."""
repo_root = Path(__file__).resolve().parents[2]
binary_path = expected_binary_path(repo_root)
frontend_dist = repo_root / "apps" / "desktop" / "dist"
Expand Down
2 changes: 2 additions & 0 deletions services/analysis-engine/src/bandscope_analysis/health.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@


class HealthReport(TypedDict):
"""Typed health payload returned by the analysis-engine bootstrap API."""

service: Literal["bandscope-analysis"]
status: Literal["ready"]
pipeline_stages: list[Literal["decode", "draft", "separate", "persist"]]
Expand Down
3 changes: 3 additions & 0 deletions services/analysis-engine/tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
"""Shared pytest helpers for analysis-engine and harness verification tests."""

from __future__ import annotations

from importlib.util import module_from_spec, spec_from_file_location
Expand All @@ -6,6 +8,7 @@


def load_module(relative_path: str, module_name: str) -> ModuleType:
"""Load a repository Python module from a path outside the package root."""
repo_root = Path(__file__).resolve().parents[3]
module_path = repo_root / relative_path
spec = spec_from_file_location(module_name, module_path)
Expand Down
3 changes: 3 additions & 0 deletions services/analysis-engine/tests/test_api.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
"""Tests for the public analysis-engine API helpers."""

from bandscope_analysis.api import get_analysis_status


def test_get_analysis_status_returns_health_payload() -> None:
"""Ensure the API helper returns the expected bootstrap status payload."""
assert get_analysis_status() == {
"service": "bandscope-analysis",
"status": "ready",
Expand Down
3 changes: 3 additions & 0 deletions services/analysis-engine/tests/test_health.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
"""Tests for the analysis-engine health helpers."""

from bandscope_analysis.health import build_health_report


def test_build_health_report_exposes_bootstrap_defaults() -> None:
"""Ensure the bootstrap health payload exposes the expected default stages."""
assert build_health_report() == {
"service": "bandscope-analysis",
"status": "ready",
Expand Down
58 changes: 58 additions & 0 deletions services/analysis-engine/tests/test_release_packaging.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
"""Tests for desktop release packaging helpers and artifact metadata."""

from __future__ import annotations

from pathlib import Path
Expand All @@ -8,6 +10,7 @@
def test_release_packaging_includes_architecture_in_artifact_identity(
monkeypatch,
) -> None:
"""Ensure artifact names encode the selected platform and architecture."""
packaging = load_module(
"scripts/release/package_desktop_artifact.py", "package_desktop_artifact"
)
Expand All @@ -26,7 +29,34 @@ def test_release_packaging_includes_architecture_in_artifact_identity(
}


def test_release_packaging_derives_artifact_identity_from_target_triple(
monkeypatch,
) -> None:
"""Ensure target triples drive archive naming when explicit artifact env vars are absent."""
packaging = load_module(
"scripts/release/package_desktop_artifact.py",
"package_desktop_artifact_identity_target",
)

monkeypatch.setenv("GITHUB_SHA", "fedcba9876543210")
monkeypatch.delenv("BANDSCOPE_ARTIFACT_OS", raising=False)
monkeypatch.delenv("BANDSCOPE_ARTIFACT_ARCH", raising=False)
monkeypatch.setenv("BANDSCOPE_TARGET_TRIPLE", "x86_64-pc-windows-msvc")
monkeypatch.setattr(packaging.platform, "system", lambda: "Darwin")
monkeypatch.setattr(packaging.platform, "machine", lambda: "arm64")

artifact = packaging.artifact_identity()

assert artifact == {
"platform": "windows",
"arch": "amd64",
"archive_name": "bandscope-windows-amd64-fedcba987654.zip",
"manifest_name": "bandscope-windows-amd64-fedcba987654.manifest.txt",
}


def test_expected_binary_path_uses_target_triple_when_provided(monkeypatch, tmp_path: Path) -> None:
"""Ensure target triples redirect packaging to the expected Tauri output path."""
packaging = load_module(
"scripts/release/package_desktop_artifact.py", "package_desktop_artifact_target"
)
Expand All @@ -47,7 +77,34 @@ def test_expected_binary_path_uses_target_triple_when_provided(monkeypatch, tmp_
)


def test_expected_binary_path_derives_windows_extension_from_target_triple(
monkeypatch, tmp_path: Path
) -> None:
"""Ensure Windows target triples select the .exe packaging path on non-Windows hosts."""
packaging = load_module(
"scripts/release/package_desktop_artifact.py", "package_desktop_artifact_windows_target"
)

monkeypatch.delenv("BANDSCOPE_ARTIFACT_OS", raising=False)
monkeypatch.setenv("BANDSCOPE_TARGET_TRIPLE", "x86_64-pc-windows-msvc")
monkeypatch.setattr(packaging.platform, "system", lambda: "Darwin")

binary_path = packaging.expected_binary_path(tmp_path)

assert binary_path == (
tmp_path
/ "apps"
/ "desktop"
/ "src-tauri"
/ "target"
/ "x86_64-pc-windows-msvc"
/ "release"
/ "bandscope-desktop.exe"
)


def test_release_packaging_maps_darwin_to_macos(monkeypatch) -> None:
"""Ensure Darwin hosts map to the repository's canonical macOS label."""
packaging = load_module(
"scripts/release/package_desktop_artifact.py", "package_desktop_artifact_platform"
)
Expand All @@ -59,6 +116,7 @@ def test_release_packaging_maps_darwin_to_macos(monkeypatch) -> None:


def test_release_packaging_main_writes_arch_specific_manifest(monkeypatch, tmp_path: Path) -> None:
"""Ensure the packaging entry point writes an architecture-aware manifest."""
packaging = load_module(
"scripts/release/package_desktop_artifact.py", "package_desktop_artifact_main"
)
Expand Down
4 changes: 4 additions & 0 deletions services/analysis-engine/tests/test_supply_chain_policy.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
"""Tests for repository supply-chain and workflow coverage checks."""

from __future__ import annotations

from pathlib import Path
Expand All @@ -6,6 +8,7 @@


def test_supply_chain_check_requires_multi_arch_runner_labels(monkeypatch, tmp_path: Path) -> None:
"""Ensure missing multi-arch workflow tokens are reported as violations."""
supply_chain = load_module("scripts/checks/verify_supply_chain.py", "verify_supply_chain")

workflow_dir = tmp_path / ".github" / "workflows"
Expand Down Expand Up @@ -50,6 +53,7 @@ def test_supply_chain_check_requires_multi_arch_runner_labels(monkeypatch, tmp_p


def test_supply_chain_check_accepts_repo_multi_arch_workflow(monkeypatch) -> None:
"""Ensure the checked-in multi-arch workflow satisfies the baseline policy."""
supply_chain = load_module("scripts/checks/verify_supply_chain.py", "verify_supply_chain_repo")
repo_root = Path(__file__).resolve().parents[3]

Expand Down
Loading