Security¶
Supply chain security¶
repomatic implements most of the practices described in Astral’s Open Source Security at Astral post, baked into a drop-in setup that any maintainer can inherit by pointing their workflows at the reusable callers.
Astral practice |
How |
|---|---|
Ban dangerous triggers ( |
The lint-workflow-security job runs |
Minimal workflow permissions |
|
Pinned actions |
All |
No force-pushes to |
|
Immutable release tags |
|
Dependency cooldowns |
|
Install-time cooldowns |
Every workflow exports |
Trusted Publishing |
PyPI uploads via OIDC with no long-lived token. The |
Cryptographic attestations |
Every binary and wheel is attested to the workflow run that built it via |
Checksums in installer scripts |
The |
Fork PR approval policy |
|
Warning
Known gap: multi-person release approval. Astral gates releases behind a dedicated GitHub deployment environment with required reviewers, so that a single compromised account cannot publish. repomatic does not enforce this, but if the repository has multiple maintainers, I recommend adding an environment: release key to the caller-side publish-pypi job (and to the upstream create-release job, if the caller exposes it) in the downstream workflow and configuring required reviewers on that environment in repo settings.
Important
One-time PyPI Trusted Publisher setup. Each downstream repository must register a Trusted Publisher entry on PyPI for its own caller workflow. The publisher config matches against the OIDC job_workflow_ref claim, which names the downstream’s workflow file (typically .github/workflows/release.yaml). Without this registration, the first PyPI upload after migration fails cleanly with a publisher mismatch error. See the PyPI Trusted Publishers documentation for the registration steps.
Third-party action minimization¶
Every third-party GitHub Action executes with access to GITHUB_TOKEN and repository secrets. Each action is a trust delegation: you depend on the maintainer’s security practices, their CI pipeline, and their transitive dependencies. A compromised action can steal secrets, inject code into builds, or tamper with releases.
repomatic has systematically eliminated 18 third-party actions since late 2025, replacing them with internal CLI commands, SHA-256-verified binary downloads, and runner built-in tools:
Removed action |
Replacement |
Strategy |
|---|---|---|
|
|
Internal CLI |
|
|
Internal CLI |
|
|
Internal CLI |
|
|
Internal CLI |
|
|
Direct binary + SHA-256 |
|
|
Direct binary + SHA-256 |
|
|
Direct binary + SHA-256 |
|
|
Direct binary + SHA-256 |
|
|
Direct binary + SHA-256 |
|
Direct |
Direct binary + SHA-256 |
|
|
Runner built-in |
|
Bash + |
Runner built-in |
|
Runner built-in Rust |
Runner built-in |
|
|
Consolidated |
|
|
First-party replacement |
|
None (integration dropped) |
Removed entirely |
|
None (feature dropped) |
Removed entirely |
|
Explicit |
Removed entirely |
The remaining third-party actions (4 of 14 total) are:
Action |
Purpose |
|---|---|
|
Core toolchain: installs |
|
Creates autofix PRs |
|
Locks inactive issues |
|
Debug diagnostics (no secrets access) |
Replacement strategies, ordered from most to least isolated:
Internal CLI: the operation runs inside
repomaticPython code with no external process.Direct binary download: checksummed binary fetched from a GitHub release URL, no action code path involved.
Runner built-in: uses tools pre-installed on the GitHub Actions runner (
gh, Rust toolchain).First-party replacement: swaps a community action for an official
actions/*equivalent maintained by GitHub.
Ruff consolidation¶
Ten separate Python linters and formatters, two of them mdformat plugins, have been absorbed into ruff, eliminating ten runtime or dev dependencies:
Removed tool |
What it did |
Replaced |
|---|---|---|
|
Static analysis and linting |
Feb 2023 |
|
Docstring convention enforcement |
Feb 2023 |
|
Unused import removal |
Feb 2023 |
|
Python syntax modernization |
Feb 2023 |
|
Import sorting |
Feb 2023 |
|
Code formatting |
Sep 2023 |
|
Docstring formatting |
Jan 2024 |
|
Python formatting in Markdown code blocks, as an mdformat plugin |
Aug 2024 |
|
Python formatting in Markdown code blocks |
Feb 2026 |
|
Same as |
Aug 2026 |
autopep8 is the only legacy formatter still in use: it handles long-line comment wrapping that ruff does not yet cover (astral-sh/ruff#7414).
uv consolidation¶
Five separate packaging and install tools have been absorbed into uv, which now handles dependency management, builds, publishing, auditing, and Python version installation:
Removed tool |
What it did |
Replaced |
|---|---|---|
|
Dependency management, lock files, virtual environments |
Jun 2024 |
|
Package building (wheels and sdists) |
Sep 2024 |
|
PyPI uploads |
Jan 2025 |
|
Wheel validation |
Jan 2025 |
|
Vulnerability scanning |
Mar 2026 |
uv also consolidated command-line usage that previously required separate tools: pip install became uv pip install / uv sync, pipx became uvx, and actions/setup-python was replaced by astral-sh/setup-uv (counted in the action minimization table above).
Two other Python packages were eliminated outside the ruff/uv consolidations: pipdeptree (replaced by an internal dep-graph implementation) and gitignore-parser (replaced by py-walk).
Permissions and token¶
Several workflows need a REPOMATIC_PAT secret to create PRs that modify files in .github/workflows/ and to trigger downstream workflows. Without it, those jobs silently fall back to the default GITHUB_TOKEN, which lacks the required permissions.
After your first push, the setup-guide job automatically opens an issue with step-by-step instructions to create and configure the token.
Concurrency and cancellation¶
All workflows use a concurrency directive to prevent redundant runs and save CI resources. When a new commit is pushed, any in-progress workflow runs for the same branch or PR are automatically cancelled.
Workflows are grouped by:
Pull requests:
{workflow-name}-{pr-number}— Multiple commits to the same PR cancel previous runsBranch pushes:
{workflow-name}-{branch-ref}— Multiple pushes to the same branch cancel previous runs
release.yaml uses a stronger protection: release commits get a unique concurrency group based on the commit SHA, so they can never be cancelled. This ensures tagging, PyPI publishing, and GitHub release creation complete successfully.
Additionally, cancel-runs.yaml actively cancels in-progress and queued runs when a PR is closed. This complements passive concurrency groups, which only trigger cancellation when a new run enters the same group — closing a PR doesn’t produce such an event.
Tip
For implementation details on how concurrency groups are computed and why release.yaml needs special handling, see the repomatic.github.actions module docstring.
AV false-positive submissions¶
Compiled Python binaries (built with Nuitka --onefile) are frequently flagged as malicious by heuristic AV engines. The onefile packaging technique (self-extracting archive with embedded Python runtime) triggers generic “packed/suspicious” signatures. This is a known issue across the Nuitka ecosystem.
The scan-virustotal job in _release-engine.yaml uploads all compiled binaries to VirusTotal on every release. This seeds AV vendor databases to reduce false positive rates for downstream distributors (Chocolatey, Scoop, etc.). Each release’s flagged / total snapshot is recorded in docs/assets/virustotal-scans.json and rendered, along with the full catalog of released binaries and their analysis links, on the binaries page. Detection counts are deliberately kept out of GitHub release notes, where they read as a malware verdict without context (see kdeldycke/meta-package-manager#1911).
When a release is flagged, the /av-false-positive skill generates per-vendor submission files with pre-written text and form field mappings. The vendor details below document the process for manual reference.
Why binaries get flagged¶
Nuitka --onefile creates a self-extracting archive that decompresses an embedded Python runtime to a temporary directory and executes it at launch. This “drop and execute from temp” pattern is behaviorally identical to trojan droppers, which triggers heuristic and ML-based detections. Two more factors compound it: Nuitka is popular with malware authors for source code protection, which poisons AV heuristics for all Nuitka-compiled binaries, and Microsoft has gone as far as suspending an Artifact Signing account over Nuitka onefile binaries.
The detection profile is consistent across projects: Linux binaries scan clean, macOS ones pick up the occasional ML false positive, Windows ARM64 stays low (fewer ARM64 heuristics in AV engines), and Windows x64 attracts the bulk of the detections through generic signatures like Gen:Variant.Application.tedy (BitDefender family), Trojan:Win32/Sabsik (Microsoft), Python/Packed.Nuitka.AL (ESET), and various ML classifiers. Pure-Python .whl and .tar.gz distributions scan clean.
The Nuitka project tracks the situation in Nuitka/Nuitka#2685, Nuitka/Nuitka#2495, Nuitka/Nuitka#2757, and Nuitka/Nuitka#3842.
Vendor portals¶
Vendor |
Engines covered |
Portal |
Format |
Turnaround |
|---|---|---|---|---|
Microsoft |
|
One file per form, 1900 char limit on additional info |
Fastest |
|
BitDefender |
|
One file per form, screenshot mandatory |
Fast |
|
ESET |
|
Email to |
Single email, password-protected ZIP ( |
Reliable |
Symantec |
|
Hash submission only (no |
3-7 business days |
|
Avast/AVG |
|
One file per form, shared engine |
Medium |
|
Sophos |
|
One file per form, 25 MB max per submission |
Up to 15 business days |
Complete directories of vendor false-positive contacts are maintained by VirusTotal and False-Positive-Center.
Submission priority¶
Submit in this order to maximize impact:
Microsoft: most influential engine. ML detections (
Sabsik,Wacatac) have the broadest downstream effect.BitDefender: powers ~6 downstream vendor engines. Highest detection-removal-per-submission ratio.
ESET: email-based channel with no portal dependency. The most reliable submission path.
Symantec: ML detections (
ML.Attribute.*) may take longer to process.Avast/AVG: shared engine, so one submission covers both.
Sophos: PUA detections require justification of the software’s legitimate purpose.
Submission content¶
Every false-positive submission should include:
The binary’s VirusTotal report link.
VirusTotal links for the clean
.whland.tar.gzsource distributions (as comparison evidence).The GitHub release link and direct download URL for the binary.
Project homepage and PyPI URL.
License from
pyproject.toml.Reference to any prior false-positive issue in the repository.
All submission text should mention that the binary is compiled with Nuitka --onefile from an open-source project.
Known portal issues¶
Microsoft: CORS errors or stuck progress modals during upload (auth session expiring). Workaround: sign out, clear cookies for
microsoft.com, sign back in, submit immediately.BitDefender: form sometimes returns “Your request could not be registered!” with no details. Retry later.
Avast: form sometimes returns “An internal error occurred while sending the form.” Retry later.
Long-term mitigations¶
False-positive submissions are a per-release moving target. The structural fixes:
Code signing with an EV certificate would reduce heuristic detections across the board, especially from Microsoft and Symantec ML models.
Switching from
--onefileto--standalonewould eliminate the self-extracting pattern entirely, at the cost of distributing a directory instead of a single.exe.Nuitka Commercial claims proprietary AV-mitigation techniques but offers no guarantees.
repomatic.virustotal API¶
Upload release binaries to VirusTotal and record detection snapshots.
Submits compiled binaries (.bin, .exe) to the VirusTotal API for malware
scanning. This seeds antivirus vendor databases with the signatures of freshly
built binaries, which keeps false-positive rates in check for downstream
distributors.
Detection statistics polled after an upload are appended to a JSON history
file, one record per binary per scan date. The sync-binaries command renders
that history into the binaries catalog page (docs/binaries.md).
Note
Scan results are deliberately kept out of GitHub release notes: a raw
flagged / total count next to a download link reads as a malware verdict to
visitors, when it is almost always Nuitka onefile false positives. See
kdeldycke/meta-package-manager#1911
for the confusion this caused. The catalog page provides the context release
notes cannot.
Note
The free-tier API allows 4 requests per minute. All API calls (uploads and polls) are rate-limited with a sleep between each request.
- repomatic.virustotal.FREE_TIER_RATE_LIMIT = 4¶
VirusTotal free-tier request budget, in API calls per minute.
The single source for the upload and polling pace: the
scan-virustotalCLI default and both client functions below derive from it.
- repomatic.virustotal.VIRUSTOTAL_GUI_URL = 'https://www.virustotal.com/gui/file/{sha256}'¶
URL template for the VirusTotal file analysis page.
- class repomatic.virustotal.DetectionStats(malicious, suspicious, undetected, harmless)[source]¶
Bases:
objectDetection statistics from a completed VirusTotal analysis.
Stores only the four categories that constitute a definitive verdict.
type-unsupported,timeout, andfailurefrom the API response are excluded from the total.
- class repomatic.virustotal.ScanResult(filename, sha256, analysis_url, detection_stats=None)[source]¶
Bases:
objectResult of uploading a single file to VirusTotal.
- detection_stats: DetectionStats | None = None¶
Detection statistics, or
Noneif analysis is still pending.
- class repomatic.virustotal.ScanRecord(tag, filename, sha256, scanned, stats)[source]¶
Bases:
objectA detection snapshot for one binary, taken on a given date.
Records accumulate in a JSON history file (see
upsert_scan_records()) committed to the repository. Each record freezes theflagged / totalverdict counts at scan time, so the history supports trend analysis across releases even after VirusTotal re-analyzes the files or vendors process false-positive reports.- stats: DetectionStats¶
Detection statistics at scan time.
- repomatic.virustotal.scan_files(api_key, file_paths, rate_limit=4)[source]¶
Upload files to VirusTotal and return scan results.
Uses the synchronous
vt.ClientAPI. Sleeps between uploads to respect the free-tier rate limit.
- repomatic.virustotal.poll_detection_stats(api_key, results, rate_limit=4, timeout=600)[source]¶
Poll VirusTotal for detection statistics of previously uploaded files.
Queries
GET /files/{sha256}for each file until analysis completes or the timeout is reached. Respects the free-tier rate limit for all API calls.- Parameters:
api_key (
str) – VirusTotal API key.results (
list[ScanResult]) – Scan results from a previous upload.rate_limit (
int) – Maximum API requests per minute (shared with uploads).timeout (
int) – Maximum seconds to wait for all analyses to complete.
- Return type:
- Returns:
Results with
detection_statspopulated (orNonefor files whose analysis did not complete before the timeout).
- repomatic.virustotal.records_from_results(results, tag, scanned=None)[source]¶
Build history records from scan results whose analysis completed.
Results still pending (no detection statistics) are skipped: a record without verdict counts carries no information the release assets don’t already provide.
- Parameters:
results (
list[ScanResult]) – Scan results, typically frompoll_detection_stats().tag (
str) – Git tag of the release the binaries belong to.scanned (
str|None) – Snapshot date inYYYY-MM-DDformat. Today (UTC) whenNone.
- Return type:
- Returns:
One record per result with detection statistics.
- repomatic.virustotal.records_from_release_notes(body, tag, scanned)[source]¶
Recover detection snapshots from a legacy release-notes table.
Before the scan history file existed, the release pipeline appended a VirusTotal table to GitHub release notes, with a
flagged / totalDetections cell frozen minutes after publication. Those cells are genuine at-release snapshots, sosync-binaries --backfill-recordsharvests them to seed the history for releases that predate the file.Note
The legacy table only recorded the flagged and total aggregates, not the malicious/suspicious/undetected/harmless split. The split is rebuilt as flagged = malicious and the remainder = undetected, which is lossless for everything the catalog consumes (
flaggedandtotal).- Parameters:
- Return type:
- Returns:
One record per table row carrying a numeric Detections cell.
- repomatic.virustotal.load_scan_records(path)[source]¶
Load scan records from a JSON history file.
- Parameters:
path (
Path) – Path to the JSON file.- Return type:
- Returns:
The records, or an empty list when the file does not exist.
- Raises:
ValueError – When the file exists but cannot be parsed. Loud on purpose: a corrupt history must never be silently clobbered by the next
upsert_scan_records()write.
- repomatic.virustotal.upsert_scan_records(path, new_records)[source]¶
Merge new records into the JSON history file at path.
Records sharing the same
(sha256, scanned)identity are replaced, so re-running a scan the same day is idempotent. The file is created (with its parent directories) when missing, and always rewritten in normalized form: sorted by version, filename, and scan date, serialized with the same layout Biome’s JSON formatter produces so theformat-jsonautofix job never rewrites it.- Parameters:
path (
Path) – Path to the JSON history file.new_records (
list[ScanRecord]) – Records to merge in.
- Return type:
- Returns:
Truewhen the file content changed.
repomatic.checksums API¶
Recompute SHA-256 checksums for the binary tool registry.
Iterates every TOOL_REGISTRY entry with a binary spec, downloads each
platform’s release artifact, and rewrites stale hashes in-place in
tool_registry.py (alongside the VERSIONS stamps). Driven by
repomatic update-checksums and, with a version override, by
sync-tool-versions so a version bump and its matching checksums land in one
pass.
- repomatic.checksums.update_registry_checksums(registry_path, version_overrides=None)[source]¶
Recompute binary checksums and version stamps in
tool_registry.py.Iterates every
TOOL_REGISTRYentry with abinaryspec, downloads each platform URL (concurrently, sized by the global--jobsoption and sequential atDEBUGverbosity or without an active CLI context), computes its SHA-256, and replaces stale hashes in-place. Also reconciles each tool’sVERSIONSstamp with the version the checksums were computed for, the basis of the offline staleness test.- Parameters:
registry_path (
Path) – Path totool_registry.py.version_overrides (
dict[str,str] |None) – Optional mapping of tool name to a version to download instead of the in-memoryToolSpec.version.sync-tool-versionspasses this so it can bump the version in the source and refresh the checksums in a single process: the in-memory registry still holds the pre-bump version because the file was edited, not reimported.
- Return type:
- Returns:
List of
(url, old_hash, new_hash)for each updated checksum. Empty if all checksums are already correct.
repomatic.binary API¶
Binary build targets and verification utilities.
Defines the Nuitka compilation targets for all supported platforms and provides native binary verification: architecture and minimum-OS floors are parsed straight from the executables’ ELF, Mach-O and PE headers, so no external tool is needed on runners or inside build containers.
- repomatic.binary.BINARY_ASSET_SUFFIXES = ('.bin', '.exe')¶
File extensions identifying compiled binaries among release assets.
The one definition of “a compiled release asset”:
scan-virustotaluploads this set, the release workflow downloads it (--patternflags in_release-engine.yaml),docs/binaries.mdlists it, and the dev-release asset globs derive from it.
- repomatic.binary.PYTHON_DIST_SUFFIXES = ('.tar.gz', '.whl')¶
File extensions identifying Python distributions among release assets.
The counterpart of
BINARY_ASSET_SUFFIXES, and the same kind of single definition:pack_binary_assets()excludes this set from the binary upload list (create-releasealready attached those), and the dev-release asset globs add it on top of the compiled binaries.
- repomatic.binary.NUITKA_BUILD_TARGETS = {'linux-arm64': {'arch': 'arm64', 'container': 'quay.io/pypa/manylinux_2_28_aarch64@sha256:e7035406e58d96b7407246af1f6514a3cbd753a0025b42b9adfbeadd3b29ba80', 'extension': 'bin', 'glibc_floor': '2.28', 'os': 'ubuntu-24.04-arm', 'platform_id': 'linux'}, 'linux-x64': {'arch': 'x64', 'container': 'quay.io/pypa/manylinux_2_28_x86_64@sha256:fdb9a9c223b215604dc7b6f7e8fff4b39bfea5fbaa7777a2e5544a60dfa437f8', 'extension': 'bin', 'glibc_floor': '2.28', 'os': 'ubuntu-24.04', 'platform_id': 'linux'}, 'macos-arm64': {'arch': 'arm64', 'extension': 'bin', 'min_os': '11.0', 'os': 'macos-26', 'platform_id': 'macos'}, 'macos-x64': {'arch': 'x64', 'extension': 'bin', 'min_os': '10.15', 'os': 'macos-26-intel', 'platform_id': 'macos'}, 'windows-arm64': {'arch': 'arm64', 'extension': 'exe', 'min_os': '11', 'os': 'windows-11-arm', 'platform_id': 'windows'}, 'windows-x64': {'arch': 'x64', 'extension': 'exe', 'min_os': '10', 'os': 'windows-2025', 'platform_id': 'windows'}}¶
GitHub-hosted runner matrix for Nuitka builds, keyed by target name.
The key doubles as the compiled binary’s short target identifier: it names the published release asset, so it is chosen for user-friendliness and must stay stable (download URLs and
docs/binaries.mdmatch on it).Values are dictionaries with the following keys:
os: Operating system name, as used in GitHub-hosted runners.Hint
Compilation only runs on the latest supported version of each OS, for each architecture. macOS and Windows do not offer their latest version on every architecture.
platform_id: Platform identifier, as defined by Extra Platform.arch: Architecture identifier.Note
Architecture IDs are inspired from those specified for self-hosted runners
Note
Maybe we should just adopt target triple.
extension: File extension of the compiled binary.container: OCI image the Linux compile and self-test jobs run in, via thecontainer:key of the release workflow. Compiling insidemanylinux_2_28caps the toolchain at glibc 2.28, so binaries stop inheriting the floor of whatever glibc the current runner image ships. Linux targets only: GitHub Actions containers do not exist for macOS and Windows runners.glibc_floor: highest glibc symbol version the compiled artifacts may require, matching the build container. Enforced byverify_binary_floor()and documented indocs/binaries.md.min_os: minimum OS version the binary runs on. On macOS the release workflow exports it asMACOSX_DEPLOYMENT_TARGETat compile time (without it, compiled objects and processed dylibs inherit the build runner’s macOS version) andverify_binary_floor()enforces it. On Windows it is documentation-only: the floor is CPython’s own Windows support policy, not a linker artifact.
- repomatic.binary.FLAT_BUILD_TARGETS = [{'arch': 'arm64', 'container': 'quay.io/pypa/manylinux_2_28_aarch64@sha256:e7035406e58d96b7407246af1f6514a3cbd753a0025b42b9adfbeadd3b29ba80', 'extension': 'bin', 'glibc_floor': '2.28', 'os': 'ubuntu-24.04-arm', 'platform_id': 'linux', 'target': 'linux-arm64'}, {'arch': 'x64', 'container': 'quay.io/pypa/manylinux_2_28_x86_64@sha256:fdb9a9c223b215604dc7b6f7e8fff4b39bfea5fbaa7777a2e5544a60dfa437f8', 'extension': 'bin', 'glibc_floor': '2.28', 'os': 'ubuntu-24.04', 'platform_id': 'linux', 'target': 'linux-x64'}, {'arch': 'arm64', 'extension': 'bin', 'min_os': '11.0', 'os': 'macos-26', 'platform_id': 'macos', 'target': 'macos-arm64'}, {'arch': 'x64', 'extension': 'bin', 'min_os': '10.15', 'os': 'macos-26-intel', 'platform_id': 'macos', 'target': 'macos-x64'}, {'arch': 'arm64', 'extension': 'exe', 'min_os': '11', 'os': 'windows-11-arm', 'platform_id': 'windows', 'target': 'windows-arm64'}, {'arch': 'x64', 'extension': 'exe', 'min_os': '10', 'os': 'windows-2025', 'platform_id': 'windows', 'target': 'windows-x64'}]¶
List of build targets in a flat format, suitable for matrix inclusion.
- repomatic.binary.binary_name(package, target, version=None)[source]¶
Compose a compiled binary’s release-asset filename.
The one definition of the naming convention:
{package}-{version}-{target}.{ext}for the versioned upload, and with no version the stable alias ({package}-{target}.{ext}) backing thereleases/latest/downloadURLs. The extension comes fromNUITKA_BUILD_TARGETS.- Return type:
- repomatic.binary.versionless_alias(filename, version)[source]¶
Map a versioned binary filename to its stable alias, or
None.Strips the
-{version}-segment (papaya-1.2.3-linux-arm64.binbecomespapaya-linux-arm64.bin). ReturnsNonefor filenames that carry no such segment or are not compiled binaries, so callers can filter and map in one pass.
- repomatic.binary.binary_filename_re(package)[source]¶
Match a package binary filename, versioned or versionless.
Captures
targetandext, both alternations derived fromNUITKA_BUILD_TARGETSso a new build target extends the pattern without anyone editing a regex. The release freeze rewrites both spellings onto the versioned form through this;tests/test_platform_keys.pypins the pattern against every target.
- repomatic.binary.pack_binary_assets(dist_dir, version)[source]¶
Pack a release’s upload list, materializing the versionless aliases.
Mirrors what the release engine’s upload step needs: every file in dist_dir except the Python distributions (
create-releasealready uploaded those), plus a byte-identical versionless alias copied beside each versioned binary so the stablereleases/latest/downloadURLs always resolve. Aliases share their sibling’s digest, which is what lets artifact attestations verify them unchanged and the binaries catalog collapse them (seebinaries_page._binary_assets).Idempotent: re-running overwrites the same aliases with the same bytes.
- repomatic.binary.BINARY_AFFECTING_PATHS: Final[tuple[str, ...]] = ('.github/workflows/_release-engine.yaml', '.github/workflows/release.yaml', 'pyproject.toml', 'tests/', 'uv.lock')¶
Path prefixes that always affect compiled binaries, regardless of the project.
Project-specific source directories (derived from
[project.scripts]inpyproject.toml) are added dynamically bybinary_affecting_paths.The release workflow entries cover both layouts: upstream keeps the
_release-engine.yamllane (which defines the Nuitka compile and binary self-test jobs) in-repo, while downstream repos call the engine cross-repo from their generatedrelease.yaml, so a pin bump there rightly triggers a rebuild.
- repomatic.binary.SKIP_BINARY_BUILD_BRANCHES: Final[frozenset[str]] = frozenset({'format-images', 'format-json', 'format-markdown', 'format-shell', 'sync-gitignore', 'sync-mailmap', 'update-dep-graph'})¶
Autofix branches whose changes cannot affect compiled binaries.
Members are PR branch names produced by autofix jobs that touch only repository housekeeping (
.mailmap,.gitignore, JSON, Markdown, images, shell scripts, dependency graph). The binary output is unchanged, soskip_binary_buildreturnsTruewhen the PR head branch matches a member, saving an expensive Nuitka compilation.Note
This set is intentionally disjoint from
repomatic.git_ops.VERSION_BUMP_BRANCHES: version-bump branches do change binaries (they rewrite the version string baked into the build), so they belong to a different policy.
- repomatic.binary.PLATFORM_FORMATS: Final[dict[str, str]] = {'linux': 'elf', 'macos': 'macho', 'windows': 'pe'}¶
Executable format expected for each build platform.
- repomatic.binary.ELF_MACHINES: Final[dict[str, str]] = {'arm64': 'EM_AARCH64', 'x64': 'EM_X86_64'}¶
Expected ELF
e_machinevalue (as decoded by pyelftools) per architecture.
- repomatic.binary.MACHO_CPU_TYPES: Final[dict[str, int]] = {'arm64': 16777228, 'x64': 16777223}¶
Expected Mach-O header
cputypeper architecture.
- repomatic.binary.PE_MACHINES: Final[dict[str, int]] = {'arm64': 43620, 'x64': 34404}¶
Expected PE COFF
Machinefield per architecture.
- repomatic.binary.MACHO_MAGIC_64: Final[int] = 4277009103¶
Magic of a 64-bit Mach-O header, in the file’s own (little) endianness.
- repomatic.binary.MACHO_FAT_MAGICS: Final[frozenset[int]] = frozenset({3405691582, 3405691583})¶
Big-endian magics of universal (fat) Mach-O containers, 32- and 64-bit.
- repomatic.binary.LC_VERSION_MIN_MACOSX: Final[int] = 36¶
Mach-O load command carrying the minimum macOS version (pre-10.14 SDKs).
- repomatic.binary.LC_BUILD_VERSION: Final[int] = 50¶
Mach-O load command carrying the platform and minimum OS (10.14+ SDKs).
- repomatic.binary.MACHO_PLATFORM_MACOS: Final[int] = 1¶
platformfield value naming macOS inside anLC_BUILD_VERSIONcommand.
- repomatic.binary.verify_binary_arch(target, binary_path)[source]¶
Verify that a binary matches the expected architecture for a target.
Parses the executable’s own headers, so it needs no external tool and behaves identically on runner VMs and inside build containers.
- Parameters:
- Raises:
ValueError – If target is unknown.
AssertionError – If binary format or architecture does not match.
- Return type:
- repomatic.binary.verify_binary_floor(target, binary_path, dist_dirs=())[source]¶
Verify the binary and its dist tree stay within the target’s OS floor.
Scans the onefile binary itself plus every native library of the given Nuitka dist directories (whose content the onefile payload repacks), and compares each file’s measured requirement to the target’s declared floor:
Linux: the highest
GLIBC_x.yversion requirement of each ELF againstglibc_floor. A higher requirement means a compiled object picked up symbols newer than the build container provides for, and the binary would die at load time on the distributions the floor promises.macOS: the
minosof each Mach-O againstmin_os, the deployment target the build exports asMACOSX_DEPLOYMENT_TARGET.Windows: nothing. PE version headers are nominal; the floor is CPython’s own Windows support policy, tracked in the docs.
- Parameters:
- Raises:
ValueError – If target is unknown.
AssertionError – If any scanned file exceeds the declared floor.
- Return type: