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
108 changes: 95 additions & 13 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -196,15 +196,16 @@ jobs:
retention-days: 30

build-daemon-library:
name: Release daemon library (macos-15-intel, ${{ matrix.build }})
name: Release daemon library (${{ matrix.os }}, ${{ matrix.build }})
strategy:
fail-fast: false
matrix:
build: [primary, independent]
runs-on: macos-15-intel
os: [macos-15-intel, windows-2025]
runs-on: ${{ matrix.os }}
timeout-minutes: 10
env:
CARGO_BUILD_JOBS: "4"
CARGO_BUILD_JOBS: ${{ matrix.os == 'macos-15-intel' && '4' || '2' }}
PERITUS_RELEASE_BUILD_ROLE: ${{ matrix.build }}
steps:
- name: Check out the candidate for this independent library compilation
Expand All @@ -217,16 +218,51 @@ jobs:
run: cargo run --locked --package xtask -- release-daemon-library
- name: Retain this role's same-run candidate-bound libraries
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: release-libraries-${{ matrix.build }}-${{ matrix.os }}-peritusd
path: target/native-daemon-libraries
if-no-files-found: error
compression-level: 0
retention-days: 1

build-cli-library:
name: Release CLI library (macos-15-intel, ${{ matrix.build }})
needs: build-daemon-library
strategy:
fail-fast: false
matrix:
build: [primary, independent]
runs-on: macos-15-intel
timeout-minutes: 10
env:
CARGO_BUILD_JOBS: "4"
PERITUS_RELEASE_BUILD_ROLE: ${{ matrix.build }}
steps:
- name: Check out the same candidate for the CLI library compilation
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Install pinned Rust
uses: dtolnay/rust-toolchain@6c977a6ca4077a0ceb28ffbe03f59d46e9ac8772 # v1
with:
toolchain: ${{ env.RUST_VERSION }}
- name: Restore only this role's original same-run daemon libraries
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
name: release-libraries-${{ matrix.build }}-macos-15-intel-peritusd
path: target/native-daemon-libraries
- name: Compile the CLI library with its own dependency features and no product binary
run: cargo run --locked --package xtask -- release-cli-library
- name: Retain the CLI libraries and original daemon compilation chain
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: release-libraries-${{ matrix.build }}-macos-15-intel-peritus
path: target/native-cli-libraries
if-no-files-found: error
compression-level: 0
retention-days: 1

build-binary:
name: Release binary ${{ matrix.target.binary }} (${{ matrix.target.os }}, ${{ matrix.build }})
needs: build-daemon-library
needs: [build-daemon-library, build-cli-library]
strategy:
fail-fast: false
matrix:
Expand Down Expand Up @@ -276,7 +312,7 @@ jobs:
cargo build --release --locked --package ${{ matrix.target.package }}
--bin ${{ matrix.target.binary }}
- name: Compile native Windows x86-64 with the pinned reproducible C compiler
if: ${{ matrix.target.os == 'windows-2025' }}
if: ${{ matrix.target.os == 'windows-2025' && matrix.target.binary != 'peritusd' }}
env:
PERITUS_RELEASE_BINARY: ${{ matrix.target.binary }}
run: cargo run --locked --package xtask -- release-windows-binary
Expand All @@ -285,14 +321,14 @@ jobs:
run: >-
cargo rustc --release --locked --package ${{ matrix.target.package }}
--bin ${{ matrix.target.binary }} -- -C link-arg=/Brepro
- name: Restore only this same-run build role's native daemon libraries
if: ${{ matrix.target.os == 'macos-15-intel' && (matrix.target.binary == 'peritusd' || matrix.target.binary == 'peritus') }}
- name: Restore only this same-run build role's native consumer libraries
if: ${{ (matrix.target.os == 'macos-15-intel' && (matrix.target.binary == 'peritusd' || matrix.target.binary == 'peritus')) || (matrix.target.os == 'windows-2025' && matrix.target.binary == 'peritusd') }}
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
name: release-libraries-${{ matrix.build }}-${{ matrix.target.os }}-peritusd
path: target/native-daemon-libraries
name: release-libraries-${{ matrix.build }}-${{ matrix.target.os }}-${{ matrix.target.binary }}
path: target/native-${{ matrix.target.binary == 'peritus' && 'cli' || 'daemon' }}-libraries
- name: Verify native library inputs and compile the final daemon binary
if: ${{ matrix.target.os == 'macos-15-intel' && matrix.target.binary == 'peritusd' }}
if: ${{ (matrix.target.os == 'macos-15-intel' || matrix.target.os == 'windows-2025') && matrix.target.binary == 'peritusd' }}
env:
PERITUS_RELEASE_BUILD_ROLE: ${{ matrix.build }}
run: cargo run --locked --package xtask -- release-daemon-binary
Expand All @@ -302,7 +338,7 @@ jobs:
PERITUS_RELEASE_BUILD_ROLE: ${{ matrix.build }}
run: cargo run --locked --package xtask -- release-cli-binary
- name: Retain actual library and binary compilation observations
if: ${{ matrix.target.os == 'macos-15-intel' && (matrix.target.binary == 'peritusd' || matrix.target.binary == 'peritus') }}
if: ${{ (matrix.target.os == 'macos-15-intel' && (matrix.target.binary == 'peritusd' || matrix.target.binary == 'peritus')) || (matrix.target.os == 'windows-2025' && matrix.target.binary == 'peritusd') }}
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: release-compile-${{ matrix.build }}-${{ matrix.target.os }}--${{ matrix.target.binary }}
Expand All @@ -317,6 +353,52 @@ jobs:
if-no-files-found: error
retention-days: 30

