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
119 changes: 119 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
name: Warehouse CI

on:
pull_request:
push:
branches: [main]
workflow_dispatch:

permissions:
contents: read

jobs:
schema-clean-room-and-manual-transaction:
runs-on: ubuntu-24.04
timeout-minutes: 20
steps:
- name: Check out inert metadata and tooling
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
with:
persist-credentials: false
fetch-depth: 0

- name: Install pinned validation dependency
run: python3 -m pip install --disable-pip-version-check -r requirements-ci.txt

- name: Validate catalog and clean-room/manual fixtures
env:
EVENT_BASE_SHA: ${{ github.event.pull_request.base.sha || github.event.before }}
run: |
set -euo pipefail
previous_args=()
base_sha="$EVENT_BASE_SHA"
if [[ -z "$base_sha" || "$base_sha" =~ ^0+$ ]]; then
base_sha="$(git rev-parse HEAD^ 2>/dev/null || true)"
fi
if [[ -n "$base_sha" ]] && git cat-file -e "$base_sha:metadata/releases/0.3.120.json" 2>/dev/null; then
previous_catalog="$(mktemp)"
trap 'rm -f -- "$previous_catalog"' EXIT
git show "$base_sha:metadata/releases/0.3.120.json" >"$previous_catalog"
previous_args=(--previous-catalog "$previous_catalog")
fi
scripts/validate-catalog "${previous_args[@]}"
python3 -m unittest discover -s tests -v

- name: Consume the exact audited forge protocol fixture
env:
WAREHOUSE_TEST_ALLOW_NONCRYPTOGRAPHIC_FIXTURE: "1"
run: |
set -euo pipefail
forge_dir="$(mktemp -d)"
component_dir="$(mktemp -d)"
trap 'rm -rf -- "$forge_dir" "$component_dir"' EXIT
git -c credential.helper= clone --quiet --filter=blob:none \
https://github.com/acedward/midnight-binary-forge.git "$forge_dir"
git -C "$forge_dir" checkout --quiet 2052e6e3d47495b8404876092d34e7bcbd560690
git -c credential.helper= clone --quiet --filter=blob:none \
https://github.com/acedward/midnight-binary-forge.git "$component_dir"
git -C "$component_dir" checkout --quiet ddd2838d0226eeaaca8f7a42ad82cba1a132bbfe
scripts/verify-candidate --test-noncryptographic-fixture \
--forge-checkout "$forge_dir" \
--forge-component-checkout "$component_dir" \
--envelope "$forge_dir/tests/fixtures/envelope/promotion-envelope-fixture-1.json" \
--bundle "$forge_dir/tests/fixtures/envelope/attestation-fixture-1.sigstore.json" \
--live-evidence "$forge_dir/tests/fixtures/envelope/live-valid.json" \
--test-integration-envelope tests/fixtures/integration-envelope-ddd.json \
--output "$forge_dir/candidate-verification.json"
python3 - "$forge_dir/candidate-verification.json" tests/fixtures/integration-envelope-ddd.json <<'PY'
import json, pathlib, sys
sys.path.insert(0, "scripts")
from promotion_tool import validate_candidate_verification_record
record = json.load(open(sys.argv[1], encoding="utf-8"))
assert record["testOnly"] is True
assert record["componentPolicy"]["issuerCommit"] == "ddd2838d0226eeaaca8f7a42ad82cba1a132bbfe"
assert record["envelopeSha256"] == "709a69fc8adc5c17da2b7e0d69d4871d4e62af4c3333ff53b96ea7c6c9452f5a"
validate_candidate_verification_record(record, pathlib.Path(sys.argv[2]).read_bytes(), require_live=False)
from tests.test_clean_room import CleanRoomReadmeTests
receipt = CleanRoomReadmeTests().receipt_from_exact_two_pin_record(record)
assert receipt["candidateVerificationSha256"]
assert receipt["preflight"]["conflictCount"] == 0
PY

- name: Prove no destination release-write workflow exists
run: python3 tests/test_workflow_policy.py

heartbeat-native:
strategy:
fail-fast: false
matrix:
os: [ubuntu-24.04, macos-15]
runs-on: ${{ matrix.os }}
timeout-minutes: 10
steps:
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
with:
persist-credentials: false
- name: Exercise portable RFC3339 heartbeat parsing
run: |
scripts/check-drift-heartbeat.sh \
--fixture tests/fixtures/heartbeat-fresh.json \
--now 2026-08-28T04:00:00Z
if scripts/check-drift-heartbeat.sh \
--fixture tests/fixtures/heartbeat-stale.json \
--now 2026-08-28T04:00:00Z; then
echo 'stale fixture unexpectedly passed' >&2
exit 1
fi

