Skip to repository content

tenant.openagents/omega

No repository description is available.

OpenAgents Git authority 2026-07-27T23:31:11.077Z Public web read
NIP-34 coordinate30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omega
MaintainersHidden in public view
References2 branches · 1 tag
Read-only clonegit clone https://openagents.com/git/tenant.openagents/omega.git
Browse files

bundle-omega-rc

1475 lines · 62.4 KB · text
1#!/usr/bin/env bash
2# Owned Omega RC macOS package entry point (OMEGA-BRAND-05).
3#
4# Produces (or dry-runs the contract for):
5#   Omega-v0.2.0-rc3-macos-arm64.dmg
6#
7# Does not claim notarization success. Notarization runs only when owner
8# notarization secrets are present; otherwise the release record records that
9# notarization was not attempted.
10
11set -euo pipefail
12
13ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
14cd "$ROOT"
15
16readonly OMEGA_RC_VERSION="0.2.0-rc18"
17readonly OMEGA_RC_SEMVER="0.2.0"
18readonly OMEGA_RC_CHANNEL="preview"
19readonly OMEGA_RC_DISPLAY_CHANNEL="rc"
20readonly OMEGA_TEAM_ID="HQWSG26L43"
21readonly OMEGA_SIGNING_IDENTITY="Developer ID Application: OpenAgents, Inc. (${OMEGA_TEAM_ID})"
22readonly OMEGA_BUNDLE_ID="com.openagents.omega.rc"
23readonly OMEGA_VOLUME_NAME="Omega RC"
24readonly OMEGA_ARTIFACT_NAME="Omega-v${OMEGA_RC_VERSION}-macos-arm64.dmg"
25readonly OMEGA_RELEASE_RECORD_SCHEMA_VERSION=1
26readonly OMEGA_BUILD_LOCK_SNAPSHOT_NAME="Cargo.lock.omega-v${OMEGA_RC_VERSION}"
27readonly OMEGA_SOURCE_VERIFIER_PATH="script/verify-omega-identity"
28readonly OMEGA_IDENTITY_BOUNDARY_LIST="BUZZ_PRIVATE_KEY,identity.key,get_nsec"
29readonly OMEGA_BRAND_VERIFIER_PATH="script/verify-omega-brand"
30readonly OMEGA_BRAND_POLICY_PATH="script/omega-brand-gate.json"
31readonly OMEGA_EFFECTD_RELEASE_TAG="omega-effectd-v0.1.0-rc.10"
32readonly OMEGA_EFFECTD_ASSET_NAME="omega-effectd-v0.1.0-macos-arm64.tar.gz"
33readonly OMEGA_EFFECTD_ASSET_SHA256="e3ab92ffe0baa0cb3082ea4bc83720d1677b633f3f0725b7c7571930ae19f7ac"
34readonly OMEGA_EFFECTD_MANIFEST_ASSET_SHA256="d797f749f57d2ab88cdad74f5ff81e882bc6052951a026c028a84fb7b022b5eb"
35readonly OMEGA_EFFECTD_SOURCE_COMMIT="e3280b4795efe189474e12b4c6cd06123cfda359"
36readonly OMEGA_EFFECTD_SOURCE_TREE="af28ecd0a57523640a96b6f3ed7411f07a7da228"
37readonly OMEGA_EFFECTD_DOWNLOAD_URL="https://github.com/OpenAgentsInc/openagents/releases/download/${OMEGA_EFFECTD_RELEASE_TAG}/${OMEGA_EFFECTD_ASSET_NAME}"
38
39DRY_RUN=false
40SKIP_BUILD=false
41SELF_TEST_NOTARIZATION_KEY=false
42TARGET_TRIPLE="aarch64-apple-darwin"
43OUTPUT_DIR="${ROOT}/target/omega-rc"
44RELEASE_RECORD_PATH=""
45BUILD_LOCK_SNAPSHOT_PATH=""
46BUILD_LOCK_SNAPSHOT_NAME=""
47BUILD_LOCK_SNAPSHOT_DIGEST=""
48OMEGA_BINARY_DIGEST=""
49CLI_BINARY_DIGEST=""
50IDENTITY_PROOF_BINARY_DIGEST=""
51SOURCE_VERIFIER_DIGEST=""
52SOURCE_VERIFIER_PASSED=false
53PACKAGED_BOUNDARY_SCAN_PASSED=false
54BRAND_VERIFIER_DIGEST=""
55BRAND_POLICY_DIGEST=""
56BRAND_SOURCE_SCAN_PASSED=false
57BRAND_PACKAGED_SCAN_PASSED=false
58OMEGA_EFFECTD_ARCHIVE_PATH="${ROOT}/target/omega-effectd-cache/${OMEGA_EFFECTD_RELEASE_TAG}/${OMEGA_EFFECTD_ASSET_NAME}"
59OMEGA_EFFECTD_MANIFEST_SHA256=""
60OMEGA_EFFECTD_WRAPPER_SHA256=""
61OMEGA_EFFECTD_BUNDLE_SHA256=""
62OMEGA_EFFECTD_SOURCE_NODE_SHA256=""
63OMEGA_EFFECTD_PACKAGED_NODE_SHA256=""
64NOTARIZATION_KEY_FILE=""
65NOTARIZATION_KEY_FILE_IS_TEMPORARY=false
66PACKAGED_BINDINGS_JSON=""
67
68usage() {
69  cat <<EOF
70Usage: ${0##*/} [--dry-run] [--skip-build] [--output-dir DIR] [--release-record PATH]
71                       [--self-test-notarization-key]
72
73Owned Omega RC packaging for macOS arm64.
74
75Options:
76  --dry-run              Validate prerequisites and release-record schema without
77                         compiling, signing, or notarizing.
78  --skip-build           Reuse an existing DMG at the expected output path and
79                         validate its existing release record without modifying
80                         or relabeling either file.
81  --output-dir DIR       Directory for the DMG and release record
82                         (default: target/omega-rc).
83  --release-record PATH  Explicit release-record JSON path.
84  --self-test-notarization-key
85                         Verify path and inline PEM input normalization without
86                         compiling, signing, or notarizing.
87  -h, --help             Show this help.
88
89Environment (owner secrets; never commit):
90  MACOS_CERTIFICATE / MACOS_CERTIFICATE_PASSWORD
91      Optional keychain import path (same shape as script/bundle-mac).
92  APPLE_NOTARIZATION_KEY / APPLE_NOTARIZATION_KEY_ID / APPLE_NOTARIZATION_ISSUER_ID
93      Required only when notarization should be attempted after signing. The
94      key may be inline PEM contents or a path to a readable PEM file.
95EOF
96}
97
98while [[ $# -gt 0 ]]; do
99  case "$1" in
100    --dry-run) DRY_RUN=true ;;
101    --skip-build) SKIP_BUILD=true ;;
102    --output-dir)
103      OUTPUT_DIR="$2"
104      shift
105      ;;
106    --release-record)
107      RELEASE_RECORD_PATH="$2"
108      shift
109      ;;
110    --self-test-notarization-key) SELF_TEST_NOTARIZATION_KEY=true ;;
111    -h|--help)
112      usage
113      exit 0
114      ;;
115    *)
116      echo "error: unknown argument: $1" >&2
117      usage >&2
118      exit 2
119      ;;
120  esac
121  shift
122done
123
124if [[ "${DRY_RUN}" == "true" && "${SKIP_BUILD}" == "true" ]]; then
125  echo "error: --dry-run and --skip-build are mutually exclusive." >&2
126  exit 2
127fi
128
129if [[ "${SELF_TEST_NOTARIZATION_KEY}" == "true" && ( "${DRY_RUN}" == "true" || "${SKIP_BUILD}" == "true" ) ]]; then
130  echo "error: --self-test-notarization-key cannot be combined with build modes." >&2
131  exit 2
132fi
133
134mkdir -p "${OUTPUT_DIR}"
135BUILD_LOCK_SNAPSHOT_PATH="${OUTPUT_DIR}/${OMEGA_BUILD_LOCK_SNAPSHOT_NAME}"
136if [[ -z "${RELEASE_RECORD_PATH}" ]]; then
137  RELEASE_RECORD_PATH="${OUTPUT_DIR}/omega-v${OMEGA_RC_VERSION}-macos-arm64.release.json"
138fi
139
140sha256_file() {
141  local path="$1"
142  if [[ ! -f "${path}" ]]; then
143    echo ""
144    return 0
145  fi
146  shasum -a 256 "${path}" | awk '{print $1}'
147}
148
149prepare_notarization_key_file() {
150  local key_input="$1"
151
152  NOTARIZATION_KEY_FILE=""
153  NOTARIZATION_KEY_FILE_IS_TEMPORARY=false
154
155  if [[ -f "${key_input}" && -r "${key_input}" ]]; then
156    NOTARIZATION_KEY_FILE="${key_input}"
157    return 0
158  fi
159
160  if [[ "${key_input}" != *"-----BEGIN PRIVATE KEY-----"* ]]; then
161    echo "error: APPLE_NOTARIZATION_KEY must be inline PEM contents or a readable file path." >&2
162    return 1
163  fi
164
165  NOTARIZATION_KEY_FILE="$(mktemp)"
166  NOTARIZATION_KEY_FILE_IS_TEMPORARY=true
167  chmod 600 "${NOTARIZATION_KEY_FILE}"
168  printf '%s\n' "${key_input}" > "${NOTARIZATION_KEY_FILE}"
169}
170
171cleanup_notarization_key_file() {
172  if [[ "${NOTARIZATION_KEY_FILE_IS_TEMPORARY}" == "true" && -n "${NOTARIZATION_KEY_FILE}" ]]; then
173    rm -f "${NOTARIZATION_KEY_FILE}"
174  fi
175  NOTARIZATION_KEY_FILE=""
176  NOTARIZATION_KEY_FILE_IS_TEMPORARY=false
177}
178
179self_test_notarization_key() {
180  local test_directory
181  local path_key
182  local inline_key
183  local expected_digest
184  local observed_digest
185
186  test_directory="$(mktemp -d)"
187  path_key="${test_directory}/AuthKey_TEST.p8"
188  inline_key=$'-----BEGIN PRIVATE KEY-----\ndeterministic-test-value\n-----END PRIVATE KEY-----'
189  printf '%s\n' "${inline_key}" > "${path_key}"
190
191  prepare_notarization_key_file "${path_key}"
192  if [[ "${NOTARIZATION_KEY_FILE}" != "${path_key}" || "${NOTARIZATION_KEY_FILE_IS_TEMPORARY}" != "false" ]]; then
193    echo "error: notarization key path normalization self-test failed." >&2
194    rm -rf "${test_directory}"
195    return 1
196  fi
197
198  prepare_notarization_key_file "${inline_key}"
199  expected_digest="$(printf '%s\n' "${inline_key}" | shasum -a 256 | awk '{print $1}')"
200  observed_digest="$(sha256_file "${NOTARIZATION_KEY_FILE}")"
201  if [[ "${expected_digest}" != "${observed_digest}" || "${NOTARIZATION_KEY_FILE_IS_TEMPORARY}" != "true" ]]; then
202    echo "error: inline notarization key normalization self-test failed." >&2
203    cleanup_notarization_key_file
204    rm -rf "${test_directory}"
205    return 1
206  fi
207
208  cleanup_notarization_key_file
209  rm -rf "${test_directory}"
210  echo "notarization key normalization self-test passed."
211}
212
213prepare_omega_effectd_archive() {
214  mkdir -p "$(dirname "${OMEGA_EFFECTD_ARCHIVE_PATH}")"
215  if [[ ! -f "${OMEGA_EFFECTD_ARCHIVE_PATH}" ]]; then
216    local download_path="${OMEGA_EFFECTD_ARCHIVE_PATH}.download"
217    curl --fail --location --silent --show-error \
218      "${OMEGA_EFFECTD_DOWNLOAD_URL}" --output "${download_path}"
219    mv "${download_path}" "${OMEGA_EFFECTD_ARCHIVE_PATH}"
220  fi
221
222  local archive_digest
223  archive_digest="$(sha256_file "${OMEGA_EFFECTD_ARCHIVE_PATH}")"
224  if [[ "${archive_digest}" != "${OMEGA_EFFECTD_ASSET_SHA256}" ]]; then
225    echo "error: cached omega-effectd archive digest does not match the pinned release asset." >&2
226    echo "  expected: ${OMEGA_EFFECTD_ASSET_SHA256}" >&2
227    echo "  observed: ${archive_digest}" >&2
228    exit 1
229  fi
230
231  local component_json
232  component_json="$(python3 - "${OMEGA_EFFECTD_ARCHIVE_PATH}" "${OMEGA_EFFECTD_SOURCE_COMMIT}" "${OMEGA_EFFECTD_SOURCE_TREE}" <<'PY'
233import hashlib
234import json
235import pathlib
236import sys
237import tarfile
238
239archive_path = pathlib.Path(sys.argv[1])
240expected_source_commit = sys.argv[2]
241expected_source_tree = sys.argv[3]
242expected_files = {
243    "omega-effectd/bin/omega-effectd",
244    "omega-effectd/component-manifest.json",
245    "omega-effectd/dist/omega-effectd.mjs",
246    "omega-effectd/licenses/Node-LICENSE.txt",
247    "omega-effectd/licenses/OpenAgents-Apache-2.0.txt",
248    "omega-effectd/licenses/THIRD_PARTY_LICENSES.txt",
249    "omega-effectd/licenses/THIRD_PARTY_NOTICES.json",
250    "omega-effectd/runtime/bin/node",
251}
252
253with tarfile.open(archive_path, "r:gz") as archive:
254    members = archive.getmembers()
255    names = [member.name for member in members]
256    if len(names) != len(set(names)):
257        raise SystemExit("omega-effectd archive contains duplicate paths")
258    for member in members:
259        path = pathlib.PurePosixPath(member.name)
260        if path.is_absolute() or ".." in path.parts:
261            raise SystemExit(f"omega-effectd archive contains unsafe path: {member.name}")
262        if not (member.isdir() or member.isfile()):
263            raise SystemExit(f"omega-effectd archive contains unsupported entry: {member.name}")
264    regular_files = {member.name for member in members if member.isfile()}
265    if regular_files != expected_files:
266        missing = sorted(expected_files - regular_files)
267        unexpected = sorted(regular_files - expected_files)
268        raise SystemExit(
269            f"omega-effectd archive inventory mismatch; missing={missing}, unexpected={unexpected}"
270        )
271
272    def bytes_for(name: str) -> bytes:
273        extracted = archive.extractfile(name)
274        if extracted is None:
275            raise SystemExit(f"omega-effectd archive entry is unreadable: {name}")
276        return extracted.read()
277
278    manifest_bytes = bytes_for("omega-effectd/component-manifest.json")
279    manifest = json.loads(manifest_bytes)
280    expected_manifest = {
281        "schema": "openagents.omega.effectd.component.v1",
282        "package": "@openagentsinc/omega-effectd",
283        "serviceVersion": "0.1.0",
284        "protocol": "openagents.omega.effectd.v1",
285        "platform": "darwin-arm64",
286    }
287    for key, expected in expected_manifest.items():
288        if manifest.get(key) != expected:
289            raise SystemExit(f"omega-effectd manifest {key} does not match the pin")
290    if manifest.get("source", {}).get("commit") != expected_source_commit:
291        raise SystemExit("omega-effectd manifest source commit does not match the pin")
292    if manifest.get("source", {}).get("tree") != expected_source_tree:
293        raise SystemExit("omega-effectd manifest source tree does not match the pin")
294
295    for relative, expected_digest in manifest.get("files", {}).items():
296        name = f"omega-effectd/{relative}"
297        if name not in regular_files:
298            raise SystemExit(f"omega-effectd manifest names missing file: {relative}")
299        observed = hashlib.sha256(bytes_for(name)).hexdigest()
300        if observed != expected_digest:
301            raise SystemExit(f"omega-effectd manifest digest mismatch: {relative}")
302
303    node_bytes = bytes_for("omega-effectd/runtime/bin/node")
304    source_node_digest = hashlib.sha256(node_bytes).hexdigest()
305    if source_node_digest != manifest.get("node", {}).get("binarySha256"):
306        raise SystemExit("omega-effectd Node binary digest does not match the manifest")
307    if manifest.get("node", {}).get("version") != "24.13.1":
308        raise SystemExit("omega-effectd Node version does not match the pin")
309
310    print(json.dumps({
311        "manifest_sha256": hashlib.sha256(manifest_bytes).hexdigest(),
312        "wrapper_sha256": hashlib.sha256(bytes_for("omega-effectd/bin/omega-effectd")).hexdigest(),
313        "bundle_sha256": hashlib.sha256(bytes_for("omega-effectd/dist/omega-effectd.mjs")).hexdigest(),
314        "source_node_sha256": source_node_digest,
315    }))
316PY
317)"
318
319  OMEGA_EFFECTD_MANIFEST_SHA256="$(jq -r '.manifest_sha256' <<<"${component_json}")"
320  OMEGA_EFFECTD_WRAPPER_SHA256="$(jq -r '.wrapper_sha256' <<<"${component_json}")"
321  OMEGA_EFFECTD_BUNDLE_SHA256="$(jq -r '.bundle_sha256' <<<"${component_json}")"
322  OMEGA_EFFECTD_SOURCE_NODE_SHA256="$(jq -r '.source_node_sha256' <<<"${component_json}")"
323  if [[ "${OMEGA_EFFECTD_MANIFEST_SHA256}" != "${OMEGA_EFFECTD_MANIFEST_ASSET_SHA256}" ]]; then
324    echo "error: omega-effectd component manifest digest does not match the pinned release asset." >&2
325    exit 1
326  fi
327}
328
329stage_omega_effectd_component() {
330  local app_path="$1"
331  local staging_directory
332  staging_directory="$(mktemp -d)"
333  tar -xzf "${OMEGA_EFFECTD_ARCHIVE_PATH}" -C "${staging_directory}"
334  local component_source="${staging_directory}/omega-effectd"
335  local component_target="${app_path}/Contents/Resources/omega-effectd"
336  if [[ ! -x "${component_source}/bin/omega-effectd" || ! -x "${component_source}/runtime/bin/node" ]]; then
337    echo "error: omega-effectd archive executables are not executable." >&2
338    rm -rf "${staging_directory}"
339    exit 1
340  fi
341  rm -rf "${component_target}"
342  ditto "${component_source}" "${component_target}"
343  rm -rf "${staging_directory}"
344
345  if [[ "$(sha256_file "${component_target}/component-manifest.json")" != "${OMEGA_EFFECTD_MANIFEST_SHA256}" || \
346        "$(sha256_file "${component_target}/bin/omega-effectd")" != "${OMEGA_EFFECTD_WRAPPER_SHA256}" || \
347        "$(sha256_file "${component_target}/dist/omega-effectd.mjs")" != "${OMEGA_EFFECTD_BUNDLE_SHA256}" || \
348        "$(sha256_file "${component_target}/runtime/bin/node")" != "${OMEGA_EFFECTD_SOURCE_NODE_SHA256}" ]]; then
349    echo "error: staged omega-effectd component differs from the verified release archive." >&2
350    exit 1
351  fi
352}
353
354require_clean_checkout() {
355  if [[ -n "$(git status --porcelain)" ]]; then
356    echo "error: working tree is dirty; refuse to build an Omega RC candidate from an unrecorded tree." >&2
357    git status --short >&2
358    exit 1
359  fi
360}
361
362require_arm64_host_or_target() {
363  if [[ "${TARGET_TRIPLE}" != "aarch64-apple-darwin" ]]; then
364    echo "error: this RC command only produces macos-arm64 artifacts (got ${TARGET_TRIPLE})." >&2
365    exit 1
366  fi
367}
368
369find_openagents_signing_identity() {
370  local identities
371  identities="$(security find-identity -v -p codesigning 2>/dev/null || true)"
372  if ! grep -Fq "${OMEGA_SIGNING_IDENTITY}" <<<"${identities}"; then
373    echo "error: required OpenAgents signing identity is missing:" >&2
374    echo "  ${OMEGA_SIGNING_IDENTITY}" >&2
375    echo "Team ID ${OMEGA_TEAM_ID} must be available in the login keychain before an RC package can be signed." >&2
376    echo "Available codesigning identities:" >&2
377    echo "${identities:-  (none)}" >&2
378    exit 1
379  fi
380}
381
382verify_packaged_identity_boundaries() {
383  local app_path="$1"
384  local forbidden_literal
385  local matches
386  local -a forbidden_literals=("BUZZ_PRIVATE_KEY" "identity.key" "get_nsec")
387  for forbidden_literal in "${forbidden_literals[@]}"; do
388    matches="$(LC_ALL=C grep -aRFl -- "${forbidden_literal}" "${app_path}" || true)"
389    if [[ -n "${matches}" ]]; then
390      echo "error: packaged Omega contains forbidden identity boundary ${forbidden_literal}:" >&2
391      echo "${matches}" >&2
392      exit 1
393    fi
394  done
395  PACKAGED_BOUNDARY_SCAN_PASSED=true
396}
397
398verify_source_identity_boundaries() {
399  "${OMEGA_SOURCE_VERIFIER_PATH}"
400  SOURCE_VERIFIER_DIGEST="$(sha256_file "${OMEGA_SOURCE_VERIFIER_PATH}")"
401  if [[ -z "${SOURCE_VERIFIER_DIGEST}" ]]; then
402    echo "error: source identity verifier digest is unavailable." >&2
403    exit 1
404  fi
405  SOURCE_VERIFIER_PASSED=true
406}
407
408# OMEGA-DELTA-0017 and OMEGA-DELTA-0018, source side.
409verify_source_brand() {
410  "${OMEGA_BRAND_VERIFIER_PATH}"
411  BRAND_VERIFIER_DIGEST="$(sha256_file "${OMEGA_BRAND_VERIFIER_PATH}")"
412  BRAND_POLICY_DIGEST="$(sha256_file "${OMEGA_BRAND_POLICY_PATH}")"
413  if [[ -z "${BRAND_VERIFIER_DIGEST}" || -z "${BRAND_POLICY_DIGEST}" ]]; then
414    echo "error: brand gate digests are unavailable." >&2
415    exit 1
416  fi
417  BRAND_SOURCE_SCAN_PASSED=true
418}
419
420# OMEGA-DELTA-0017 and OMEGA-DELTA-0018, packaged side.
421#
422# 0.2.0-rc10 shipped thirteen Zed strings in its signed Info.plist and Zed logo
423# marks on its status bar while every brand check on omega#16 passed, because
424# those checks read the executable for three identity literals. Nothing had
425# ever read Info.plist, and no string scan can see a logo. This runs against
426# the built bundle, before signing, so a packaging regression fails here rather
427# than in a macOS permission dialog on the owner's machine.
428verify_packaged_brand() {
429  local app_path="$1"
430  "${OMEGA_BRAND_VERIFIER_PATH}" --app "${app_path}"
431  BRAND_PACKAGED_SCAN_PASSED=true
432}
433
434validate_release_record_schema() {
435  local path="$1"
436  python3 - \
437    "${path}" \
438    "${OMEGA_RELEASE_RECORD_SCHEMA_VERSION}" \
439    "${OMEGA_RC_VERSION}" \
440    "${OMEGA_ARTIFACT_NAME}" \
441    "${OMEGA_BUILD_LOCK_SNAPSHOT_NAME}" <<'PY'
442import json
443import re
444import sys
445
446path = sys.argv[1]
447expected_schema = int(sys.argv[2])
448expected_version, expected_artifact, expected_build_lock = sys.argv[3:]
449with open(path, encoding="utf-8") as handle:
450    record = json.load(handle)
451
452def valid_sha256(value):
453    return isinstance(value, str) and re.fullmatch(r"[0-9a-f]{64}", value) is not None
454
455required = [
456    "schema_version",
457    "product",
458    "channel",
459    "version",
460    "bundle_version",
461    "artifact_name",
462    "volume_name",
463    "bundle_identifier",
464    "team_id",
465    "signing_identity",
466    "source",
467    "toolchains",
468    "execution",
469    "build_inputs",
470    "components",
471    "digests",
472    "identity_boundaries",
473    "brand_gate",
474    "notarization",
475    "legal",
476    "publication",
477]
478missing = [key for key in required if key not in record]
479if missing:
480    raise SystemExit(f"release-record missing keys: {', '.join(missing)}")
481
482if record["schema_version"] != expected_schema:
483    raise SystemExit(
484        f"schema_version {record['schema_version']} != expected {expected_schema}"
485    )
486if record["product"] != "Omega":
487    raise SystemExit("product must be Omega")
488if record["channel"] != "rc":
489    raise SystemExit("channel must be rc")
490if record["version"] != expected_version:
491    raise SystemExit(f"version must be {expected_version}")
492if record["artifact_name"] != expected_artifact:
493    raise SystemExit(f"artifact_name must be {expected_artifact}")
494if record["bundle_version"] != "0.2.0":
495    raise SystemExit("bundle_version must be 0.2.0")
496if record["publication"].get("repository") != "OpenAgentsInc/omega":
497    raise SystemExit("publication.repository must be OpenAgentsInc/omega")
498if record["publication"].get("tag") != f"v{expected_version}":
499    raise SystemExit(f"publication.tag must be v{expected_version}")
500if record["publication"].get("prerelease") is not True:
501    raise SystemExit("publication.prerelease must be true")
502if record["publication"].get("latest") is not False:
503    raise SystemExit("publication.latest must be false")
504if record["legal"].get("commercial_terms_attached") is not False:
505    raise SystemExit("legal.commercial_terms_attached must be false")
506legal_notices = record["legal"].get("notices")
507if not isinstance(legal_notices, dict) or not legal_notices:
508    raise SystemExit("legal.notices must bind packaged paths to SHA-256 digests")
509if not all(
510    isinstance(name, str) and name and valid_sha256(digest)
511    for name, digest in legal_notices.items()
512):
513    raise SystemExit("every legal notice must have a path and SHA-256 digest")
514if "commit" not in record["source"]:
515    raise SystemExit("source.commit is required")
516digests = record["digests"]
517for key in ("cargo_lock_sha256", "icon_family_manifest_sha256"):
518    if key not in digests or not valid_sha256(digests[key]):
519        raise SystemExit(f"digests.{key} must be a SHA-256 digest")
520if "package_sha256" not in digests:
521    raise SystemExit("digests.package_sha256 is required (null when absent)")
522for key in ("omega_binary_sha256", "cli_binary_sha256"):
523    if key not in digests:
524        raise SystemExit(f"digests.{key} is required (null when absent)")
525packaged_icons = digests.get("packaged_icon_outputs")
526if not isinstance(packaged_icons, dict) or set(packaged_icons) != {
527    "Contents/Resources/openagents-icon.icns",
528    "Contents/Resources/Document.icns",
529}:
530    raise SystemExit("digests.packaged_icon_outputs has the wrong inventory")
531if not all(valid_sha256(digest) for digest in packaged_icons.values()):
532    raise SystemExit("every packaged icon output must have a SHA-256 digest")
533
534execution = record["execution"]
535if not isinstance(execution.get("dry_run"), bool):
536    raise SystemExit("execution.dry_run must be a boolean")
537
538owner_blockers = record.get("owner_blockers")
539if not isinstance(owner_blockers, list) or not all(
540    isinstance(blocker, str) and blocker for blocker in owner_blockers
541):
542    raise SystemExit("owner_blockers must be a list of non-empty strings")
543notarization = record["notarization"]
544# OMEGA-DELTA-0023. A stapled disk image whose application carries no ticket is
545# the 0.2.0-rc11 shape: Gatekeeper acceptance of the installed product would
546# rest on an online lookup and offline first start could not be proven.
547if "app_stapled" not in notarization:
548    raise SystemExit("notarization.app_stapled is required")
549if notarization.get("stapled") is True and notarization.get("app_stapled") is not True:
550    raise SystemExit(
551        "the disk image is stapled but Omega.app is not; offline first start "
552        "cannot be proven from this candidate"
553    )
554if notarization.get("stapled") is True and any(
555    "notarization" in blocker.lower() for blocker in owner_blockers
556):
557    raise SystemExit("stapled releases must not retain a notarization blocker")
558if execution["dry_run"] is False and any(
559    "developer id" in blocker.lower() for blocker in owner_blockers
560):
561    raise SystemExit("built releases must not retain a Developer ID blocker")
562if not any("publication" in blocker.lower() for blocker in owner_blockers):
563    raise SystemExit("release record must retain the external publication blocker")
564
565build_lock = record["build_inputs"].get("cargo_lock_snapshot", {})
566for key in ("name", "sha256"):
567    if key not in build_lock:
568        raise SystemExit(f"build_inputs.cargo_lock_snapshot.{key} is required")
569
570effectd = record["components"].get("omega_effectd", {})
571expected_effectd = {
572    "package": "@openagentsinc/omega-effectd",
573    "service_version": "0.1.0",
574    "protocol": "openagents.omega.effectd.v1",
575    "platform": "darwin-arm64",
576    "release_tag": "omega-effectd-v0.1.0-rc.10",
577    "asset_name": "omega-effectd-v0.1.0-macos-arm64.tar.gz",
578    "archive_sha256": "e3ab92ffe0baa0cb3082ea4bc83720d1677b633f3f0725b7c7571930ae19f7ac",
579    "manifest_sha256": "d797f749f57d2ab88cdad74f5ff81e882bc6052951a026c028a84fb7b022b5eb",
580    "source_commit": "e3280b4795efe189474e12b4c6cd06123cfda359",
581    "source_tree": "af28ecd0a57523640a96b6f3ed7411f07a7da228",
582}
583for key, expected in expected_effectd.items():
584    if effectd.get(key) != expected:
585        raise SystemExit(f"components.omega_effectd.{key} does not match the pin")
586for key in ("wrapper_sha256", "bundle_sha256", "source_node_sha256"):
587    if not valid_sha256(effectd.get(key)):
588        raise SystemExit(f"components.omega_effectd.{key} must be a SHA-256 digest")
589if effectd.get("packaged_node_sha256") is not None and not valid_sha256(effectd["packaged_node_sha256"]):
590    raise SystemExit("components.omega_effectd.packaged_node_sha256 must be null or a SHA-256 digest")
591
592identity_proof = record["components"].get("identity_proof_driver", {})
593expected_identity_proof = {
594    "path": "Contents/MacOS/omega-identity-proof",
595    "protocol": "openagents.omega.identity-proof.v1",
596    "keyring_service": "com.openagents.omega.identity-proof.v1",
597    "keyring_account": "disposable-proof-only",
598}
599for key, expected in expected_identity_proof.items():
600    if identity_proof.get(key) != expected:
601        raise SystemExit(f"components.identity_proof_driver.{key} is invalid")
602if identity_proof.get("binary_sha256") is not None and not valid_sha256(identity_proof["binary_sha256"]):
603    raise SystemExit("components.identity_proof_driver.binary_sha256 must be null or a SHA-256 digest")
604
605boundaries = record["identity_boundaries"]
606source_verifier = boundaries.get("source_verifier", {})
607if source_verifier.get("path") != "script/verify-omega-identity":
608    raise SystemExit("identity_boundaries.source_verifier.path is invalid")
609if source_verifier.get("passed") is not True:
610    raise SystemExit("identity_boundaries.source_verifier.passed must be true")
611if not valid_sha256(source_verifier.get("sha256")):
612    raise SystemExit("identity_boundaries.source_verifier.sha256 must be a SHA-256 digest")
613packaged_scan = boundaries.get("packaged_scan", {})
614if packaged_scan.get("forbidden_literals") != [
615    "BUZZ_PRIVATE_KEY",
616    "identity.key",
617    "get_nsec",
618]:
619    raise SystemExit("identity_boundaries.packaged_scan.forbidden_literals is invalid")
620
621# OMEGA-DELTA-0017 and OMEGA-DELTA-0018. A release record that cannot say the
622# brand gate ran is not evidence that it ran.
623brand = record["brand_gate"]
624brand_source = brand.get("source_verifier", {})
625if brand_source.get("path") != "script/verify-omega-brand":
626    raise SystemExit("brand_gate.source_verifier.path is invalid")
627if brand_source.get("passed") is not True:
628    raise SystemExit("brand_gate.source_verifier.passed must be true")
629if not valid_sha256(brand_source.get("sha256")):
630    raise SystemExit("brand_gate.source_verifier.sha256 must be a SHA-256 digest")
631brand_policy = brand.get("policy", {})
632if brand_policy.get("path") != "script/omega-brand-gate.json":
633    raise SystemExit("brand_gate.policy.path is invalid")
634if not valid_sha256(brand_policy.get("sha256")):
635    raise SystemExit("brand_gate.policy.sha256 must be a SHA-256 digest")
636brand_packaged = brand.get("packaged_scan", {})
637if brand_packaged.get("surfaces") != ["Contents/Info.plist", "embedded icon assets"]:
638    raise SystemExit("brand_gate.packaged_scan.surfaces is invalid")
639
640if execution["dry_run"]:
641    if any(build_lock.get(key) is not None for key in ("name", "sha256")):
642        raise SystemExit("dry-run must not claim a build Cargo.lock snapshot")
643    if any(
644        digests.get(key) is not None
645        for key in ("package_sha256", "omega_binary_sha256", "cli_binary_sha256")
646    ):
647        raise SystemExit("dry-run must not claim package or binary digests")
648    if packaged_scan.get("passed") is not False:
649        raise SystemExit("dry-run packaged boundary scan must be false")
650    if brand_packaged.get("passed") is not False:
651        raise SystemExit("dry-run packaged brand scan must be false")
652    if effectd.get("packaged_node_sha256") is not None:
653        raise SystemExit("dry-run must not claim a signed packaged omega-effectd Node digest")
654    if identity_proof.get("binary_sha256") is not None:
655        raise SystemExit("dry-run must not claim an identity proof driver digest")
656else:
657    if set(legal_notices) != {"LICENSE-GPL", "LICENSE-APACHE", "licenses.md"}:
658        raise SystemExit("built release legal notice inventory is incomplete")
659    for key in ("name", "sha256"):
660        if not isinstance(build_lock.get(key), str) or not build_lock[key]:
661            raise SystemExit(f"build_inputs.cargo_lock_snapshot.{key} must be non-empty")
662    if build_lock["name"] != expected_build_lock:
663        raise SystemExit("build_inputs.cargo_lock_snapshot.name is invalid")
664    if not valid_sha256(build_lock["sha256"]):
665        raise SystemExit("build_inputs.cargo_lock_snapshot.sha256 must be a SHA-256 digest")
666    if digests["cargo_lock_sha256"] != build_lock["sha256"]:
667        raise SystemExit("build Cargo.lock snapshot digest must match cargo_lock_sha256")
668    for key in ("package_sha256", "omega_binary_sha256", "cli_binary_sha256"):
669        if not valid_sha256(digests.get(key)):
670            raise SystemExit(f"digests.{key} must be a SHA-256 digest")
671    if packaged_scan.get("passed") is not True:
672        raise SystemExit("built package boundary scan must pass")
673    if brand_packaged.get("passed") is not True:
674        raise SystemExit("built package brand scan must pass")
675    if not valid_sha256(effectd.get("packaged_node_sha256")):
676        raise SystemExit("built package must bind the signed omega-effectd Node digest")
677    if not valid_sha256(identity_proof.get("binary_sha256")):
678        raise SystemExit("built package must bind the signed identity proof driver digest")
679print(f"release-record schema ok: {path}")
680PY
681}
682
683collect_icon_digests_json() {
684  python3 - <<'PY'
685import json
686from pathlib import Path
687
688manifest_path = Path("crates/zed/resources/icon_family/manifest.json")
689manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
690outputs = {
691    relative: {"sha256": record["sha256"], "format": record["format"]}
692    for relative, record in manifest.get("outputs", {}).items()
693}
694print(json.dumps({
695    "manifest_sha256": __import__("hashlib").sha256(manifest_path.read_bytes()).hexdigest(),
696    "pinned_png_sha256": manifest["pinned_source"]["png_sha256"],
697    "pinned_icns_sha256": manifest["pinned_source"]["icns_sha256"],
698    "outputs": outputs,
699}))
700PY
701}
702
703collect_source_packaged_bindings_json() {
704  python3 - <<'PY'
705import hashlib
706import json
707from pathlib import Path
708
709legal = {}
710for source, packaged in (
711    (Path("LICENSE-GPL"), "LICENSE-GPL"),
712    (Path("LICENSE-APACHE"), "LICENSE-APACHE"),
713    (Path("assets/licenses.md"), "licenses.md"),
714):
715    if source.is_file():
716        legal[packaged] = hashlib.sha256(source.read_bytes()).hexdigest()
717
718icons = {}
719for source, packaged in (
720    (
721        Path("crates/zed/resources/icon_family/source/openagents-icon.icns"),
722        "Contents/Resources/openagents-icon.icns",
723    ),
724    (
725        Path("crates/zed/resources/Document.icns"),
726        "Contents/Resources/Document.icns",
727    ),
728):
729    if not source.is_file():
730        raise SystemExit(f"missing packaged icon source: {source}")
731    icons[packaged] = hashlib.sha256(source.read_bytes()).hexdigest()
732
733print(json.dumps({"legal_notices": legal, "icon_outputs": icons}, sort_keys=True))
734PY
735}
736
737collect_staged_packaged_bindings_json() {
738  local staged_root="$1"
739  python3 - "${staged_root}" <<'PY'
740import hashlib
741import json
742import plistlib
743import sys
744from pathlib import Path
745
746root = Path(sys.argv[1])
747app = root / "Omega.app"
748if not app.is_dir():
749    raise SystemExit(f"staged Omega app is missing: {app}")
750
751legal = {}
752for name in ("LICENSE-GPL", "LICENSE-APACHE", "licenses.md"):
753    path = root / name
754    if path.is_file():
755        legal[name] = hashlib.sha256(path.read_bytes()).hexdigest()
756if set(legal) != {"LICENSE-GPL", "LICENSE-APACHE", "licenses.md"}:
757    raise SystemExit("staged package legal notice inventory is incomplete")
758
759info = plistlib.loads((app / "Contents/Info.plist").read_bytes())
760icon_name = info.get("CFBundleIconFile")
761if not isinstance(icon_name, str) or not icon_name:
762    raise SystemExit("staged Omega Info.plist has no CFBundleIconFile")
763if not icon_name.endswith(".icns"):
764    icon_name += ".icns"
765icon_paths = [
766    Path("Contents/Resources") / icon_name,
767    Path("Contents/Resources/Document.icns"),
768]
769icons = {}
770for relative in icon_paths:
771    path = app / relative
772    if not path.is_file():
773        raise SystemExit(f"staged packaged icon is missing: {relative}")
774    icons[relative.as_posix()] = hashlib.sha256(path.read_bytes()).hexdigest()
775
776print(json.dumps({"legal_notices": legal, "icon_outputs": icons}, sort_keys=True))
777PY
778}
779
780write_release_record() {
781  local package_path="$1"
782  local notarization_status="$2"
783  local notarization_attempted="$3"
784  local notarization_stapled="$4"
785  local app_stapled="$5"
786  local package_digest
787  if [[ "${DRY_RUN}" == "true" ]]; then
788    package_digest=""
789  else
790    package_digest="$(sha256_file "${package_path}")"
791  fi
792  if [[ -z "${package_digest}" ]]; then
793    package_digest="null"
794  fi
795
796  local commit upstream_commit upstream_remote rustc_version host_triple cargo_lock_digest icon_json packaged_bindings_json
797  commit="$(git rev-parse HEAD)"
798  # The upstream Zed remote is named `zed`. `upstream` is the retired name and
799  # is accepted only until every checkout has been renamed. This value is
800  # release provenance, so an absent remote must fail loudly rather than
801  # silently record a null upstream commit.
802  for upstream_remote in zed upstream; do
803    upstream_commit="$(git rev-parse "${upstream_remote}/main" 2>/dev/null || true)"
804    if [[ -z "${upstream_commit}" ]]; then
805      upstream_commit="$(git merge-base HEAD "${upstream_remote}/main" 2>/dev/null || true)"
806    fi
807    [[ -n "${upstream_commit}" ]] && break
808  done
809  if [[ -z "${upstream_commit}" ]]; then
810    echo "error: no upstream Zed remote found (tried zed/main and upstream/main)." >&2
811    echo "       add it with: git remote add zed https://github.com/zed-industries/zed.git" >&2
812    echo "       then: git remote set-url --push zed DISABLED && git fetch zed main" >&2
813    exit 1
814  fi
815  rustc_version="$(rustc --version)"
816  host_triple="$(rustc -vV | awk '/^host:/{print $2}')"
817  cargo_lock_digest="$(sha256_file Cargo.lock)"
818  icon_json="$(collect_icon_digests_json)"
819  packaged_bindings_json="${PACKAGED_BINDINGS_JSON:-$(collect_source_packaged_bindings_json)}"
820
821  OMEGA_RC_RECORD_OUT="${RELEASE_RECORD_PATH}" \
822  OMEGA_RC_SCHEMA_VERSION="${OMEGA_RELEASE_RECORD_SCHEMA_VERSION}" \
823  OMEGA_RC_VERSION_VALUE="${OMEGA_RC_VERSION}" \
824  OMEGA_RC_BUNDLE_VERSION_VALUE="${OMEGA_RC_SEMVER}" \
825  OMEGA_RC_ARTIFACT_NAME="${OMEGA_ARTIFACT_NAME}" \
826  OMEGA_RC_VOLUME_NAME="${OMEGA_VOLUME_NAME}" \
827  OMEGA_RC_BUNDLE_ID="${OMEGA_BUNDLE_ID}" \
828  OMEGA_RC_TEAM_ID="${OMEGA_TEAM_ID}" \
829  OMEGA_RC_SIGNING_IDENTITY="${OMEGA_SIGNING_IDENTITY}" \
830  OMEGA_RC_COMMIT="${commit}" \
831  OMEGA_RC_UPSTREAM_COMMIT="${upstream_commit}" \
832  OMEGA_RC_RUSTC="${rustc_version}" \
833  OMEGA_RC_HOST="${host_triple}" \
834  OMEGA_RC_CARGO_LOCK_DIGEST="${cargo_lock_digest}" \
835  OMEGA_RC_BUILD_LOCK_SNAPSHOT_NAME="${BUILD_LOCK_SNAPSHOT_NAME:-}" \
836  OMEGA_RC_BUILD_LOCK_SNAPSHOT_DIGEST="${BUILD_LOCK_SNAPSHOT_DIGEST}" \
837  OMEGA_RC_ICON_JSON="${icon_json}" \
838  OMEGA_RC_PACKAGED_BINDINGS_JSON="${packaged_bindings_json}" \
839  OMEGA_RC_PACKAGE_DIGEST="${package_digest}" \
840  OMEGA_RC_OMEGA_BINARY_DIGEST="${OMEGA_BINARY_DIGEST}" \
841  OMEGA_RC_CLI_BINARY_DIGEST="${CLI_BINARY_DIGEST}" \
842  OMEGA_RC_IDENTITY_PROOF_BINARY_DIGEST="${IDENTITY_PROOF_BINARY_DIGEST}" \
843  OMEGA_RC_EFFECTD_RELEASE_TAG="${OMEGA_EFFECTD_RELEASE_TAG}" \
844  OMEGA_RC_EFFECTD_ASSET_NAME="${OMEGA_EFFECTD_ASSET_NAME}" \
845  OMEGA_RC_EFFECTD_ARCHIVE_DIGEST="${OMEGA_EFFECTD_ASSET_SHA256}" \
846  OMEGA_RC_EFFECTD_SOURCE_COMMIT="${OMEGA_EFFECTD_SOURCE_COMMIT}" \
847  OMEGA_RC_EFFECTD_SOURCE_TREE="${OMEGA_EFFECTD_SOURCE_TREE}" \
848  OMEGA_RC_EFFECTD_MANIFEST_DIGEST="${OMEGA_EFFECTD_MANIFEST_SHA256}" \
849  OMEGA_RC_EFFECTD_WRAPPER_DIGEST="${OMEGA_EFFECTD_WRAPPER_SHA256}" \
850  OMEGA_RC_EFFECTD_BUNDLE_DIGEST="${OMEGA_EFFECTD_BUNDLE_SHA256}" \
851  OMEGA_RC_EFFECTD_SOURCE_NODE_DIGEST="${OMEGA_EFFECTD_SOURCE_NODE_SHA256}" \
852  OMEGA_RC_EFFECTD_PACKAGED_NODE_DIGEST="${OMEGA_EFFECTD_PACKAGED_NODE_SHA256}" \
853  OMEGA_RC_SOURCE_VERIFIER_PATH="${OMEGA_SOURCE_VERIFIER_PATH}" \
854  OMEGA_RC_SOURCE_VERIFIER_DIGEST="${SOURCE_VERIFIER_DIGEST}" \
855  OMEGA_RC_SOURCE_VERIFIER_PASSED="${SOURCE_VERIFIER_PASSED}" \
856  OMEGA_RC_PACKAGED_SCAN_PASSED="${PACKAGED_BOUNDARY_SCAN_PASSED}" \
857  OMEGA_RC_IDENTITY_BOUNDARIES="${OMEGA_IDENTITY_BOUNDARY_LIST}" \
858  OMEGA_RC_BRAND_VERIFIER_PATH="${OMEGA_BRAND_VERIFIER_PATH}" \
859  OMEGA_RC_BRAND_VERIFIER_DIGEST="${BRAND_VERIFIER_DIGEST}" \
860  OMEGA_RC_BRAND_POLICY_PATH="${OMEGA_BRAND_POLICY_PATH}" \
861  OMEGA_RC_BRAND_POLICY_DIGEST="${BRAND_POLICY_DIGEST}" \
862  OMEGA_RC_BRAND_SOURCE_PASSED="${BRAND_SOURCE_SCAN_PASSED}" \
863  OMEGA_RC_BRAND_PACKAGED_PASSED="${BRAND_PACKAGED_SCAN_PASSED}" \
864  OMEGA_RC_DRY_RUN="${DRY_RUN}" \
865  OMEGA_RC_NOTARIZATION_STATUS="${notarization_status}" \
866  OMEGA_RC_NOTARIZATION_ATTEMPTED="${notarization_attempted}" \
867  OMEGA_RC_NOTARIZATION_STAPLED="${notarization_stapled}" \
868  OMEGA_RC_NOTARIZATION_APP_STAPLED="${app_stapled}" \
869  OMEGA_RC_PACKAGE_PATH="${package_path}" \
870  python3 <<'PY'
871import json
872import os
873import subprocess
874from pathlib import Path
875
876
877def worktree_state() -> tuple[bool, list[str]]:
878    """Read whether the tree this candidate was built from was recorded.
879
880    `require_clean_checkout` refuses to *start* from a dirty tree, so anything
881    seen here appeared during the build. This runs after packaging, and the
882    build generates `assets/licenses.md` through `script/generate-licenses`,
883    which is untracked — so every candidate recorded `dirty: true` on the
884    strength of its own output.
885
886    That reads as "the commit does not determine these bytes", which is the
887    opposite of true, and an independent reviewer correctly treated it as
888    weakening the source binding. A bare boolean cannot tell a reviewer which
889    reading applies, so the entries are recorded beside it: a tracked
890    modification really does break the commit-to-bytes binding, an untracked
891    build output does not.
892    """
893    status = subprocess.run(
894        ["git", "status", "--porcelain"],
895        capture_output=True,
896        text=True,
897        check=True,
898    )
899    entries = [line for line in status.stdout.splitlines() if line.strip()]
900    tracked = [line for line in entries if not line.startswith("??")]
901    return bool(tracked), entries
902
903
904source_is_dirty, worktree_entries = worktree_state()
905icons = json.loads(os.environ["OMEGA_RC_ICON_JSON"])
906packaged_bindings = json.loads(os.environ["OMEGA_RC_PACKAGED_BINDINGS_JSON"])
907upstream = os.environ.get("OMEGA_RC_UPSTREAM_COMMIT") or None
908package_digest = os.environ["OMEGA_RC_PACKAGE_DIGEST"]
909package_sha256 = None if package_digest == "null" else package_digest
910package_path = os.environ["OMEGA_RC_PACKAGE_PATH"]
911version = os.environ["OMEGA_RC_VERSION_VALUE"]
912build_lock_name = os.environ.get("OMEGA_RC_BUILD_LOCK_SNAPSHOT_NAME") or None
913build_lock_digest = os.environ.get("OMEGA_RC_BUILD_LOCK_SNAPSHOT_DIGEST") or None
914omega_binary_digest = os.environ.get("OMEGA_RC_OMEGA_BINARY_DIGEST") or None
915cli_binary_digest = os.environ.get("OMEGA_RC_CLI_BINARY_DIGEST") or None
916identity_proof_binary_digest = os.environ.get("OMEGA_RC_IDENTITY_PROOF_BINARY_DIGEST") or None
917dry_run = os.environ["OMEGA_RC_DRY_RUN"] == "true"
918notarization_attempted = os.environ["OMEGA_RC_NOTARIZATION_ATTEMPTED"] == "true"
919notarization_stapled = os.environ["OMEGA_RC_NOTARIZATION_STAPLED"] == "true"
920
921owner_blockers = []
922if dry_run:
923    owner_blockers.append(
924        "Developer ID signing must complete before this dry-run record can become a release candidate"
925    )
926if not notarization_stapled:
927    owner_blockers.append(
928        "Owner notarization must complete and staple the candidate before distribution"
929    )
930owner_blockers.append("Human publication of the GitHub prerelease (never latest)")
931
932record = {
933    "schema_version": int(os.environ["OMEGA_RC_SCHEMA_VERSION"]),
934    "product": "Omega",
935    "channel": "rc",
936    "version": version,
937    "bundle_version": os.environ["OMEGA_RC_BUNDLE_VERSION_VALUE"],
938    "artifact_name": os.environ["OMEGA_RC_ARTIFACT_NAME"],
939    "volume_name": os.environ["OMEGA_RC_VOLUME_NAME"],
940    "bundle_identifier": os.environ["OMEGA_RC_BUNDLE_ID"],
941    "team_id": os.environ["OMEGA_RC_TEAM_ID"],
942    "signing_identity": os.environ["OMEGA_RC_SIGNING_IDENTITY"],
943    "source": {
944        "commit": os.environ["OMEGA_RC_COMMIT"],
945        "upstream_commit": upstream,
946        # True only for a *tracked* modification, which is what actually
947        # breaks the commit-to-bytes binding. The entries are recorded beside
948        # it so a reader never has to guess which kind they are looking at.
949        "dirty": source_is_dirty,
950        "worktree_entries": worktree_entries,
951        "repository": "https://github.com/OpenAgentsInc/omega",
952    },
953    "toolchains": {
954        "rustc": os.environ["OMEGA_RC_RUSTC"],
955        "host": os.environ["OMEGA_RC_HOST"],
956        "target": "aarch64-apple-darwin",
957    },
958    "execution": {
959        "dry_run": dry_run,
960    },
961    "build_inputs": {
962        "cargo_lock_snapshot": {
963            "name": build_lock_name,
964            "sha256": build_lock_digest,
965        },
966    },
967    "digests": {
968        "cargo_lock_sha256": os.environ["OMEGA_RC_CARGO_LOCK_DIGEST"],
969        "icon_family_manifest_sha256": icons["manifest_sha256"],
970        "pinned_png_sha256": icons["pinned_png_sha256"],
971        "pinned_icns_sha256": icons["pinned_icns_sha256"],
972        "icon_outputs": icons["outputs"],
973        "packaged_icon_outputs": packaged_bindings["icon_outputs"],
974        "package_sha256": package_sha256,
975        "omega_binary_sha256": omega_binary_digest,
976        "cli_binary_sha256": cli_binary_digest,
977    },
978    "components": {
979        "omega_effectd": {
980            "package": "@openagentsinc/omega-effectd",
981            "service_version": "0.1.0",
982            "protocol": "openagents.omega.effectd.v1",
983            "platform": "darwin-arm64",
984            "release_tag": os.environ["OMEGA_RC_EFFECTD_RELEASE_TAG"],
985            "asset_name": os.environ["OMEGA_RC_EFFECTD_ASSET_NAME"],
986            "archive_sha256": os.environ["OMEGA_RC_EFFECTD_ARCHIVE_DIGEST"],
987            "source_commit": os.environ["OMEGA_RC_EFFECTD_SOURCE_COMMIT"],
988            "source_tree": os.environ["OMEGA_RC_EFFECTD_SOURCE_TREE"],
989            "manifest_sha256": os.environ["OMEGA_RC_EFFECTD_MANIFEST_DIGEST"],
990            "wrapper_sha256": os.environ["OMEGA_RC_EFFECTD_WRAPPER_DIGEST"],
991            "bundle_sha256": os.environ["OMEGA_RC_EFFECTD_BUNDLE_DIGEST"],
992            "source_node_sha256": os.environ["OMEGA_RC_EFFECTD_SOURCE_NODE_DIGEST"],
993            "packaged_node_sha256": (
994                os.environ.get("OMEGA_RC_EFFECTD_PACKAGED_NODE_DIGEST") or None
995            ),
996        },
997        "identity_proof_driver": {
998            "path": "Contents/MacOS/omega-identity-proof",
999            "binary_sha256": identity_proof_binary_digest,
1000            "protocol": "openagents.omega.identity-proof.v1",
1001            "keyring_service": "com.openagents.omega.identity-proof.v1",
1002            "keyring_account": "disposable-proof-only",
1003        },
1004    },
1005    "identity_boundaries": {
1006        "source_verifier": {
1007            "path": os.environ["OMEGA_RC_SOURCE_VERIFIER_PATH"],
1008            "sha256": os.environ["OMEGA_RC_SOURCE_VERIFIER_DIGEST"],
1009            "passed": os.environ["OMEGA_RC_SOURCE_VERIFIER_PASSED"] == "true",
1010        },
1011        "packaged_scan": {
1012            "passed": os.environ["OMEGA_RC_PACKAGED_SCAN_PASSED"] == "true",
1013            "forbidden_literals": os.environ["OMEGA_RC_IDENTITY_BOUNDARIES"].split(","),
1014        },
1015    },
1016    "brand_gate": {
1017        "source_verifier": {
1018            "path": os.environ["OMEGA_RC_BRAND_VERIFIER_PATH"],
1019            "sha256": os.environ["OMEGA_RC_BRAND_VERIFIER_DIGEST"],
1020            "passed": os.environ["OMEGA_RC_BRAND_SOURCE_PASSED"] == "true",
1021        },
1022        "policy": {
1023            "path": os.environ["OMEGA_RC_BRAND_POLICY_PATH"],
1024            "sha256": os.environ["OMEGA_RC_BRAND_POLICY_DIGEST"],
1025        },
1026        "packaged_scan": {
1027            "passed": os.environ["OMEGA_RC_BRAND_PACKAGED_PASSED"] == "true",
1028            "surfaces": ["Contents/Info.plist", "embedded icon assets"],
1029        },
1030    },
1031    "notarization": {
1032        "attempted": notarization_attempted,
1033        "stapled": notarization_stapled,
1034        "app_stapled": os.environ["OMEGA_RC_NOTARIZATION_APP_STAPLED"] == "true",
1035        "status": os.environ["OMEGA_RC_NOTARIZATION_STATUS"],
1036    },
1037    "legal": {
1038        "commercial_terms_attached": False,
1039        "notices": packaged_bindings["legal_notices"],
1040    },
1041    "publication": {
1042        "repository": "OpenAgentsInc/omega",
1043        "prerelease": True,
1044        "latest": False,
1045        "tag": f"v{version}",
1046    },
1047    "package_path": package_path if not dry_run and Path(package_path).is_file() else None,
1048    "owner_blockers": owner_blockers,
1049}
1050
1051out_path = Path(os.environ["OMEGA_RC_RECORD_OUT"])
1052out_path.write_text(json.dumps(record, indent=2) + "\n", encoding="utf-8")
1053print(f"wrote release record: {out_path}")
1054PY
1055
1056  validate_release_record_schema "${RELEASE_RECORD_PATH}"
1057}
1058
1059validate_existing_release_record() {
1060  local package_path="$1"
1061  if [[ ! -f "${RELEASE_RECORD_PATH}" ]]; then
1062    echo "error: --skip-build requires existing release record at ${RELEASE_RECORD_PATH}" >&2
1063    exit 1
1064  fi
1065  validate_release_record_schema "${RELEASE_RECORD_PATH}"
1066
1067  local commit package_digest verifier_digest
1068  commit="$(git rev-parse HEAD)"
1069  package_digest="$(sha256_file "${package_path}")"
1070  verifier_digest="$(sha256_file "${OMEGA_SOURCE_VERIFIER_PATH}")"
1071  python3 - \
1072    "${RELEASE_RECORD_PATH}" \
1073    "${OUTPUT_DIR}" \
1074    "${commit}" \
1075    "${OMEGA_ARTIFACT_NAME}" \
1076    "${package_digest}" \
1077    "${verifier_digest}" <<'PY'
1078import hashlib
1079import json
1080import sys
1081from pathlib import Path
1082
1083record_path, output_dir, commit, artifact_name, package_digest, verifier_digest = (
1084    sys.argv[1:]
1085)
1086record = json.loads(Path(record_path).read_text(encoding="utf-8"))
1087
1088if record["execution"]["dry_run"]:
1089    raise SystemExit("--skip-build cannot reuse a dry-run release record")
1090if record["source"].get("commit") != commit:
1091    raise SystemExit("--skip-build release record commit does not match HEAD")
1092if record.get("artifact_name") != artifact_name:
1093    raise SystemExit("--skip-build release record artifact name is stale")
1094if record["digests"].get("package_sha256") != package_digest:
1095    raise SystemExit("--skip-build package digest does not match release record")
1096if record["identity_boundaries"]["source_verifier"].get("sha256") != verifier_digest:
1097    raise SystemExit("--skip-build source verifier digest does not match release record")
1098
1099snapshot = record["build_inputs"]["cargo_lock_snapshot"]
1100snapshot_name = snapshot["name"]
1101if Path(snapshot_name).name != snapshot_name:
1102    raise SystemExit("--skip-build Cargo.lock snapshot name must be a basename")
1103snapshot_path = Path(output_dir) / snapshot_name
1104if not snapshot_path.is_file():
1105    raise SystemExit(f"--skip-build Cargo.lock snapshot is missing: {snapshot_path}")
1106snapshot_digest = hashlib.sha256(snapshot_path.read_bytes()).hexdigest()
1107if snapshot_digest != snapshot["sha256"]:
1108    raise SystemExit("--skip-build Cargo.lock snapshot digest does not match release record")
1109
1110print(f"validated existing Omega RC artifact and release record: {record_path}")
1111PY
1112}
1113
1114restore_channel_and_version() {
1115  (
1116    cd "${ROOT}"
1117    if [[ -f "${_OMEGA_RC_CHANNEL_BACKUP_FILE:-}" ]]; then
1118      cp "${_OMEGA_RC_CHANNEL_BACKUP_FILE}" crates/zed/RELEASE_CHANNEL
1119      rm -f "${_OMEGA_RC_CHANNEL_BACKUP_FILE}"
1120    fi
1121    if [[ -f "${_OMEGA_RC_CARGO_BACKUP_FILE:-}" ]]; then
1122      cp "${_OMEGA_RC_CARGO_BACKUP_FILE}" crates/zed/Cargo.toml
1123      rm -f "${_OMEGA_RC_CARGO_BACKUP_FILE}"
1124    fi
1125    if [[ -f "${_OMEGA_RC_LOCK_BACKUP_FILE:-}" ]]; then
1126      cp "${_OMEGA_RC_LOCK_BACKUP_FILE}" Cargo.lock
1127      rm -f "${_OMEGA_RC_LOCK_BACKUP_FILE}"
1128    fi
1129    rm -f crates/zed/Cargo.toml.backup
1130  )
1131}
1132
1133set_channel_and_version() {
1134  _OMEGA_RC_CHANNEL_BACKUP_FILE="$(mktemp)"
1135  _OMEGA_RC_CARGO_BACKUP_FILE="$(mktemp)"
1136  _OMEGA_RC_LOCK_BACKUP_FILE="$(mktemp)"
1137  cp crates/zed/RELEASE_CHANNEL "${_OMEGA_RC_CHANNEL_BACKUP_FILE}"
1138  cp crates/zed/Cargo.toml "${_OMEGA_RC_CARGO_BACKUP_FILE}"
1139  cp Cargo.lock "${_OMEGA_RC_LOCK_BACKUP_FILE}"
1140
1141  printf '%s\n' "${OMEGA_RC_CHANNEL}" > crates/zed/RELEASE_CHANNEL
1142  python3 - <<PY
1143import re
1144from pathlib import Path
1145cargo = Path("crates/zed/Cargo.toml")
1146text = cargo.read_text(encoding="utf-8")
1147text2, count = re.subn(
1148    r'(?m)^version = "[^"]+"',
1149    'version = "${OMEGA_RC_SEMVER}"',
1150    text,
1151    count=1,
1152)
1153if count != 1:
1154    raise SystemExit("failed to set crates/zed/Cargo.toml version")
1155cargo.write_text(text2, encoding="utf-8")
1156PY
1157  export ZED_RELEASE_CHANNEL="${OMEGA_RC_CHANNEL}"
1158  export ZED_APP_VERSION="${OMEGA_RC_SEMVER}"
1159  export RELEASE_CHANNEL="${OMEGA_RC_CHANNEL}"
1160}
1161
1162maybe_import_certificate() {
1163  if [[ -z "${MACOS_CERTIFICATE:-}" || -z "${MACOS_CERTIFICATE_PASSWORD:-}" ]]; then
1164    return 0
1165  fi
1166  echo "Importing MACOS_CERTIFICATE into a temporary keychain..."
1167  security create-keychain -p "${MACOS_CERTIFICATE_PASSWORD}" omega-rc.keychain || true
1168  security default-keychain -s omega-rc.keychain
1169  security unlock-keychain -p "${MACOS_CERTIFICATE_PASSWORD}" omega-rc.keychain
1170  security set-keychain-settings omega-rc.keychain
1171  echo "${MACOS_CERTIFICATE}" | base64 --decode > /tmp/omega-rc-certificate.p12
1172  security import /tmp/omega-rc-certificate.p12 -k omega-rc.keychain -P "${MACOS_CERTIFICATE_PASSWORD}" -T /usr/bin/codesign
1173  rm -f /tmp/omega-rc-certificate.p12
1174  security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "${MACOS_CERTIFICATE_PASSWORD}" omega-rc.keychain
1175  _OMEGA_RC_KEYCHAIN_CREATED=1
1176}
1177
1178cleanup() {
1179  restore_channel_and_version || true
1180  cleanup_notarization_key_file
1181  if [[ "${_OMEGA_RC_KEYCHAIN_CREATED:-0}" == "1" ]]; then
1182    security default-keychain -s login.keychain || true
1183    security delete-keychain omega-rc.keychain || true
1184  fi
1185}
1186
1187trap cleanup EXIT
1188
1189if [[ "${SELF_TEST_NOTARIZATION_KEY}" == "true" ]]; then
1190  self_test_notarization_key
1191  exit 0
1192fi
1193
1194require_arm64_host_or_target
1195require_clean_checkout
1196verify_source_identity_boundaries
1197verify_source_brand
1198prepare_omega_effectd_archive
1199
1200DMG_PATH="${OUTPUT_DIR}/${OMEGA_ARTIFACT_NAME}"
1201
1202if [[ "${SKIP_BUILD}" == "true" ]]; then
1203  if [[ ! -f "${DMG_PATH}" ]]; then
1204    echo "error: --skip-build requires existing package at ${DMG_PATH}" >&2
1205    exit 1
1206  fi
1207  validate_existing_release_record "${DMG_PATH}"
1208  exit 0
1209fi
1210
1211if [[ "${DRY_RUN}" == "true" ]]; then
1212  echo "Omega RC dry-run"
1213  echo "  version:            ${OMEGA_RC_VERSION}"
1214  echo "  channel file value: ${OMEGA_RC_CHANNEL} (maps to ${OMEGA_RC_DISPLAY_CHANNEL})"
1215  echo "  bundle id:          ${OMEGA_BUNDLE_ID}"
1216  echo "  volume:             ${OMEGA_VOLUME_NAME}"
1217  echo "  artifact:           ${OMEGA_ARTIFACT_NAME}"
1218  echo "  team:               ${OMEGA_TEAM_ID}"
1219  echo "  signing identity:   ${OMEGA_SIGNING_IDENTITY}"
1220  find_openagents_signing_identity
1221  # Five arguments, not four: `app_stapled` arrived with the app-level staple
1222  # check and this call was not widened with it, so every dry run since has
1223  # died on `$5: unbound variable` after printing the plan. A dry run is the
1224  # affordance an independent reviewer is told to use before trusting a
1225  # candidate, so it has to reach the end.
1226  write_release_record "${DMG_PATH}" "not_attempted" "false" "false" "false"
1227  echo "dry-run complete; no compile, codesign, or notarization was performed."
1228  exit 0
1229fi
1230
1231find_openagents_signing_identity
1232maybe_import_certificate
1233find_openagents_signing_identity
1234
1235set_channel_and_version
1236
1237echo "Preparing Omega RC ${OMEGA_RC_VERSION} (channel=${OMEGA_RC_CHANNEL})"
1238echo "Updating temporary Cargo.lock for zed ${OMEGA_RC_SEMVER}"
1239cargo update --package zed --precise "${OMEGA_RC_SEMVER}"
1240cp Cargo.lock "${BUILD_LOCK_SNAPSHOT_PATH}"
1241BUILD_LOCK_SNAPSHOT_NAME="${OMEGA_BUILD_LOCK_SNAPSHOT_NAME}"
1242BUILD_LOCK_SNAPSHOT_DIGEST="$(sha256_file "${BUILD_LOCK_SNAPSHOT_PATH}")"
1243if [[ -z "${BUILD_LOCK_SNAPSHOT_DIGEST}" ]]; then
1244  echo "error: failed to capture build Cargo.lock snapshot." >&2
1245  exit 1
1246fi
1247
1248script/generate-licenses
1249
1250rustup target add "${TARGET_TRIPLE}"
1251
1252cargo_bundle_version="$(cargo -q bundle --help 2>&1 | head -n 1 || echo "")"
1253if [[ "${cargo_bundle_version}" != "cargo-bundle v0.6.1-zed" ]]; then
1254  cargo install cargo-bundle --git https://github.com/zed-industries/cargo-bundle.git --branch zed-deploy
1255fi
1256
1257export CXXFLAGS="-stdlib=libc++"
1258export ZED_BUNDLE=true
1259export ZED_COMMIT_SHA="$(git rev-parse HEAD)"
1260
1261echo "Compiling omega binaries for ${TARGET_TRIPLE}"
1262cargo build --locked --release --package zed --package cli --package omega_identity --target "${TARGET_TRIPLE}"
1263cargo build --locked --release --package remote_server --target "${TARGET_TRIPLE}"
1264
1265echo "Creating application bundle"
1266pushd crates/zed >/dev/null
1267cp Cargo.toml Cargo.toml.backup
1268sed \
1269  -i.backup \
1270  "s/package.metadata.bundle-${OMEGA_RC_CHANNEL}/package.metadata.bundle/" \
1271  Cargo.toml
1272app_path="$(TERM=xterm-256color CARGO_TERM_COLOR=never cargo bundle --release --target "${TARGET_TRIPLE}" --select-workspace-root | xargs)"
1273if [[ "$(sha256_file "${ROOT}/Cargo.lock")" != "${BUILD_LOCK_SNAPSHOT_DIGEST}" ]]; then
1274  echo "error: cargo-bundle changed Cargo.lock after the locked release build." >&2
1275  exit 1
1276fi
1277mv Cargo.toml.backup Cargo.toml
1278rm -f Cargo.toml.backup
1279popd >/dev/null
1280
1281document_icon_source="crates/zed/resources/Document.icns"
1282document_icon_target="${app_path}/Contents/Resources/Document.icns"
1283if [[ -f "${document_icon_source}" ]]; then
1284  mkdir -p "$(dirname "${document_icon_target}")"
1285  cp "${document_icon_source}" "${document_icon_target}"
1286fi
1287
1288# Do not embed the inherited Zed preview provisioning profile (team MQ55VZLNZQ).
1289rm -f "${app_path}/Contents/embedded.provisionprofile"
1290
1291cp "target/${TARGET_TRIPLE}/release/omega" "${app_path}/Contents/MacOS/omega"
1292cp "target/${TARGET_TRIPLE}/release/cli" "${app_path}/Contents/MacOS/cli"
1293cp "target/${TARGET_TRIPLE}/release/omega-identity-proof" "${app_path}/Contents/MacOS/omega-identity-proof"
1294stage_omega_effectd_component "${app_path}"
1295verify_packaged_identity_boundaries "${app_path}"
1296verify_packaged_brand "${app_path}"
1297
1298# Strip associated-domains entitlement: it requires an Omega-owned profile.
1299entitlements_path="${OUTPUT_DIR}/omega-rc.entitlements"
1300sed '/com.apple.developer.associated-domains/,+1d' \
1301  crates/zed/resources/zed.entitlements > "${entitlements_path}"
1302effectd_node_entitlements_path="${OUTPUT_DIR}/omega-effectd-node.entitlements"
1303plutil -create xml1 "${effectd_node_entitlements_path}"
1304plutil -insert 'com\.apple\.security\.cs\.allow-jit' -bool true \
1305  "${effectd_node_entitlements_path}"
1306plutil -insert 'com\.apple\.security\.cs\.allow-unsigned-executable-memory' -bool true \
1307  "${effectd_node_entitlements_path}"
1308
1309echo "Code signing with ${OMEGA_SIGNING_IDENTITY}"
1310/usr/bin/codesign --force --timestamp --options runtime \
1311  --entitlements "${effectd_node_entitlements_path}" \
1312  --sign "${OMEGA_SIGNING_IDENTITY}" \
1313  "${app_path}/Contents/Resources/omega-effectd/runtime/bin/node" -v
1314OMEGA_EFFECTD_PACKAGED_NODE_SHA256="$(sha256_file "${app_path}/Contents/Resources/omega-effectd/runtime/bin/node")"
1315/usr/bin/codesign --deep --force --timestamp --options runtime \
1316  --sign "${OMEGA_SIGNING_IDENTITY}" "${app_path}/Contents/MacOS/cli" -v
1317/usr/bin/codesign --deep --force --timestamp --options runtime \
1318  --sign "${OMEGA_SIGNING_IDENTITY}" "${app_path}/Contents/MacOS/omega-identity-proof" -v
1319/usr/bin/codesign --deep --force --timestamp --options runtime \
1320  --entitlements "${entitlements_path}" \
1321  --sign "${OMEGA_SIGNING_IDENTITY}" "${app_path}/Contents/MacOS/omega" -v
1322/usr/bin/codesign --force --timestamp --options runtime \
1323  --entitlements "${entitlements_path}" \
1324  --sign "${OMEGA_SIGNING_IDENTITY}" "${app_path}" -v
1325OMEGA_BINARY_DIGEST="$(sha256_file "${app_path}/Contents/MacOS/omega")"
1326CLI_BINARY_DIGEST="$(sha256_file "${app_path}/Contents/MacOS/cli")"
1327IDENTITY_PROOF_BINARY_DIGEST="$(sha256_file "${app_path}/Contents/MacOS/omega-identity-proof")"
1328if [[ -z "${OMEGA_BINARY_DIGEST}" || -z "${CLI_BINARY_DIGEST}" || -z "${IDENTITY_PROOF_BINARY_DIGEST}" ]]; then
1329  echo "error: signed Omega binary digests are unavailable." >&2
1330  exit 1
1331fi
1332
1333# OMEGA-DELTA-0023. Staple the APPLICATION, before the disk image is built.
1334#
1335# This script used to staple the DMG only. A DMG ticket covers the disk image
1336# the owner throws away; it does not travel with the application that ends up
1337# in /Applications, so `stapler validate /Applications/Omega.app` reported no
1338# ticket and Gatekeeper acceptance of the installed product could rest on an
1339# online lookup with Apple. omega#16 requires OFFLINE first start, which that
1340# cannot prove. The app is submitted and stapled here so the disk image below
1341# is assembled from an already-stapled application.
1342app_stapled="false"
1343if [[ -n "${APPLE_NOTARIZATION_KEY:-}" && -n "${APPLE_NOTARIZATION_KEY_ID:-}" && -n "${APPLE_NOTARIZATION_ISSUER_ID:-}" ]]; then
1344  echo "Submitting Omega.app for notarization (owner secrets present)..."
1345  prepare_notarization_key_file "${APPLE_NOTARIZATION_KEY}"
1346  xcode_bin_dir_path="$(xcode-select -p)/usr/bin"
1347  app_notarization_zip="${OUTPUT_DIR}/omega-app-notarization.zip"
1348  rm -f "${app_notarization_zip}"
1349  # notarytool takes an archive, not a bundle directory. ditto --keepParent
1350  # preserves the .app wrapper and its extended attributes.
1351  /usr/bin/ditto -c -k --keepParent "${app_path}" "${app_notarization_zip}"
1352  if "${xcode_bin_dir_path}/notarytool" submit --wait \
1353    --key "${NOTARIZATION_KEY_FILE}" \
1354    --key-id "${APPLE_NOTARIZATION_KEY_ID}" \
1355    --issuer "${APPLE_NOTARIZATION_ISSUER_ID}" \
1356    "${app_notarization_zip}"; then
1357    "${xcode_bin_dir_path}/stapler" staple "${app_path}"
1358    if "${xcode_bin_dir_path}/stapler" validate "${app_path}"; then
1359      app_stapled="true"
1360      echo "Stapled and validated ${app_path}"
1361    else
1362      echo "error: ${app_path} has no valid notarization ticket after stapling." >&2
1363      exit 1
1364    fi
1365  else
1366    echo "warning: app notarization submit failed; Omega.app remains signed but unstapled." >&2
1367    echo "         offline first start cannot be proven from this candidate." >&2
1368  fi
1369  rm -f "${app_notarization_zip}"
1370  cleanup_notarization_key_file
1371else
1372  echo "Notarization secrets absent; Omega.app is signed but not stapled."
1373  echo "Offline first start cannot be proven from this candidate."
1374fi
1375
1376dmg_source_directory="${OUTPUT_DIR}/dmg-src"
1377rm -rf "${dmg_source_directory}"
1378mkdir -p "${dmg_source_directory}"
1379mv "${app_path}" "${dmg_source_directory}/"
1380stapled_app_path="${dmg_source_directory}/$(basename "${app_path}")"
1381ln -s /Applications "${dmg_source_directory}/Applications"
1382cp LICENSE-GPL LICENSE-APACHE "${dmg_source_directory}/"
1383if [[ -f assets/licenses.md ]]; then
1384  cp assets/licenses.md "${dmg_source_directory}/"
1385fi
1386PACKAGED_BINDINGS_JSON="$(collect_staged_packaged_bindings_json "${dmg_source_directory}")"
1387
1388echo "Creating DMG ${DMG_PATH}"
1389rm -f "${DMG_PATH}"
1390
1391# `hdiutil create -srcfolder` mounts its staging volume under /Volumes and
1392# copies there. On this host that copy began failing with
1393#
1394#   could not access /Volumes/Omega RC/Omega.app - Operation not permitted
1395#
1396# after 0.2.0-rc16 had built the same way from the same directory. The source
1397# tree is readable, carries no special flags, and /Volumes holds no stale
1398# mount; the refusal is on the destination, which is the shape of a privacy
1399# restriction on mounted volumes rather than anything about the artifact.
1400#
1401# A release must not depend on whether this machine will let a tool write
1402# under /Volumes, so the image is built at a mountpoint we choose. Same
1403# contents, same compressed format, no /Volumes involvement.
1404dmg_scratch_image="${OUTPUT_DIR}/.dmg-scratch.dmg"
1405dmg_scratch_mount="${OUTPUT_DIR}/.dmg-mnt"
1406rm -f "${dmg_scratch_image}"
1407rm -rf "${dmg_scratch_mount}"
1408mkdir -p "${dmg_scratch_mount}"
1409
1410dmg_staged_megabytes="$(du -sm "${dmg_source_directory}" | cut -f1)"
1411hdiutil create -size "$(( dmg_staged_megabytes + 120 ))m" -fs HFS+ \
1412  -volname "${OMEGA_VOLUME_NAME}" -layout NONE "${dmg_scratch_image}" >/dev/null
1413hdiutil attach "${dmg_scratch_image}" -nobrowse -quiet -mountpoint "${dmg_scratch_mount}"
1414cp -R "${dmg_source_directory}/." "${dmg_scratch_mount}/"
1415hdiutil detach "${dmg_scratch_mount}" -quiet
1416hdiutil convert "${dmg_scratch_image}" -format UDZO -o "${DMG_PATH}" >/dev/null
1417rm -f "${dmg_scratch_image}"
1418rmdir "${dmg_scratch_mount}" 2>/dev/null || true
1419
1420rm -f "${dmg_source_directory}/Applications"
1421
1422/usr/bin/codesign --deep --force --timestamp --options runtime \
1423  --sign "${OMEGA_SIGNING_IDENTITY}" "${DMG_PATH}" -v
1424
1425notarization_status="not_attempted"
1426notarization_attempted="false"
1427notarization_stapled="false"
1428
1429if [[ -n "${APPLE_NOTARIZATION_KEY:-}" && -n "${APPLE_NOTARIZATION_KEY_ID:-}" && -n "${APPLE_NOTARIZATION_ISSUER_ID:-}" ]]; then
1430  notarization_attempted="true"
1431  echo "Submitting DMG for notarization (owner secrets present)..."
1432  prepare_notarization_key_file "${APPLE_NOTARIZATION_KEY}"
1433  xcode_bin_dir_path="$(xcode-select -p)/usr/bin"
1434  if "${xcode_bin_dir_path}/notarytool" submit --wait \
1435    --key "${NOTARIZATION_KEY_FILE}" \
1436    --key-id "${APPLE_NOTARIZATION_KEY_ID}" \
1437    --issuer "${APPLE_NOTARIZATION_ISSUER_ID}" \
1438    "${DMG_PATH}"; then
1439    "${xcode_bin_dir_path}/stapler" staple "${DMG_PATH}"
1440    if ! "${xcode_bin_dir_path}/stapler" validate "${DMG_PATH}"; then
1441      echo "error: ${DMG_PATH} has no valid notarization ticket after stapling." >&2
1442      exit 1
1443    fi
1444    notarization_status="submitted_and_stapled"
1445    notarization_stapled="true"
1446    # OMEGA-DELTA-0023. Both artifacts are validated, and the app ticket is
1447    # re-validated through its staged path so a stapled bundle cannot be
1448    # reported on the strength of the DMG's ticket alone.
1449    if ! "${xcode_bin_dir_path}/stapler" validate "${stapled_app_path}"; then
1450      echo "error: the staged Omega.app inside the disk image has no ticket." >&2
1451      exit 1
1452    fi
1453    echo "Stapled and validated ${DMG_PATH} and ${stapled_app_path}"
1454  else
1455    notarization_status="submit_failed"
1456    echo "warning: notarization submit failed; DMG remains signed but unstapled." >&2
1457  fi
1458  cleanup_notarization_key_file
1459else
1460  echo "Notarization secrets absent; leaving DMG signed but not notarized."
1461  echo "Release record will set notarization.status=not_attempted."
1462fi
1463
1464write_release_record "${DMG_PATH}" "${notarization_status}" "${notarization_attempted}" "${notarization_stapled}" "${app_stapled}"
1465
1466echo "Omega RC package ready:"
1467echo "  ${DMG_PATH}"
1468echo "  ${RELEASE_RECORD_PATH}"
1469if [[ "${notarization_stapled}" != "true" ]]; then
1470  echo "Note: notarization was not completed in this run."
1471fi
1472if [[ "${app_stapled}" != "true" ]]; then
1473  echo "Note: Omega.app is not stapled, so offline first start is not proven."
1474fi
1475
Served at tenant.openagents/omega Member data and write actions are omitted.