check-native-staging:
name: Compare previous native staging (${{ matrix.target.os }})
if: ${{ github.event_name == 'workflow_dispatch' }}
needs: build-binary
strategy:
fail-fast: false
matrix:
target:
- { os: macos-15-intel, binary: peritus }
- { os: windows-2025, binary: peritusd }
runs-on: ${{ matrix.target.os }}
timeout-minutes: 10
env:
CARGO_BUILD_JOBS: ${{ matrix.target.os == 'macos-15-intel' && '4' || '2' }}
PERITUS_RELEASE_BUILD_ROLE: primary
steps:
- name: Check out the exact candidate for a diagnostic previous-path compilation
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Install pinned Rust
uses: dtolnay/rust-toolchain@6c977a6ca4077a0ceb28ffbe03f59d46e9ac8772 # v1
with:
toolchain: ${{ env.RUST_VERSION }}
- name: Restore the actual staged product only for byte comparison
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
name: release-bin-primary-${{ matrix.target.os }}--${{ matrix.target.binary }}
path: target/staging-candidate
- name: Restore the original libraries used by the previous Intel Mac CLI path
if: ${{ matrix.target.os == 'macos-15-intel' }}
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
name: release-libraries-primary-macos-15-intel-peritusd
path: target/native-daemon-libraries
- name: Compile and compare the previous path without creating release evidence
env:
PERITUS_RELEASE_BINARY: ${{ matrix.target.binary }}
run: cargo run --locked --package xtask -- release-staging-check
- name: Retain diagnostic comparison only, never its product binary
if: ${{ always() }}
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: release-staging-check-${{ matrix.target.os }}
path: target/native-staging-check.json
if-no-files-found: error
retention-days: 30

assemble:
name: Assemble package (${{ matrix.os }}, ${{ matrix.build }})
needs: build-binary
Expand Down Expand Up @@ -344,8 +426,8 @@ jobs:
merge-multiple: true
- name: Assemble, archive, checksum, and record native package
run: cargo run --locked --package xtask -- release-package-assemble
- name: Restore both matching CLI and daemon compilation records for native assembly
if: ${{ matrix.os == 'macos-15-intel' }}
- name: Restore the matching staged compilation records for native assembly
if: ${{ matrix.os == 'macos-15-intel' || matrix.os == 'windows-2025' }}
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
pattern: release-compile-${{ matrix.build }}-${{ matrix.os }}--*
Expand Down
46 changes: 31 additions & 15 deletions packaging/native-build.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,23 @@ each role's own binaries and requires complete byte-identical archive/checksum
outputs before release attestation. Manual validation cannot create a release,
sign packages, or publish. Passing a development run is not final H4 qualification.

## Intel macOS library and binary phases
## Bounded native library and binary phases