heartbeat-minimal-container:
runs-on: ubuntu-24.04
timeout-minutes: 10
steps:
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
with:
persist-credentials: false
- name: Exercise without GNU date
run: |
docker run --rm -v "$PWD:/repo:ro" -w /repo \
alpine:3.22.1@sha256:4bcff63911fcb4448bd4fdacec207030997caf25e9bea4045fa6c8c44de311d1 \
sh -lc 'apk add --no-cache bash jq >/dev/null && bash scripts/check-drift-heartbeat.sh --fixture tests/fixtures/heartbeat-fresh.json --now 2026-08-28T04:00:00Z'
22 changes: 18 additions & 4 deletions .github/workflows/release-drift.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ concurrency:
jobs:
drift:
runs-on: ubuntu-24.04
timeout-minutes: 15
timeout-minutes: 90
env:
GH_TOKEN: ${{ github.token }}
SIMULATE_FAILURE: ${{ inputs.simulate_failure || 'false' }}
Expand Down Expand Up @@ -55,9 +55,23 @@ jobs:
exit 1
fi

- name: Check the permanent development release
- name: Check the complete permanent development release
shell: bash
run: scripts/check-release-drift.sh --repo effectstream/binaries --release 0.3.120 --expected-asset-count 66
run: |
set -o pipefail
scripts/check-release-drift.sh \
--repo effectstream/binaries --release 0.3.120 \
--baseline metadata/baselines/0.3.120-current.json \
2>&1 | tee drift-report.txt

- name: Retain the non-secret drift report
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: release-drift-report-${{ github.run_id }}
path: drift-report.txt
if-no-files-found: warn
retention-days: 30

- name: Simulate a visible failure
if: env.SIMULATE_FAILURE == 'true'
Expand All @@ -78,5 +92,5 @@ jobs:
echo "- Workflow run: ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}"
echo "- Conclusion: inspect the job conclusion above; the workflow has no write permission."
echo
echo 'On failure, `acedward` acknowledges this run by inspecting the retained summary, records the run ID/time, then follows the README enable → dispatch → fresh-success sequence.'
echo "On failure, \`acedward\` acknowledges this run by inspecting the retained summary, records the run ID/time, then follows the README enable → dispatch → fresh-success sequence."
} >>"$GITHUB_STEP_SUMMARY"
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
__pycache__/
*.py[cod]
.pytest_cache/
.coverage
.venv/
receipts/
journals/
124 changes: 124 additions & 0 deletions MACOS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
# Temporary owner handoff: Developer ID and notarization

> **DEVELOPMENT ONLY — NOT FOR PRODUCTION USE.** Initial macOS release assets have no Developer ID and may be blocked by Gatekeeper. Never modify or replace an existing `0.3.120` asset.

This temporary file is for the owner performing the later signing step on a trusted macOS host. It contains no credential, identity, password, profile value, private key, or candidate-specific secret. Keep all working files outside Git in a mode-`0700` directory. Do not enable shell tracing and do not paste Keychain or notarization output containing private metadata into public logs.

Official references:

