From 9f2c0b74c18f80a85c96b1daff68fa48540c81ce Mon Sep 17 00:00:00 2001 From: "Edward A." Date: Fri, 28 Aug 2026 19:05:56 -0400 Subject: [PATCH] Simplify README and add generated file catalog --- .github/workflows/ci.yml | 1 + .github/workflows/release-drift.yml | 2 +- CONTRIBUTING.md | 100 ++++++++ MACOS.md | 2 + README.md | 345 +++++++++------------------- docs/MACOS_SIGNING.md | 13 ++ docs/PROOF_DATA.md | 22 ++ docs/PUBLISHING.md | 109 +++++++++ scripts/render-readme-catalog | 109 +++++++++ tests/test_clean_room.py | 97 ++++++-- 10 files changed, 550 insertions(+), 250 deletions(-) create mode 100644 CONTRIBUTING.md create mode 100644 docs/MACOS_SIGNING.md create mode 100644 docs/PROOF_DATA.md create mode 100644 docs/PUBLISHING.md create mode 100755 scripts/render-readme-catalog diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3887405..888fdab 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -40,6 +40,7 @@ jobs: previous_args=(--previous-catalog "$previous_catalog") fi scripts/validate-catalog "${previous_args[@]}" + scripts/render-readme-catalog --check python3 -m unittest discover -s tests -v - name: Consume the exact audited forge protocol fixture diff --git a/.github/workflows/release-drift.yml b/.github/workflows/release-drift.yml index f2d8742..cdc546f 100644 --- a/.github/workflows/release-drift.yml +++ b/.github/workflows/release-drift.yml @@ -92,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 docs/PUBLISHING.md enable → dispatch → fresh-success sequence." } >>"$GITHUB_STEP_SUMMARY" diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..7e604d9 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,100 @@ +# Contributing development artifacts + +This document defines artifact contribution, naming, metadata, evidence, and validation requirements. See [publishing operations](docs/PUBLISHING.md), [public proof-data policy](docs/PROOF_DATA.md), and [macOS signing policy](docs/MACOS_SIGNING.md) for the corresponding specialist procedures. + +## Permanent append-only rules + +- The only canonical destination is repository `effectstream/binaries` (numeric ID `1117580582`, node ID `R_kgDOQpztJg`) and release `0.3.120` (numeric ID `270761136`, node ID `RE_kwDOQpztJs4QI3yw`). There is no successor release and no `legacyLocations` fallback. +- Never overwrite, delete, replace, rename, or use `--clobber` on an existing asset. Corrected bytes, changed source, a new proof-data generation, or later Developer-ID signing require a new family-conforming version/name and catalog row. +- Publication state is `planned → uploading → verified → published`, plus `revoked`. Stable resolution returns only `published`. Per-asset upload progress exists only in a local mode-`0600` journal. +- Every catalog row and resolver result is `distributionTier=development-only` and `releaseMutability=mutable-warehouse`. + +A reviewed `planned` catalog row exposes only the candidate basename, `state=candidate`, exact byte count, and SHA-256; destination URLs and asset IDs do not exist yet and must not be invented. The upload remains journal-only while in flight. After complete independent read-back, a reviewed `uploading → verified` change fills the observed destination identity, and a final reviewed `verified → published` change updates the stable index. CI compares each catalog change with the reviewed PR base and rejects skipped or reversed transitions; running the validator locally uses the merge-base with `origin/main`, so fetch the current base first or pass `--previous-catalog` explicitly. Only the one-time first-catalog PR whose reviewed base contains no catalog reconstructs its exact prior 66 rows from the immutable independent-download baseline. + +The schemas, exact backfill, stable index, family contracts, proof-data contract, and initial proposal are: + +- [`metadata/schema/artifact-catalog-v1.schema.json`](metadata/schema/artifact-catalog-v1.schema.json) +- [`metadata/releases/0.3.120.json`](metadata/releases/0.3.120.json) +- [`metadata/index.json`](metadata/index.json) +- [`metadata/contracts/families-v1.json`](metadata/contracts/families-v1.json) +- [`metadata/contracts/proof-data-q8b-v1.json`](metadata/contracts/proof-data-q8b-v1.json) +- [`metadata/proposals/initial-31-v1.json`](metadata/proposals/initial-31-v1.json) + +## Binary names, selectors, layouts, and coverage + +Canonical operating-system tokens are `linux|macos`; canonical architectures are `amd64|arm64`. Resolver inputs also accept `darwin|osx → macos`, `x86_64|x64 → amd64`, and `aarch64 → arm64`. Aliases never appear in canonical names. + +| Family | Exact outer name | Archive/install contract | +|---|---|---| +| Avail | `avail-node-{os}-{arch}-v{version}.tar.gz` | Existing exceptions are cataloged; new executable installs `0755` | +| Celestia app | `celestia-appd-{os}-{arch}-v{version}.tar.gz` | `LICENSE`, `README.md`, `celestia-appd`; executable `0755` | +| Celestia node | `celestia-node-{os}-{arch}-v{version}.tar.gz` | `LICENSE`, `README.md`, `celestia`; executable `0755` | +| Indexer | `indexer-standalone-{os}-{arch}-v{version}.zip` | exactly one root executable named `indexer-standalone-{os}-{arch}-v{version}` (the archive basename without `.zip`), stored/installed `0755` | +| Midnight node | `midnight-node-{os}-{arch}-{version}.zip` | root versioned executable plus `res/` | +| Midnight node toolkit | `midnight-node-toolkit-{os}-{arch}-{version}.zip` | exactly one root `midnight-node-toolkit`, stored/installed `0755` | +| Proof-server executable | `midnight-proof-server-{os}-{arch}-{version}.zip` | one root versioned executable; new publication is deferred | +| Legacy proof-server executable | `midnight-proof-server-{os}-{arch}-ledger-{version}.zip` | `variant=ledger`; new publication is deferred | + +For each new binary family/version set, `linux/amd64` and native `macos/arm64` are required, `linux/arm64` is desired, and `macos/amd64` is optional unless separately required. Existing exact published rows count toward completeness. A native target may not be satisfied by architecture fallback or a Linux-host Apple-SDK cross-build. + +The selected toolkit set is exactly: + +- `midnight-node-toolkit-linux-amd64-2.0.0-rc.4.zip` +- `midnight-node-toolkit-linux-arm64-2.0.0-rc.4.zip` +- `midnight-node-toolkit-macos-arm64-2.0.0-rc.4.zip` + +Linux toolkits are deterministic repacks of exact upstream assets; macOS arm64 is a native locked source build. A new family cannot be forced through a generic template: obtain owner approval, then add its outer name, inner layout, install mapping, schema rule, resolver rule, and positive/negative golden fixtures before building or uploading. + +## Choose the artifact operation + +Use exactly one operation and record it: + +| Operation | Use when | Required identity evidence | +|---|---|---| +| `build` | source must be compiled | full source SHA/tag, locked dependency set, compiler/toolchain/image digest, flags/features, native runner label/OS/arch, reproducibility and license/runtime closure | +| `identity-mirror` | upstream bytes and name are retained | upstream repository/release/object and asset ID/node ID/name/URL/size/SHA-256; output digest must equal input | +| `rename-only` | upstream bytes are retained under an approved warehouse name | all identity-mirror fields, explicit old/new name mapping, output digest equal to input | +| `repackage` | approved upstream members are placed in the family archive contract | typed `deterministic-repackage-v1` record: exact input asset ID/node/name/URL/size/digest; each input member path/size/digest mapped to one exact output path/size/digest/mode/fixed timestamp; fixed archive algorithm/compression/order/path policy; outer size/digest; distinct-runner two-run digests plus independent read-back; source manifest, checksums, provenance, member lineage, license/runtime closure, and software SBOM | +| `assemble-data` | public noarch objects form a structured deterministic archive | every source URL/path/size/hash/destination/mode, lineage/license evidence, deterministic member-manifest digest | + +Do not use `identity-mirror` if bytes change. Do not use `repackage` to hide an unknown source. A repackage record is invalid if any mapped byte, path, mode, timestamp, input identity, output identity, or either independent run differs. Known/new software rows must also match the machine-readable family coverage tier and exact archive/install layout. No payload moves forward until redistribution/license evidence and software runtime closure (or proof-data public lineage) are reviewed. + +## Required metadata and evidence + +Every row records: + +- schema and semantic ID; `artifactKind=software|proof-data`; family/version/variant or exact proof selector; +- target tier and `linux|macos` plus `amd64|arm64`, or exactly `platform=noarch` for proof data; +- immutable source/build/upstream identities, operation, locked inputs, license and redistribution evidence; +- GitHub asset numeric/node ID, exact name, API/download URL, state, size, API digest, independent SHA-256, timestamps, and content type; +- compressed/raw and expanded/member contract, stored mode, install path/mode, safety limits and legacy anomalies; +- dependencies/runtime closure, or exact proof-server source/image compatibility; +- SRS generation/K/official alias/cache alias, or Ledger-static semver/cache namespace/member-manifest revision; +- forge source manifest/checksums/provenance/attestation and software SBOM, or proof-data member/lineage manifest (never fabricate a software SBOM for data); +- publication state, `development-only`, `mutable-warehouse`, and applicable macOS signing/notarization evidence. + +Candidate evidence remains in the immutable forge release and is referenced by warehouse metadata. Only typed payloads append to `0.3.120`; forge evidence assets are not destination payloads. The initial invariant is ten binary payloads plus twenty raw SRS objects and one Ledger-static archive: `payloadCount=31`. + +## Prepare and validate a proposal + +Start from a clean branch and edit metadata only. Do not download candidate bytes into the Git checkout. + +```sh +python3 -m pip install -r requirements-ci.txt +scripts/validate-catalog +scripts/resolve --family indexer-standalone --version 4.4.0-rc.1 --os darwin --arch aarch64 +``` + +For a new publication, commit the exact proposal and hidden planned catalog rows on the reviewed branch first. A future family, K, or static revision is valid only when that same review extends the corresponding machine-readable contract and binds the proposal to the new contract digests. CI exercises the real proposal/planned-catalog/preflight boundary; changing only a README example, outer filename, or claimed hash cannot authorize arbitrary bytes. + +The resolver returns exactly one published `0.3.120` URL and SHA-256. It never guesses across OS, architecture, version, variant, K, SRS generation, or Ledger-static revision. Download to a new temporary directory, hash before extraction, then use a bounded family-aware extractor. + +`Compact` and `compactc` proposals fail validation. Compact 0.34 is downloaded directly from the official LFDT-Minokawa `compactc-v0.34.0` release by pinned upstream asset identity/digest. It must never be built, mirrored, repackaged, uploaded, or cataloged here. + +## Executable examples and clean-room fixture + +CI executes the resolver example above and [`tests/test_clean_room.py`](tests/test_clean_room.py) using only documentation-linked contracts, schemas, proposals, metadata, templates, and scripts. The clean-room fixtures materialize deterministic family-conforming ZIP bytes, bind their real outer/member sizes and SHA-256 values into a planned row, and execute the production preflight payload/archive inspector and receipt boundary; the hosted two-pin fixture does the same before its exact verifier record crosses receipt validation. They also execute the stable resolver, validate the bound prerequisite/component-policy records, exercise future-K rejection and same-semver Ledger-static correction resolution, and prove an unreviewed family-contract edit remains fail-closed until its parser/schema/install/test changes land together. Their direct negatives cover Compact/count drift and mixed or ambiguous selectors. The companion manual-transaction fixtures exercise the live prerequisite and component-policy gates and reject stale or cross-state prerequisite evidence, pre-remediation/unrelated/regressed component issuers, foreign release drift, and unsafe continuation. Documentation changes that disagree with these executable surfaces fail CI. + +## Compact 0.34 direct-upstream policy + +**Do not publish Compact.** Resolve stable `compactc-v0.34.0` directly from official LFDT-Minokawa by the pinned source commit, release asset ID, size, and SHA-256 for the exact host. Compiler 0.34 adoption is a coordinated runtime-0.19/Ledger-9 migration. This schema, validator, candidate, catalog, and release must contain zero Compact payloads. diff --git a/MACOS.md b/MACOS.md index 751c0ab..10f16a2 100644 --- a/MACOS.md +++ b/MACOS.md @@ -1,5 +1,7 @@ # Temporary owner handoff: Developer ID and notarization +Permanent signing states and append-only publication policy are documented in [`docs/MACOS_SIGNING.md`](docs/MACOS_SIGNING.md). This temporary owner handoff remains until the signed-artifact workflow is completed and confirmed. + > **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. diff --git a/README.md b/README.md index 2209b5e..ef545b1 100644 --- a/README.md +++ b/README.md @@ -4,242 +4,119 @@ [![Release drift](https://github.com/effectstream/binaries/actions/workflows/release-drift.yml/badge.svg)](https://github.com/effectstream/binaries/actions/workflows/release-drift.yml) -This repository is the metadata and operator surface for the permanent development-artifact warehouse at [`effectstream/binaries@0.3.120`](https://github.com/effectstream/binaries/releases/tag/0.3.120). Binary and public proof-data bytes live only as GitHub Release assets. They are appended manually from a maintainer's local authenticated session. Destination GitHub Actions has read-only `contents` permission and must never upload, alter, replace, or delete a release asset. +Development binaries and public proof data are published in [`effectstream/binaries@0.3.120`](https://github.com/effectstream/binaries/releases/tag/0.3.120). -Release `0.3.120` is intentionally mutable as a location. A URL is not an identity. The committed SHA-256 is the identity and must be checked before installation or execution. +## Current files -## 1. Permanent append-only rules +This catalog is generated from the committed [`metadata/index.json`](metadata/index.json). -- The only canonical destination is repository `effectstream/binaries` (numeric ID `1117580582`, node ID `R_kgDOQpztJg`) and release `0.3.120` (numeric ID `270761136`, node ID `RE_kwDOQpztJs4QI3yw`). There is no successor release and no `legacyLocations` fallback. -- Never overwrite, delete, replace, rename, or use `--clobber` on an existing asset. Corrected bytes, changed source, a new proof-data generation, or later Developer-ID signing require a new family-conforming version/name and catalog row. -- Publication state is `planned → uploading → verified → published`, plus `revoked`. Stable resolution returns only `published`. Per-asset upload progress exists only in a local mode-`0600` journal. -- Every catalog row and resolver result is `distributionTier=development-only` and `releaseMutability=mutable-warehouse`. + +97 files: 76 software and 21 proof-data. -A reviewed `planned` catalog row exposes only the candidate basename, `state=candidate`, exact byte count, and SHA-256; destination URLs and asset IDs do not exist yet and must not be invented. The upload remains journal-only while in flight. After complete independent read-back, a reviewed `uploading → verified` change fills the observed destination identity, and a final reviewed `verified → published` change updates the stable index. CI compares each catalog change with the reviewed PR base and rejects skipped or reversed transitions; running the validator locally uses the merge-base with `origin/main`, so fetch the current base first or pass `--previous-catalog` explicitly. Only the one-time first-catalog PR whose reviewed base contains no catalog reconstructs its exact prior 66 rows from the immutable independent-download baseline. - -The schemas, exact backfill, stable index, family contracts, proof-data contract, and initial proposal are: - -- [`metadata/schema/artifact-catalog-v1.schema.json`](metadata/schema/artifact-catalog-v1.schema.json) -- [`metadata/releases/0.3.120.json`](metadata/releases/0.3.120.json) -- [`metadata/index.json`](metadata/index.json) -- [`metadata/contracts/families-v1.json`](metadata/contracts/families-v1.json) -- [`metadata/contracts/proof-data-q8b-v1.json`](metadata/contracts/proof-data-q8b-v1.json) -- [`metadata/proposals/initial-31-v1.json`](metadata/proposals/initial-31-v1.json) - -## 2. Binary names, selectors, layouts, and coverage - -Canonical operating-system tokens are `linux|macos`; canonical architectures are `amd64|arm64`. Resolver inputs also accept `darwin|osx → macos`, `x86_64|x64 → amd64`, and `aarch64 → arm64`. Aliases never appear in canonical names. - -| Family | Exact outer name | Archive/install contract | -|---|---|---| -| Avail | `avail-node-{os}-{arch}-v{version}.tar.gz` | Existing exceptions are cataloged; new executable installs `0755` | -| Celestia app | `celestia-appd-{os}-{arch}-v{version}.tar.gz` | `LICENSE`, `README.md`, `celestia-appd`; executable `0755` | -| Celestia node | `celestia-node-{os}-{arch}-v{version}.tar.gz` | `LICENSE`, `README.md`, `celestia`; executable `0755` | -| Indexer | `indexer-standalone-{os}-{arch}-v{version}.zip` | exactly one root executable named `indexer-standalone-{os}-{arch}-v{version}` (the archive basename without `.zip`), stored/installed `0755` | -| Midnight node | `midnight-node-{os}-{arch}-{version}.zip` | root versioned executable plus `res/` | -| Midnight node toolkit | `midnight-node-toolkit-{os}-{arch}-{version}.zip` | exactly one root `midnight-node-toolkit`, stored/installed `0755` | -| Proof-server executable | `midnight-proof-server-{os}-{arch}-{version}.zip` | one root versioned executable; new publication is deferred | -| Legacy proof-server executable | `midnight-proof-server-{os}-{arch}-ledger-{version}.zip` | `variant=ledger`; new publication is deferred | - -For each new binary family/version set, `linux/amd64` and native `macos/arm64` are required, `linux/arm64` is desired, and `macos/amd64` is optional unless separately required. Existing exact published rows count toward completeness. A native target may not be satisfied by architecture fallback or a Linux-host Apple-SDK cross-build. - -The selected toolkit set is exactly: - -- `midnight-node-toolkit-linux-amd64-2.0.0-rc.4.zip` -- `midnight-node-toolkit-linux-arm64-2.0.0-rc.4.zip` -- `midnight-node-toolkit-macos-arm64-2.0.0-rc.4.zip` - -Linux toolkits are deterministic repacks of exact upstream assets; macOS arm64 is a native locked source build. A new family cannot be forced through a generic template: obtain owner approval, then add its outer name, inner layout, install mapping, schema rule, resolver rule, and positive/negative golden fixtures before building or uploading. - -## 3. Choose the artifact operation - -Use exactly one operation and record it: - -| Operation | Use when | Required identity evidence | +| Kind | File | SHA-256 | |---|---|---| -| `build` | source must be compiled | full source SHA/tag, locked dependency set, compiler/toolchain/image digest, flags/features, native runner label/OS/arch, reproducibility and license/runtime closure | -| `identity-mirror` | upstream bytes and name are retained | upstream repository/release/object and asset ID/node ID/name/URL/size/SHA-256; output digest must equal input | -| `rename-only` | upstream bytes are retained under an approved warehouse name | all identity-mirror fields, explicit old/new name mapping, output digest equal to input | -| `repackage` | approved upstream members are placed in the family archive contract | typed `deterministic-repackage-v1` record: exact input asset ID/node/name/URL/size/digest; each input member path/size/digest mapped to one exact output path/size/digest/mode/fixed timestamp; fixed archive algorithm/compression/order/path policy; outer size/digest; distinct-runner two-run digests plus independent read-back; source manifest, checksums, provenance, member lineage, license/runtime closure, and software SBOM | -| `assemble-data` | public noarch objects form a structured deterministic archive | every source URL/path/size/hash/destination/mode, lineage/license evidence, deterministic member-manifest digest | - -Do not use `identity-mirror` if bytes change. Do not use `repackage` to hide an unknown source. A repackage record is invalid if any mapped byte, path, mode, timestamp, input identity, output identity, or either independent run differs. Known/new software rows must also match the machine-readable family coverage tier and exact archive/install layout. No payload moves forward until redistribution/license evidence and software runtime closure (or proof-data public lineage) are reviewed. - -## 4. Required metadata and evidence - -Every row records: - -- schema and semantic ID; `artifactKind=software|proof-data`; family/version/variant or exact proof selector; -- target tier and `linux|macos` plus `amd64|arm64`, or exactly `platform=noarch` for proof data; -- immutable source/build/upstream identities, operation, locked inputs, license and redistribution evidence; -- GitHub asset numeric/node ID, exact name, API/download URL, state, size, API digest, independent SHA-256, timestamps, and content type; -- compressed/raw and expanded/member contract, stored mode, install path/mode, safety limits and legacy anomalies; -- dependencies/runtime closure, or exact proof-server source/image compatibility; -- SRS generation/K/official alias/cache alias, or Ledger-static semver/cache namespace/member-manifest revision; -- forge source manifest/checksums/provenance/attestation and software SBOM, or proof-data member/lineage manifest (never fabricate a software SBOM for data); -- publication state, `development-only`, `mutable-warehouse`, and applicable macOS signing/notarization evidence. - -Candidate evidence remains in the immutable forge release and is referenced by warehouse metadata. Only typed payloads append to `0.3.120`; forge evidence assets are not destination payloads. The initial invariant is ten binary payloads plus twenty raw SRS objects and one Ledger-static archive: `payloadCount=31`. - -## 5. Prepare and validate a proposal - -Start from a clean branch and edit metadata only. Do not download candidate bytes into the Git checkout. - -```sh -python3 -m pip install -r requirements-ci.txt -scripts/validate-catalog -scripts/resolve --family indexer-standalone --version 4.4.0-rc.1 --os darwin --arch aarch64 -``` - -For a new publication, commit the exact proposal and hidden planned catalog rows on the reviewed branch first. A future family, K, or static revision is valid only when that same review extends the corresponding machine-readable contract and binds the proposal to the new contract digests. CI exercises the real proposal/planned-catalog/preflight boundary; changing only a README example, outer filename, or claimed hash cannot authorize arbitrary bytes. - -The resolver returns exactly one published `0.3.120` URL and SHA-256. It never guesses across OS, architecture, version, variant, K, SRS generation, or Ledger-static revision. Download to a new temporary directory, hash before extraction, then use a bounded family-aware extractor. - -`Compact` and `compactc` proposals fail validation. Compact 0.34 is downloaded directly from the official LFDT-Minokawa `compactc-v0.34.0` release by pinned upstream asset identity/digest. It must never be built, mirrored, repackaged, uploaded, or cataloged here. - -## 6. Manual prerequisite and transaction sequence - -The following sequence is mandatory and ordered. Candidate bytes are inert: never execute, source, extract during privileged verification, expand into a command, use as a path other than an exact safe basename, interpret workflow syntax, or print untrusted bytes to logs. Never enable shell tracing around authentication or upload commands. Do not put tokens, Apple credentials, wallets, environment files, databases, private caches, or secrets in Git, staging, Actions artifacts, receipts, journals, logs, or release assets. - -1. Obtain explicit live-upload authority naming the repo/release/candidate. Record a non-secret authority reference. Fetch the reviewed warehouse PR, confirm the exact full commit SHA, clean worktree, and exact `origin`. -2. Confirm GitHub host/account/effective write permission and exact repo/release numeric+node identities. Authentication reports are suppressed to avoid credential metadata. -3. Independently verify the allowlisted immutable forge repository/workflow/ref/full SHA, candidate release/tag/ID/node ID/immutable state, canonical envelope, source manifest, checksums, staging asset-list digest, raw attestation bundle, and every inert asset size/digest. The preflight command itself freshly queries `github.com`, downloads the staging artifact and every candidate-release asset, compares those bytes with the local candidate, and reruns the raw protocol/attestation verifier; it never trusts an operator-supplied verification record. The warehouse consumes the exact audited promotion implementation pinned by [`protocol/forge-promotion-envelope-v1.json`](protocol/forge-promotion-envelope-v1.json) and separately requires the live candidate issuer to descend from both the independently audited component-policy remediation and the Phase-6 policy revision pinned by [`protocol/forge-component-policy-v1.json`](protocol/forge-component-policy-v1.json). The exact current component/build schemas and validator blobs must match that second pin. Its older audited fixture blobs remain in an explicit `testFixture` lane used only with the noncryptographic test marker; they cannot authorize a live candidate. The warehouse does not redefine forge canonicalization, and a pre-revision, unrelated, dirty, or regressed live issuer is rejected. -4. Capture the complete FR-039 snapshot through all pages. It binds repository/release/body identities plus every legacy asset ID/node ID/name/state/size/API and independent download digest/API URL/download URL/content type/timestamps. A partial name-only inventory is invalid. -5. Run complete-set zero-write preflight. It reports every absent, identical no-op, and conflicting candidate name. Any conflict creates no release write, journal, catalog state, or stable index change. -6. Bind explicit authority, exact proposal, canonical candidate/envelope/list, complete snapshot hash, and intended warning-body digest into a mode-`0600` receipt. Type the exact receipt-hash confirmation. -7. Re-download/recheck the complete live snapshot immediately before the first write. This reduces but cannot eliminate concurrent-publisher TOCTOU. -8. Upload only absent safe basenames through the create-only GitHub API. There is no delete/edit/`--clobber` path. Fsync a sanitized mode-`0600` journal after every API response. -9. On the first duplicate, unexpected response, or drift: stop all remaining writes; capture a fresh complete snapshot and journal; reconcile only if every observed candidate byte belongs to the same receipt. A foreign digest or any legacy-asset change hard-stops. -10. Independently re-download the complete release (legacy plus additions), validate every identity/hash, and reconstruct proof data. Transition through `verified`; merge stable `published` catalog/index last; then run drift check. - -Commands (values are deliberately explicit; no credential value is an argument): - -```sh -REVIEWED_HEAD=0123456789abcdef0123456789abcdef01234567 -AUTHORITY_REF=owner-approval-reference -FORGE_CHECKOUT=/absolute/read-only/path/to/midnight-binary-forge -FORGE_COMPONENT_CHECKOUT=/absolute/read-only/path/to/candidate-issuer/midnight-binary-forge -CANDIDATE_DIR=/absolute/private/mode-0700/candidate -RECEIPT_DIR=/absolute/private/mode-0700/receipts - -scripts/check-manual-publisher-prereqs.sh \ - --repo effectstream/binaries --account acedward --release 0.3.120 \ - --reviewed-head "$REVIEWED_HEAD" --authority-ref "$AUTHORITY_REF" \ - --output "$RECEIPT_DIR/prerequisite.json" - -scripts/snapshot-0.3.120 --output "$RECEIPT_DIR/preflight-snapshot.json" \ - --independent-downloads - -scripts/preflight-upload \ - --candidate-dir "$CANDIDATE_DIR/payloads" \ - --candidate-manifest "$CANDIDATE_DIR/candidate-assets.json" \ - --proposal metadata/proposals/initial-31-v1.json \ - --planned-catalog "$CANDIDATE_DIR/planned-catalog.json" \ - --snapshot "$RECEIPT_DIR/preflight-snapshot.json" \ - --candidate-envelope "$CANDIDATE_DIR/promotion-envelope-initial-31-v1.json" \ - --authority "$AUTHORITY_REF" \ - --prerequisite-record "$RECEIPT_DIR/prerequisite.json" \ - --candidate-bundle "$CANDIDATE_DIR/attestation-initial-31-v1.sigstore.json" \ - --forge-checkout "$FORGE_CHECKOUT" \ - --forge-component-checkout "$FORGE_COMPONENT_CHECKOUT" \ - --intended-release-body metadata/templates/release-body.md \ - --receipt "$RECEIPT_DIR/receipt.json" \ - --report "$RECEIPT_DIR/conflicts.json" - -# Read the exact receipt digest printed by preflight. Do not script acceptance. -scripts/upload-0.3.120 \ - --receipt "$RECEIPT_DIR/receipt.json" \ - --candidate-dir "$CANDIDATE_DIR/payloads" \ - --candidate-manifest "$CANDIDATE_DIR/candidate-assets.json" \ - --journal "$RECEIPT_DIR/journal.json" \ - --confirm 'UPLOAD effectstream/binaries 0.3.120 ' \ - --forge-component-checkout "$FORGE_COMPONENT_CHECKOUT" \ - --execute - -scripts/verify-release --receipt "$RECEIPT_DIR/receipt.json" \ - --output-snapshot "$RECEIPT_DIR/final-snapshot.json" -scripts/check-drift -``` - -Preflight issues no receipt until the live release body is the exact committed warning-body template. If the current body is still the reviewed old value, stop before preflight, apply only the exact `metadata/templates/release-body.md` body under separate confirmed authority, read the release back into a new full snapshot, and run the fresh prerequisite/preflight sequence into new files. An arbitrary pre-existing body, the old body, or any other release drift cannot be folded into a receipt or carried into asset creation. - -The prerequisite and freshly generated candidate-verification records are canonical, mode-`0600`, and digest-bound into the receipt. Preflight and upload repeat the live checkout/account/repository/release/component-policy checks so a record captured in an earlier state cannot authorize a later state. The candidate-envelope, planned-catalog, and intended-body digests are always computed from the exact verified files; no operator-supplied digest can substitute them. Keep receipt/journal directories outside Git at `0700`; receipt/journal files are new-only, atomically written and fsynced at `0600`. Retain a sanitized final receipt/journal as audit evidence, never authentication output or response headers that may reveal credential metadata. - -The typed transaction records are defined by [`publisher-prerequisite-v1`](metadata/schema/publisher-prerequisite-v1.schema.json), [`promotion-live-evidence-v1`](metadata/schema/promotion-live-evidence-v1.schema.json), [`candidate-verification-v1`](metadata/schema/candidate-verification-v1.schema.json), [`promotion-receipt-v1`](metadata/schema/promotion-receipt-v1.schema.json), and [`promotion-journal-v1`](metadata/schema/promotion-journal-v1.schema.json). Duplicate JSON keys, unknown fields, missing live identities, stale prerequisite records, digest rebinding, or a broken journal event chain fail closed. - -## 7. Executable examples and clean-room fixture - -CI executes the resolver example above and [`tests/test_clean_room.py`](tests/test_clean_room.py) using only README-linked contracts, schemas, proposals, metadata, templates, and scripts. The clean-room fixtures materialize deterministic family-conforming ZIP bytes, bind their real outer/member sizes and SHA-256 values into a planned row, and execute the production preflight payload/archive inspector and receipt boundary; the hosted two-pin fixture does the same before its exact verifier record crosses receipt validation. They also execute the stable resolver, validate the bound prerequisite/component-policy records, exercise future-K rejection and same-semver Ledger-static correction resolution, and prove an unreviewed family-contract edit remains fail-closed until its parser/schema/install/test changes land together. Their direct negatives cover Compact/count drift and mixed or ambiguous selectors. The companion manual-transaction fixtures exercise the live prerequisite and component-policy gates and reject stale or cross-state prerequisite evidence, pre-remediation/unrelated/regressed component issuers, foreign release drift, and unsafe continuation. README changes that disagree with these executable surfaces fail CI. - -## 8. Conflict, interruption, revocation, and drift - -- Identical existing name+size+digest is a no-op. A same name with different bytes is a hard conflict; never replace it. -- After interruption, run `scripts/reconcile-upload` with the receipt and a fresh full snapshot. If and only if it reports the exact same-receipt candidate additions plus absent names and zero foreign/repository/release/body/pagination/legacy drift, rerun the prerequisite probe into a new private record and repeat the same `scripts/upload-0.3.120` command with `--resume --resume-prerequisite-record /absolute/private/new-prerequisite.json`. The authenticated existing mode-`0600` journal, nonce, event hash chain, and exact receipt hash preserve lineage. A fresh transaction uses a new snapshot, prerequisite/candidate records, receipt, and journal. Foreign bytes or changed legacy fields hard-stop. -- A revoked artifact remains in the release/catalog as evidence, changes to `revoked`, disappears from stable resolution, and gets a reviewed incident advisory. A corrected new version/name is appended only after that PR. -- Consumer digest rejection is immediate. The daily read-only workflow provides best-effort detection within 24 hours plus GitHub delay. GitHub may disable schedules after 60 inactive days, so `acedward` runs the heartbeat at least weekly and before every upload or demo. Schedule-stop detection is bounded only by that check. - -```sh -scripts/check-drift-heartbeat.sh --repo effectstream/binaries \ - --workflow release-drift.yml --max-age-hours 36 -``` - -A green badge is not fresh evidence. Disabled, missing, failed, or older-than-36-hour state alerts. Recovery is explicit and read-only until inspection: - -```sh -gh workflow enable release-drift.yml --repo effectstream/binaries -previous_run_id=$(gh run list --repo effectstream/binaries --workflow release-drift.yml \ - --event workflow_dispatch --limit 1 --json databaseId --jq '.[0].databaseId // ""') -dispatch_started=$(date -u +%Y-%m-%dT%H:%M:%SZ) -gh workflow run release-drift.yml --repo effectstream/binaries -run_id=$(gh run list --repo effectstream/binaries --workflow release-drift.yml \ - --event workflow_dispatch --created ">=$dispatch_started" --limit 1 \ - --json databaseId --jq '.[0].databaseId') -test -n "$run_id" -test "$run_id" != "$previous_run_id" -gh run watch "$run_id" --repo effectstream/binaries --exit-status -scripts/check-drift-heartbeat.sh --repo effectstream/binaries \ - --workflow release-drift.yml --max-age-hours 36 -``` - -The captured `run_id` must be the newly dispatched run; never substitute an older green run. If listing races dispatch, query again before watching. Record that ID in the incident acknowledgement. - -On failure, `acedward` inspects the retained job report, records run ID/time and acknowledgement, then either records a false-alarm/fresh-success result or opens the reviewed manual `revoked` incident PR/advisory. The workflow only reports and never auto-revokes or auto-blesses bytes. - -`metadata/baselines/0.3.120-current.json` is a rotatable pointer to the reviewed current live release snapshot, not a permanent initial-state oracle. Whenever an authorized body or asset append is independently verified, add a new immutable full snapshot under `metadata/baselines/`, record its raw-file SHA-256 in the pointer, and update both at the first post-write reviewed catalog state; later `verified`/`published` states retain that exact snapshot unless another authorized release change occurred. Never rewrite, rename, or delete an old snapshot. CI binds every pointed live asset to a non-planned catalog destination identity and rejects a pointer that blesses uncataloged bytes. A subsequent preflight must equal the pointed repository/release/legacy asset identities and may perform only its separately intended body transition; stale initial, pre-existing foreign, or unreviewed live drift is rejected. - -## 9. macOS distribution signing - -Initial macOS candidates without Developer ID are user-facing `UNSIGNED_DEVELOPMENT_ONLY`, even when the Apple linker added an ad-hoc signature. Machine metadata separately records `codeSignatureKind=none|linker-adhoc|developer-id`, CDHash, authorities, Team ID, hardened-runtime state, and strict verification. - -Distribution-signing states are: - -- `UNSIGNED_DEVELOPMENT_ONLY` — no Developer ID; Gatekeeper warnings are expected; -- `DEVELOPER_ID_SIGNED_NOT_NOTARIZED`; -- `DEVELOPER_ID_SIGNED_NOTARIZED_ONLINE_TICKET` — standalone CLI/ZIP, `stapling=not-applicable`, ticket checked online. - -Applying Developer ID changes Mach-O bytes/CDHash. Packaging after signing changes the ZIP digest. Never sign or repackage an existing released asset in place: choose a distinct family-conforming version/name and append a new row. Owner-only temporary steps are in [`MACOS.md`](MACOS.md). That file remains until owner-confirmed completion; after deletion these permanent states, naming rules, and durable evidence remain here and in the catalog/audit. - -## 10. Compact 0.34 direct-upstream policy - -**Do not publish Compact.** Resolve stable `compactc-v0.34.0` directly from official LFDT-Minokawa by the pinned source commit, release asset ID, size, and SHA-256 for the exact host. Compiler 0.34 adoption is a coordinated runtime-0.19/Ledger-9 migration. This schema, validator, candidate, catalog, and release must contain zero Compact payloads. - -## 11. Public proof-data guide - -The selected architecture-neutral set is exactly twenty raw assets `bls_midnight_2p0` through `bls_midnight_2p19` plus `midnight-ledger-static-noarch-9.0.0.zip`: 21 payloads published once, never per OS, architecture, or proof-server release. These are public SRS/built-in Ledger inputs, not project-specific AA proving keys, wallets, private caches, or software binaries. - -K1–K19 use `srsGeneration=midnight-trusted-setup@3ea610263b228af24840f7b00661ee22360db6d8`; official names `midnight-srs-2p{k}` map explicitly to cache names `bls_midnight_2p{k}`. K0 has no ceremony catalog row and truthfully uses provider compatibility provenance `midnight-ledger-provider-compat@7a89f45d29792be7e09ca5eb246f1e69f0b2a179/sha256:59b30b…`. All raw files install `0644` under their literal cache names. - -The Ledger archive restores only twelve `zswap/9/*` and `dust/9/*` files at `0644`. Its semantic identity is the canonical, file-only, path-sorted `ledger-static-member-manifest-v1` projection (`path`, `bytes`, `sha256`, `mode`) and full `memberManifestSha256`; deterministic ZIP directory/type/order evidence is retained separately as the ZIP-layout manifest and never changes the semantic digest algorithm. Its version identity is `ledgerStaticSemver=9.0.0` plus `cacheNamespace=9`, not a proof-server RC. Exact rc.5 source plus the two pinned OCI digests accept static-9. Exact source `cd652d7…`/static-10 and its architecture-specific images reject static-9 while reusing the unchanged SRS. - -Append-only correction rules: - -- Changed bytes for an existing K use `midnight-srs-noarch-2p{k}-{generation}.bin`, where generation is `ts-`, `provider--sha256-`, or `sha256-`. Multiple same-K rows require the explicit full generation and install to the mapped literal alias; never guess latest. -- A normal static semver bump uses `midnight-ledger-static-noarch-{semver}.zip`. Changed bytes under unchanged semver use `midnight-ledger-static-noarch-{semver}-manifest-sha256-{full-member-manifest-digest}.zip` with `ledgerStaticRevision=manifest-sha256:`. Multiple same-semver rows require the full member manifest. -- A changed same-semver Ledger archive never inherits rc.5 compatibility merely because its namespace is `9`: its typed correction record must bind the exact new member manifest, source commit, both tested image digests, pass result, and reviewed evidence digest/reference. Static-10 source/images remain a hard negative. -- Byte-identical proof data adds exact compatibility metadata without another upload. - -Bootstrap/adoption rules: derive every generated BZKIR's K through the proof-server `/k` endpoint. Download only selected published rows, verify outer/raw and every member, compute the combined SRS+Ledger content-manifest SHA, take an exclusive lock, stage/fsync/verify on the same persistent filesystem, rename to immutable `generations/`, and atomically replace `current` while both readers are stopped. Resolve the pointer once and mount the same fixed generation path read-only into both services as `MIDNIGHT_PP`. Never mutate a generation in place. Retain the previous complete generation on failure; with readers quiesced, quarantine/repair a corrupt same-digest generation and garbage-collect only non-current/unreferenced generations. - -Do not point `MIDNIGHT_PARAM_SOURCE` at the flat GitHub Release: nested Ledger paths cannot resolve there. The documented fallback remains `https://srs.midnight.network/`. An opt-in fallback uses a separate disposable writable copy and never mutates the persistent verified generation. Offline tests block that origin, cold-start/restart both rc.5 variants, exercise K18/K19 and Compact ZKIR-v2/v3, corrupt every member class, and prove exact rc.7/static-10 rejection. - -K20+, a new Ledger namespace, any same-K generation, or a custom/project key needs owner approval, a new reviewed manifest, K query, size/storage/license/compatibility review, tests, and receipt. K24/K25 each exceed GitHub's under-2-GiB per-asset limit and cannot be single release assets. No allowlist grows silently. +| proof-data | [bls_midnight_2p0](https://github.com/effectstream/binaries/releases/download/0.3.120/bls_midnight_2p0) | `59b30b3114a34ccbbfb599376e178fb8d9b3366cae2174c2f1da20e75847f823` | +| proof-data | [bls_midnight_2p1](https://github.com/effectstream/binaries/releases/download/0.3.120/bls_midnight_2p1) | `bbe04fe3c70d0c138447cb086b4baddc30cb8bb2a004114bc02e6f739516280e` | +| proof-data | [bls_midnight_2p10](https://github.com/effectstream/binaries/releases/download/0.3.120/bls_midnight_2p10) | `46b2290933cbed4c378889e4ba971f1a92888331ffb09466acd4ff61a1e2cb42` | +| proof-data | [bls_midnight_2p11](https://github.com/effectstream/binaries/releases/download/0.3.120/bls_midnight_2p11) | `9901589d7956ff58be0d85569b2f455b77b58c3758026ffb5bbe4807000b96d1` | +| proof-data | [bls_midnight_2p12](https://github.com/effectstream/binaries/releases/download/0.3.120/bls_midnight_2p12) | `ef08eb3fcf62df8f72c515cffa027e681808b530cb016eea104115545ef6d5c8` | +| proof-data | [bls_midnight_2p13](https://github.com/effectstream/binaries/releases/download/0.3.120/bls_midnight_2p13) | `d3324910969c4cc54143b8045b649e5c3a4bd5fb7b8f85fe1b770f640ce1c803` | +| proof-data | [bls_midnight_2p14](https://github.com/effectstream/binaries/releases/download/0.3.120/bls_midnight_2p14) | `fc253016885ec830e97808c9ec920bb5cab5c21af590380a6cb5eb0538e2b244` | +| proof-data | [bls_midnight_2p15](https://github.com/effectstream/binaries/releases/download/0.3.120/bls_midnight_2p15) | `724c7c3d779148bb113c7ee9c034b2f27db16e6bdf315fde90105a9bad00b1de` | +| proof-data | [bls_midnight_2p16](https://github.com/effectstream/binaries/releases/download/0.3.120/bls_midnight_2p16) | `09c877216d6589b370263e18af40a030a901b41a7a7c37ef58c9901db41f05c6` | +| proof-data | [bls_midnight_2p17](https://github.com/effectstream/binaries/releases/download/0.3.120/bls_midnight_2p17) | `4a9ef6c7c0619aab74eede44b13e753e3ba54508a02dd3b7106a949aabb73b74` | +| proof-data | [bls_midnight_2p18](https://github.com/effectstream/binaries/releases/download/0.3.120/bls_midnight_2p18) | `e8436dc5d8b598f169c127c745135d889744007e6d384ff126df8d1332522f86` | +| proof-data | [bls_midnight_2p19](https://github.com/effectstream/binaries/releases/download/0.3.120/bls_midnight_2p19) | `8e8dc15c4362f05c912f1e770559a3945db3e58a374def416ed5d3e65ad5b10e` | +| proof-data | [bls_midnight_2p2](https://github.com/effectstream/binaries/releases/download/0.3.120/bls_midnight_2p2) | `80e15568fa1a0117db893239be7fa5e34a6bcc3a8c3bfa7709534b9cb88eb6c1` | +| proof-data | [bls_midnight_2p3](https://github.com/effectstream/binaries/releases/download/0.3.120/bls_midnight_2p3) | `4be827a6472193df80d8f08b4b25a85baef436fdd1965d89b6af89f4ec4e99e2` | +| proof-data | [bls_midnight_2p4](https://github.com/effectstream/binaries/releases/download/0.3.120/bls_midnight_2p4) | `232f401fad10c7ddf8828d2aa4c85c6506c5da09795998cecaeb9f75fc8f6ada` | +| proof-data | [bls_midnight_2p5](https://github.com/effectstream/binaries/releases/download/0.3.120/bls_midnight_2p5) | `0a1c9229f315fc1868ff25f668fb83aec4d09f4f23a706b5197c692c619d72c6` | +| proof-data | [bls_midnight_2p6](https://github.com/effectstream/binaries/releases/download/0.3.120/bls_midnight_2p6) | `cf2ad6be7d0fedf5bec2aaa35f6be4aca33053d74268fdf5aa54fcb2891ea6df` | +| proof-data | [bls_midnight_2p7](https://github.com/effectstream/binaries/releases/download/0.3.120/bls_midnight_2p7) | `e82ae890c080188355f37feaffe91372584cd810615082d9143d4dec0453fd9d` | +| proof-data | [bls_midnight_2p8](https://github.com/effectstream/binaries/releases/download/0.3.120/bls_midnight_2p8) | `909b707551eaaea79828e883cde6fc46ab15986c3b1d791bed462c9e2805c933` | +| proof-data | [bls_midnight_2p9](https://github.com/effectstream/binaries/releases/download/0.3.120/bls_midnight_2p9) | `b9009f1098bcefffec3c461ab3a5e3a17f7e5599f0f08c70fcdc55a89227bcbd` | +| proof-data | [midnight-ledger-static-noarch-9.0.0.zip](https://github.com/effectstream/binaries/releases/download/0.3.120/midnight-ledger-static-noarch-9.0.0.zip) | `d7e8ccfdbc55a2b7139aadd4797d665f888a4502b63ebae24d23314eeee341b2` | +| software | [avail-node-macos-arm64-v2.3.0.1.tar.gz](https://github.com/effectstream/binaries/releases/download/0.3.120/avail-node-macos-arm64-v2.3.0.1.tar.gz) | `b504a3aed177893f7768c5dd480256c227a0f43d73d10339456dc8d316fbccc2` | +| software | [celestia-appd-linux-amd64-v6.4.10.tar.gz](https://github.com/effectstream/binaries/releases/download/0.3.120/celestia-appd-linux-amd64-v6.4.10.tar.gz) | `fa1826187a91514c6d506e22f1fcce25afbd551b98458caf899801b662a0c8da` | +| software | [celestia-appd-linux-arm64-v6.4.10.tar.gz](https://github.com/effectstream/binaries/releases/download/0.3.120/celestia-appd-linux-arm64-v6.4.10.tar.gz) | `52cc9d59f9db5e3d2b7de91008c808f46ba319922db4a39404735b0a5dd6a76b` | +| software | [celestia-appd-macos-amd64-v6.4.10.tar.gz](https://github.com/effectstream/binaries/releases/download/0.3.120/celestia-appd-macos-amd64-v6.4.10.tar.gz) | `c84eddae85ce6f6a2cd3668efe646956828fbacd4d12a846bb45f5b7ac946ce6` | +| software | [celestia-appd-macos-arm64-v6.4.10.tar.gz](https://github.com/effectstream/binaries/releases/download/0.3.120/celestia-appd-macos-arm64-v6.4.10.tar.gz) | `832f4b919d79768b960e55f6d778366d9afbb95df6a860e344dffbb92e85b5be` | +| software | [celestia-node-linux-amd64-v0.28.4.tar.gz](https://github.com/effectstream/binaries/releases/download/0.3.120/celestia-node-linux-amd64-v0.28.4.tar.gz) | `bb8b9fd2ac859945fdf2f8c84ba038f75e01be54e56d1a946cbbd4b540f120d9` | +| software | [celestia-node-linux-arm64-v0.28.4.tar.gz](https://github.com/effectstream/binaries/releases/download/0.3.120/celestia-node-linux-arm64-v0.28.4.tar.gz) | `09eb0505c5265bb08dfd09f14aa397516efd89d7b8f120e06f133d9e387ad50c` | +| software | [celestia-node-macos-amd64-v0.28.4.tar.gz](https://github.com/effectstream/binaries/releases/download/0.3.120/celestia-node-macos-amd64-v0.28.4.tar.gz) | `eb02ccc9662f5cad269650c449e173408ec24d58cb9ded143d9c2711b31014b1` | +| software | [celestia-node-macos-arm64-v0.28.4.tar.gz](https://github.com/effectstream/binaries/releases/download/0.3.120/celestia-node-macos-arm64-v0.28.4.tar.gz) | `d2d637282489ea1ffc37a5fded4997be7140a7e77a43f0ea61a68f0f73069821` | +| software | [indexer-standalone-linux-amd64-v2.1.4.zip](https://github.com/effectstream/binaries/releases/download/0.3.120/indexer-standalone-linux-amd64-v2.1.4.zip) | `1b41cb9a73432ea738834253ea07ee8f79d5a4aecf28f0cf372b4d7567eb0eb3` | +| software | [indexer-standalone-linux-amd64-v3.0.0-alpha.21.zip](https://github.com/effectstream/binaries/releases/download/0.3.120/indexer-standalone-linux-amd64-v3.0.0-alpha.21.zip) | `cefe3e0836e3087dbc0105710369fd5aeffe6487652e87b8e59b03d9550e7bc2` | +| software | [indexer-standalone-linux-amd64-v3.0.0.zip](https://github.com/effectstream/binaries/releases/download/0.3.120/indexer-standalone-linux-amd64-v3.0.0.zip) | `ce122beb19cc86245de4c6dd1953d0424754054908ff39bef42875e94031a24c` | +| software | [indexer-standalone-linux-amd64-v4.0.0-rc.5.zip](https://github.com/effectstream/binaries/releases/download/0.3.120/indexer-standalone-linux-amd64-v4.0.0-rc.5.zip) | `d98660f43a4456afe7f6e89069e34dfe8b892c0ffb298af1e6d6a7b809fd9281` | +| software | [indexer-standalone-linux-amd64-v4.0.0.zip](https://github.com/effectstream/binaries/releases/download/0.3.120/indexer-standalone-linux-amd64-v4.0.0.zip) | `38262a6917f1d120b54d9d2c73525cdb76d42ea39c704b32e5d0110dc8918697` | +| software | [indexer-standalone-linux-amd64-v4.2.0.zip](https://github.com/effectstream/binaries/releases/download/0.3.120/indexer-standalone-linux-amd64-v4.2.0.zip) | `630ad6f77f44b4df07b379110c0223baa23ae373db0843dc2d6f3bd4c15780da` | +| software | [indexer-standalone-linux-amd64-v4.3.3.zip](https://github.com/effectstream/binaries/releases/download/0.3.120/indexer-standalone-linux-amd64-v4.3.3.zip) | `467a496dde5794180eb3b25ce7544428cf214b05fc51f95d34cc703826f0c1e4` | +| software | [indexer-standalone-linux-amd64-v4.4.0-rc.1.zip](https://github.com/effectstream/binaries/releases/download/0.3.120/indexer-standalone-linux-amd64-v4.4.0-rc.1.zip) | `eae945b7381af69cd42c4d480f7be14117d6e24524816aa58db2b8bfd7aee3b4` | +| software | [indexer-standalone-linux-amd64-v4.4.0-rc.3.zip](https://github.com/effectstream/binaries/releases/download/0.3.120/indexer-standalone-linux-amd64-v4.4.0-rc.3.zip) | `4b5df2ae3ed01f378adfb64d1c0d20d306470f8fba23a36638f937a4486a9434` | +| software | [indexer-standalone-linux-arm64-v2.1.4.zip](https://github.com/effectstream/binaries/releases/download/0.3.120/indexer-standalone-linux-arm64-v2.1.4.zip) | `b5cd7e6f51a725b33579a871ff434b88b74fc9c989f0b93d0bef16305d909750` | +| software | [indexer-standalone-linux-arm64-v3.0.0-alpha.21.zip](https://github.com/effectstream/binaries/releases/download/0.3.120/indexer-standalone-linux-arm64-v3.0.0-alpha.21.zip) | `c8c7edfb4c5aa8164ee9cce2e14e2aeabbec358d9ee712aa9cae70b9741298ee` | +| software | [indexer-standalone-linux-arm64-v4.2.0.zip](https://github.com/effectstream/binaries/releases/download/0.3.120/indexer-standalone-linux-arm64-v4.2.0.zip) | `43aca10706edacd191a84856ddfba573c7f435025757f5c0ee745c686ed68401` | +| software | [indexer-standalone-linux-arm64-v4.3.3.zip](https://github.com/effectstream/binaries/releases/download/0.3.120/indexer-standalone-linux-arm64-v4.3.3.zip) | `244fc6b1dde1dc4400ac2ea27f1ef4ce3794776cb7d153b85b93cb1ff8764212` | +| software | [indexer-standalone-linux-arm64-v4.4.0-rc.3.zip](https://github.com/effectstream/binaries/releases/download/0.3.120/indexer-standalone-linux-arm64-v4.4.0-rc.3.zip) | `eb44e8493df141d552334399dc25277e76cd500e937bedd5c6ff42a068fb15d0` | +| software | [indexer-standalone-macos-amd64-v4.4.0-rc.3.zip](https://github.com/effectstream/binaries/releases/download/0.3.120/indexer-standalone-macos-amd64-v4.4.0-rc.3.zip) | `28590ac9c35ed464cabdf121ac745ec7aff5c7fd6af2165bf46e4ab018fbe1cc` | +| software | [indexer-standalone-macos-arm64-v2.1.4.zip](https://github.com/effectstream/binaries/releases/download/0.3.120/indexer-standalone-macos-arm64-v2.1.4.zip) | `72f61d248a92832d4f4c16d46d703e873ef599692feaa5e9e14b6c390cc7efb9` | +| software | [indexer-standalone-macos-arm64-v3.0.0-alpha.21.zip](https://github.com/effectstream/binaries/releases/download/0.3.120/indexer-standalone-macos-arm64-v3.0.0-alpha.21.zip) | `6c5733e0051fc73680f83b3e6bf7e2d0c5948ab6cc1ad860c9d5b31ee458f3f1` | +| software | [indexer-standalone-macos-arm64-v3.0.0.zip](https://github.com/effectstream/binaries/releases/download/0.3.120/indexer-standalone-macos-arm64-v3.0.0.zip) | `1f567c6da8124e5247fa41b2013465075029b75287748c9fe5d36e5e7c52ee4a` | +| software | [indexer-standalone-macos-arm64-v4.0.0-rc.5.zip](https://github.com/effectstream/binaries/releases/download/0.3.120/indexer-standalone-macos-arm64-v4.0.0-rc.5.zip) | `bd20be5cc347206b36ce4004e3a034aa9e10087335b0b68421f45549f9cf9d65` | +| software | [indexer-standalone-macos-arm64-v4.0.0.zip](https://github.com/effectstream/binaries/releases/download/0.3.120/indexer-standalone-macos-arm64-v4.0.0.zip) | `34957bc6c33b1ac7c52576bf0b3339906d6554579ee39eeb02f6a2fea837b932` | +| software | [indexer-standalone-macos-arm64-v4.2.0.zip](https://github.com/effectstream/binaries/releases/download/0.3.120/indexer-standalone-macos-arm64-v4.2.0.zip) | `b0928b4bc0f7f5aeff274647094297cd1a3573fd2d4e399b367209cbcbf3c73a` | +| software | [indexer-standalone-macos-arm64-v4.3.3.zip](https://github.com/effectstream/binaries/releases/download/0.3.120/indexer-standalone-macos-arm64-v4.3.3.zip) | `bb44742626bf741ca4eb33d76e495b9460a693e34c3129886243ec2ed081491c` | +| software | [indexer-standalone-macos-arm64-v4.4.0-rc.1.zip](https://github.com/effectstream/binaries/releases/download/0.3.120/indexer-standalone-macos-arm64-v4.4.0-rc.1.zip) | `39a3715f709a6c5b215802a1c7a290937cc19772cbb8f5a994330b3c4b987309` | +| software | [indexer-standalone-macos-arm64-v4.4.0-rc.3.zip](https://github.com/effectstream/binaries/releases/download/0.3.120/indexer-standalone-macos-arm64-v4.4.0-rc.3.zip) | `b75e96c088b705722d561c6b46997759ed73b494dde0de72964851b5eda09ad2` | +| software | [midnight-node-linux-amd64-0.12.1.zip](https://github.com/effectstream/binaries/releases/download/0.3.120/midnight-node-linux-amd64-0.12.1.zip) | `201be564297d9cb7bc19cf18ac47f8548088ba6f8ad5395f15a3b0a7e3097afd` | +| software | [midnight-node-linux-amd64-0.18.0-rc.10.zip](https://github.com/effectstream/binaries/releases/download/0.3.120/midnight-node-linux-amd64-0.18.0-rc.10.zip) | `b976db9a02880bd605ae830c8ea8e365ff4d058a9c8d6a46972e2a07a71c2ce0` | +| software | [midnight-node-linux-amd64-0.20.0.zip](https://github.com/effectstream/binaries/releases/download/0.3.120/midnight-node-linux-amd64-0.20.0.zip) | `efaea87a463191da41fdfd16a936bb695797d40135fd567797e75fce8dfaee92` | +| software | [midnight-node-linux-amd64-0.22.0-rc.10.zip](https://github.com/effectstream/binaries/releases/download/0.3.120/midnight-node-linux-amd64-0.22.0-rc.10.zip) | `06fec1997d2e8c25f01da64ae1276588f48276eb47b25a583bd471dfb6b8b04e` | +| software | [midnight-node-linux-amd64-0.22.0.zip](https://github.com/effectstream/binaries/releases/download/0.3.120/midnight-node-linux-amd64-0.22.0.zip) | `7cc0cca8c59bc1dbf5d952691e9e5536d40195a5404e4a978e4990052af59021` | +| software | [midnight-node-linux-amd64-0.22.2.zip](https://github.com/effectstream/binaries/releases/download/0.3.120/midnight-node-linux-amd64-0.22.2.zip) | `1133d14878029b81dbb3f8e50653484af8e90264ffcd99ad0b43e730b7c13924` | +| software | [midnight-node-linux-amd64-0.22.5.zip](https://github.com/effectstream/binaries/releases/download/0.3.120/midnight-node-linux-amd64-0.22.5.zip) | `79bf34670986e6df9e182112543b762c9bdf0cbde4fd0290b393d17e9bdc8e2d` | +| software | [midnight-node-linux-amd64-1.0.0.zip](https://github.com/effectstream/binaries/releases/download/0.3.120/midnight-node-linux-amd64-1.0.0.zip) | `3826cefd5e50d3755f8d32ceb0fcf8a7c7165d35fe964bdb26b3b16657c339e4` | +| software | [midnight-node-linux-amd64-2.0.0-rc.4.zip](https://github.com/effectstream/binaries/releases/download/0.3.120/midnight-node-linux-amd64-2.0.0-rc.4.zip) | `8f53e9dfb2c70ec2fb98fd6958466ef107685774ca4d93660bc63e7686948879` | +| software | [midnight-node-linux-arm64-0.12.1.zip](https://github.com/effectstream/binaries/releases/download/0.3.120/midnight-node-linux-arm64-0.12.1.zip) | `177870d4d239a86cfdb09e95cf648cb4dcb68764d81a2f265ed987bfd0d7d585` | +| software | [midnight-node-linux-arm64-0.18.0-rc.10.zip](https://github.com/effectstream/binaries/releases/download/0.3.120/midnight-node-linux-arm64-0.18.0-rc.10.zip) | `a7f0d3e54db9de34802972745bea00a12aaf185f33a1572d5f2086dd3b17870f` | +| software | [midnight-node-linux-arm64-0.22.5.zip](https://github.com/effectstream/binaries/releases/download/0.3.120/midnight-node-linux-arm64-0.22.5.zip) | `aa0453c669f1f35f752fd35fe984339cff6d8722d649e786446a4f10cb5ff227` | +| software | [midnight-node-linux-arm64-1.0.0.zip](https://github.com/effectstream/binaries/releases/download/0.3.120/midnight-node-linux-arm64-1.0.0.zip) | `37ed130900b24df881e527cdb7ea21c124251d4b819d3b139a4a4252ec3dc603` | +| software | [midnight-node-linux-arm64-2.0.0-rc.4.zip](https://github.com/effectstream/binaries/releases/download/0.3.120/midnight-node-linux-arm64-2.0.0-rc.4.zip) | `490ef12ddf58a2a188f70edbfce974fd8d6cfa392e131232aa04e28557dbc55c` | +| software | [midnight-node-macos-arm64-0.12.1.zip](https://github.com/effectstream/binaries/releases/download/0.3.120/midnight-node-macos-arm64-0.12.1.zip) | `3a79143bab554040bf896333da2ae39ed6fb531b555ad281cb21dda7e09c517e` | +| software | [midnight-node-macos-arm64-0.18.0-rc.10.zip](https://github.com/effectstream/binaries/releases/download/0.3.120/midnight-node-macos-arm64-0.18.0-rc.10.zip) | `7b3d4b450576c1b96d70fc5142c67e7ec3d6b4c90788e8e268574af52c00a642` | +| software | [midnight-node-macos-arm64-0.20.0.zip](https://github.com/effectstream/binaries/releases/download/0.3.120/midnight-node-macos-arm64-0.20.0.zip) | `246fffe36e5aea067f8a4eeb8ccd973b6783886ab2c7da962c158eda1a445cb6` | +| software | [midnight-node-macos-arm64-0.22.0-rc.10.zip](https://github.com/effectstream/binaries/releases/download/0.3.120/midnight-node-macos-arm64-0.22.0-rc.10.zip) | `241a669cc837276954c4ea644233cc9c12a3c891677b8f804e4d6d5044dee2a4` | +| software | [midnight-node-macos-arm64-0.22.0.zip](https://github.com/effectstream/binaries/releases/download/0.3.120/midnight-node-macos-arm64-0.22.0.zip) | `f3ab91566a8f7f9244856cb373dfa7aa1f6db19d2293896cb365417da365220e` | +| software | [midnight-node-macos-arm64-0.22.2.zip](https://github.com/effectstream/binaries/releases/download/0.3.120/midnight-node-macos-arm64-0.22.2.zip) | `c16467acb5931bb72e71e309f0d09ba6fc17c493c07855f57749cf00c2e45fc1` | +| software | [midnight-node-macos-arm64-0.22.5.zip](https://github.com/effectstream/binaries/releases/download/0.3.120/midnight-node-macos-arm64-0.22.5.zip) | `dfb99619ba6387c149d258787994f757acfb2a241cedb798f746dce9f73cedfa` | +| software | [midnight-node-macos-arm64-1.0.0.zip](https://github.com/effectstream/binaries/releases/download/0.3.120/midnight-node-macos-arm64-1.0.0.zip) | `614f1009f10adb050935061e3f0e405fc86abfc851718257d5a3a82a1a67b6ed` | +| software | [midnight-node-macos-arm64-2.0.0-rc.4.zip](https://github.com/effectstream/binaries/releases/download/0.3.120/midnight-node-macos-arm64-2.0.0-rc.4.zip) | `4ee77c1043dec716f7a1b133f0ebb8f23bbc3a704f348ae5708a6b58b330ed8c` | +| software | [midnight-node-toolkit-linux-amd64-2.0.0-rc.4.zip](https://github.com/effectstream/binaries/releases/download/0.3.120/midnight-node-toolkit-linux-amd64-2.0.0-rc.4.zip) | `92836fa7e301ec153fbeeb18ffc113eea4503732ff335f88c2823ad3e527524c` | +| software | [midnight-node-toolkit-linux-arm64-2.0.0-rc.4.zip](https://github.com/effectstream/binaries/releases/download/0.3.120/midnight-node-toolkit-linux-arm64-2.0.0-rc.4.zip) | `4887874e114dafac8807e524b9d7694e1debd098a8d06ede0831ed7fec576528` | +| software | [midnight-node-toolkit-macos-arm64-2.0.0-rc.4.zip](https://github.com/effectstream/binaries/releases/download/0.3.120/midnight-node-toolkit-macos-arm64-2.0.0-rc.4.zip) | `8df786b56f80bd4c2ea4226240a9855481f7c3d56e5794d939d4391dcfb9a02c` | +| software | [midnight-proof-server-linux-amd64-9.0.0-rc.5.zip](https://github.com/effectstream/binaries/releases/download/0.3.120/midnight-proof-server-linux-amd64-9.0.0-rc.5.zip) | `a0db7b0613a86618d672c6aa6064519fb95aba6f9352cfbb351fed885d622124` | +| software | [midnight-proof-server-linux-amd64-ledger-4.0.0.zip](https://github.com/effectstream/binaries/releases/download/0.3.120/midnight-proof-server-linux-amd64-ledger-4.0.0.zip) | `11cf7c80d0d6b0023ac63956cc8a1c223da9b3edc159840ee592266193e7ada1` | +| software | [midnight-proof-server-linux-amd64-ledger-6.1.0-alpha.6.zip](https://github.com/effectstream/binaries/releases/download/0.3.120/midnight-proof-server-linux-amd64-ledger-6.1.0-alpha.6.zip) | `9a3fd5cf0cd5599e0589531f7c352b86acc64212deccb8f1ca080cb63673c54d` | +| software | [midnight-proof-server-linux-amd64-ledger-7.0.0.zip](https://github.com/effectstream/binaries/releases/download/0.3.120/midnight-proof-server-linux-amd64-ledger-7.0.0.zip) | `75b7bfe81c245942463ba11f1f8cafcb9510f26e004af765c9a7523ed710e1c5` | +| software | [midnight-proof-server-linux-amd64-ledger-8.0.2.zip](https://github.com/effectstream/binaries/releases/download/0.3.120/midnight-proof-server-linux-amd64-ledger-8.0.2.zip) | `23ec424ed7082bdd012686c078babafd63f51cdb6065597a8ee13f01ae3f2434` | +| software | [midnight-proof-server-linux-amd64-ledger-8.0.3.zip](https://github.com/effectstream/binaries/releases/download/0.3.120/midnight-proof-server-linux-amd64-ledger-8.0.3.zip) | `7daaf43cd6344f8f1c9a0b70867d72609e1717c2616b9c96687632cbab6c33e8` | +| software | [midnight-proof-server-linux-amd64-ledger-8.1.0.zip](https://github.com/effectstream/binaries/releases/download/0.3.120/midnight-proof-server-linux-amd64-ledger-8.1.0.zip) | `21d9893c947948fd183711d15db3e0b16563ef44fb2849f417fe36724f9df279` | +| software | [midnight-proof-server-linux-arm64-ledger-4.0.0.zip](https://github.com/effectstream/binaries/releases/download/0.3.120/midnight-proof-server-linux-arm64-ledger-4.0.0.zip) | `217af8b1fb894ec1a720df6eb636fb606c092a404b1a01518aba7d7fffef1204` | +| software | [midnight-proof-server-linux-arm64-ledger-6.1.0-alpha.6.zip](https://github.com/effectstream/binaries/releases/download/0.3.120/midnight-proof-server-linux-arm64-ledger-6.1.0-alpha.6.zip) | `40acf35d194c9ebea324e416a8a1002292b85799ae78e989e38b825049dc6d9b` | +| software | [midnight-proof-server-linux-arm64-ledger-8.1.0.zip](https://github.com/effectstream/binaries/releases/download/0.3.120/midnight-proof-server-linux-arm64-ledger-8.1.0.zip) | `13aa2fb681012b21e59f0c9db0048ecff146d08b38c6699c62c99106179d8221` | +| software | [midnight-proof-server-macos-arm64-9.0.0-rc.5.zip](https://github.com/effectstream/binaries/releases/download/0.3.120/midnight-proof-server-macos-arm64-9.0.0-rc.5.zip) | `2149ba808892122cfab9ace2e382f4addecc2ecbe06b17dcd1bffece5a5be891` | +| software | [midnight-proof-server-macos-arm64-ledger-4.0.0.zip](https://github.com/effectstream/binaries/releases/download/0.3.120/midnight-proof-server-macos-arm64-ledger-4.0.0.zip) | `a642fdf5cdb9b125faa12312041a7efa5ce649bdde26312a5fc06f19d38c76e0` | +| software | [midnight-proof-server-macos-arm64-ledger-6.1.0-alpha.6.zip](https://github.com/effectstream/binaries/releases/download/0.3.120/midnight-proof-server-macos-arm64-ledger-6.1.0-alpha.6.zip) | `960f226754d50463a0e11207f9f7207ab5d1d975e6f0863c7523f68cfb6712ed` | +| software | [midnight-proof-server-macos-arm64-ledger-7.0.0.zip](https://github.com/effectstream/binaries/releases/download/0.3.120/midnight-proof-server-macos-arm64-ledger-7.0.0.zip) | `36a63a9938715a96f64b5cb6f461b9bffe996b3373052515801553a1c2ceda40` | +| software | [midnight-proof-server-macos-arm64-ledger-8.0.2.zip](https://github.com/effectstream/binaries/releases/download/0.3.120/midnight-proof-server-macos-arm64-ledger-8.0.2.zip) | `2a9ccb0ae0017097174aa3bef6ba0cdebb224630cc501125877dbb3ed3186067` | +| software | [midnight-proof-server-macos-arm64-ledger-8.0.3.zip](https://github.com/effectstream/binaries/releases/download/0.3.120/midnight-proof-server-macos-arm64-ledger-8.0.3.zip) | `56acea531aa3c7b64297de1d1e744bef6a65f9969e297107af891d84d12709b9` | +| software | [midnight-proof-server-macos-arm64-ledger-8.1.0.zip](https://github.com/effectstream/binaries/releases/download/0.3.120/midnight-proof-server-macos-arm64-ledger-8.1.0.zip) | `e9894e62c5753097a0a086c6a9c11ec0af3a57b0be52c141c33f70f63a528ad2` | + + +## Documentation + +- [Contributing and artifact requirements](CONTRIBUTING.md) +- [Publishing, recovery, and drift operations](docs/PUBLISHING.md) +- [Public proof-data policy](docs/PROOF_DATA.md) +- [macOS signing and notarization policy](docs/MACOS_SIGNING.md) diff --git a/docs/MACOS_SIGNING.md b/docs/MACOS_SIGNING.md new file mode 100644 index 0000000..0ecd473 --- /dev/null +++ b/docs/MACOS_SIGNING.md @@ -0,0 +1,13 @@ +# macOS distribution signing + +Initial macOS candidates without Developer ID are user-facing `UNSIGNED_DEVELOPMENT_ONLY`, even when the Apple linker added an ad-hoc signature. Machine metadata separately records `codeSignatureKind=none|linker-adhoc|developer-id`, CDHash, authorities, Team ID, hardened-runtime state, and strict verification. + +Distribution-signing states are: + +- `UNSIGNED_DEVELOPMENT_ONLY` — no Developer ID; Gatekeeper warnings are expected; +- `DEVELOPER_ID_SIGNED_NOT_NOTARIZED`; +- `DEVELOPER_ID_SIGNED_NOTARIZED_ONLINE_TICKET` — standalone CLI/ZIP, `stapling=not-applicable`, ticket checked online. + +Applying Developer ID changes Mach-O bytes/CDHash. Packaging after signing changes the ZIP digest. Never sign or repackage an existing released asset in place: choose a distinct family-conforming version/name and append a new row. Owner-only temporary steps are in [`MACOS.md`](../MACOS.md). That file remains until owner-confirmed completion; after deletion these permanent states, naming rules, and durable evidence remain here and in the catalog/audit. + +See [`CONTRIBUTING.md`](../CONTRIBUTING.md) for append-only contribution and metadata requirements. diff --git a/docs/PROOF_DATA.md b/docs/PROOF_DATA.md new file mode 100644 index 0000000..7c646f5 --- /dev/null +++ b/docs/PROOF_DATA.md @@ -0,0 +1,22 @@ +# Public proof-data guide + +The selected architecture-neutral set is exactly twenty raw assets `bls_midnight_2p0` through `bls_midnight_2p19` plus `midnight-ledger-static-noarch-9.0.0.zip`: 21 payloads published once, never per OS, architecture, or proof-server release. These are public SRS/built-in Ledger inputs, not project-specific AA proving keys, wallets, private caches, or software binaries. + +K1–K19 use `srsGeneration=midnight-trusted-setup@3ea610263b228af24840f7b00661ee22360db6d8`; official names `midnight-srs-2p{k}` map explicitly to cache names `bls_midnight_2p{k}`. K0 has no ceremony catalog row and truthfully uses provider compatibility provenance `midnight-ledger-provider-compat@7a89f45d29792be7e09ca5eb246f1e69f0b2a179/sha256:59b30b…`. All raw files install `0644` under their literal cache names. + +The Ledger archive restores only twelve `zswap/9/*` and `dust/9/*` files at `0644`. Its semantic identity is the canonical, file-only, path-sorted `ledger-static-member-manifest-v1` projection (`path`, `bytes`, `sha256`, `mode`) and full `memberManifestSha256`; deterministic ZIP directory/type/order evidence is retained separately as the ZIP-layout manifest and never changes the semantic digest algorithm. Its version identity is `ledgerStaticSemver=9.0.0` plus `cacheNamespace=9`, not a proof-server RC. Exact rc.5 source plus the two pinned OCI digests accept static-9. Exact source `cd652d7…`/static-10 and its architecture-specific images reject static-9 while reusing the unchanged SRS. + +Append-only correction rules: + +- Changed bytes for an existing K use `midnight-srs-noarch-2p{k}-{generation}.bin`, where generation is `ts-`, `provider--sha256-`, or `sha256-`. Multiple same-K rows require the explicit full generation and install to the mapped literal alias; never guess latest. +- A normal static semver bump uses `midnight-ledger-static-noarch-{semver}.zip`. Changed bytes under unchanged semver use `midnight-ledger-static-noarch-{semver}-manifest-sha256-{full-member-manifest-digest}.zip` with `ledgerStaticRevision=manifest-sha256:`. Multiple same-semver rows require the full member manifest. +- A changed same-semver Ledger archive never inherits rc.5 compatibility merely because its namespace is `9`: its typed correction record must bind the exact new member manifest, source commit, both tested image digests, pass result, and reviewed evidence digest/reference. Static-10 source/images remain a hard negative. +- Byte-identical proof data adds exact compatibility metadata without another upload. + +Bootstrap/adoption rules: derive every generated BZKIR's K through the proof-server `/k` endpoint. Download only selected published rows, verify outer/raw and every member, compute the combined SRS+Ledger content-manifest SHA, take an exclusive lock, stage/fsync/verify on the same persistent filesystem, rename to immutable `generations/`, and atomically replace `current` while both readers are stopped. Resolve the pointer once and mount the same fixed generation path read-only into both services as `MIDNIGHT_PP`. Never mutate a generation in place. Retain the previous complete generation on failure; with readers quiesced, quarantine/repair a corrupt same-digest generation and garbage-collect only non-current/unreferenced generations. + +Do not point `MIDNIGHT_PARAM_SOURCE` at the flat GitHub Release: nested Ledger paths cannot resolve there. The documented fallback remains `https://srs.midnight.network/`. An opt-in fallback uses a separate disposable writable copy and never mutates the persistent verified generation. Offline tests block that origin, cold-start/restart both rc.5 variants, exercise K18/K19 and Compact ZKIR-v2/v3, corrupt every member class, and prove exact rc.7/static-10 rejection. + +K20+, a new Ledger namespace, any same-K generation, or a custom/project key needs owner approval, a new reviewed manifest, K query, size/storage/license/compatibility review, tests, and receipt. K24/K25 each exceed GitHub's under-2-GiB per-asset limit and cannot be single release assets. No allowlist grows silently. + +See [`CONTRIBUTING.md`](../CONTRIBUTING.md) for artifact metadata and evidence requirements. diff --git a/docs/PUBLISHING.md b/docs/PUBLISHING.md new file mode 100644 index 0000000..0ae38d5 --- /dev/null +++ b/docs/PUBLISHING.md @@ -0,0 +1,109 @@ +# Publishing, recovery, and drift operations + +Run all commands from the repository root. Artifact requirements and proposal preparation are defined in [`CONTRIBUTING.md`](../CONTRIBUTING.md). + +## Manual prerequisite and transaction sequence + +The following sequence is mandatory and ordered. Candidate bytes are inert: never execute, source, extract during privileged verification, expand into a command, use as a path other than an exact safe basename, interpret workflow syntax, or print untrusted bytes to logs. Never enable shell tracing around authentication or upload commands. Do not put tokens, Apple credentials, wallets, environment files, databases, private caches, or secrets in Git, staging, Actions artifacts, receipts, journals, logs, or release assets. + +1. Obtain explicit live-upload authority naming the repo/release/candidate. Record a non-secret authority reference. Fetch the reviewed warehouse PR, confirm the exact full commit SHA, clean worktree, and exact `origin`. +2. Confirm GitHub host/account/effective write permission and exact repo/release numeric+node identities. Authentication reports are suppressed to avoid credential metadata. +3. Independently verify the allowlisted immutable forge repository/workflow/ref/full SHA, candidate release/tag/ID/node ID/immutable state, canonical envelope, source manifest, checksums, staging asset-list digest, raw attestation bundle, and every inert asset size/digest. The preflight command itself freshly queries `github.com`, downloads the staging artifact and every candidate-release asset, compares those bytes with the local candidate, and reruns the raw protocol/attestation verifier; it never trusts an operator-supplied verification record. The warehouse consumes the exact audited promotion implementation pinned by [`protocol/forge-promotion-envelope-v1.json`](../protocol/forge-promotion-envelope-v1.json) and separately requires the live candidate issuer to descend from both the independently audited component-policy remediation and the Phase-6 policy revision pinned by [`protocol/forge-component-policy-v1.json`](../protocol/forge-component-policy-v1.json). The exact current component/build schemas and validator blobs must match that second pin. Its older audited fixture blobs remain in an explicit `testFixture` lane used only with the noncryptographic test marker; they cannot authorize a live candidate. The warehouse does not redefine forge canonicalization, and a pre-revision, unrelated, dirty, or regressed live issuer is rejected. +4. Capture the complete FR-039 snapshot through all pages. It binds repository/release/body identities plus every legacy asset ID/node ID/name/state/size/API and independent download digest/API URL/download URL/content type/timestamps. A partial name-only inventory is invalid. +5. Run complete-set zero-write preflight. It reports every absent, identical no-op, and conflicting candidate name. Any conflict creates no release write, journal, catalog state, or stable index change. +6. Bind explicit authority, exact proposal, canonical candidate/envelope/list, complete snapshot hash, and intended warning-body digest into a mode-`0600` receipt. Type the exact receipt-hash confirmation. +7. Re-download/recheck the complete live snapshot immediately before the first write. This reduces but cannot eliminate concurrent-publisher TOCTOU. +8. Upload only absent safe basenames through the create-only GitHub API. There is no delete/edit/`--clobber` path. Fsync a sanitized mode-`0600` journal after every API response. +9. On the first duplicate, unexpected response, or drift: stop all remaining writes; capture a fresh complete snapshot and journal; reconcile only if every observed candidate byte belongs to the same receipt. A foreign digest or any legacy-asset change hard-stops. +10. Independently re-download the complete release (legacy plus additions), validate every identity/hash, and reconstruct proof data. Transition through `verified`; merge stable `published` catalog/index last; then run drift check. + +Commands (values are deliberately explicit; no credential value is an argument): + +```sh +REVIEWED_HEAD=0123456789abcdef0123456789abcdef01234567 +AUTHORITY_REF=owner-approval-reference +FORGE_CHECKOUT=/absolute/read-only/path/to/midnight-binary-forge +FORGE_COMPONENT_CHECKOUT=/absolute/read-only/path/to/candidate-issuer/midnight-binary-forge +CANDIDATE_DIR=/absolute/private/mode-0700/candidate +RECEIPT_DIR=/absolute/private/mode-0700/receipts + +scripts/check-manual-publisher-prereqs.sh \ + --repo effectstream/binaries --account acedward --release 0.3.120 \ + --reviewed-head "$REVIEWED_HEAD" --authority-ref "$AUTHORITY_REF" \ + --output "$RECEIPT_DIR/prerequisite.json" + +scripts/snapshot-0.3.120 --output "$RECEIPT_DIR/preflight-snapshot.json" \ + --independent-downloads + +scripts/preflight-upload \ + --candidate-dir "$CANDIDATE_DIR/payloads" \ + --candidate-manifest "$CANDIDATE_DIR/candidate-assets.json" \ + --proposal metadata/proposals/initial-31-v1.json \ + --planned-catalog "$CANDIDATE_DIR/planned-catalog.json" \ + --snapshot "$RECEIPT_DIR/preflight-snapshot.json" \ + --candidate-envelope "$CANDIDATE_DIR/promotion-envelope-initial-31-v1.json" \ + --authority "$AUTHORITY_REF" \ + --prerequisite-record "$RECEIPT_DIR/prerequisite.json" \ + --candidate-bundle "$CANDIDATE_DIR/attestation-initial-31-v1.sigstore.json" \ + --forge-checkout "$FORGE_CHECKOUT" \ + --forge-component-checkout "$FORGE_COMPONENT_CHECKOUT" \ + --intended-release-body metadata/templates/release-body.md \ + --receipt "$RECEIPT_DIR/receipt.json" \ + --report "$RECEIPT_DIR/conflicts.json" + +# Read the exact receipt digest printed by preflight. Do not script acceptance. +scripts/upload-0.3.120 \ + --receipt "$RECEIPT_DIR/receipt.json" \ + --candidate-dir "$CANDIDATE_DIR/payloads" \ + --candidate-manifest "$CANDIDATE_DIR/candidate-assets.json" \ + --journal "$RECEIPT_DIR/journal.json" \ + --confirm 'UPLOAD effectstream/binaries 0.3.120 ' \ + --forge-component-checkout "$FORGE_COMPONENT_CHECKOUT" \ + --execute + +scripts/verify-release --receipt "$RECEIPT_DIR/receipt.json" \ + --output-snapshot "$RECEIPT_DIR/final-snapshot.json" +scripts/check-drift +``` + +Preflight issues no receipt until the live release body is the exact committed warning-body template. If the current body is still the reviewed old value, stop before preflight, apply only the exact `metadata/templates/release-body.md` body under separate confirmed authority, read the release back into a new full snapshot, and run the fresh prerequisite/preflight sequence into new files. An arbitrary pre-existing body, the old body, or any other release drift cannot be folded into a receipt or carried into asset creation. + +The prerequisite and freshly generated candidate-verification records are canonical, mode-`0600`, and digest-bound into the receipt. Preflight and upload repeat the live checkout/account/repository/release/component-policy checks so a record captured in an earlier state cannot authorize a later state. The candidate-envelope, planned-catalog, and intended-body digests are always computed from the exact verified files; no operator-supplied digest can substitute them. Keep receipt/journal directories outside Git at `0700`; receipt/journal files are new-only, atomically written and fsynced at `0600`. Retain a sanitized final receipt/journal as audit evidence, never authentication output or response headers that may reveal credential metadata. + +The typed transaction records are defined by [`publisher-prerequisite-v1`](../metadata/schema/publisher-prerequisite-v1.schema.json), [`promotion-live-evidence-v1`](../metadata/schema/promotion-live-evidence-v1.schema.json), [`candidate-verification-v1`](../metadata/schema/candidate-verification-v1.schema.json), [`promotion-receipt-v1`](../metadata/schema/promotion-receipt-v1.schema.json), and [`promotion-journal-v1`](../metadata/schema/promotion-journal-v1.schema.json). Duplicate JSON keys, unknown fields, missing live identities, stale prerequisite records, digest rebinding, or a broken journal event chain fail closed. + +## Conflict, interruption, revocation, and drift + +- Identical existing name+size+digest is a no-op. A same name with different bytes is a hard conflict; never replace it. +- After interruption, run `scripts/reconcile-upload` with the receipt and a fresh full snapshot. If and only if it reports the exact same-receipt candidate additions plus absent names and zero foreign/repository/release/body/pagination/legacy drift, rerun the prerequisite probe into a new private record and repeat the same `scripts/upload-0.3.120` command with `--resume --resume-prerequisite-record /absolute/private/new-prerequisite.json`. The authenticated existing mode-`0600` journal, nonce, event hash chain, and exact receipt hash preserve lineage. A fresh transaction uses a new snapshot, prerequisite/candidate records, receipt, and journal. Foreign bytes or changed legacy fields hard-stop. +- A revoked artifact remains in the release/catalog as evidence, changes to `revoked`, disappears from stable resolution, and gets a reviewed incident advisory. A corrected new version/name is appended only after that PR. +- Consumer digest rejection is immediate. The daily read-only workflow provides best-effort detection within 24 hours plus GitHub delay. GitHub may disable schedules after 60 inactive days, so `acedward` runs the heartbeat at least weekly and before every upload or demo. Schedule-stop detection is bounded only by that check. + +```sh +scripts/check-drift-heartbeat.sh --repo effectstream/binaries \ + --workflow release-drift.yml --max-age-hours 36 +``` + +A green badge is not fresh evidence. Disabled, missing, failed, or older-than-36-hour state alerts. Recovery is explicit and read-only until inspection: + +```sh +gh workflow enable release-drift.yml --repo effectstream/binaries +previous_run_id=$(gh run list --repo effectstream/binaries --workflow release-drift.yml \ + --event workflow_dispatch --limit 1 --json databaseId --jq '.[0].databaseId // ""') +dispatch_started=$(date -u +%Y-%m-%dT%H:%M:%SZ) +gh workflow run release-drift.yml --repo effectstream/binaries +run_id=$(gh run list --repo effectstream/binaries --workflow release-drift.yml \ + --event workflow_dispatch --created ">=$dispatch_started" --limit 1 \ + --json databaseId --jq '.[0].databaseId') +test -n "$run_id" +test "$run_id" != "$previous_run_id" +gh run watch "$run_id" --repo effectstream/binaries --exit-status +scripts/check-drift-heartbeat.sh --repo effectstream/binaries \ + --workflow release-drift.yml --max-age-hours 36 +``` + +The captured `run_id` must be the newly dispatched run; never substitute an older green run. If listing races dispatch, query again before watching. Record that ID in the incident acknowledgement. + +On failure, `acedward` inspects the retained job report, records run ID/time and acknowledgement, then either records a false-alarm/fresh-success result or opens the reviewed manual `revoked` incident PR/advisory. The workflow only reports and never auto-revokes or auto-blesses bytes. + +`metadata/baselines/0.3.120-current.json` is a rotatable pointer to the reviewed current live release snapshot, not a permanent initial-state oracle. Whenever an authorized body or asset append is independently verified, add a new immutable full snapshot under `metadata/baselines/`, record its raw-file SHA-256 in the pointer, and update both at the first post-write reviewed catalog state; later `verified`/`published` states retain that exact snapshot unless another authorized release change occurred. Never rewrite, rename, or delete an old snapshot. CI binds every pointed live asset to a non-planned catalog destination identity and rejects a pointer that blesses uncataloged bytes. A subsequent preflight must equal the pointed repository/release/legacy asset identities and may perform only its separately intended body transition; stale initial, pre-existing foreign, or unreviewed live drift is rejected. diff --git a/scripts/render-readme-catalog b/scripts/render-readme-catalog new file mode 100755 index 0000000..f1c188b --- /dev/null +++ b/scripts/render-readme-catalog @@ -0,0 +1,109 @@ +#!/usr/bin/env python3 +"""Render README's stable file catalog from committed metadata/index.json.""" + +from __future__ import annotations + +import argparse +import difflib +import json +from pathlib import Path +import re +import sys +from typing import NoReturn + + +ROOT = Path(__file__).resolve().parents[1] +README = ROOT / "README.md" +INDEX = ROOT / "metadata/index.json" +BEGIN = "" +END = "" + + +def fail(message: str) -> NoReturn: + raise SystemExit(f"render-readme-catalog: {message}") + + +def render() -> str: + index = json.loads(INDEX.read_text(encoding="utf-8")) + entries = index.get("entries") + if not isinstance(entries, list): + fail("metadata/index.json entries must be an array") + + required = {"artifactKind", "assetName", "semanticId", "sha256", "url"} + identities: set[tuple[str, str, str]] = set() + names: set[str] = set() + rows: list[dict[str, str]] = [] + for entry in entries: + if not isinstance(entry, dict) or not required.issubset(entry): + fail("every stable-index entry must contain the catalog projection fields") + kind = entry["artifactKind"] + name = entry["assetName"] + semantic_id = entry["semanticId"] + sha256 = entry["sha256"] + url = entry["url"] + if kind not in {"software", "proof-data"}: + fail(f"unsupported artifact kind: {kind!r}") + if not all(isinstance(value, str) for value in (name, semantic_id, sha256, url)): + fail("catalog projection fields must be strings") + if not re.fullmatch(r"[0-9a-f]{64}", sha256): + fail(f"invalid SHA-256 for {name}") + if any(token in name for token in ("|", "[", "]", "(", ")", "\n", "\r")): + fail(f"asset name is not safe for a Markdown link: {name!r}") + identity = (name, url, sha256) + if identity in identities or name in names: + fail(f"duplicate stable catalog identity: {name}") + identities.add(identity) + names.add(name) + rows.append(entry) + + rows.sort(key=lambda row: (row["artifactKind"], row["assetName"], row["semanticId"])) + software = sum(row["artifactKind"] == "software" for row in rows) + proof_data = sum(row["artifactKind"] == "proof-data" for row in rows) + lines = [ + f"{len(rows)} files: {software} software and {proof_data} proof-data.", + "", + "| Kind | File | SHA-256 |", + "|---|---|---|", + ] + lines.extend( + f'| {row["artifactKind"]} | [{row["assetName"]}]({row["url"]}) | `{row["sha256"]}` |' + for row in rows + ) + return "\n".join(lines) + + +def expected_readme(current: str) -> str: + if current.count(BEGIN) != 1 or current.count(END) != 1: + fail("README must contain each generated catalog marker exactly once") + before, remainder = current.split(BEGIN, 1) + _generated, after = remainder.split(END, 1) + return f"{before}{BEGIN}\n{render()}\n{END}{after}" + + +def main() -> int: + parser = argparse.ArgumentParser() + parser.add_argument("--check", action="store_true", help="fail if README is stale") + args = parser.parse_args() + current = README.read_text(encoding="utf-8") + expected = expected_readme(current) + if args.check: + if current == expected: + print("PASS README catalog matches metadata/index.json") + return 0 + sys.stderr.writelines( + difflib.unified_diff( + current.splitlines(keepends=True), + expected.splitlines(keepends=True), + fromfile="README.md", + tofile="README.md (rendered)", + ) + ) + return 1 + if current != expected: + README.write_text(expected, encoding="utf-8") + print("WROTE README catalog from metadata/index.json") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tests/test_clean_room.py b/tests/test_clean_room.py index a38b932..586d033 100644 --- a/tests/test_clean_room.py +++ b/tests/test_clean_room.py @@ -43,6 +43,7 @@ class CleanRoomReadmeTests(unittest.TestCase): @classmethod def setUpClass(cls) -> None: cls.readme = (ROOT / "README.md").read_text(encoding="utf-8") + cls.index = load_json(ROOT / "metadata/index.json") def future_proposal(self, candidates: list[dict], *, binary_names: list[str], proof_names: list[str], family_contract: dict | None = None, proof_contract: dict | None = None) -> dict: family_contract = family_contract or load_json(ROOT / "metadata/contracts/families-v1.json") @@ -159,28 +160,94 @@ def receipt_from_exact_two_pin_record(self, verification: dict) -> dict: validate_receipt_bindings(receipt, manifest, allow_test_verification=True) return receipt - def test_warning_is_first_and_all_eleven_contracts_are_documented(self) -> None: - self.assertTrue(self.readme.startswith("> **DEVELOPMENT ONLY — NOT FOR PRODUCTION USE.**")) - required = [ + def test_readme_is_warning_first_and_focused(self) -> None: + first_line = ( + "> **DEVELOPMENT ONLY — NOT FOR PRODUCTION USE.** Release `0.3.120` is mutable; " + "verify every downloaded SHA-256 against committed metadata before installation or execution." + ) + self.assertEqual(self.readme.splitlines()[0], first_line) + self.assertEqual(self.readme.count(""), 1) + self.assertEqual(self.readme.count(""), 1) + self.assertIn("## Current files", self.readme) + self.assertIn("## Documentation", self.readme) + for operator_heading in [ "Permanent append-only rules", "Binary names, selectors, layouts, and coverage", "Choose the artifact operation", "Required metadata and evidence", "Prepare and validate a proposal", "Manual prerequisite and transaction sequence", "Executable examples and clean-room fixture", "Conflict, interruption, revocation, and drift", "macOS distribution signing", "Compact 0.34 direct-upstream policy", "Public proof-data guide", - "explicit live-upload authority", "effective write permission", "numeric ID", "node ID", - "inert", "mode-`0600` journal", "TOCTOU", "stable `published` catalog/index last", - "atomic", "MIDNIGHT_PP", "K24/K25", "static-10", - "--planned-catalog", "--candidate-bundle", "--resume-prerequisite-record", - "0.3.120-current.json", "run_id", "--created", 'gh run watch "$run_id"', + ]: + self.assertNotIn(operator_heading, self.readme) + + def test_generated_readme_catalog_exactly_matches_stable_index(self) -> None: + result = subprocess.run( + [str(ROOT / "scripts/render-readme-catalog"), "--check"], + cwd=ROOT, text=True, capture_output=True, + ) + self.assertEqual(result.returncode, 0, result.stderr) + rows = re.findall( + r"^\| (software|proof-data) \| \[([^]]+)\]\((https://[^)]+)\) \| `([0-9a-f]{64})` \|$", + self.readme, + flags=re.MULTILINE, + ) + actual = [(kind, name, url, sha256) for kind, name, url, sha256 in rows] + expected = [ + (entry["artifactKind"], entry["assetName"], entry["url"], entry["sha256"]) + for entry in self.index["entries"] + ] + self.assertEqual(len(actual), 97) + self.assertEqual(len(set(actual)), 97) + self.assertEqual(sum(kind == "software" for kind, *_ in actual), 76) + self.assertEqual(sum(kind == "proof-data" for kind, *_ in actual), 21) + self.assertCountEqual(actual, expected) + for _kind, name, _url, sha256 in actual: + row = next(line for line in self.readme.splitlines() if f"[{name}]" in line) + self.assertEqual(row.count(f"[{name}]"), 1) + self.assertEqual(row.count(sha256), 1) + + def test_document_split_retains_all_operator_contracts(self) -> None: + contracts = { + ROOT / "CONTRIBUTING.md": [ + "Permanent append-only rules", "Binary names, selectors, layouts, and coverage", + "Choose the artifact operation", "Required metadata and evidence", + "Prepare and validate a proposal", "Executable examples and clean-room fixture", + "Compact 0.34 direct-upstream policy", "numeric ID", "node ID", + ], + ROOT / "docs/PUBLISHING.md": [ + "Manual prerequisite and transaction sequence", + "Conflict, interruption, revocation, and drift", "explicit live-upload authority", + "effective write permission", "inert", "mode-`0600` journal", "TOCTOU", + "stable `published` catalog/index last", "--planned-catalog", "--candidate-bundle", + "--resume-prerequisite-record", "0.3.120-current.json", "run_id", "--created", + 'gh run watch "$run_id"', + ], + ROOT / "docs/PROOF_DATA.md": [ + "Public proof-data guide", "atomically replace", "MIDNIGHT_PP", "K24/K25", "static-10", + ], + ROOT / "docs/MACOS_SIGNING.md": [ + "macOS distribution signing", "UNSIGNED_DEVELOPMENT_ONLY", + "DEVELOPER_ID_SIGNED_NOTARIZED_ONLINE_TICKET", "distinct family-conforming", + ], + } + for path, required in contracts.items(): + text = path.read_text(encoding="utf-8") + for phrase in required: + self.assertIn(phrase, text, f"{phrase!r} missing from {path.relative_to(ROOT)}") + + def test_all_documentation_relative_links_resolve(self) -> None: + markdown_files = [ + ROOT / "README.md", ROOT / "CONTRIBUTING.md", ROOT / "MACOS.md", + ROOT / "docs/PUBLISHING.md", ROOT / "docs/PROOF_DATA.md", ROOT / "docs/MACOS_SIGNING.md", ] - for text in required: - self.assertIn(text, self.readme) + for source in markdown_files: + text = source.read_text(encoding="utf-8") + for link in re.findall(r"\[[^]]+\]\(([^)]+)\)", text): + if link.startswith(("https://", "http://", "mailto:", "#")): + continue + target = link.split("#", 1)[0] + self.assertTrue((source.parent / target).exists(), f"{source.relative_to(ROOT)} -> {link}") - def test_readme_linked_files_exist_and_example_executes(self) -> None: - links = re.findall(r"\[[^]]+\]\(([^)]+)\)", self.readme) - local = [link for link in links if not link.startswith(("https://", "http://", "#"))] - for link in local: - self.assertTrue((ROOT / link).exists(), link) + def test_documented_resolver_example_executes(self) -> None: result = subprocess.run( [str(ROOT / "scripts/resolve"), "--family", "indexer-standalone", "--version", "4.4.0-rc.1", "--os", "darwin", "--arch", "aarch64"], cwd=ROOT, text=True, capture_output=True,