The Intel macOS daemon and CLI exceeded the ten-minute job ceiling even with
four build jobs. The daemon library producer and each final binary now have
separate bounded native phases. The other native binary commands, supported platforms, release profile, locked
four build jobs. The native x86-64 Windows daemon also exceeded that ceiling.
The daemon library producer and each final binary now have separate bounded
native phases on these hosts. Intel macOS adds a dedicated CLI-library phase
between its original daemon libraries and the final CLI binary. The other native binary commands, supported platforms, release profile, locked
dependencies, and ten-minute limits are unchanged. The binary matrix waits for
both independent library producers before starting; no role borrows another
all independent library producers before starting; no role borrows another
role's compilation.

The existing library target is selected with `cargo build --release --locked
--package peritus-daemon --lib`. The final phase still runs the corresponding
`--package peritus-daemon --bin peritusd` build for the daemon, or
`--package peritus-cli --bin peritus` for the CLI. The CLI retains its own
dependency features: Cargo recompiles feature-affected libraries when necessary.
dependency features: `cargo build --release --locked --package peritus-cli --lib`
recompiles feature-affected libraries in the intermediate stage when necessary.
This is partial library reuse, not a forced shared feature graph or a promise
that only the final binary target compiles. Cargo's [target selection](https://doc.rust-lang.org/cargo/commands/cargo-build.html#target-selection)
does not change the declared release profile.
Expand All @@ -34,13 +37,18 @@ cargo xtask release-daemon-library
cargo xtask release-daemon-binary
# In a separate fresh checkout at that same absolute path, restore the original
# library-only artifact again, never the daemon binary consumer's target tree:
cargo xtask release-cli-library
# In another fresh checkout, restore only this role's target/native-cli-libraries:
cargo xtask release-cli-binary
```

These commands reject existing compilation/product outputs. They use the fixed
`target/native-daemon` Cargo directory. Linux can exercise the transfer mechanics
locally, but Linux timing is not evidence of native macOS capacity. Windows does
not use this handoff.
locally, but Linux timing is not evidence of native macOS or Windows capacity.
Windows uses only the daemon-library and daemon-binary stages, with two build
jobs, pinned native clang-cl 20.1.8, and the original `cargo rustc ... --bin
peritusd -- -C link-arg=/Brepro` final command. The actual `.exe` bytes are
retained; no post-link rewriting or prebuilt executable reuse is allowed.

## Admission and independence

Expand All @@ -62,14 +70,18 @@ The library and binary invocations retain distinct real times and identifiers.

Each final phase retains `target/native-peritusd-build.json` or
`target/native-peritus-build.json` alongside its separately uploaded binary.
Assembly requires exactly these two same-run, same-role records, bound to their
respective package and binary, with distinct final invocations and an identical
original library record. It checks both hashes against the actual archive
members, not loose package projections. Binary compilation records use schema v2;
native assembly schema v3 retains both in `binary_compilations`. Other platforms
retain an empty map. Old daemon-only binary/assembly development observations
do not qualify a new candidate under this protocol. The daemon library transport
remains schema v1. The independent comparison remains mandatory.
Intel macOS assembly requires exactly these two same-run, same-role records,
bound to their respective package and binary. The CLI library record must retain
the exact original daemon library as `previous_library`; daemon library records
must not have a parent. All dependent invocations must be distinct and ordered.
Windows x86-64 assembly requires exactly its daemon record and rechecks the
pinned C compiler identity. Assembly checks hashes against actual tar/ZIP
members, not loose package projections. Library records use schema v2, binary
records v3, and native assembly v4 retains them in `binary_compilations`.
Other platforms retain an empty map. Earlier development observations do not
qualify a new candidate under this protocol. The independent comparison remains
mandatory. Reruns must include producers and consumers from the same workflow
attempt; retrying just a consumer against an older attempt is intentionally rejected.

## Verification

Expand All @@ -78,3 +90,7 @@ compile-failure, archived-binary, and native workflow boundary regressions.
Real fresh-source transfer and unsplit-build byte comparison are also required
before a new workflow is considered ready. Hosted native completion must be
observed on the exact commit; local fixtures cannot establish its timing.
Manual dispatch additionally compiles the previous Intel Mac CLI path (from
its original daemon libraries) and the previous cold Windows daemon path, then
compares them with the actual staged products. These diagnostic binaries cannot
enter assembly: only a separate `native-staging-diagnostic` report is uploaded.
41 changes: 29 additions & 12 deletions packaging/native_build.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@


def compile_phase(stage, expected):
arguments = cargo_arguments(stage, expected["binary"])
arguments = cargo_arguments(stage, expected["binary"], expected["environment"].get("system"))
environment = os.environ.copy()
if expected["environment"].get("system") == "Windows":
environment["CC_x86_64_pc_windows_msvc"] = expected["environment"]["cc"]["path"]
environment["CARGO_TARGET_DIR"] = str(ROOT / "target/native-daemon")
environment["CARGO_BUILD_JOBS"] = str(expected["environment"]["cargo_build_jobs"])
environment["CARGO_INCREMENTAL"] = "0"
Expand All @@ -28,31 +30,45 @@ def compile_phase(stage, expected):
return observed


def library():
expected = inputs.binding()
tree, bundle = ROOT / "target/native-daemon", ROOT / "target/native-daemon-libraries"
def library(binary_name="peritusd"):
expected = inputs.binding(binary_name)
tree, bundle = ROOT / "target/native-daemon", library_directory(binary_name)
if tree.exists() or tree.is_symlink() or bundle.exists() or bundle.is_symlink():
raise ValueError("native library compilation requires fresh output directories")
inputs.normalize_verified_sources(expected["candidate"])
previous = None
if binary_name == "peritus":
previous = transport.restore(library_directory("peritusd"), tree, inputs.daemon_binding(expected))
observed = compile_phase("library", expected)
transport.save(tree, bundle, expected, observed)
transport.save(tree, bundle, expected, observed, previous)


def library_directory(binary_name):
package = transport.binary_package(binary_name).removeprefix("peritus-")
return ROOT / f"target/native-{package}-libraries"


def binary(binary_name="peritusd"):
expected = inputs.binding(binary_name)
tree = ROOT / "target/native-daemon"
product = ROOT / "target/release" / binary_name
windows = expected["environment"].get("system") == "Windows"
filename = binary_name + (".exe" if windows else "")
product = ROOT / "target/release" / filename
record_path = ROOT / "target" / transport.record_filename(binary_name)
if product.exists() or product.is_symlink() or record_path.exists() or record_path.is_symlink():
raise ValueError("native final compilation refuses to overwrite a product or record")
inputs.normalize_verified_sources(expected["candidate"])
earlier = transport.restore(ROOT / "target/native-daemon-libraries", tree,
earlier = transport.restore(library_directory(binary_name), tree,
inputs.library_binding(expected))
observed = compile_phase("binary", expected)
compiled = transport.regular(tree / "release" / binary_name)
if not compiled.stat().st_size or not compiled.stat().st_mode & 0o100:
compiled = transport.regular(tree / "release" / filename)
if not compiled.stat().st_size or (not windows and not compiled.stat().st_mode & 0o100):
raise ValueError("native final compilation did not produce its executable binary")
record = {"schema_version": 2, "kind": "native-release-binary-compilation",
if windows:
with compiled.open("rb") as payload:
if payload.read(2) != b"MZ":
raise ValueError("native Windows compilation did not produce a PE executable")
record = {"schema_version": 3, "kind": "native-release-binary-compilation",
"binding": expected, "library": earlier, "observation": observed,
"binary": {"sha256": transport.digest(compiled), "byte_length": compiled.stat().st_size}}
inputs.validate_binary_record(record, expected["candidate"], expected["role"], compiled, binary_name)
Expand All @@ -67,11 +83,12 @@ def binary(binary_name="peritusd"):
if __name__ == "__main__":
parser = argparse.ArgumentParser(description=__doc__)
commands = parser.add_subparsers(dest="phase", required=True)
commands.add_parser("library")
producer = commands.add_parser("library")
producer.add_argument("binary", nargs="?", default="peritusd", choices=tuple(transport.BINARY_PACKAGES))
consumer = commands.add_parser("binary")
consumer.add_argument("binary", choices=tuple(transport.BINARY_PACKAGES))
arguments = parser.parse_args()
if arguments.phase == "library":
library()
library(arguments.binary)
else:
binary(arguments.binary)
Loading
Loading