- [Inside Code Signing: Hashes](https://developer.apple.com/documentation/technotes/tn3126-inside-code-signing-hashes)
- [Create Developer ID certificates](https://developer.apple.com/help/account/certificates/create-developer-id-certificates)
- [Create distribution-signed code](https://developer.apple.com/documentation/xcode/creating-distribution-signed-code-for-the-mac/)
- [Customizing the notarization workflow](https://developer.apple.com/documentation/security/customizing-the-notarization-workflow)

## 1. Freeze and inspect the initial bytes

Choose one binary and verify its downloaded ZIP SHA-256 against the published catalog before extraction. Record the released asset ID/name/size/SHA-256, source SHA, architecture, and initial ZIP digest. Extract into a fresh directory and identify the one intended Mach-O; reject links, traversal, extra executables, AppleDouble, or unexpected members.

```sh
codesign --display --verbose=4 -- "$BINARY" 2>before-codesign-display.txt || true
codesign --verify --strict --verbose=4 -- "$BINARY" 2>before-codesign-verify.txt || true
lipo -archs "$BINARY"
otool -L "$BINARY"
vtool -show-build "$BINARY"
shasum -a 256 "$BINARY" >before-binary.sha256
```

Classify `codeSignatureKind` truthfully:

- `none`: `codesign` reports no signature;
- `linker-adhoc`: a signature exists with no Developer ID authority/Team ID (common on Apple Silicon);
- `developer-id`: a valid Developer ID Application authority and Team ID exist.

Record the before-state CDHash, every authority line, Team ID (or null), hardened-runtime state, and strict-verification result. `none` or `linker-adhoc` remains user-facing `UNSIGNED_DEVELOPMENT_ONLY`.

## 2. Select the identity and stable code identifier

List identities locally and choose a valid `Developer ID Application` identity whose Team ID is owned by the operator. Never copy the certificate private key or identity output into Git.

```sh
security find-identity -v -p codesigning
```

Choose and record a stable reverse-DNS code identifier for this binary family. Pick a **distinct family-conforming follow-up version/name before signing**. Developer ID changes Mach-O bytes/CDHash and the later ZIP digest, so the released no-Developer-ID name is permanently reserved and must not be reused.

## 3. Sign inside-out, without `--deep`

If an archive ever contains nested signable code, sign the deepest verified item first and work outward. These initial contracts are raw CLI binaries, so sign the one binary. Do not use `--deep`.

```sh
codesign --force --timestamp --options runtime \
--identifier "$CODE_IDENTIFIER" \
--sign "$DEVELOPER_IDENTITY" \
-- "$BINARY"
```

Then capture strict post-sign evidence:

```sh
codesign --display --verbose=4 -- "$BINARY" 2>after-codesign-display.txt
codesign --verify --strict --verbose=4 -- "$BINARY" 2>after-codesign-verify.txt
shasum -a 256 "$BINARY" >after-binary.sha256
```

Require Developer ID authority, expected Team ID/identifier, hardened runtime, timestamp, new CDHash, and strict verification success. Prove the before/after Mach-O digests differ.

## 4. Package after signing

Create the exact deterministic family archive only after signing. Re-run the complete archive/name/member/mode/architecture/linkage/version/smoke checks and write a new SHA-256. Never alter the accepted ZIP afterward.

The new ZIP name/version and catalog row must be distinct. The old row and bytes remain unchanged. `--clobber`, delete, replace, and in-place signing are forbidden.

## 5. Store notarization credentials in Keychain

Create a Keychain profile interactively. Do not put Apple ID passwords, app-specific passwords, issuer IDs, keys, certificates, or profile secrets in shell history, environment files, scripts, Git, Actions, archives, receipts, or logs.

```sh
xcrun notarytool store-credentials "$NOTARY_PROFILE"
```

The command prompts locally. Record only the non-secret profile label in private operator notes.

## 6. Submit once, wait, and retain the log

Submit the final byte-identical ZIP and wait:

```sh
xcrun notarytool submit "$FINAL_ZIP" \
--keychain-profile "$NOTARY_PROFILE" --wait \
--output-format json >notary-submit.json
```

Require `Accepted`, record submission ID and timestamps, then fetch and inspect the log locally:

```sh
xcrun notarytool log "$SUBMISSION_ID" \
--keychain-profile "$NOTARY_PROFILE" >notary-log.json
```

Redact/check evidence before publication. Never re-ZIP after acceptance; doing so creates unsubmitted bytes.

## 7. Online-ticket and Gatekeeper verification

Raw CLI binaries and ZIP archives cannot be stapled. Record `stapling=not-applicable`; notarization is delivered through Apple's online ticket lookup. Verify the exact binary/ZIP using supported online checks and Gatekeeper:

```sh
codesign --check-notarization --verbose=4 -- "$BINARY"
spctl --assess --type execute --verbose=4 -- "$BINARY"
shasum -a 256 "$FINAL_ZIP"
```

On a separate clean Apple-Silicon Mac, download the ZIP through a browser so quarantine metadata is present, verify its catalog SHA-256, extract normally, and run `codesign --verify --strict`, `spctl`, architecture/linkage/version/help, and the exact family smoke probe. Do not strip quarantine to make the test pass.

## 8. Evidence and append-only publication

The reviewed row/evidence must include:

- old asset ID/name/Mach-O/ZIP digests and before `codeSignatureKind`;
- new distinct version/name, source/build identity, exact binary/ZIP SHA-256;
- stable identifier, authorities, Team ID, CDHash, timestamp, hardened runtime and strict verification;
- distribution state `DEVELOPER_ID_SIGNED_NOT_NOTARIZED` or `DEVELOPER_ID_SIGNED_NOTARIZED_ONLINE_TICKET`;
- notarization submission ID/status/times, sanitized log digest/reference, `stapling=not-applicable`, online-ticket result, Gatekeeper result, and clean quarantined-download smoke;
- proof that the old release asset was not changed and the new asset passed normal preflight/read-back/drift gates.

Only after owner confirmation, new-name P7/P8 publication, durable catalog/audit evidence, and verification may a follow-up PR delete this temporary file. The permanent README signing states, append-only rule, and evidence requirements remain.
Loading