@hasna/recordings 0.2.11 → 0.2.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (167) hide show
  1. package/README.md +349 -69
  2. package/bun.lock +3 -0
  3. package/dist/__tests__/helpers/native-fs-guard.d.ts +2 -0
  4. package/dist/__tests__/helpers/native-fs-guard.d.ts.map +1 -0
  5. package/dist/cli/index.js +1728 -320
  6. package/dist/cli/macos-permissions.d.ts +13 -0
  7. package/dist/cli/macos-permissions.d.ts.map +1 -0
  8. package/dist/cli/options.d.ts +12 -0
  9. package/dist/cli/options.d.ts.map +1 -1
  10. package/dist/db/pg-migrations.d.ts.map +1 -1
  11. package/dist/db/recordings.d.ts +2 -1
  12. package/dist/db/recordings.d.ts.map +1 -1
  13. package/dist/db/remote-storage.d.ts +5 -1
  14. package/dist/db/remote-storage.d.ts.map +1 -1
  15. package/dist/index.d.ts +2 -2
  16. package/dist/index.d.ts.map +1 -1
  17. package/dist/index.js +628 -147
  18. package/dist/lib/bun-runtime.d.ts +11 -0
  19. package/dist/lib/bun-runtime.d.ts.map +1 -0
  20. package/dist/lib/config.d.ts +8 -1
  21. package/dist/lib/config.d.ts.map +1 -1
  22. package/dist/lib/enhancer.d.ts +10 -2
  23. package/dist/lib/enhancer.d.ts.map +1 -1
  24. package/dist/lib/install-maintenance.d.ts +24 -0
  25. package/dist/lib/install-maintenance.d.ts.map +1 -0
  26. package/dist/lib/machine.d.ts +2 -0
  27. package/dist/lib/machine.d.ts.map +1 -0
  28. package/dist/lib/recorder.d.ts +2 -1
  29. package/dist/lib/recorder.d.ts.map +1 -1
  30. package/dist/lib/recording-create-identity.d.ts +13 -0
  31. package/dist/lib/recording-create-identity.d.ts.map +1 -0
  32. package/dist/lib/release-install-policy.d.ts +31 -0
  33. package/dist/lib/release-install-policy.d.ts.map +1 -0
  34. package/dist/lib/transcriber.d.ts.map +1 -1
  35. package/dist/mcp/index.d.ts.map +1 -1
  36. package/dist/mcp/index.js +977 -194
  37. package/dist/sdk/index.js +5 -1
  38. package/dist/sdk/v1.generated.d.ts +4 -0
  39. package/dist/sdk/v1.generated.d.ts.map +1 -1
  40. package/dist/server/cloud-config.d.ts +9 -0
  41. package/dist/server/cloud-config.d.ts.map +1 -0
  42. package/dist/server/cloud-readiness.d.ts +10 -0
  43. package/dist/server/cloud-readiness.d.ts.map +1 -0
  44. package/dist/server/cloud.d.ts +9 -18
  45. package/dist/server/cloud.d.ts.map +1 -1
  46. package/dist/server/index.js +1654 -304
  47. package/dist/server/migrate-command.d.ts +11 -0
  48. package/dist/server/migrate-command.d.ts.map +1 -0
  49. package/dist/server/openapi.d.ts +22 -0
  50. package/dist/server/openapi.d.ts.map +1 -1
  51. package/dist/server/repo.d.ts +8 -1
  52. package/dist/server/repo.d.ts.map +1 -1
  53. package/dist/server/serve.d.ts +6 -1
  54. package/dist/server/serve.d.ts.map +1 -1
  55. package/dist/server/v1.d.ts.map +1 -1
  56. package/dist/storage.js +515 -80
  57. package/dist/store.d.ts +3 -1
  58. package/dist/store.d.ts.map +1 -1
  59. package/dist/types/index.d.ts +10 -0
  60. package/dist/types/index.d.ts.map +1 -1
  61. package/dist/version.d.ts +1 -1
  62. package/package.json +7 -4
  63. package/packaging/macos/Empty.entitlements +5 -0
  64. package/packaging/macos/Library/LaunchDaemons/com.hasna.recordings.updater.plist +34 -0
  65. package/packaging/macos/Verifier.entitlements +10 -0
  66. package/packaging/macos/artifact-verifier.sb +33 -0
  67. package/packaging/macos/build_release_pkg.sh +872 -0
  68. package/packaging/macos/managed_bootstrap.sh +623 -0
  69. package/packaging/macos/pkgutil_fingerprint.awk +37 -0
  70. package/packaging/macos/release_lifecycle.ts +463 -0
  71. package/packaging/macos/scripts/postinstall +565 -0
  72. package/packaging/macos/scripts/preinstall +297 -0
  73. package/scripts/build_companion_cli.sh +337 -0
  74. package/scripts/build_native_fs_guard.sh +59 -0
  75. package/scripts/generate-sdk.ts +19 -1
  76. package/scripts/install_macos_app.sh +1919 -98
  77. package/scripts/macos_artifact.ts +5316 -0
  78. package/scripts/migrate.ts +38 -10
  79. package/scripts/native/prebuilds/darwin-universal/recordings_fs_guard.node +0 -0
  80. package/scripts/native/recordings_fs_guard.c +1167 -0
  81. package/scripts/native_fs_guard.ts +158 -0
  82. package/scripts/release-guard.ts +20 -1
  83. package/scripts/resolve_tailscale_cli.sh +389 -0
  84. package/scripts/smoke_macos_app.sh +573 -0
  85. package/src/native/Recordings/App/ContentView.swift +83 -0
  86. package/src/native/Recordings/App/MenuBarStatusView.swift +102 -0
  87. package/src/native/Recordings/App/RecordWorkspaceView.swift +468 -0
  88. package/src/native/Recordings/App/RecordingDetailView.swift +138 -0
  89. package/src/native/Recordings/App/RecordingsApp.swift +367 -44
  90. package/src/native/Recordings/App/RecordingsListView.swift +130 -0
  91. package/src/native/Recordings/App/RecordingsStore.swift +206 -0
  92. package/src/native/Recordings/App/RuntimeSmoke.swift +158 -0
  93. package/src/native/Recordings/App/SidebarView.swift +212 -0
  94. package/src/native/Recordings/App/Theme.swift +87 -0
  95. package/src/native/Recordings/Package.resolved +5 -5
  96. package/src/native/Recordings/Package.swift +62 -2
  97. package/src/native/Recordings/RecordingsLib/AccessibilityPromptGate.swift +104 -0
  98. package/src/native/Recordings/RecordingsLib/BrandAssets.swift +0 -32
  99. package/src/native/Recordings/RecordingsLib/ChromeSurface.swift +17 -0
  100. package/src/native/Recordings/RecordingsLib/Info.plist +7 -3
  101. package/src/native/Recordings/RecordingsLib/MenuBarPresentation.swift +37 -0
  102. package/src/native/Recordings/RecordingsLib/NativeAppDiagnostics.swift +32 -2
  103. package/src/native/Recordings/RecordingsLib/NativeMachineIdentity.swift +24 -0
  104. package/src/native/Recordings/RecordingsLib/NativePCMRecorder.swift +262 -23
  105. package/src/native/Recordings/RecordingsLib/OpenAIAPIKeyStore.swift +56 -0
  106. package/src/native/Recordings/RecordingsLib/PermissionRequestLaunchPlan.swift +56 -0
  107. package/src/native/Recordings/RecordingsLib/ProjectStore.swift +321 -25
  108. package/src/native/Recordings/RecordingsLib/RealtimeTranscriptionClient.swift +605 -90
  109. package/src/native/Recordings/RecordingsLib/Recording.swift +141 -0
  110. package/src/native/Recordings/RecordingsLib/RecordingEngine.swift +3704 -376
  111. package/src/native/Recordings/RecordingsLib/RecordingStartControlPresentation.swift +29 -0
  112. package/src/native/Recordings/RecordingsLib/RecordingsCLI.entitlements +10 -0
  113. package/src/native/Recordings/RecordingsLib/RecordingsCLI.swift +222 -0
  114. package/src/native/Recordings/RecordingsLib/SettingsView.swift +45 -11
  115. package/src/native/Recordings/RecordingsLib/SpeechIntent.swift +331 -0
  116. package/src/native/Recordings/RecordingsLib/SpeechIntentClassifier.swift +232 -0
  117. package/src/native/Recordings/RecordingsLib/VoiceShortcuts.swift +17 -7
  118. package/src/native/Recordings/RecordingsTests/CLIRunnerTests.swift +1343 -0
  119. package/src/native/Recordings/RecordingsTests/MenuBarPresentationTests.swift +98 -0
  120. package/src/native/Recordings/RecordingsTests/NativeAppDiagnosticsTests.swift +20 -0
  121. package/src/native/Recordings/RecordingsTests/NativePCMRecorderTests.swift +370 -1
  122. package/src/native/Recordings/RecordingsTests/PasteTargetTests.swift +987 -1
  123. package/src/native/Recordings/RecordingsTests/ProjectStoreTests.swift +385 -0
  124. package/src/native/Recordings/RecordingsTests/RealtimeTranscriptionTests.swift +835 -8
  125. package/src/native/Recordings/RecordingsTests/RecordingBridgeTests.swift +180 -0
  126. package/src/native/Recordings/RecordingsTests/RecordingEngineDeliveryTests.swift +760 -0
  127. package/src/native/Recordings/RecordingsTests/RecordingStartControlPresentationTests.swift +42 -0
  128. package/src/native/Recordings/RecordingsTests/RecordingStartGateTests.swift +264 -0
  129. package/src/native/Recordings/RecordingsTests/RecordingStartTimingTests.swift +129 -0
  130. package/src/native/Recordings/RecordingsTests/SpeechIntentTests.swift +600 -0
  131. package/src/native/Recordings/RecordingsTests/TranscriptResolutionTests.swift +62 -1
  132. package/src/native/Recordings/RecordingsTests/TranscriptionResultIdentityTests.swift +21 -0
  133. package/src/native/Recordings/Updater/BootstrapPreflight/BootstrapPreflightMain.swift +739 -0
  134. package/src/native/Recordings/Updater/Broker/ActivationRecoveryPolicy.swift +254 -0
  135. package/src/native/Recordings/Updater/Broker/ApplicationNamespace.swift +463 -0
  136. package/src/native/Recordings/Updater/Broker/ApplicationProcessQuiescence.swift +55 -0
  137. package/src/native/Recordings/Updater/Broker/ArtifactIngest.swift +341 -0
  138. package/src/native/Recordings/Updater/Broker/AtomicActivation.swift +477 -0
  139. package/src/native/Recordings/Updater/Broker/BrokerMain.swift +624 -0
  140. package/src/native/Recordings/Updater/Broker/CanonicalTreeCopy.swift +63 -0
  141. package/src/native/Recordings/Updater/Broker/CodeValidation.swift +561 -0
  142. package/src/native/Recordings/Updater/Broker/DarwinACLValidator.swift +66 -0
  143. package/src/native/Recordings/Updater/Broker/HostOSProductVersion.swift +50 -0
  144. package/src/native/Recordings/Updater/Broker/InstallJournal.swift +362 -0
  145. package/src/native/Recordings/Updater/Broker/InstallRecovery.swift +662 -0
  146. package/src/native/Recordings/Updater/Broker/MonotonicState.swift +456 -0
  147. package/src/native/Recordings/Updater/Broker/PeerIdentity.swift +274 -0
  148. package/src/native/Recordings/Updater/Broker/VerifierRunner.swift +81 -0
  149. package/src/native/Recordings/Updater/BrokerTests/ActivationRecoveryPolicyTests.swift +259 -0
  150. package/src/native/Recordings/Updater/BrokerTests/CanonicalReleaseOrderTests.swift +21 -0
  151. package/src/native/Recordings/Updater/Client/ClientMain.swift +119 -0
  152. package/src/native/Recordings/Updater/Protocol/BoundedProcess.swift +159 -0
  153. package/src/native/Recordings/Updater/Protocol/CandidateMetadataPolicy.swift +214 -0
  154. package/src/native/Recordings/Updater/Protocol/HostOSVersionPolicy.swift +55 -0
  155. package/src/native/Recordings/Updater/Protocol/MonotonicReleasePolicy.swift +152 -0
  156. package/src/native/Recordings/Updater/Protocol/ReleaseEnvelope.swift +229 -0
  157. package/src/native/Recordings/Updater/Protocol/UpdateProtocol.swift +119 -0
  158. package/src/native/Recordings/Updater/ProtocolTests/BoundedProcessRunnerTests.swift +60 -0
  159. package/src/native/Recordings/Updater/ProtocolTests/CandidateMetadataPolicyTests.swift +168 -0
  160. package/src/native/Recordings/Updater/ProtocolTests/HostOSVersionPolicyTests.swift +62 -0
  161. package/src/native/Recordings/Updater/ProtocolTests/MonotonicReleasePolicyTests.swift +172 -0
  162. package/src/native/Recordings/Updater/ProtocolTests/ReleaseEnvelopeValidationTests.swift +65 -0
  163. package/src/native/Recordings/Updater/Signer/SignerMain.swift +210 -0
  164. package/src/native/Recordings/Updater/VerifierLauncher/RecordingsVerifierLauncher.c +725 -0
  165. package/src/native/Recordings/Updater/VerifierLauncher/include/RecordingsVerifierLauncher.h +33 -0
  166. package/src/native/Recordings/build.sh +1973 -236
  167. package/src/native/Recordings/RecordingsLib/MenuBarPopover.swift +0 -380
@@ -1,272 +1,2009 @@
1
- #!/usr/bin/env bash
2
- # Build Recordings.app for macOS 26
3
- # Usage: ./build.sh [debug|release]
1
+ #!/bin/bash
2
+ # Build, sign, notarize, and finalize a Recordings.app artifact.
3
+ # Usage: ./build.sh [debug|local] | ./build.sh release <initial-bootstrap|app-update>
4
4
 
5
5
  set -euo pipefail
6
+ umask 077
7
+
8
+ HOST_UNAME_EXECUTABLE="/usr/bin/uname"
9
+ SYSTEM_DIRNAME_EXECUTABLE="/usr/bin/dirname"
10
+ SYSTEM_PWD_EXECUTABLE="/bin/pwd"
11
+ SANITIZED_PATH="/usr/bin:/bin:/usr/sbin:/sbin"
12
+
13
+ require_bootstrap_executable() {
14
+ local label="$1"
15
+ local executable="$2"
16
+ case "$executable" in
17
+ /*) ;;
18
+ *)
19
+ echo "$label must be configured as an absolute executable path." >&2
20
+ exit 1
21
+ ;;
22
+ esac
23
+ if [ ! -f "$executable" ] || [ ! -x "$executable" ]; then
24
+ echo "$label is missing or is not an executable file: $executable" >&2
25
+ exit 1
26
+ fi
27
+ }
28
+
29
+ require_bootstrap_executable "HOST_UNAME_EXECUTABLE" "$HOST_UNAME_EXECUTABLE"
30
+ require_bootstrap_executable "SYSTEM_DIRNAME_EXECUTABLE" "$SYSTEM_DIRNAME_EXECUTABLE"
31
+ require_bootstrap_executable "SYSTEM_PWD_EXECUTABLE" "$SYSTEM_PWD_EXECUTABLE"
32
+ HOST_PLATFORM="$("$HOST_UNAME_EXECUTABLE" -s)"
33
+ readonly HOST_PLATFORM
34
+
35
+ select_executable() {
36
+ local system_executable="$1"
37
+ local test_override="${2:-}"
38
+ if [ "$HOST_PLATFORM" = "Darwin" ] || [ -z "$test_override" ]; then
39
+ printf '%s\n' "$system_executable"
40
+ else
41
+ printf '%s\n' "$test_override"
42
+ fi
43
+ }
44
+
45
+ DIRNAME_EXECUTABLE="$(select_executable "$SYSTEM_DIRNAME_EXECUTABLE" "${RECORDINGS_TEST_DIRNAME_EXECUTABLE:-}")"
46
+ PWD_EXECUTABLE="$(select_executable "$SYSTEM_PWD_EXECUTABLE" "${RECORDINGS_TEST_PWD_EXECUTABLE:-}")"
47
+ require_bootstrap_executable "DIRNAME_EXECUTABLE" "$DIRNAME_EXECUTABLE"
48
+ require_bootstrap_executable "PWD_EXECUTABLE" "$PWD_EXECUTABLE"
6
49
 
7
50
  MODE="${1:-release}"
8
- SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
51
+ RELEASE_SUBTYPE="${2:-}"
52
+ SCRIPT_DIR="$(cd "$("$DIRNAME_EXECUTABLE" "$0")" && "$PWD_EXECUTABLE" -P)"
53
+ PACKAGE_ROOT="$(cd "$SCRIPT_DIR/../../.." && "$PWD_EXECUTABLE" -P)"
9
54
  cd "$SCRIPT_DIR"
10
55
 
11
- # A STABLE certificate identity keeps the app's TCC designated requirement
12
- # constant across rebuilds so macOS permission grants (Microphone /
13
- # Accessibility) persist. Ad-hoc ("-") pins the identity to this exact binary's
14
- # CDHash and forces re-authorization after every build, so we NEVER default to
15
- # it. When no explicit identity is provided we create (once) and reuse a
16
- # per-machine self-signed code-signing certificate held in a DEDICATED signing
17
- # keychain, so the default postinstall path is self-contained AND works over a
18
- # headless SSH session no out-of-band env and no unlocked login keychain
19
- # required.
20
- SIGNING_CN="Hasna Recordings Signing"
21
- SIGNING_DIR="${HOME}/.hasna/recordings/signing"
22
- # Dedicated code-signing keychain. We NEVER touch the login keychain: over SSH
23
- # the login keychain is locked and codesign would block on an interactive
24
- # unlock/allow prompt. A dedicated keychain with a KNOWN password (stored in the
25
- # vault) can be created, unlocked, and key-partition-listed non-interactively.
26
- SIGNING_KEYCHAIN="${SIGNING_DIR}/recordings-signing.keychain-db"
27
-
28
- # Resolve the macOS keychain/codesign tools by ABSOLUTE path when present, so a
29
- # same-named shim earlier on PATH (e.g. the Hasna `security` CLI installed in
30
- # ~/.bun/bin) cannot shadow the real /usr/bin/security. On hosts without those
31
- # absolute paths (e.g. the Linux unit-test sandbox) we fall back to PATH so the
32
- # tests can stub them.
33
- SECURITY="/usr/bin/security"; [ -x "$SECURITY" ] || SECURITY="security"
34
- CODESIGN="/usr/bin/codesign"; [ -x "$CODESIGN" ] || CODESIGN="codesign"
35
- # SHA-1 hash of the signing identity, resolved by ensure_local_signing_identity.
36
- SIGNING_IDENTITY=""
37
-
38
- # Vault keys are host-scoped so each machine owns its own signing material.
39
- signing_host() {
40
- hostname -s 2>/dev/null || hostname 2>/dev/null || echo unknown
41
- }
42
- vault_prefix() {
43
- printf 'hasna/machine/%s/recordings/signing' "$(signing_host)"
44
- }
45
-
46
- # Append the dedicated keychain to the USER search list (never replacing it) so
47
- # codesign / find-identity can see the identity while leaving login and other
48
- # keychains intact.
49
- add_signing_keychain_to_search_list() {
50
- local target="$SIGNING_KEYCHAIN"
51
- local -a current=()
52
- local line trimmed
53
- while IFS= read -r line; do
54
- # `security list-keychains` prints each path indented and quoted.
55
- trimmed="${line#"${line%%[![:space:]]*}"}" # strip leading whitespace
56
- trimmed="${trimmed%\"}"; trimmed="${trimmed#\"}"
57
- [ -n "$trimmed" ] || continue
58
- [ "$trimmed" = "$target" ] && return 0 # already in the list
59
- current+=("$trimmed")
60
- done < <("$SECURITY" list-keychains -d user 2>/dev/null)
61
- "$SECURITY" list-keychains -d user -s "$target" ${current[@]+"${current[@]}"} >/dev/null 2>&1 \
62
- || echo "WARN: could not add $target to the keychain search list" >&2
63
- }
64
-
65
- # Create/reuse the "Hasna Recordings Signing" identity inside the DEDICATED
66
- # signing keychain, unlocking it non-interactively with the known vault-stored
67
- # password so codesign can sign over SSH without any prompt. Reusing the same
68
- # certificate is what makes the designated requirement (certificate-based)
69
- # stable across rebuilds so TCC grants survive. Returns 0 when usable.
70
- ensure_local_signing_identity() {
71
- command -v "$SECURITY" >/dev/null 2>&1 || { echo "ERROR: 'security' not found" >&2; return 1; }
72
- command -v openssl >/dev/null 2>&1 || { echo "ERROR: 'openssl' not found" >&2; return 1; }
73
- # The dedicated keychain's password lives in the vault; without the vault CLI
74
- # we cannot manage it non-interactively, so fail closed rather than fall back
75
- # to the (headless-hostile) login keychain.
76
- command -v secrets >/dev/null 2>&1 || { echo "ERROR: 'secrets' vault CLI not found; cannot manage the dedicated signing keychain password" >&2; return 1; }
77
-
78
- local prefix pw_key cert_key key_key
79
- prefix="$(vault_prefix)"
80
- pw_key="${prefix}/keychain_password"
81
- cert_key="${prefix}/certificate_pem_b64"
82
- key_key="${prefix}/private_key_pem_b64"
83
-
84
- mkdir -p "$SIGNING_DIR" && chmod 700 "$SIGNING_DIR" 2>/dev/null || true
85
-
86
- # 1. Known keychain password: generate once, persist in the vault, reuse.
87
- local kc_pw
88
- kc_pw="$(secrets get "$pw_key" 2>/dev/null || true)"
89
- if [ -z "$kc_pw" ]; then
90
- kc_pw="$(openssl rand -base64 24 | tr -d '\n')"
91
- if ! secrets set "$pw_key" "$kc_pw" --type password \
92
- --label "Recordings signing keychain password ($(signing_host))" >/dev/null 2>&1; then
93
- echo "ERROR: failed to store signing keychain password in the vault ($pw_key)" >&2
94
- return 1
56
+ case "$MODE" in
57
+ debug|local|release) ;;
58
+ *)
59
+ echo "Mode must be debug, local, or release" >&2
60
+ exit 2
61
+ ;;
62
+ esac
63
+ if [ "$MODE" = "release" ]; then
64
+ if [ -z "$RELEASE_SUBTYPE" ]; then
65
+ echo "Release builds require an explicit subtype: initial-bootstrap or app-update." >&2
66
+ exit 2
67
+ fi
68
+ case "$RELEASE_SUBTYPE" in
69
+ initial-bootstrap|app-update) ;;
70
+ *)
71
+ echo "Release subtype must be initial-bootstrap or app-update." >&2
72
+ exit 2
73
+ ;;
74
+ esac
75
+ if [ "$#" -ne 2 ]; then
76
+ echo "Release builds accept exactly one subtype argument." >&2
77
+ exit 2
78
+ fi
79
+ elif [ "$#" -ne 1 ]; then
80
+ echo "Debug and local builds do not accept a release subtype." >&2
81
+ exit 2
82
+ fi
83
+ readonly RELEASE_SUBTYPE
84
+ RELEASE_ARCHITECTURES=(arm64 x86_64)
85
+
86
+ CODESIGN_IDENTITY="${RECORDINGS_CODESIGN_IDENTITY:-}"
87
+ EXPECTED_TEAM_ID="${RECORDINGS_EXPECTED_TEAM_IDENTIFIER:-}"
88
+ NOTARY_PROFILE="${RECORDINGS_NOTARY_KEYCHAIN_PROFILE:-}"
89
+ INSTALLER_IDENTITY="${RECORDINGS_INSTALLER_CODESIGN_IDENTITY:-}"
90
+ RELEASE_SEQUENCE="${RECORDINGS_RELEASE_SEQUENCE:-}"
91
+ KEY_EPOCH="${RECORDINGS_RELEASE_KEY_EPOCH:-}"
92
+ ENVELOPE_EXPIRES_AT_UTC="${RECORDINGS_RELEASE_ENVELOPE_EXPIRES_AT_UTC:-}"
93
+ ENVELOPE_PRIVATE_KEY="${RECORDINGS_RELEASE_ENVELOPE_PRIVATE_KEY:-}"
94
+ ENVELOPE_PUBLIC_KEY="${RECORDINGS_RELEASE_ENVELOPE_PUBLIC_KEY:-}"
95
+ COMPATIBLE_COHORT_MANIFEST="${RECORDINGS_RELEASE_COMPATIBLE_COHORT_MANIFEST:-}"
96
+ LOCAL_APPROVED_TARGET="${RECORDINGS_LOCAL_APPROVED_TARGET:-}"
97
+ LOCAL_APPROVED_TARGET_IDENTITY_KIND="${RECORDINGS_LOCAL_APPROVED_TARGET_IDENTITY_KIND:-}"
98
+ LOCAL_APPROVED_TARGET_IDENTITY_SHA256="${RECORDINGS_LOCAL_APPROVED_TARGET_IDENTITY_SHA256:-}"
99
+ PLIST_BUDDY="$(select_executable "/usr/libexec/PlistBuddy" "${RECORDINGS_TEST_PLIST_BUDDY_EXECUTABLE:-${PLIST_BUDDY:-}}")"
100
+ PLUTIL="$(select_executable "/usr/bin/plutil" "${RECORDINGS_TEST_PLUTIL_EXECUTABLE:-${PLUTIL:-}}")"
101
+ BUN_EXECUTABLE="${BUN_EXECUTABLE:-}"
102
+ TAILSCALE_RESOLVER="$PACKAGE_ROOT/scripts/resolve_tailscale_cli.sh"
103
+ BASH_EXECUTABLE="$(select_executable "/bin/bash" "${RECORDINGS_TEST_BASH_EXECUTABLE:-}")"
104
+ GIT_EXECUTABLE="$(select_executable "/usr/bin/git" "${RECORDINGS_TEST_GIT_EXECUTABLE:-}")"
105
+ TAR_EXECUTABLE="$(select_executable "/usr/bin/tar" "${RECORDINGS_TEST_TAR_EXECUTABLE:-}")"
106
+ SWIFT_EXECUTABLE="$(select_executable "/usr/bin/swift" "${RECORDINGS_TEST_SWIFT_EXECUTABLE:-}")"
107
+ CODESIGN_EXECUTABLE="$(select_executable "/usr/bin/codesign" "${RECORDINGS_TEST_CODESIGN_EXECUTABLE:-}")"
108
+ XCRUN_EXECUTABLE="$(select_executable "/usr/bin/xcrun" "${RECORDINGS_TEST_XCRUN_EXECUTABLE:-}")"
109
+ SPCTL_EXECUTABLE="$(select_executable "/usr/sbin/spctl" "${RECORDINGS_TEST_SPCTL_EXECUTABLE:-}")"
110
+ SYSPOLICY_CHECK_EXECUTABLE="$(select_executable "/usr/bin/syspolicy_check" "${RECORDINGS_TEST_SYSPOLICY_CHECK_EXECUTABLE:-}")"
111
+ DITTO_EXECUTABLE="$(select_executable "/usr/bin/ditto" "${RECORDINGS_TEST_DITTO_EXECUTABLE:-}")"
112
+ SHASUM_EXECUTABLE="$(select_executable "/usr/bin/shasum" "${RECORDINGS_TEST_SHASUM_EXECUTABLE:-}")"
113
+ AWK_EXECUTABLE="$(select_executable "/usr/bin/awk" "${RECORDINGS_TEST_AWK_EXECUTABLE:-}")"
114
+ BASENAME_EXECUTABLE="$(select_executable "/usr/bin/basename" "${RECORDINGS_TEST_BASENAME_EXECUTABLE:-}")"
115
+ CAT_EXECUTABLE="$(select_executable "/bin/cat" "${RECORDINGS_TEST_CAT_EXECUTABLE:-}")"
116
+ CHMOD_EXECUTABLE="$(select_executable "/bin/chmod" "${RECORDINGS_TEST_CHMOD_EXECUTABLE:-}")"
117
+ CP_EXECUTABLE="$(select_executable "/bin/cp" "${RECORDINGS_TEST_CP_EXECUTABLE:-}")"
118
+ ENV_EXECUTABLE="$(select_executable "/usr/bin/env" "${RECORDINGS_TEST_ENV_EXECUTABLE:-}")"
119
+ HEAD_EXECUTABLE="$(select_executable "/usr/bin/head" "${RECORDINGS_TEST_HEAD_EXECUTABLE:-}")"
120
+ HOSTNAME_EXECUTABLE="$(select_executable "/bin/hostname" "${RECORDINGS_TEST_HOSTNAME_EXECUTABLE:-}")"
121
+ MKDIR_EXECUTABLE="$(select_executable "/bin/mkdir" "${RECORDINGS_TEST_MKDIR_EXECUTABLE:-}")"
122
+ MKTEMP_EXECUTABLE="$(select_executable "/usr/bin/mktemp" "${RECORDINGS_TEST_MKTEMP_EXECUTABLE:-}")"
123
+ MV_EXECUTABLE="$(select_executable "/bin/mv" "${RECORDINGS_TEST_MV_EXECUTABLE:-}")"
124
+ LN_EXECUTABLE="$(select_executable "/bin/ln" "${RECORDINGS_TEST_LN_EXECUTABLE:-}")"
125
+ RMDIR_EXECUTABLE="$(select_executable "/bin/rmdir" "${RECORDINGS_TEST_RMDIR_EXECUTABLE:-}")"
126
+ RM_EXECUTABLE="$(select_executable "/bin/rm" "${RECORDINGS_TEST_RM_EXECUTABLE:-}")"
127
+ SED_EXECUTABLE="$(select_executable "/usr/bin/sed" "${RECORDINGS_TEST_SED_EXECUTABLE:-}")"
128
+ TR_EXECUTABLE="$(select_executable "/usr/bin/tr" "${RECORDINGS_TEST_TR_EXECUTABLE:-}")"
129
+ GREP_EXECUTABLE="$(select_executable "/usr/bin/grep" "${RECORDINGS_TEST_GREP_EXECUTABLE:-}")"
130
+ FIND_EXECUTABLE="$(select_executable "/usr/bin/find" "${RECORDINGS_TEST_FIND_EXECUTABLE:-}")"
131
+ LS_EXECUTABLE="$(select_executable "/bin/ls" "${RECORDINGS_TEST_LS_EXECUTABLE:-}")"
132
+ ID_EXECUTABLE="$(select_executable "/usr/bin/id" "${RECORDINGS_TEST_ID_EXECUTABLE:-}")"
133
+ LIPO_EXECUTABLE="$(select_executable "/usr/bin/lipo" "${RECORDINGS_TEST_LIPO_EXECUTABLE:-}")"
134
+ STAT_EXECUTABLE="$(select_executable "/usr/bin/stat" "${RECORDINGS_TEST_STAT_EXECUTABLE:-}")"
135
+ TEST_GIT_EXECUTABLE="$(select_executable "" "${RECORDINGS_TEST_GIT_EXECUTABLE:-}")"
136
+ TEST_SWIFT_EXECUTABLE="$(select_executable "" "${RECORDINGS_TEST_SWIFT_EXECUTABLE:-}")"
137
+
138
+ require_executable() {
139
+ local label="$1"
140
+ local executable="$2"
141
+ case "$executable" in
142
+ /*) ;;
143
+ *)
144
+ echo "$label must be configured as an absolute executable path." >&2
145
+ exit 1
146
+ ;;
147
+ esac
148
+ if [ ! -f "$executable" ] || [ ! -x "$executable" ]; then
149
+ echo "$label is missing or is not an executable file: $executable" >&2
150
+ exit 1
151
+ fi
152
+ }
153
+
154
+ require_bun_executable() {
155
+ local executable="$1"
156
+ if [ -z "$executable" ]; then
157
+ echo "Release builds require BUN_EXECUTABLE as an explicit absolute Bun executable path." >&2
158
+ exit 1
159
+ fi
160
+ require_executable "BUN_EXECUTABLE" "$executable"
161
+ if ! "$ENV_EXECUTABLE" -i \
162
+ HOME="/tmp" \
163
+ PATH="$SANITIZED_PATH" \
164
+ "$executable" -e '
165
+ import { realpathSync, statSync } from "node:fs";
166
+ const expected = process.argv[1];
167
+ const actual = process.execPath;
168
+ if (!expected || realpathSync(actual) !== realpathSync(expected)) process.exit(66);
169
+ if (!statSync(actual).isFile()) process.exit(66);
170
+ if (!/^\d+\.\d+\.\d+(?:[-+].*)?$/.test(Bun.version)) process.exit(66);
171
+ ' "$executable"; then
172
+ echo "BUN_EXECUTABLE did not identify the Bun executable that actually ran." >&2
173
+ exit 1
174
+ fi
175
+ }
176
+
177
+ run_source_git() {
178
+ "$ENV_EXECUTABLE" -i \
179
+ HOME="/tmp" \
180
+ PATH="$SANITIZED_PATH" \
181
+ TMPDIR="/tmp" \
182
+ GIT_CONFIG_NOSYSTEM="1" \
183
+ GIT_CONFIG_GLOBAL="/dev/null" \
184
+ GIT_NO_REPLACE_OBJECTS="1" \
185
+ GIT_OPTIONAL_LOCKS="0" \
186
+ "$GIT_EXECUTABLE" \
187
+ -c core.fsmonitor=false \
188
+ -c core.hooksPath=/dev/null \
189
+ -C "$PACKAGE_ROOT" \
190
+ "$@"
191
+ }
192
+
193
+ read_source_sha() {
194
+ local source_sha
195
+ if ! source_sha="$(run_source_git rev-parse --verify 'HEAD^{commit}')"; then
196
+ echo "Could not resolve the source git revision." >&2
197
+ return 1
198
+ fi
199
+ if ! [[ "$source_sha" =~ ^[0-9a-f]{40}$ ]]; then
200
+ echo "Resolved git revision is not a full 40-character commit SHA." >&2
201
+ return 1
202
+ fi
203
+ printf '%s\n' "$source_sha"
204
+ }
205
+
206
+ require_clean_source() {
207
+ local status
208
+ status="$(run_source_git status --porcelain=v1 --untracked-files=all)"
209
+ if [ -n "$status" ]; then
210
+ echo "Source worktree must be clean before building." >&2
211
+ return 1
212
+ fi
213
+ }
214
+
215
+ verify_source_unchanged() {
216
+ local current_sha
217
+ current_sha="$(read_source_sha)"
218
+ if [ "$current_sha" != "$SOURCE_SHA" ]; then
219
+ echo "Source revision changed during the build; refusing to emit an artifact." >&2
220
+ return 1
221
+ fi
222
+ require_clean_source
223
+ }
224
+
225
+ require_executable "GIT_EXECUTABLE" "$GIT_EXECUTABLE"
226
+ require_executable "TAR_EXECUTABLE" "$TAR_EXECUTABLE"
227
+ require_executable "SWIFT_EXECUTABLE" "$SWIFT_EXECUTABLE"
228
+ require_executable "CODESIGN_EXECUTABLE" "$CODESIGN_EXECUTABLE"
229
+ require_executable "DITTO_EXECUTABLE" "$DITTO_EXECUTABLE"
230
+ require_executable "BASH_EXECUTABLE" "$BASH_EXECUTABLE"
231
+ require_executable "PLIST_BUDDY" "$PLIST_BUDDY"
232
+ require_executable "PLUTIL" "$PLUTIL"
233
+ require_executable "AWK_EXECUTABLE" "$AWK_EXECUTABLE"
234
+ require_executable "BASENAME_EXECUTABLE" "$BASENAME_EXECUTABLE"
235
+ require_executable "CAT_EXECUTABLE" "$CAT_EXECUTABLE"
236
+ require_executable "CHMOD_EXECUTABLE" "$CHMOD_EXECUTABLE"
237
+ require_executable "CP_EXECUTABLE" "$CP_EXECUTABLE"
238
+ require_executable "ENV_EXECUTABLE" "$ENV_EXECUTABLE"
239
+ require_executable "HEAD_EXECUTABLE" "$HEAD_EXECUTABLE"
240
+ require_executable "HOSTNAME_EXECUTABLE" "$HOSTNAME_EXECUTABLE"
241
+ require_executable "MKDIR_EXECUTABLE" "$MKDIR_EXECUTABLE"
242
+ require_executable "MKTEMP_EXECUTABLE" "$MKTEMP_EXECUTABLE"
243
+ require_executable "MV_EXECUTABLE" "$MV_EXECUTABLE"
244
+ require_executable "LN_EXECUTABLE" "$LN_EXECUTABLE"
245
+ require_executable "RMDIR_EXECUTABLE" "$RMDIR_EXECUTABLE"
246
+ require_executable "RM_EXECUTABLE" "$RM_EXECUTABLE"
247
+ require_executable "SED_EXECUTABLE" "$SED_EXECUTABLE"
248
+ require_executable "TR_EXECUTABLE" "$TR_EXECUTABLE"
249
+ require_executable "GREP_EXECUTABLE" "$GREP_EXECUTABLE"
250
+ require_executable "FIND_EXECUTABLE" "$FIND_EXECUTABLE"
251
+ require_executable "LS_EXECUTABLE" "$LS_EXECUTABLE"
252
+ require_executable "ID_EXECUTABLE" "$ID_EXECUTABLE"
253
+ require_executable "STAT_EXECUTABLE" "$STAT_EXECUTABLE"
254
+ if [ "$HOST_PLATFORM" = "Darwin" ]; then
255
+ require_executable "LIPO_EXECUTABLE" "$LIPO_EXECUTABLE"
256
+ fi
257
+ require_bun_executable "$BUN_EXECUTABLE"
258
+ if [ "$MODE" = "release" ]; then
259
+ require_executable "XCRUN_EXECUTABLE" "$XCRUN_EXECUTABLE"
260
+ require_executable "SPCTL_EXECUTABLE" "$SPCTL_EXECUTABLE"
261
+ require_executable "SYSPOLICY_CHECK_EXECUTABLE" "$SYSPOLICY_CHECK_EXECUTABLE"
262
+ require_executable "SHASUM_EXECUTABLE" "$SHASUM_EXECUTABLE"
263
+ fi
264
+
265
+ SOURCE_SHA="$(read_source_sha)"
266
+ readonly SOURCE_SHA
267
+ require_clean_source
268
+
269
+ OPERATOR_HOME="${HOME:-}"
270
+ case "$OPERATOR_HOME" in
271
+ /*) ;;
272
+ *)
273
+ echo "Build requires an absolute HOME path." >&2
274
+ exit 1
275
+ ;;
276
+ esac
277
+ if [ ! -d "$OPERATOR_HOME" ]; then
278
+ echo "Build HOME is not a directory: $OPERATOR_HOME" >&2
279
+ exit 1
280
+ fi
281
+
282
+ BUILD_ROOT="/tmp"
283
+ if [ "$MODE" = "release" ] && [ "$HOST_PLATFORM" = "Darwin" ]; then
284
+ BUILD_ROOT="/private/var/recordings-build"
285
+ BUILD_ATTESTATION="/Library/Application Support/Hasna/Recordings/BuildTrust/isolated-builder-v1"
286
+ if [ "$($ID_EXECUTABLE -un)" != "_recordingsbuild" ]; then
287
+ echo "Release builds may only run as the isolated _recordingsbuild identity." >&2
288
+ exit 1
289
+ fi
290
+ if [ ! -f "$BUILD_ATTESTATION" ] || [ -L "$BUILD_ATTESTATION" ] || \
291
+ [ "$($STAT_EXECUTABLE -f '%u' "$BUILD_ATTESTATION")" != "0" ]; then
292
+ echo "Managed isolated-builder attestation is missing or unsafe." >&2
293
+ exit 1
294
+ fi
295
+ case "$($STAT_EXECUTABLE -f '%Lp' "$BUILD_ATTESTATION")" in
296
+ 400|440|444) ;;
297
+ *) echo "Managed isolated-builder attestation mode is unsafe." >&2; exit 1 ;;
298
+ esac
299
+ if [ "$("$CAT_EXECUTABLE" "$BUILD_ATTESTATION")" != "recordings-isolated-builder-v1" ]; then
300
+ echo "Managed isolated-builder attestation content is invalid." >&2
301
+ exit 1
302
+ fi
303
+ if [ ! -d "$BUILD_ROOT" ] || [ -L "$BUILD_ROOT" ] || \
304
+ [ "$($STAT_EXECUTABLE -f '%u' "$BUILD_ROOT")" != "$($ID_EXECUTABLE -u)" ] || \
305
+ [ "$($STAT_EXECUTABLE -f '%Lp' "$BUILD_ROOT")" != "700" ]; then
306
+ echo "Managed release build root must be owned by _recordingsbuild with mode 0700." >&2
307
+ exit 1
308
+ fi
309
+ BUILD_ROOT_PARENT="$($DIRNAME_EXECUTABLE "$BUILD_ROOT")"
310
+ if [ "$($STAT_EXECUTABLE -f '%u' "$BUILD_ROOT_PARENT")" != "0" ]; then
311
+ echo "Managed release build parent must be root-owned." >&2
312
+ exit 1
313
+ fi
314
+ case "$($STAT_EXECUTABLE -f '%Lp' "$BUILD_ROOT_PARENT")" in
315
+ *[2367][0-9]|*[0-9][2367]) echo "Managed release build parent is group/other writable." >&2; exit 1 ;;
316
+ esac
317
+ fi
318
+ RELEASE_OUTPUT_ROOT=""
319
+ RELEASE_STAGING_DIR=""
320
+ RELEASE_FINAL_DIR=""
321
+ RELEASE_RESERVATION=""
322
+ RELEASE_RESERVATION_OWNED=0
323
+ RELEASE_DIRECTORY_PUBLISHED=0
324
+ RELEASE_PUBLICATION_IDENTITY_SHA256=""
325
+ PACKAGE_PUBLICATION_IDENTITY_SHA256=""
326
+ ENVELOPE_PUBLIC_KEY_SNAPSHOT=""
327
+ ENVELOPE_PUBLIC_KEY_SHA256=""
328
+ COMPATIBLE_COHORT_SHA256=""
329
+ BUILD_WORK_DIR="$($MKTEMP_EXECUTABLE -d "$BUILD_ROOT/recordings-native-build.XXXXXX")"
330
+ BUILD_HOME="$BUILD_WORK_DIR/home"
331
+ SWIFT_SCRATCH_PATH="$BUILD_WORK_DIR/swift"
332
+ SOURCE_PACKAGE_ROOT="$BUILD_WORK_DIR/source"
333
+ "$MKDIR_EXECUTABLE" -p "$BUILD_HOME" "$SWIFT_SCRATCH_PATH" "$SOURCE_PACKAGE_ROOT"
334
+ cleanup_build_work() {
335
+ local status=$?
336
+ trap - EXIT
337
+ "$RM_EXECUTABLE" -rf "$BUILD_WORK_DIR"
338
+ if [ "$RELEASE_DIRECTORY_PUBLISHED" -eq 0 ] && [ -n "$RELEASE_STAGING_DIR" ]; then
339
+ "$RM_EXECUTABLE" -rf "$RELEASE_STAGING_DIR"
340
+ fi
341
+ if [ "$RELEASE_RESERVATION_OWNED" -eq 1 ] && \
342
+ [ ! -e "$RELEASE_FINAL_DIR" ] && [ ! -L "$RELEASE_FINAL_DIR" ]; then
343
+ "$RM_EXECUTABLE" -rf "$RELEASE_RESERVATION"
344
+ fi
345
+ exit "$status"
346
+ }
347
+ trap cleanup_build_work EXIT
348
+
349
+ if [ "$HOST_PLATFORM" != "Darwin" ] && [ -n "$TEST_GIT_EXECUTABLE" ]; then
350
+ # Explicit non-Darwin fixtures do not contain an object database. Production Darwin
351
+ # builds never take this branch and always compile the exact archived commit below.
352
+ SOURCE_PACKAGE_ROOT="$PACKAGE_ROOT"
353
+ else
354
+ if ! run_source_git archive --format=tar "$SOURCE_SHA" | \
355
+ "$TAR_EXECUTABLE" -x -f - -C "$SOURCE_PACKAGE_ROOT"; then
356
+ echo "Could not materialize the exact source commit for an isolated build." >&2
357
+ exit 1
358
+ fi
359
+ fi
360
+ SOURCE_NATIVE_DIR="$SOURCE_PACKAGE_ROOT/src/native/Recordings"
361
+ RELEASE_LIFECYCLE_HELPER="$SOURCE_PACKAGE_ROOT/packaging/macos/release_lifecycle.ts"
362
+ TAILSCALE_RESOLVER="$SOURCE_PACKAGE_ROOT/scripts/resolve_tailscale_cli.sh"
363
+ APP_ENTITLEMENTS="$SOURCE_NATIVE_DIR/RecordingsLib/Recordings.entitlements"
364
+ HELPER_ENTITLEMENTS="$SOURCE_NATIVE_DIR/RecordingsLib/RecordingsCLI.entitlements"
365
+ if [ "$HOST_PLATFORM" != "Darwin" ] && [ "$SOURCE_PACKAGE_ROOT" = "$PACKAGE_ROOT" ]; then
366
+ # Preserve the historical fixture log contract. Production Darwin builds always
367
+ # sign against the absolute entitlements paths in the archived source snapshot.
368
+ APP_ENTITLEMENTS="RecordingsLib/Recordings.entitlements"
369
+ HELPER_ENTITLEMENTS="RecordingsLib/RecordingsCLI.entitlements"
370
+ fi
371
+ if [ "$SOURCE_PACKAGE_ROOT" != "$PACKAGE_ROOT" ]; then
372
+ for required_snapshot_input in \
373
+ "$SOURCE_NATIVE_DIR/Package.swift" \
374
+ "$SOURCE_NATIVE_DIR/RecordingsLib/Info.plist" \
375
+ "$SOURCE_NATIVE_DIR/RecordingsLib/Recordings.entitlements" \
376
+ "$SOURCE_NATIVE_DIR/RecordingsLib/RecordingsCLI.entitlements" \
377
+ "$SOURCE_PACKAGE_ROOT/scripts/build_companion_cli.sh" \
378
+ "$SOURCE_PACKAGE_ROOT/scripts/smoke_macos_app.sh" \
379
+ "$SOURCE_PACKAGE_ROOT/scripts/macos_artifact.ts" \
380
+ "$SOURCE_PACKAGE_ROOT/scripts/native_fs_guard.ts" \
381
+ "$SOURCE_PACKAGE_ROOT/scripts/build_native_fs_guard.sh" \
382
+ "$SOURCE_PACKAGE_ROOT/scripts/native/recordings_fs_guard.c" \
383
+ "$SOURCE_PACKAGE_ROOT/packaging/macos/build_release_pkg.sh" \
384
+ "$SOURCE_PACKAGE_ROOT/packaging/macos/release_lifecycle.ts" \
385
+ "$SOURCE_PACKAGE_ROOT/packaging/macos/Verifier.entitlements" \
386
+ "$SOURCE_PACKAGE_ROOT/packaging/macos/Empty.entitlements" \
387
+ "$SOURCE_PACKAGE_ROOT/packaging/macos/artifact-verifier.sb" \
388
+ "$SOURCE_PACKAGE_ROOT/packaging/macos/Library/LaunchDaemons/com.hasna.recordings.updater.plist" \
389
+ "$SOURCE_PACKAGE_ROOT/packaging/macos/scripts/preinstall" \
390
+ "$SOURCE_PACKAGE_ROOT/packaging/macos/scripts/postinstall" \
391
+ "$SOURCE_PACKAGE_ROOT/package.json" \
392
+ "$SOURCE_PACKAGE_ROOT/bun.lock" \
393
+ "$SOURCE_PACKAGE_ROOT/bunfig.toml"; do
394
+ if [ ! -f "$required_snapshot_input" ]; then
395
+ echo "Archived source input is missing: $required_snapshot_input" >&2
396
+ exit 1
95
397
  fi
96
- echo "Generated dedicated signing keychain password (stored in vault: $pw_key)."
398
+ done
399
+ fi
400
+
401
+ generate_and_verify_native_fs_guard() {
402
+ [ "$HOST_PLATFORM" = "Darwin" ] || return 0
403
+ local dependency_root="$BUILD_WORK_DIR/native-guard-dependencies"
404
+ local pack_root="$BUILD_WORK_DIR/native-guard-pack"
405
+ local addon="$SOURCE_PACKAGE_ROOT/scripts/native/prebuilds/darwin-universal/recordings_fs_guard.node"
406
+ "$MKDIR_EXECUTABLE" -p "$dependency_root" "$pack_root"
407
+ "$CP_EXECUTABLE" "$SOURCE_PACKAGE_ROOT/package.json" "$SOURCE_PACKAGE_ROOT/bun.lock" \
408
+ "$SOURCE_PACKAGE_ROOT/bunfig.toml" "$dependency_root/"
409
+ run_bun install \
410
+ --cwd "$dependency_root" \
411
+ --frozen-lockfile \
412
+ --ignore-scripts \
413
+ --minimum-release-age=604800 \
414
+ --no-progress
415
+ "$ENV_EXECUTABLE" -i \
416
+ HOME="$BUILD_HOME" \
417
+ PATH="$SANITIZED_PATH" \
418
+ TMPDIR="$BUILD_WORK_DIR" \
419
+ "$BASH_EXECUTABLE" "$SOURCE_PACKAGE_ROOT/scripts/build_native_fs_guard.sh" \
420
+ "$addon" "$dependency_root/node_modules/node-api-headers/include"
421
+ /usr/bin/lipo -verify_arch arm64 x86_64 "$addon"
422
+ if [ "$MODE" = "release" ]; then
423
+ run_codesign --force --sign "$CODESIGN_IDENTITY" --options runtime --timestamp "$addon"
424
+ else
425
+ run_codesign --force --sign "$CODESIGN_IDENTITY" --options runtime "$addon"
426
+ fi
427
+ run_codesign --verify --strict --all-architectures --verbose=2 "$addon"
428
+ run_bun -e '
429
+ import { createRequire } from "node:module";
430
+ const addon = createRequire(import.meta.url)(process.argv[1]);
431
+ const expected = [
432
+ "chmodHandle", "close", "copyRegularNoReplaceAt", "fsyncHandle",
433
+ "linkNoReplaceAt", "mkdirAt", "openDirAt", "openRegularAt", "openTrustedHome",
434
+ "readDir", "readRegularAt", "removeTreeAt", "removeTreeHandleAt",
435
+ "renameHandleNoReplaceAt", "renameNoReplaceAt", "renameReplaceAt",
436
+ "sameBinding", "sha256Handle", "sha256RegularAt", "statAt", "statHandle",
437
+ "unlinkDirAt", "unlinkFileAt", "unlinkFileHandleAt", "writeFileAt",
438
+ ];
439
+ const actual = Object.getOwnPropertyNames(addon).sort();
440
+ if (JSON.stringify(actual) !== JSON.stringify(expected)) {
441
+ console.error(`Native filesystem guard exports are incompatible: ${actual.join(", ")}`);
442
+ process.exit(66);
443
+ }
444
+ ' "$addon"
445
+ (
446
+ cd "$SOURCE_PACKAGE_ROOT"
447
+ run_bun pm pack --destination "$pack_root" --ignore-scripts >/dev/null
448
+ )
449
+ local package_archives=("$pack_root"/*.tgz)
450
+ if [ "${#package_archives[@]}" -ne 1 ] || [ ! -f "${package_archives[0]}" ]; then
451
+ echo "Native filesystem guard package staging did not emit exactly one tarball." >&2
452
+ return 1
453
+ fi
454
+ local package_listing
455
+ package_listing="$("$TAR_EXECUTABLE" -tzf "${package_archives[0]}")"
456
+ if ! "$GREP_EXECUTABLE" -Fxq \
457
+ "package/scripts/native/prebuilds/darwin-universal/recordings_fs_guard.node" \
458
+ <<<"$package_listing"; then
459
+ echo "Packed release tarball is missing the generated native filesystem guard." >&2
460
+ return 1
461
+ fi
462
+ if "$GREP_EXECUTABLE" -Eq \
463
+ '(^|/)(\.recordings-fs-guard-build|arm64\.node|x86_64\.node|.*\.(o|obj))($|/)' \
464
+ <<<"$package_listing"; then
465
+ echo "Packed release tarball contains native filesystem guard intermediates." >&2
466
+ return 1
467
+ fi
468
+ }
469
+
470
+ RUN_BUN_TEST_ENVIRONMENT=()
471
+ COMPANION_TEST_ENVIRONMENT=()
472
+ SMOKE_TEST_ENVIRONMENT=()
473
+ XCRUN_TEST_ENVIRONMENT=()
474
+ RELEASE_SENSITIVE_TEST_ENVIRONMENT=()
475
+ if [ "$HOST_PLATFORM" != "Darwin" ]; then
476
+ if [ -n "${MARKER_DIRECTORY:-}" ]; then
477
+ RUN_BUN_TEST_ENVIRONMENT+=("MARKER_DIRECTORY=$MARKER_DIRECTORY")
478
+ SMOKE_TEST_ENVIRONMENT+=("MARKER_DIRECTORY=$MARKER_DIRECTORY")
479
+ fi
480
+ COMPANION_TEST_ENVIRONMENT+=(
481
+ "BREAK_SIGNED_HELPER=${BREAK_SIGNED_HELPER:-0}"
482
+ "MALFORMED_SIGNED_HELPER_OUTPUT=${MALFORMED_SIGNED_HELPER_OUTPUT:-0}"
483
+ )
484
+ XCRUN_TEST_ENVIRONMENT+=(
485
+ "MARKER_DIRECTORY=${MARKER_DIRECTORY:-}"
486
+ "NOTARY_SUBMIT_REJECTED=${NOTARY_SUBMIT_REJECTED:-0}"
487
+ "NOTARY_LOG_ISSUES=${NOTARY_LOG_ISSUES:-0}"
488
+ )
489
+ RELEASE_SENSITIVE_TEST_ENVIRONMENT+=(
490
+ "MARKER_DIRECTORY=${MARKER_DIRECTORY:-}"
491
+ "EXPECTED_HELPER_ENTITLEMENTS=${EXPECTED_HELPER_ENTITLEMENTS:-}"
492
+ "EXTRA_HELPER_ENTITLEMENT=${EXTRA_HELPER_ENTITLEMENT:-0}"
493
+ "SIGNING_AUTHORITY=${SIGNING_AUTHORITY:-}"
494
+ "SIGNING_TEAM=${SIGNING_TEAM:-}"
495
+ "SIGNING_FLAGS=${SIGNING_FLAGS:-}"
496
+ "MISSING_TIMESTAMP=${MISSING_TIMESTAMP:-0}"
497
+ )
498
+ fi
499
+
500
+ run_bun() {
501
+ "$ENV_EXECUTABLE" -i \
502
+ HOME="$BUILD_HOME" \
503
+ PATH="$SANITIZED_PATH" \
504
+ TMPDIR="$BUILD_WORK_DIR" \
505
+ ${RUN_BUN_TEST_ENVIRONMENT[0]+"${RUN_BUN_TEST_ENVIRONMENT[@]}"} \
506
+ "$BUN_EXECUTABLE" "$@"
507
+ }
508
+
509
+ run_xcrun() {
510
+ "$ENV_EXECUTABLE" -i \
511
+ HOME="$OPERATOR_HOME" \
512
+ PATH="$SANITIZED_PATH" \
513
+ TMPDIR="$BUILD_WORK_DIR" \
514
+ ${XCRUN_TEST_ENVIRONMENT[0]+"${XCRUN_TEST_ENVIRONMENT[@]}"} \
515
+ "$XCRUN_EXECUTABLE" "$@"
516
+ }
517
+
518
+ run_release_sensitive_tool() {
519
+ local executable="$1"
520
+ shift
521
+ "$ENV_EXECUTABLE" -i \
522
+ HOME="$OPERATOR_HOME" \
523
+ PATH="$SANITIZED_PATH" \
524
+ TMPDIR="$BUILD_WORK_DIR" \
525
+ ${RELEASE_SENSITIVE_TEST_ENVIRONMENT[0]+"${RELEASE_SENSITIVE_TEST_ENVIRONMENT[@]}"} \
526
+ "$executable" "$@"
527
+ }
528
+
529
+ run_sensitive_tool() {
530
+ if [ "$MODE" = "release" ]; then
531
+ run_release_sensitive_tool "$@"
532
+ else
533
+ "$@"
97
534
  fi
535
+ }
98
536
 
99
- # 2. Create the dedicated keychain if missing; ALWAYS unlock it with the
100
- # known password. Disable auto-lock so the key stays reachable headless.
101
- if [ ! -f "$SIGNING_KEYCHAIN" ]; then
102
- echo "Creating dedicated code-signing keychain: $SIGNING_KEYCHAIN"
103
- if ! "$SECURITY" create-keychain -p "$kc_pw" "$SIGNING_KEYCHAIN" >/dev/null 2>&1; then
104
- echo "ERROR: failed to create dedicated signing keychain" >&2; return 1
537
+ run_codesign() {
538
+ if [ "$MODE" = "release" ]; then
539
+ run_release_sensitive_tool "$CODESIGN_EXECUTABLE" "$@"
540
+ else
541
+ "$CODESIGN_EXECUTABLE" "$@"
542
+ fi
543
+ }
544
+
545
+ run_swift() {
546
+ "$ENV_EXECUTABLE" -i \
547
+ HOME="$BUILD_HOME" \
548
+ PATH="$SANITIZED_PATH" \
549
+ TMPDIR="$BUILD_WORK_DIR" \
550
+ "$SWIFT_EXECUTABLE" "$@"
551
+ }
552
+
553
+ run_lipo() {
554
+ if [ "$MODE" = "release" ]; then
555
+ run_release_sensitive_tool "$LIPO_EXECUTABLE" "$@"
556
+ else
557
+ "$LIPO_EXECUTABLE" "$@"
558
+ fi
559
+ }
560
+
561
+ verify_exact_binary_architectures() {
562
+ local binary="$1"
563
+ shift
564
+ local actual_architectures
565
+ local actual_architecture
566
+ local expected_architecture
567
+ local actual_count=0
568
+ local match_count
569
+
570
+ if ! actual_architectures="$(run_lipo -archs "$binary")"; then
571
+ echo "Could not read binary architectures: $binary" >&2
572
+ return 1
573
+ fi
574
+ for actual_architecture in $actual_architectures; do
575
+ actual_count=$((actual_count + 1))
576
+ case " $* " in
577
+ *" $actual_architecture "*) ;;
578
+ *)
579
+ echo "Binary contains unsupported architecture $actual_architecture: $binary" >&2
580
+ return 1
581
+ ;;
582
+ esac
583
+ done
584
+ if [ "$actual_count" -ne "$#" ]; then
585
+ echo "Binary architecture count does not match release policy: $binary" >&2
586
+ return 1
587
+ fi
588
+ for expected_architecture in "$@"; do
589
+ match_count=0
590
+ for actual_architecture in $actual_architectures; do
591
+ if [ "$actual_architecture" = "$expected_architecture" ]; then
592
+ match_count=$((match_count + 1))
593
+ fi
594
+ done
595
+ if [ "$match_count" -ne 1 ]; then
596
+ echo "Binary must contain exactly one $expected_architecture slice: $binary" >&2
597
+ return 1
105
598
  fi
599
+ done
600
+ }
601
+
602
+ merge_release_swift_product() {
603
+ local product="$1"
604
+ local output="$2"
605
+ local arm64_input="$RELEASE_ARM64_PRODUCT_DIR/$product"
606
+ local x86_64_input="$RELEASE_X86_64_PRODUCT_DIR/$product"
607
+ verify_exact_binary_architectures "$arm64_input" arm64
608
+ verify_exact_binary_architectures "$x86_64_input" x86_64
609
+ run_lipo -create "$arm64_input" "$x86_64_input" -output "$output"
610
+ "$CHMOD_EXECUTABLE" 0755 "$output"
611
+ verify_exact_binary_architectures "$output" arm64 x86_64
612
+ }
613
+
614
+ normalize_unsigned_launch_file_mode() {
615
+ local path="$1"
616
+ local label="$2"
617
+ local hardlinked
618
+ local mode_match
619
+
620
+ if [ ! -f "$path" ] || [ -L "$path" ]; then
621
+ echo "$label is missing, linked, or not a regular file: $path" >&2
622
+ return 1
623
+ fi
624
+ if ! hardlinked="$("$FIND_EXECUTABLE" "$path" -xdev -type f -links +1 -print -quit)"; then
625
+ echo "Could not inspect $label link count: $path" >&2
626
+ return 1
627
+ fi
628
+ if [ -n "$hardlinked" ]; then
629
+ echo "$label must not be multiply linked: $path" >&2
630
+ return 1
631
+ fi
632
+ "$CHMOD_EXECUTABLE" 0755 "$path"
633
+ if ! mode_match="$("$FIND_EXECUTABLE" "$path" -xdev -type f -links 1 -perm 0755 -print -quit)"; then
634
+ echo "Could not verify $label mode: $path" >&2
635
+ return 1
636
+ fi
637
+ if [ "$mode_match" != "$path" ]; then
638
+ echo "$label must have exact mode 0755: $path" >&2
639
+ return 1
640
+ fi
641
+ }
642
+
643
+ normalize_unsigned_app_bundle_modes() {
644
+ local app="$1"
645
+ local unexpected
646
+ local hardlinked
647
+ local bad_mode
648
+ local launch_file
649
+ local -a launch_files=(
650
+ "$MACOS/Recordings"
651
+ "$HELPERS/recordings"
652
+ )
653
+
654
+ if [ ! -d "$app" ] || [ -L "$app" ]; then
655
+ echo "App bundle root is missing, linked, or not a directory: $app" >&2
656
+ return 1
657
+ fi
658
+ if [ -e "$UPDATE_CLIENT" ] || [ -L "$UPDATE_CLIENT" ]; then
659
+ launch_files+=("$UPDATE_CLIENT")
106
660
  fi
107
- if ! "$SECURITY" unlock-keychain -p "$kc_pw" "$SIGNING_KEYCHAIN" >/dev/null 2>&1; then
108
- echo "ERROR: failed to unlock dedicated signing keychain (stale vault password?)" >&2; return 1
109
- fi
110
- # No timeout / no lock-on-sleep: keep it usable across a headless build.
111
- "$SECURITY" set-keychain-settings "$SIGNING_KEYCHAIN" >/dev/null 2>&1 || true
112
-
113
- # 3. Make the dedicated keychain discoverable to codesign/find-identity.
114
- add_signing_keychain_to_search_list
115
-
116
- # 4. Reuse the identity if it is already present in the dedicated keychain.
117
- # NOTE: no -v (valid-only) filter. A self-signed identity is untrusted
118
- # (CSSMERR_TP_NOT_TRUSTED) — and marking it trusted needs GUI auth that is
119
- # unavailable over SSH so it is invisible to `find-identity -v`. codesign
120
- # can nonetheless sign with it, so we locate it by its SHA-1 HASH (which is
121
- # also what we pass to `codesign --sign`, decoupling signing from the
122
- # certificate's common name).
123
- SIGNING_IDENTITY="$("$SECURITY" find-identity -p codesigning "$SIGNING_KEYCHAIN" 2>/dev/null | grep -oE '[0-9A-F]{40}' | head -1)"
124
- if [ -n "$SIGNING_IDENTITY" ]; then
125
- # Re-assert non-interactive key access with the KNOWN password.
126
- "$SECURITY" set-key-partition-list -S apple-tool:,apple: -k "$kc_pw" "$SIGNING_KEYCHAIN" >/dev/null 2>&1 || true
127
- return 0
128
- fi
129
-
130
- # 5. Materialize cert+key: reuse the vault copy if present (keeps the DR — and
131
- # the TCC grants bound to it — stable even if the keychain is recreated),
132
- # otherwise mint a new self-signed identity and persist it to the vault.
133
- local work key crt p12 vault_cert_b64 vault_key_b64
134
- work="$(mktemp -d)" || return 1
135
- key="$work/key.pem"; crt="$work/cert.pem"; p12="$work/identity.p12"
136
-
137
- vault_cert_b64="$(secrets get "$cert_key" 2>/dev/null || true)"
138
- vault_key_b64="$(secrets get "$key_key" 2>/dev/null || true)"
139
- if [ -n "$vault_cert_b64" ] && [ -n "$vault_key_b64" ]; then
140
- printf '%s' "$vault_cert_b64" | openssl base64 -d -A > "$crt" 2>/dev/null || true
141
- printf '%s' "$vault_key_b64" | openssl base64 -d -A > "$key" 2>/dev/null || true
142
- [ -s "$crt" ] && [ -s "$key" ] && echo "Reusing signing certificate from vault ($cert_key)."
143
- fi
144
- if [ ! -s "$crt" ] || [ ! -s "$key" ]; then
145
- echo "Minting new self-signed code-signing certificate \"$SIGNING_CN\" (one-time)..."
146
- cat > "$work/req.cnf" <<EOF
147
- [ req ]
148
- distinguished_name = dn
149
- x509_extensions = ext
150
- prompt = no
151
- [ dn ]
152
- CN = $SIGNING_CN
153
- [ ext ]
154
- basicConstraints = critical,CA:false
155
- keyUsage = critical,digitalSignature
156
- extendedKeyUsage = critical,codeSigning
157
- EOF
158
- if ! openssl req -x509 -newkey rsa:2048 -nodes -keyout "$key" -out "$crt" \
159
- -days 3650 -config "$work/req.cnf" >/dev/null 2>&1; then
160
- rm -rf "$work"; echo "ERROR: failed to generate self-signed signing certificate" >&2; return 1
661
+ if ! unexpected="$("$FIND_EXECUTABLE" "$app" -xdev ! -type d ! -type f -print -quit)"; then
662
+ echo "Could not inspect the app bundle tree structure." >&2
663
+ return 1
664
+ fi
665
+ if [ -n "$unexpected" ]; then
666
+ echo "App bundle tree contains a symbolic link or special file: $unexpected" >&2
667
+ return 1
668
+ fi
669
+ if ! hardlinked="$("$FIND_EXECUTABLE" "$app" -xdev -type f -links +1 -print -quit)"; then
670
+ echo "Could not inspect app bundle regular-file link counts." >&2
671
+ return 1
672
+ fi
673
+ if [ -n "$hardlinked" ]; then
674
+ echo "App bundle tree contains a multiply-linked regular file: $hardlinked" >&2
675
+ return 1
676
+ fi
677
+ if ! "$FIND_EXECUTABLE" "$app" -xdev -type d -exec "$CHMOD_EXECUTABLE" 0755 {} +; then
678
+ echo "Could not normalize app bundle directory modes." >&2
679
+ return 1
680
+ fi
681
+ if ! "$FIND_EXECUTABLE" "$app" -xdev -type f -exec "$CHMOD_EXECUTABLE" 0644 {} +; then
682
+ echo "Could not normalize app bundle data-file modes." >&2
683
+ return 1
684
+ fi
685
+ for launch_file in "${launch_files[@]}"; do
686
+ normalize_unsigned_launch_file_mode "$launch_file" "App bundle launch file"
687
+ done
688
+ if ! bad_mode="$("$FIND_EXECUTABLE" "$app" -xdev -type d ! -perm 0755 -print -quit)"; then
689
+ echo "Could not verify app bundle directory modes." >&2
690
+ return 1
691
+ fi
692
+ if [ -n "$bad_mode" ]; then
693
+ echo "App bundle directory does not have exact mode 0755: $bad_mode" >&2
694
+ return 1
695
+ fi
696
+ if ! bad_mode="$("$FIND_EXECUTABLE" "$app" -xdev -type f \
697
+ ! -path "$MACOS/Recordings" \
698
+ ! -path "$HELPERS/recordings" \
699
+ ! -path "$UPDATE_CLIENT" \
700
+ ! -perm 0644 -print -quit)"; then
701
+ echo "Could not verify app bundle data-file modes." >&2
702
+ return 1
703
+ fi
704
+ if [ -n "$bad_mode" ]; then
705
+ echo "App bundle data file does not have exact mode 0644: $bad_mode" >&2
706
+ return 1
707
+ fi
708
+ }
709
+
710
+ normalize_unsigned_app_data_file_mode() {
711
+ local path="$1"
712
+ local label="$2"
713
+ local hardlinked
714
+ local mode_match
715
+
716
+ if [ ! -f "$path" ] || [ -L "$path" ]; then
717
+ echo "$label is missing, linked, or not a regular file: $path" >&2
718
+ return 1
719
+ fi
720
+ if ! hardlinked="$("$FIND_EXECUTABLE" "$path" -xdev -type f -links +1 -print -quit)"; then
721
+ echo "Could not inspect $label link count: $path" >&2
722
+ return 1
723
+ fi
724
+ if [ -n "$hardlinked" ]; then
725
+ echo "$label must not be multiply linked: $path" >&2
726
+ return 1
727
+ fi
728
+ "$CHMOD_EXECUTABLE" 0644 "$path"
729
+ if ! mode_match="$("$FIND_EXECUTABLE" "$path" -xdev -type f -links 1 -perm 0644 -print -quit)"; then
730
+ echo "Could not verify $label mode: $path" >&2
731
+ return 1
732
+ fi
733
+ if [ "$mode_match" != "$path" ]; then
734
+ echo "$label must have exact mode 0644: $path" >&2
735
+ return 1
736
+ fi
737
+ }
738
+
739
+ verify_app_bundle_modes() {
740
+ local app="$1"
741
+ local unexpected
742
+ local hardlinked
743
+ local bad_mode
744
+ local launch_file
745
+ local mode_match
746
+ local main="$app/Contents/MacOS/Recordings"
747
+ local companion="$app/Contents/Helpers/recordings"
748
+ local client="$app/Contents/Helpers/recordings-update-client"
749
+ local -a launch_files=(
750
+ "$main"
751
+ "$companion"
752
+ )
753
+
754
+ if [ ! -d "$app" ] || [ -L "$app" ]; then
755
+ echo "Final unsigned app bundle root is missing, linked, or not a directory: $app" >&2
756
+ return 1
757
+ fi
758
+ if [ -e "$client" ] || [ -L "$client" ]; then
759
+ launch_files+=("$client")
760
+ fi
761
+ if ! unexpected="$("$FIND_EXECUTABLE" "$app" -xdev ! -type d ! -type f -print -quit)"; then
762
+ echo "Could not inspect the final unsigned app bundle tree structure." >&2
763
+ return 1
764
+ fi
765
+ if [ -n "$unexpected" ]; then
766
+ echo "Final unsigned app bundle contains a symbolic link or special file: $unexpected" >&2
767
+ return 1
768
+ fi
769
+ if ! hardlinked="$("$FIND_EXECUTABLE" "$app" -xdev -type f -links +1 -print -quit)"; then
770
+ echo "Could not inspect final unsigned app bundle link counts." >&2
771
+ return 1
772
+ fi
773
+ if [ -n "$hardlinked" ]; then
774
+ echo "Final unsigned app bundle contains a multiply-linked regular file: $hardlinked" >&2
775
+ return 1
776
+ fi
777
+ if ! bad_mode="$("$FIND_EXECUTABLE" "$app" -xdev -type d ! -perm 0755 -print -quit)"; then
778
+ echo "Could not verify final unsigned app bundle directory modes." >&2
779
+ return 1
780
+ fi
781
+ if [ -n "$bad_mode" ]; then
782
+ echo "Final unsigned app bundle directory does not have exact mode 0755: $bad_mode" >&2
783
+ return 1
784
+ fi
785
+ for launch_file in "${launch_files[@]}"; do
786
+ if ! mode_match="$("$FIND_EXECUTABLE" "$launch_file" -xdev -type f -links 1 -perm 0755 -print -quit)"; then
787
+ echo "Could not verify final app launch-file mode: $launch_file" >&2
788
+ return 1
161
789
  fi
162
- # Persist so future rebuilds even after keychain re-creation or machine
163
- # re-provisioning reuse the SAME identity and keep the DR stable.
164
- secrets set "$cert_key" "$(openssl base64 -A -in "$crt")" --type certificate \
165
- --label "Recordings signing cert ($(signing_host))" >/dev/null 2>&1 \
166
- || echo "WARN: could not persist signing certificate to vault ($cert_key)" >&2
167
- secrets set "$key_key" "$(openssl base64 -A -in "$key")" --type private_key \
168
- --label "Recordings signing key ($(signing_host))" >/dev/null 2>&1 \
169
- || echo "WARN: could not persist signing key to vault ($key_key)" >&2
170
- fi
171
-
172
- # Package as PKCS#12 for `security import`. macOS's Security framework cannot
173
- # read the PBKDF2/AES encoding OpenSSL 3 emits by default, so prefer -legacy
174
- # (RC2/3DES + SHA1 MAC) and fall back for OpenSSL builds without it. Use a
175
- # non-empty transport passphrase (the keychain password): an empty p12
176
- # password trips "MAC verification failed" on macOS import.
177
- if ! openssl pkcs12 -export -legacy -inkey "$key" -in "$crt" -name "$SIGNING_CN" \
178
- -out "$p12" -passout pass:"$kc_pw" >/dev/null 2>&1; then
179
- if ! openssl pkcs12 -export -inkey "$key" -in "$crt" -name "$SIGNING_CN" \
180
- -out "$p12" -passout pass:"$kc_pw" >/dev/null 2>&1; then
181
- rm -rf "$work"; echo "ERROR: failed to package signing identity as PKCS#12" >&2; return 1
790
+ if [ "$mode_match" != "$launch_file" ]; then
791
+ echo "Final app launch file must be singly linked with exact mode 0755: $launch_file" >&2
792
+ return 1
182
793
  fi
794
+ done
795
+ if ! bad_mode="$("$FIND_EXECUTABLE" "$app" -xdev -type f \
796
+ ! -path "$main" \
797
+ ! -path "$companion" \
798
+ ! -path "$client" \
799
+ ! -perm 0644 -print -quit)"; then
800
+ echo "Could not verify final unsigned app bundle data-file modes." >&2
801
+ return 1
183
802
  fi
184
- # Import into the DEDICATED keychain (never the login keychain), allowlisting
185
- # codesign so it may use the private key.
186
- if ! "$SECURITY" import "$p12" -k "$SIGNING_KEYCHAIN" -P "$kc_pw" -T /usr/bin/codesign >/dev/null 2>&1; then
187
- rm -rf "$work"; echo "ERROR: failed to import signing identity into $SIGNING_KEYCHAIN" >&2; return 1
803
+ if [ -n "$bad_mode" ]; then
804
+ echo "Final unsigned app bundle data file does not have exact mode 0644: $bad_mode" >&2
805
+ return 1
806
+ fi
807
+ }
808
+
809
+ require_path_without_extended_acl() {
810
+ local path="$1"
811
+ local acl_listing
812
+ local acl_status
813
+
814
+ acl_listing="$("$LS_EXECUTABLE" -lade "$path")" || {
815
+ echo "Could not inspect app output extended ACLs on: $path" >&2
816
+ return 1
817
+ }
818
+ if printf '%s\n' "$acl_listing" | "$AWK_EXECUTABLE" '
819
+ NR == 1 && length($1) == 11 && substr($1, 11, 1) == "+" { found = 1 }
820
+ NR > 1 && $1 ~ /^[0-9]+:$/ { found = 1 }
821
+ END { exit found ? 0 : 1 }
822
+ '; then
823
+ echo "App output contains an unexpected extended ACL: $path" >&2
824
+ return 1
825
+ else
826
+ acl_status=$?
827
+ if [ "$acl_status" -ne 1 ]; then
828
+ echo "Could not evaluate app output extended ACLs on: $path" >&2
829
+ return 1
830
+ fi
188
831
  fi
189
- # Trust the self-signed root for code signing so `codesign --verify` passes.
190
- "$SECURITY" add-trusted-cert -r trustRoot -p codeSign -k "$SIGNING_KEYCHAIN" "$crt" >/dev/null 2>&1 || true
191
- # THE headless fix: grant codesign non-interactive access to the imported key
192
- # using the KNOWN keychain password (never an empty password, never the login
193
- # keychain), so signing works over SSH with no interactive allow prompt.
194
- if ! "$SECURITY" set-key-partition-list -S apple-tool:,apple: -k "$kc_pw" "$SIGNING_KEYCHAIN" >/dev/null 2>&1; then
195
- echo "WARN: set-key-partition-list did not complete; codesign may prompt for key access" >&2
832
+ }
833
+
834
+ require_app_tree_without_extended_acl() {
835
+ local tree="$1"
836
+ local acl_listing
837
+ local acl_status
838
+
839
+ require_path_without_extended_acl "$tree"
840
+ acl_listing="$("$LS_EXECUTABLE" -laeR "$tree")" || {
841
+ echo "Could not recursively inspect app output extended ACLs on: $tree" >&2
842
+ return 1
843
+ }
844
+ if printf '%s\n' "$acl_listing" | "$AWK_EXECUTABLE" '
845
+ length($1) == 11 && substr($1, 11, 1) == "+" { found = 1 }
846
+ $1 ~ /^[0-9]+:$/ { found = 1 }
847
+ END { exit found ? 0 : 1 }
848
+ '; then
849
+ echo "App output tree contains an unexpected extended ACL: $tree" >&2
850
+ return 1
851
+ else
852
+ acl_status=$?
853
+ if [ "$acl_status" -ne 1 ]; then
854
+ echo "Could not evaluate recursive app output extended ACLs on: $tree" >&2
855
+ return 1
856
+ fi
196
857
  fi
197
- rm -rf "$work"
858
+ }
198
859
 
199
- # Locate the freshly imported identity by hash (again, no -v: it is an
200
- # untrusted self-signed cert). codesign signs by this hash.
201
- SIGNING_IDENTITY="$("$SECURITY" find-identity -p codesigning "$SIGNING_KEYCHAIN" 2>/dev/null | grep -oE '[0-9A-F]{40}' | head -1)"
202
- [ -n "$SIGNING_IDENTITY" ]
860
+ require_root_owned_nonwritable_directory() {
861
+ local directory="$1"
862
+ local mode
863
+ [ -d "$directory" ] && [ ! -L "$directory" ] && \
864
+ [ "$("$STAT_EXECUTABLE" -f '%u' "$directory")" = "0" ] || {
865
+ echo "Compatible-cohort ancestry is missing, linked, or not root-owned: $directory" >&2
866
+ return 1
867
+ }
868
+ mode="$("$STAT_EXECUTABLE" -f '%Lp' "$directory")"
869
+ [ $((8#$mode & 8#022)) -eq 0 ] || {
870
+ echo "Compatible-cohort ancestry is group/other writable: $directory" >&2
871
+ return 1
872
+ }
873
+ require_path_without_extended_acl "$directory"
203
874
  }
204
875
 
205
- echo "Building Recordings.app ($MODE)..."
206
- swift build -c "$MODE" --product App
876
+ prepare_compatible_cohort_manifest() {
877
+ local cohort_root="/Library/Application Support/Hasna/Recordings/BuildTrust/compatible-cohorts"
878
+ local relative current component leaf
879
+ [ "$(cd "$cohort_root" && "$PWD_EXECUTABLE" -P)" = "$cohort_root" ] || {
880
+ echo "Compatible-cohort directory is not canonical." >&2
881
+ return 1
882
+ }
883
+ require_root_owned_nonwritable_directory "/"
884
+ relative="${cohort_root#/}"
885
+ current=""
886
+ IFS='/' read -r -a cohort_components <<<"$relative"
887
+ for component in "${cohort_components[@]}"; do
888
+ current="$current/$component"
889
+ require_root_owned_nonwritable_directory "$current"
890
+ done
891
+ leaf="$("$BASENAME_EXECUTABLE" "$COMPATIBLE_COHORT_MANIFEST")"
892
+ [ "$COMPATIBLE_COHORT_MANIFEST" = "$cohort_root/$leaf" ] || {
893
+ echo "Compatible-cohort manifest path must be canonical." >&2
894
+ return 1
895
+ }
896
+ [ -f "$COMPATIBLE_COHORT_MANIFEST" ] && [ ! -L "$COMPATIBLE_COHORT_MANIFEST" ] && \
897
+ [ "$("$STAT_EXECUTABLE" -f '%u' "$COMPATIBLE_COHORT_MANIFEST")" = "0" ] && \
898
+ [ "$("$STAT_EXECUTABLE" -f '%Lp' "$COMPATIBLE_COHORT_MANIFEST")" = "444" ] && \
899
+ [ "$("$FIND_EXECUTABLE" "$COMPATIBLE_COHORT_MANIFEST" -xdev -type f -links 1 -perm 0444 -print -quit)" = "$COMPATIBLE_COHORT_MANIFEST" ] || {
900
+ echo "Compatible-cohort manifest must be a singly linked root-owned mode-0444 regular file." >&2
901
+ return 1
902
+ }
903
+ require_path_without_extended_acl "$COMPATIBLE_COHORT_MANIFEST"
904
+ COMPATIBLE_COHORT_SNAPSHOT="$BUILD_WORK_DIR/compatible-cohort.json"
905
+ COMPATIBLE_COHORT_SHA256="$(run_bun \
906
+ "$SOURCE_PACKAGE_ROOT/scripts/macos_artifact.ts" snapshot-regular-file \
907
+ --source "$COMPATIBLE_COHORT_MANIFEST" \
908
+ --destination "$COMPATIBLE_COHORT_SNAPSHOT" \
909
+ --maximum-bytes 65536)"
910
+ [ "$leaf" = "${COMPATIBLE_COHORT_SHA256}.json" ] || {
911
+ echo "Compatible-cohort manifest filename must authenticate its snapshotted contents." >&2
912
+ return 1
913
+ }
914
+ run_bun "$RELEASE_LIFECYCLE_HELPER" validate-compatible-cohort \
915
+ --manifest "$COMPATIBLE_COHORT_SNAPSHOT" \
916
+ --public-key "$ENVELOPE_PUBLIC_KEY_SNAPSHOT" \
917
+ --team-id "$EXPECTED_TEAM_ID" \
918
+ --key-epoch "$KEY_EPOCH"
919
+ }
920
+
921
+ prepare_envelope_public_key() {
922
+ ENVELOPE_PUBLIC_KEY_SNAPSHOT="$BUILD_WORK_DIR/release-envelope-public.raw"
923
+ ENVELOPE_PUBLIC_KEY_SHA256="$(run_bun \
924
+ "$SOURCE_PACKAGE_ROOT/scripts/macos_artifact.ts" snapshot-regular-file \
925
+ --source "$ENVELOPE_PUBLIC_KEY" \
926
+ --destination "$ENVELOPE_PUBLIC_KEY_SNAPSHOT" \
927
+ --maximum-bytes 32 \
928
+ --expected-bytes 32)"
929
+ }
930
+
931
+ BUILD_CONFIGURATION="release"
932
+ ARTIFACT_POLICY="release"
933
+ APPROVED_TARGET="fleet"
934
+ APPROVED_TARGET_IDENTITY_KIND="none"
935
+ APPROVED_TARGET_IDENTITY_SHA256="none"
936
+ BUILDER_IDENTITY_KIND="none"
937
+ BUILDER_IDENTITY_SHA256="none"
938
+ if [ "$MODE" = "release" ]; then
939
+ if [ -z "$CODESIGN_IDENTITY" ] || [ "$CODESIGN_IDENTITY" = "-" ]; then
940
+ echo "Release builds require RECORDINGS_CODESIGN_IDENTITY for a Developer ID Application identity." >&2
941
+ exit 1
942
+ fi
943
+ if [ -z "$EXPECTED_TEAM_ID" ]; then
944
+ echo "Release builds require RECORDINGS_EXPECTED_TEAM_IDENTIFIER to pin the Developer ID team." >&2
945
+ exit 1
946
+ fi
947
+ if [ -z "$NOTARY_PROFILE" ]; then
948
+ echo "Release builds require RECORDINGS_NOTARY_KEYCHAIN_PROFILE for notarization." >&2
949
+ exit 1
950
+ fi
951
+ if ! [[ "$RELEASE_SEQUENCE" =~ ^[1-9][0-9]*$ ]] || ! [[ "$KEY_EPOCH" =~ ^[1-9][0-9]*$ ]]; then
952
+ echo "Release builds require positive release sequence and key epoch values." >&2
953
+ exit 1
954
+ fi
955
+ if [ -z "$ENVELOPE_EXPIRES_AT_UTC" ]; then
956
+ echo "Release builds require an explicit release-envelope expiry." >&2
957
+ exit 1
958
+ fi
959
+ for envelope_key in "$ENVELOPE_PRIVATE_KEY" "$ENVELOPE_PUBLIC_KEY"; do
960
+ case "$envelope_key" in /*) ;; *) echo "Release envelope key paths must be absolute." >&2; exit 1 ;; esac
961
+ if [ ! -f "$envelope_key" ] || [ -L "$envelope_key" ]; then
962
+ echo "A release envelope key file is missing or unsafe." >&2
963
+ exit 1
964
+ fi
965
+ done
966
+ prepare_envelope_public_key
967
+ if [ "$RELEASE_SUBTYPE" = "initial-bootstrap" ]; then
968
+ if [[ "$INSTALLER_IDENTITY" != "Developer ID Installer:"* ]]; then
969
+ echo "Initial-bootstrap releases require RECORDINGS_INSTALLER_CODESIGN_IDENTITY for the Developer ID Installer identity." >&2
970
+ exit 1
971
+ fi
972
+ if [ -n "$COMPATIBLE_COHORT_MANIFEST" ]; then
973
+ echo "Initial-bootstrap releases do not consume RECORDINGS_RELEASE_COMPATIBLE_COHORT_MANIFEST." >&2
974
+ exit 1
975
+ fi
976
+ else
977
+ if [ -n "$INSTALLER_IDENTITY" ]; then
978
+ echo "App-update releases do not consume RECORDINGS_INSTALLER_CODESIGN_IDENTITY." >&2
979
+ exit 1
980
+ fi
981
+ if [ "$HOST_PLATFORM" != "Darwin" ] && [ -n "$TEST_GIT_EXECUTABLE" ]; then
982
+ [ -f "$COMPATIBLE_COHORT_MANIFEST" ] && [ ! -L "$COMPATIBLE_COHORT_MANIFEST" ] || {
983
+ echo "App-update test fixture requires one regular compatible-cohort manifest." >&2
984
+ exit 1
985
+ }
986
+ else
987
+ case "$COMPATIBLE_COHORT_MANIFEST" in
988
+ "/Library/Application Support/Hasna/Recordings/BuildTrust/compatible-cohorts/"*.json) ;;
989
+ *) echo "App-update releases require one root-preauthorized RECORDINGS_RELEASE_COMPATIBLE_COHORT_MANIFEST." >&2; exit 1 ;;
990
+ esac
991
+ fi
992
+ fi
993
+ elif [ "$MODE" = "local" ]; then
994
+ if [ "$LOCAL_APPROVED_TARGET" != "station06" ]; then
995
+ echo "Local-only builds currently require RECORDINGS_LOCAL_APPROVED_TARGET=station06." >&2
996
+ exit 1
997
+ fi
998
+ if ! [[ "$LOCAL_APPROVED_TARGET_IDENTITY_SHA256" =~ ^[a-f0-9]{64}$ ]]; then
999
+ echo "Local-only builds require an authenticated RECORDINGS_LOCAL_APPROVED_TARGET_IDENTITY_SHA256." >&2
1000
+ exit 1
1001
+ fi
1002
+ if [ "$LOCAL_APPROVED_TARGET_IDENTITY_KIND" != "tailscale_node_id_sha256" ]; then
1003
+ echo "New local-only builds require RECORDINGS_LOCAL_APPROVED_TARGET_IDENTITY_KIND=tailscale_node_id_sha256." >&2
1004
+ exit 1
1005
+ fi
1006
+ BUILD_HOST="$("$HOSTNAME_EXECUTABLE" -s)"
1007
+ if [ "$BUILD_HOST" = "$LOCAL_APPROVED_TARGET" ]; then
1008
+ echo "Local-only artifacts must be built on a non-target Mac." >&2
1009
+ exit 1
1010
+ fi
1011
+ if [ ! -r "$TAILSCALE_RESOLVER" ]; then
1012
+ echo "Packaged Tailscale CLI resolver is missing." >&2
1013
+ exit 1
1014
+ fi
1015
+ # shellcheck source=/dev/null
1016
+ source "$TAILSCALE_RESOLVER"
1017
+ if ! TAILSCALE_CLI="$(recordings_resolve_trusted_tailscale_app_cli "$BUILD_WORK_DIR")"; then
1018
+ echo "Tailscale is required to authenticate the non-target build Mac." >&2
1019
+ exit 1
1020
+ fi
1021
+ if ! BUILDER_IDENTITY_SHA256="$(recordings_run_trusted_tailscale_status "$TAILSCALE_CLI" "$BUILD_WORK_DIR" | run_bun "$SOURCE_PACKAGE_ROOT/scripts/macos_artifact.ts" tailscale-node-id-sha256 --expected-hostname "$BUILD_HOST")"; then
1022
+ echo "Could not authenticate the live Tailscale node identity for the build Mac." >&2
1023
+ exit 1
1024
+ fi
1025
+ if ! [[ "$BUILDER_IDENTITY_SHA256" =~ ^[a-f0-9]{64}$ ]]; then
1026
+ echo "Build Mac Tailscale node identity did not produce a valid SHA-256 digest." >&2
1027
+ exit 1
1028
+ fi
1029
+ BUILDER_IDENTITY_KIND="tailscale_node_id_sha256"
1030
+ if [ "$BUILDER_IDENTITY_SHA256" = "$LOCAL_APPROVED_TARGET_IDENTITY_SHA256" ]; then
1031
+ echo "Local-only artifacts must be built on a different authenticated Tailscale node." >&2
1032
+ exit 1
1033
+ fi
1034
+ CODESIGN_IDENTITY="-"
1035
+ EXPECTED_TEAM_ID="ADHOC"
1036
+ ARTIFACT_POLICY="local_only"
1037
+ APPROVED_TARGET="$LOCAL_APPROVED_TARGET"
1038
+ APPROVED_TARGET_IDENTITY_KIND="$LOCAL_APPROVED_TARGET_IDENTITY_KIND"
1039
+ APPROVED_TARGET_IDENTITY_SHA256="$LOCAL_APPROVED_TARGET_IDENTITY_SHA256"
1040
+ echo "WARNING: local-only artifacts are ad-hoc signed, non-notarized, and restricted to ${APPROVED_TARGET}." >&2
1041
+ echo "WARNING: installing can change code identity and require Microphone or Accessibility reauthorization." >&2
1042
+ else
1043
+ BUILD_CONFIGURATION="debug"
1044
+ CODESIGN_IDENTITY="-"
1045
+ EXPECTED_TEAM_ID="ADHOC"
1046
+ ARTIFACT_POLICY="local_only"
1047
+ APPROVED_TARGET="debug-only"
1048
+ echo "WARNING: debug builds are ad-hoc signed and non-distributable; never install or upload this output." >&2
1049
+ fi
207
1050
 
208
- # Create .app bundle
209
- BUILD_DIR=".build/$MODE"
210
- APP_DIR="$BUILD_DIR/Recordings.app"
1051
+ COMPATIBLE_COHORT_SNAPSHOT=""
1052
+ if [ "$MODE" = "release" ] && [ "$RELEASE_SUBTYPE" = "app-update" ]; then
1053
+ if [ "$HOST_PLATFORM" = "Darwin" ]; then
1054
+ prepare_compatible_cohort_manifest
1055
+ elif [ -n "$TEST_GIT_EXECUTABLE" ]; then
1056
+ COMPATIBLE_COHORT_SNAPSHOT="$BUILD_WORK_DIR/compatible-cohort-fixture.json"
1057
+ COMPATIBLE_COHORT_SHA256="$(run_bun \
1058
+ "$SOURCE_PACKAGE_ROOT/scripts/macos_artifact.ts" snapshot-regular-file \
1059
+ --source "$COMPATIBLE_COHORT_MANIFEST" \
1060
+ --destination "$COMPATIBLE_COHORT_SNAPSHOT" \
1061
+ --maximum-bytes 65536)"
1062
+ run_bun "$RELEASE_LIFECYCLE_HELPER" validate-compatible-cohort \
1063
+ --manifest "$COMPATIBLE_COHORT_SNAPSHOT" \
1064
+ --public-key "$ENVELOPE_PUBLIC_KEY_SNAPSHOT" \
1065
+ --team-id "$EXPECTED_TEAM_ID" \
1066
+ --key-epoch "$KEY_EPOCH"
1067
+ fi
1068
+ fi
1069
+
1070
+ generate_and_verify_native_fs_guard
1071
+
1072
+ echo "Building Recordings.app ($MODE)..."
1073
+ if [ "$MODE" = "release" ]; then
1074
+ OUTPUT_BUILD_DIR="$BUILD_ROOT/release-output"
1075
+ else
1076
+ OUTPUT_BUILD_DIR="$SCRIPT_DIR/.build/$BUILD_CONFIGURATION"
1077
+ fi
1078
+ OUTPUT_APP_DIR="$OUTPUT_BUILD_DIR/Recordings.app"
1079
+ SWIFT_PRODUCT_DIR="$SWIFT_SCRATCH_PATH/$BUILD_CONFIGURATION"
1080
+ APP_DIR="$BUILD_WORK_DIR/Recordings.app"
211
1081
  CONTENTS="$APP_DIR/Contents"
212
1082
  MACOS="$CONTENTS/MacOS"
213
1083
  RESOURCES="$CONTENTS/Resources"
1084
+ HELPERS="$CONTENTS/Helpers"
1085
+ COMPANION_BUILD_SCRIPT="$SOURCE_PACKAGE_ROOT/scripts/build_companion_cli.sh"
1086
+ SMOKE_SCRIPT="$SOURCE_PACKAGE_ROOT/scripts/smoke_macos_app.sh"
214
1087
 
215
- rm -rf "$APP_DIR"
216
- mkdir -p "$MACOS" "$RESOURCES"
1088
+ if [ "$MODE" = "release" ]; then
1089
+ if [ -e "$OUTPUT_BUILD_DIR" ]; then
1090
+ [ -d "$OUTPUT_BUILD_DIR" ] && [ ! -L "$OUTPUT_BUILD_DIR" ] && \
1091
+ [ "$("$STAT_EXECUTABLE" -f '%u' "$OUTPUT_BUILD_DIR")" = "$("$ID_EXECUTABLE" -u)" ] && \
1092
+ [ "$("$STAT_EXECUTABLE" -f '%Lp' "$OUTPUT_BUILD_DIR")" = "700" ] || {
1093
+ echo "Managed release output must be an isolated builder-owned 0700 directory." >&2
1094
+ exit 1
1095
+ }
1096
+ else
1097
+ "$MKDIR_EXECUTABLE" -m 0700 "$OUTPUT_BUILD_DIR"
1098
+ fi
1099
+ else
1100
+ "$RM_EXECUTABLE" -rf "$OUTPUT_BUILD_DIR"
1101
+ "$MKDIR_EXECUTABLE" -p "$OUTPUT_BUILD_DIR"
1102
+ fi
1103
+ if [ "$MODE" = "release" ]; then
1104
+ run_swift test -c "$BUILD_CONFIGURATION" \
1105
+ --package-path "$SOURCE_NATIVE_DIR" \
1106
+ --scratch-path "$SWIFT_SCRATCH_PATH/tests"
1107
+ fi
1108
+ SWIFT_RESOURCE_PRODUCT_DIR="$SWIFT_PRODUCT_DIR"
1109
+ if [ "$MODE" = "release" ] && [ "$HOST_PLATFORM" = "Darwin" ]; then
1110
+ RELEASE_ARM64_PRODUCT_DIR=""
1111
+ RELEASE_X86_64_PRODUCT_DIR=""
1112
+ RELEASE_UPDATER_PRODUCTS=(recordings-update-client recordings-envelope-signer)
1113
+ if [ "$RELEASE_SUBTYPE" = "initial-bootstrap" ]; then
1114
+ RELEASE_UPDATER_PRODUCTS+=(recordings-update-broker recordings-bootstrap-preflight)
1115
+ fi
1116
+ for swift_architecture in "${RELEASE_ARCHITECTURES[@]}"; do
1117
+ swift_arch_scratch="$SWIFT_SCRATCH_PATH/$swift_architecture"
1118
+ run_swift build -c "$BUILD_CONFIGURATION" \
1119
+ --arch "$swift_architecture" \
1120
+ --product App \
1121
+ --package-path "$SOURCE_NATIVE_DIR" \
1122
+ --scratch-path "$swift_arch_scratch"
1123
+ for updater_product in "${RELEASE_UPDATER_PRODUCTS[@]}"; do
1124
+ run_swift build -c "$BUILD_CONFIGURATION" \
1125
+ --arch "$swift_architecture" \
1126
+ --product "$updater_product" \
1127
+ --package-path "$SOURCE_NATIVE_DIR" \
1128
+ --scratch-path "$swift_arch_scratch"
1129
+ done
1130
+ swift_arch_product_dir="$(run_swift build -c "$BUILD_CONFIGURATION" \
1131
+ --arch "$swift_architecture" \
1132
+ --package-path "$SOURCE_NATIVE_DIR" \
1133
+ --scratch-path "$swift_arch_scratch" \
1134
+ --show-bin-path)"
1135
+ case "$swift_arch_product_dir" in
1136
+ "$swift_arch_scratch"/*) ;;
1137
+ *)
1138
+ echo "Swift product path escaped the architecture-specific scratch root." >&2
1139
+ exit 1
1140
+ ;;
1141
+ esac
1142
+ if [ ! -d "$swift_arch_product_dir" ]; then
1143
+ echo "Swift did not emit an architecture-specific product directory." >&2
1144
+ exit 1
1145
+ fi
1146
+ if [ "$swift_architecture" = "arm64" ]; then
1147
+ RELEASE_ARM64_PRODUCT_DIR="$swift_arch_product_dir"
1148
+ else
1149
+ RELEASE_X86_64_PRODUCT_DIR="$swift_arch_product_dir"
1150
+ fi
1151
+ done
1152
+ SWIFT_PRODUCT_DIR="$BUILD_WORK_DIR/swift-products-universal"
1153
+ "$MKDIR_EXECUTABLE" -p "$SWIFT_PRODUCT_DIR"
1154
+ SWIFT_RESOURCE_PRODUCT_DIR="$RELEASE_ARM64_PRODUCT_DIR"
1155
+ merge_release_swift_product "App" "$SWIFT_PRODUCT_DIR/App"
1156
+ merge_release_swift_product "recordings-update-client" "$SWIFT_PRODUCT_DIR/recordings-update-client"
1157
+ merge_release_swift_product "recordings-envelope-signer" "$SWIFT_PRODUCT_DIR/recordings-envelope-signer"
1158
+ if [ "$RELEASE_SUBTYPE" = "initial-bootstrap" ]; then
1159
+ merge_release_swift_product "recordings-update-broker" "$SWIFT_PRODUCT_DIR/recordings-update-broker"
1160
+ merge_release_swift_product "recordings-bootstrap-preflight" "$SWIFT_PRODUCT_DIR/recordings-bootstrap-preflight"
1161
+ fi
1162
+ else
1163
+ run_swift build -c "$BUILD_CONFIGURATION" \
1164
+ --product App \
1165
+ --package-path "$SOURCE_NATIVE_DIR" \
1166
+ --scratch-path "$SWIFT_SCRATCH_PATH"
1167
+ fi
1168
+ if [ "$HOST_PLATFORM" != "Darwin" ] && [ -n "$TEST_SWIFT_EXECUTABLE" ]; then
1169
+ SWIFT_PRODUCT_DIR="$OUTPUT_BUILD_DIR"
1170
+ SWIFT_RESOURCE_PRODUCT_DIR="$OUTPUT_BUILD_DIR"
1171
+ fi
1172
+
1173
+ "$RM_EXECUTABLE" -rf "$APP_DIR"
1174
+ "$MKDIR_EXECUTABLE" -p "$MACOS" "$RESOURCES" "$HELPERS"
1175
+ "$CP_EXECUTABLE" "$SWIFT_PRODUCT_DIR/App" "$MACOS/Recordings"
1176
+ UPDATE_BROKER="$BUILD_WORK_DIR/com.hasna.recordings.updater"
1177
+ UPDATE_CLIENT="$HELPERS/recordings-update-client"
1178
+ ENVELOPE_SIGNER="$BUILD_WORK_DIR/recordings-envelope-signer"
1179
+ BOOTSTRAP_PREFLIGHT_VERIFIER="$BUILD_WORK_DIR/recordings-bootstrap-preflight"
1180
+ ARTIFACT_VERIFIER="$BUILD_WORK_DIR/com.hasna.recordings.artifact-verifier"
1181
+ if [ "$MODE" = "release" ] && [ "$HOST_PLATFORM" = "Darwin" ]; then
1182
+ "$CP_EXECUTABLE" "$SWIFT_PRODUCT_DIR/recordings-update-client" "$UPDATE_CLIENT"
1183
+ "$CHMOD_EXECUTABLE" 0755 "$UPDATE_CLIENT"
1184
+ "$CP_EXECUTABLE" "$SWIFT_PRODUCT_DIR/recordings-envelope-signer" "$ENVELOPE_SIGNER"
1185
+ if [ "$RELEASE_SUBTYPE" = "initial-bootstrap" ]; then
1186
+ "$CP_EXECUTABLE" "$SWIFT_PRODUCT_DIR/recordings-update-broker" "$UPDATE_BROKER"
1187
+ "$CP_EXECUTABLE" "$SWIFT_PRODUCT_DIR/recordings-bootstrap-preflight" "$BOOTSTRAP_PREFLIGHT_VERIFIER"
1188
+ "$CHMOD_EXECUTABLE" 0755 "$BOOTSTRAP_PREFLIGHT_VERIFIER"
1189
+ VERIFIER_ARM64="$BUILD_WORK_DIR/recordings-artifact-verifier.arm64"
1190
+ VERIFIER_X86_64="$BUILD_WORK_DIR/recordings-artifact-verifier.x86_64"
1191
+ for verifier_target in arm64 x64; do
1192
+ verifier_output="$VERIFIER_ARM64"
1193
+ [ "$verifier_target" = "arm64" ] || verifier_output="$VERIFIER_X86_64"
1194
+ run_bun build \
1195
+ --compile \
1196
+ --target="bun-darwin-${verifier_target}" \
1197
+ --reject-unresolved \
1198
+ --no-compile-autoload-dotenv \
1199
+ --no-compile-autoload-bunfig \
1200
+ --no-compile-autoload-tsconfig \
1201
+ --no-compile-autoload-package-json \
1202
+ "$SOURCE_PACKAGE_ROOT/scripts/macos_artifact.ts" \
1203
+ --outfile "$verifier_output"
1204
+ done
1205
+ run_lipo -create "$VERIFIER_ARM64" "$VERIFIER_X86_64" -output "$ARTIFACT_VERIFIER"
1206
+ "$CHMOD_EXECUTABLE" 0755 "$ARTIFACT_VERIFIER"
1207
+ verify_exact_binary_architectures "$ARTIFACT_VERIFIER" arm64 x86_64
1208
+ "$RM_EXECUTABLE" -f "$VERIFIER_ARM64" "$VERIFIER_X86_64"
1209
+ fi
1210
+ elif [ "$MODE" = "release" ] && [ -n "$TEST_SWIFT_EXECUTABLE" ]; then
1211
+ "$CP_EXECUTABLE" "$SWIFT_PRODUCT_DIR/recordings-update-client" "$UPDATE_CLIENT"
1212
+ "$CHMOD_EXECUTABLE" 0755 "$UPDATE_CLIENT"
1213
+ "$CP_EXECUTABLE" "$SWIFT_PRODUCT_DIR/recordings-envelope-signer" "$ENVELOPE_SIGNER"
1214
+ "$CHMOD_EXECUTABLE" 0755 "$ENVELOPE_SIGNER"
1215
+ fi
1216
+ COMPANION_BUILD_KIND="native"
1217
+ if [ "$MODE" = "release" ] && [ "$HOST_PLATFORM" = "Darwin" ]; then
1218
+ COMPANION_BUILD_KIND="universal"
1219
+ fi
1220
+ "$ENV_EXECUTABLE" -i \
1221
+ HOME="$BUILD_HOME" \
1222
+ PATH="$SANITIZED_PATH" \
1223
+ TMPDIR="$BUILD_WORK_DIR" \
1224
+ ${COMPANION_TEST_ENVIRONMENT[0]+"${COMPANION_TEST_ENVIRONMENT[@]}"} \
1225
+ "$BASH_EXECUTABLE" "$COMPANION_BUILD_SCRIPT" "$HELPERS/recordings" "$BUN_EXECUTABLE" "$COMPANION_BUILD_KIND"
1226
+ if [ "$MODE" = "release" ] && [ "$HOST_PLATFORM" = "Darwin" ]; then
1227
+ verify_exact_binary_architectures "$MACOS/Recordings" arm64 x86_64
1228
+ verify_exact_binary_architectures "$UPDATE_CLIENT" arm64 x86_64
1229
+ verify_exact_binary_architectures "$ENVELOPE_SIGNER" arm64 x86_64
1230
+ verify_exact_binary_architectures "$HELPERS/recordings" arm64 x86_64
1231
+ if [ "$RELEASE_SUBTYPE" = "initial-bootstrap" ]; then
1232
+ verify_exact_binary_architectures "$UPDATE_BROKER" arm64 x86_64
1233
+ verify_exact_binary_architectures "$ARTIFACT_VERIFIER" arm64 x86_64
1234
+ verify_exact_binary_architectures "$BOOTSTRAP_PREFLIGHT_VERIFIER" arm64 x86_64
1235
+ fi
1236
+ fi
1237
+ "$CP_EXECUTABLE" "$SOURCE_NATIVE_DIR/RecordingsLib/Info.plist" "$CONTENTS/Info.plist"
1238
+ VERSION="$("$PLIST_BUDDY" -c 'Print :CFBundleShortVersionString' "$CONTENTS/Info.plist")"
1239
+
1240
+ compute_release_publication_identity() {
1241
+ local -a identity_arguments=(
1242
+ --component "release_kind=$RELEASE_SUBTYPE"
1243
+ --component "source_sha=$SOURCE_SHA"
1244
+ --component "version=$VERSION"
1245
+ --component "codesign_identity=$CODESIGN_IDENTITY"
1246
+ --component "team_id=$EXPECTED_TEAM_ID"
1247
+ --component "notary_profile=$NOTARY_PROFILE"
1248
+ --component "release_sequence=$RELEASE_SEQUENCE"
1249
+ --component "key_epoch=$KEY_EPOCH"
1250
+ --component "expires_at_utc=$ENVELOPE_EXPIRES_AT_UTC"
1251
+ )
1252
+ identity_arguments+=(--component "envelope_public_key_sha256=$ENVELOPE_PUBLIC_KEY_SHA256")
1253
+ if [ "$RELEASE_SUBTYPE" = "initial-bootstrap" ]; then
1254
+ identity_arguments+=(--component "installer_identity=$INSTALLER_IDENTITY")
1255
+ else
1256
+ identity_arguments+=(--component "compatible_cohort_sha256=$COMPATIBLE_COHORT_SHA256")
1257
+ fi
1258
+ RELEASE_PUBLICATION_IDENTITY_SHA256="$(run_bun \
1259
+ "$SOURCE_PACKAGE_ROOT/scripts/macos_artifact.ts" release-publication-identity \
1260
+ "${identity_arguments[@]}")"
1261
+ [[ "$RELEASE_PUBLICATION_IDENTITY_SHA256" =~ ^[a-f0-9]{64}$ ]] || {
1262
+ echo "Release publication identity is invalid." >&2
1263
+ return 1
1264
+ }
1265
+ }
217
1266
 
218
- # Copy binary
219
- cp "$BUILD_DIR/App" "$MACOS/Recordings"
1267
+ reserve_release_output() {
1268
+ local release_set_basename reserved_output
1269
+ release_set_basename="Recordings-${VERSION}-macos-${RELEASE_SUBTYPE}"
1270
+ compute_release_publication_identity
1271
+ RELEASE_OUTPUT_ROOT="$OUTPUT_BUILD_DIR"
1272
+ RELEASE_FINAL_DIR="$RELEASE_OUTPUT_ROOT/${release_set_basename}.release"
1273
+ RELEASE_RESERVATION="$RELEASE_OUTPUT_ROOT/.${release_set_basename}.reservation"
1274
+ RELEASE_OUTPUT_ALIASES=(
1275
+ "${release_set_basename}.zip"
1276
+ "${release_set_basename}.manifest.json"
1277
+ "${release_set_basename}.notary-submit.json"
1278
+ "${release_set_basename}.notary-log.json"
1279
+ )
1280
+ if [ "$RELEASE_SUBTYPE" = "initial-bootstrap" ]; then
1281
+ RELEASE_OUTPUT_ALIASES+=(
1282
+ "${release_set_basename}-updater.pkg"
1283
+ "${release_set_basename}-updater.notary-submit.json"
1284
+ "${release_set_basename}-updater.notary-log.json"
1285
+ "${release_set_basename}-updater.pkg.sha256"
1286
+ "${release_set_basename}-updater.bootstrap-envelope.json"
1287
+ "${release_set_basename}-updater.compatible-cohort.json"
1288
+ "${release_set_basename}-updater.compatible-cohort.json.sha256"
1289
+ )
1290
+ else
1291
+ RELEASE_OUTPUT_ALIASES+=("${release_set_basename}.update-envelope.json")
1292
+ fi
1293
+ if [ -e "$RELEASE_FINAL_DIR" ] || [ -L "$RELEASE_FINAL_DIR" ]; then
1294
+ run_bun "$SOURCE_PACKAGE_ROOT/scripts/macos_artifact.ts" complete-release-publication \
1295
+ --destination "$RELEASE_FINAL_DIR" \
1296
+ --reservation "$RELEASE_RESERVATION" \
1297
+ --output-root "$RELEASE_OUTPUT_ROOT" \
1298
+ --publication-identity-sha256 "$RELEASE_PUBLICATION_IDENTITY_SHA256"
1299
+ run_bun "$SOURCE_PACKAGE_ROOT/scripts/macos_artifact.ts" assert-release-publication-complete \
1300
+ --destination "$RELEASE_FINAL_DIR" \
1301
+ --output-root "$RELEASE_OUTPUT_ROOT" \
1302
+ --publication-identity-sha256 "$RELEASE_PUBLICATION_IDENTITY_SHA256"
1303
+ echo "Recovered authenticated same-version release publication: $RELEASE_FINAL_DIR"
1304
+ exit 0
1305
+ fi
1306
+ [ ! -e "$RELEASE_FINAL_DIR" ] && [ ! -L "$RELEASE_FINAL_DIR" ] || {
1307
+ echo "Release output already exists; same-version publication is immutable: $RELEASE_FINAL_DIR" >&2
1308
+ exit 1
1309
+ }
1310
+ for reserved_output in "${RELEASE_OUTPUT_ALIASES[@]}"; do
1311
+ [ ! -e "$RELEASE_OUTPUT_ROOT/$reserved_output" ] && \
1312
+ [ ! -L "$RELEASE_OUTPUT_ROOT/$reserved_output" ] || {
1313
+ echo "Release compatibility output already exists and cannot be replaced: $reserved_output" >&2
1314
+ exit 1
1315
+ }
1316
+ done
1317
+ if ! "$MKDIR_EXECUTABLE" -m 0700 "$RELEASE_RESERVATION"; then
1318
+ echo "Release output is already reserved by another or interrupted builder." >&2
1319
+ exit 1
1320
+ fi
1321
+ RELEASE_RESERVATION_OWNED=1
1322
+ [ ! -e "$RELEASE_FINAL_DIR" ] && [ ! -L "$RELEASE_FINAL_DIR" ] || {
1323
+ echo "Release output appeared while acquiring its reservation." >&2
1324
+ exit 1
1325
+ }
1326
+ for reserved_output in "${RELEASE_OUTPUT_ALIASES[@]}"; do
1327
+ [ ! -e "$RELEASE_OUTPUT_ROOT/$reserved_output" ] && \
1328
+ [ ! -L "$RELEASE_OUTPUT_ROOT/$reserved_output" ] || {
1329
+ echo "Release compatibility output appeared while acquiring its reservation." >&2
1330
+ exit 1
1331
+ }
1332
+ done
1333
+ RELEASE_STAGING_DIR="$($MKTEMP_EXECUTABLE -d "$RELEASE_OUTPUT_ROOT/.${release_set_basename}.staging.XXXXXX")"
1334
+ "$CHMOD_EXECUTABLE" 0700 "$RELEASE_STAGING_DIR"
1335
+ OUTPUT_BUILD_DIR="$RELEASE_STAGING_DIR"
1336
+ OUTPUT_APP_DIR="$OUTPUT_BUILD_DIR/Recordings.app"
1337
+ }
220
1338
 
221
- # Copy Info.plist
222
- cp RecordingsLib/Info.plist "$CONTENTS/Info.plist"
1339
+ if [ "$MODE" = "release" ]; then
1340
+ reserve_release_output
1341
+ fi
223
1342
 
224
- # Copy SwiftPM resource bundles used by Bundle.module.
225
- for bundle in "$BUILD_DIR"/*.resources "$BUILD_DIR"/*.bundle .build/*/"$MODE"/*.resources .build/*/"$MODE"/*.bundle; do
1343
+ for bundle in "$SWIFT_RESOURCE_PRODUCT_DIR"/*.resources "$SWIFT_RESOURCE_PRODUCT_DIR"/*.bundle "$SWIFT_SCRATCH_PATH"/*/"$BUILD_CONFIGURATION"/*.resources "$SWIFT_SCRATCH_PATH"/*/"$BUILD_CONFIGURATION"/*.bundle; do
226
1344
  [ -e "$bundle" ] || continue
227
- rm -rf "$RESOURCES/$(basename "$bundle")"
228
- ditto "$bundle" "$RESOURCES/$(basename "$bundle")"
1345
+ "$RM_EXECUTABLE" -rf "$RESOURCES/$("$BASENAME_EXECUTABLE" "$bundle")"
1346
+ run_sensitive_tool "$DITTO_EXECUTABLE" "$bundle" "$RESOURCES/$("$BASENAME_EXECUTABLE" "$bundle")"
229
1347
  done
230
1348
 
231
- # FAIL CLOSED on missing entitlements. Without the entitlements file the app
232
- # would be signed with no Microphone/Accessibility entitlements (or skipped and
233
- # left effectively unsigned) a silent, safety-critical regression. Refuse to
234
- # build instead.
235
- if [ ! -f RecordingsLib/Recordings.entitlements ]; then
236
- echo "ERROR: RecordingsLib/Recordings.entitlements is missing." >&2
237
- echo "Refusing to build: the app must be signed with its entitlements;" >&2
238
- echo "skipping signing would ship an unsigned/unentitled Recordings.app." >&2
239
- exit 1
1349
+ APP_SIGN_ARGUMENTS=(--force --sign "$CODESIGN_IDENTITY")
1350
+ HELPER_SIGN_ARGUMENTS=(--force --sign "$CODESIGN_IDENTITY" --options runtime)
1351
+ if [ "$MODE" != "debug" ]; then
1352
+ APP_SIGN_ARGUMENTS+=(--options runtime)
1353
+ fi
1354
+ if [ "$MODE" = "release" ]; then
1355
+ APP_SIGN_ARGUMENTS+=(--timestamp)
1356
+ HELPER_SIGN_ARGUMENTS+=(--timestamp)
240
1357
  fi
1358
+ normalize_unsigned_app_bundle_modes "$APP_DIR"
1359
+ for standalone_launch_file in \
1360
+ "$UPDATE_BROKER" \
1361
+ "$ARTIFACT_VERIFIER" \
1362
+ "$ENVELOPE_SIGNER" \
1363
+ "$BOOTSTRAP_PREFLIGHT_VERIFIER"; do
1364
+ [ -e "$standalone_launch_file" ] || [ -L "$standalone_launch_file" ] || continue
1365
+ normalize_unsigned_launch_file_mode "$standalone_launch_file" "Release launch file"
1366
+ done
241
1367
 
242
- # Sign with entitlements using a STABLE identity. A signing failure must fail
243
- # the build: silently falling back to ad-hoc would reintroduce identity churn
244
- # and invalidate existing TCC grants.
245
- #
246
- # Precedence:
247
- # 1. RECORDINGS_CODESIGN_IDENTITY — explicit identity (Developer ID, station
248
- # signing cert, or "-" for an intentional ad-hoc build). An explicit
249
- # identity that fails to sign fails the build.
250
- # 2. The per-machine "Hasna Recordings Signing" self-signed certificate in the
251
- # DEDICATED signing keychain, created once and reused, so the default build
252
- # is stably signed non-interactively (works over SSH) with no out-of-band
253
- # configuration. We never fall back to ad-hoc by default.
254
- if [ -n "${RECORDINGS_CODESIGN_IDENTITY:-}" ]; then
255
- "$CODESIGN" --force --sign "$RECORDINGS_CODESIGN_IDENTITY" --entitlements RecordingsLib/Recordings.entitlements "$APP_DIR"
256
- elif ensure_local_signing_identity; then
257
- "$CODESIGN" --force --keychain "$SIGNING_KEYCHAIN" --sign "$SIGNING_IDENTITY" --entitlements RecordingsLib/Recordings.entitlements "$APP_DIR"
1368
+ if [ "$MODE" = "release" ]; then
1369
+ run_codesign "${HELPER_SIGN_ARGUMENTS[@]}" \
1370
+ --entitlements "$HELPER_ENTITLEMENTS" \
1371
+ "$HELPERS/recordings"
258
1372
  else
259
- echo "ERROR: no stable code-signing identity is available and refusing to" >&2
260
- echo "ad-hoc sign: ad-hoc signatures change the app's designated requirement" >&2
261
- echo "on every build and break macOS Microphone/Accessibility grants." >&2
262
- echo "Set RECORDINGS_CODESIGN_IDENTITY to a valid signing identity and retry." >&2
1373
+ "$CODESIGN_EXECUTABLE" "${HELPER_SIGN_ARGUMENTS[@]}" \
1374
+ --entitlements "$HELPER_ENTITLEMENTS" \
1375
+ "$HELPERS/recordings"
1376
+ fi
1377
+
1378
+ if [ "$MODE" = "release" ] && [ "$HOST_PLATFORM" = "Darwin" ]; then
1379
+ run_codesign --force --sign "$CODESIGN_IDENTITY" --identifier com.hasna.recordings.update-client \
1380
+ --options runtime --timestamp --entitlements "$SOURCE_PACKAGE_ROOT/packaging/macos/Empty.entitlements" \
1381
+ "$UPDATE_CLIENT"
1382
+ run_codesign --force --sign "$CODESIGN_IDENTITY" --identifier com.hasna.recordings.envelope-signer \
1383
+ --options runtime --timestamp --entitlements "$SOURCE_PACKAGE_ROOT/packaging/macos/Empty.entitlements" \
1384
+ "$ENVELOPE_SIGNER"
1385
+ RELEASE_SIGNED_UPDATER_CODE=("$UPDATE_CLIENT" "$ENVELOPE_SIGNER")
1386
+ if [ "$RELEASE_SUBTYPE" = "initial-bootstrap" ]; then
1387
+ run_codesign --force --sign "$CODESIGN_IDENTITY" --identifier com.hasna.recordings.updater \
1388
+ --options runtime --timestamp --entitlements "$SOURCE_PACKAGE_ROOT/packaging/macos/Empty.entitlements" \
1389
+ "$UPDATE_BROKER"
1390
+ run_codesign --force --sign "$CODESIGN_IDENTITY" --identifier com.hasna.recordings.artifact-verifier \
1391
+ --options runtime --timestamp \
1392
+ --entitlements "$SOURCE_PACKAGE_ROOT/packaging/macos/Verifier.entitlements" \
1393
+ "$ARTIFACT_VERIFIER"
1394
+ run_codesign --force --sign "$CODESIGN_IDENTITY" --identifier com.hasna.recordings.bootstrap-preflight \
1395
+ --options runtime --timestamp --entitlements "$SOURCE_PACKAGE_ROOT/packaging/macos/Empty.entitlements" \
1396
+ "$BOOTSTRAP_PREFLIGHT_VERIFIER"
1397
+ RELEASE_SIGNED_UPDATER_CODE+=("$UPDATE_BROKER" "$ARTIFACT_VERIFIER" "$BOOTSTRAP_PREFLIGHT_VERIFIER")
1398
+ fi
1399
+ for updater_code in "${RELEASE_SIGNED_UPDATER_CODE[@]}"; do
1400
+ run_codesign --verify --strict --all-architectures --verbose=2 "$updater_code"
1401
+ done
1402
+ fi
1403
+
1404
+ verify_helper_entitlements() {
1405
+ local helper="$1"
1406
+ local entitlement_plist
1407
+ local entitlement_json
1408
+ entitlement_plist="$(run_sensitive_tool "$MKTEMP_EXECUTABLE" "$BUILD_WORK_DIR/helper-entitlements.XXXXXX")"
1409
+ if ! run_codesign -d --entitlements :- "$helper" >"$entitlement_plist" 2>/dev/null; then
1410
+ "$RM_EXECUTABLE" -f "$entitlement_plist"
1411
+ echo "Companion CLI signed entitlements could not be read back." >&2
1412
+ return 1
1413
+ fi
1414
+ entitlement_json="$(run_sensitive_tool "$PLUTIL" -convert json -o - "$entitlement_plist" | run_sensitive_tool "$TR_EXECUTABLE" -d '[:space:]')"
1415
+ "$RM_EXECUTABLE" -f "$entitlement_plist"
1416
+ if ! "$ENV_EXECUTABLE" -i \
1417
+ HOME="$BUILD_HOME" \
1418
+ PATH="$SANITIZED_PATH" \
1419
+ TMPDIR="$BUILD_WORK_DIR" \
1420
+ ENTITLEMENT_JSON="$entitlement_json" \
1421
+ "$BUN_EXECUTABLE" -e '
1422
+ const actual = JSON.parse(process.env.ENTITLEMENT_JSON ?? "null");
1423
+ const expected = [
1424
+ "com.apple.security.cs.allow-jit",
1425
+ "com.apple.security.cs.allow-unsigned-executable-memory",
1426
+ ];
1427
+ if (!actual || Array.isArray(actual)) process.exit(1);
1428
+ const keys = Object.keys(actual).sort();
1429
+ if (JSON.stringify(keys) !== JSON.stringify(expected)) process.exit(1);
1430
+ if (!keys.every((key) => actual[key] === true)) process.exit(1);
1431
+ '; then
1432
+ echo "Companion CLI has unexpected hardened-runtime entitlements: ${entitlement_json:-missing}." >&2
1433
+ return 1
1434
+ fi
1435
+ }
1436
+
1437
+ has_hardened_runtime_flag() {
1438
+ local details="$1"
1439
+ local flags
1440
+ flags="$(printf '%s\n' "$details" | run_sensitive_tool "$SED_EXECUTABLE" -n 's/^CodeDirectory .*flags=[^(]*(\([^)]*\)).*/\1/p' | run_sensitive_tool "$HEAD_EXECUTABLE" -n 1)"
1441
+ case ",$flags," in
1442
+ *,runtime,*) return 0 ;;
1443
+ *) return 1 ;;
1444
+ esac
1445
+ }
1446
+
1447
+ verify_hardened_helper() {
1448
+ local details
1449
+ run_codesign --verify --strict --verbose=2 "$HELPERS/recordings"
1450
+ details="$(run_codesign -d --verbose=4 "$HELPERS/recordings" 2>&1)"
1451
+ if ! has_hardened_runtime_flag "$details"; then
1452
+ echo "Companion CLI is missing hardened runtime signing." >&2
1453
+ exit 1
1454
+ fi
1455
+ verify_helper_entitlements "$HELPERS/recordings"
1456
+ }
1457
+
1458
+ run_signed_helper_contract() {
1459
+ local contract_home
1460
+ local version
1461
+ local project_output
1462
+ local recording_output
1463
+ local helper_executable
1464
+ contract_home="$(run_sensitive_tool "$MKTEMP_EXECUTABLE" -d "$BUILD_WORK_DIR/signed-helper-contract.XXXXXX")"
1465
+ helper_executable="$HELPERS/recordings"
1466
+ local -a contract_environment=(
1467
+ "$ENV_EXECUTABLE" -i
1468
+ HOME="$contract_home"
1469
+ PATH="/usr/bin:/bin:/usr/sbin:/sbin"
1470
+ TMPDIR="$contract_home"
1471
+ HASNA_RECORDINGS_STORAGE_MODE="local"
1472
+ RECORDINGS_STORAGE_MODE="local"
1473
+ HASNA_RECORDINGS_DB_PATH="$contract_home/recordings.db"
1474
+ RECORDINGS_AUDIO_DIR="$contract_home/audio"
1475
+ )
1476
+ contract_run() {
1477
+ (cd "$contract_home" && "${contract_environment[@]}" "$@")
1478
+ }
1479
+ if ! version="$(contract_run "$helper_executable" --version)" || \
1480
+ [ "$version" != "$VERSION" ] || \
1481
+ ! project_output="$(contract_run "$helper_executable" --json project register \
1482
+ --name "Signed Helper Contract" \
1483
+ --path "recordings-app://build/signed-helper-contract")" || \
1484
+ [[ "$project_output" != *"Signed Helper Contract"* ]] || \
1485
+ ! recording_output="$(contract_run "$helper_executable" --json save-text \
1486
+ "Signed helper contract" \
1487
+ --source "native_build_contract" \
1488
+ --post-processing off)" || \
1489
+ [[ "$recording_output" != *"Signed helper contract"* ]]; then
1490
+ "$RM_EXECUTABLE" -rf "$contract_home"
1491
+ echo "Post-sign signed companion CLI contract failed." >&2
1492
+ exit 1
1493
+ fi
1494
+ if ! "$ENV_EXECUTABLE" -i \
1495
+ HOME="$BUILD_HOME" \
1496
+ PATH="$SANITIZED_PATH" \
1497
+ TMPDIR="$BUILD_WORK_DIR" \
1498
+ PROJECT_JSON="$project_output" \
1499
+ RECORDING_JSON="$recording_output" \
1500
+ "$BUN_EXECUTABLE" -e '
1501
+ const project = JSON.parse(process.env.PROJECT_JSON ?? "null");
1502
+ const recording = JSON.parse(process.env.RECORDING_JSON ?? "null");
1503
+ if (project?.name !== "Signed Helper Contract") process.exit(1);
1504
+ if (project?.path !== "recordings-app://build/signed-helper-contract") process.exit(1);
1505
+ if (recording?.raw_text !== "Signed helper contract") process.exit(1);
1506
+ '; then
1507
+ "$RM_EXECUTABLE" -rf "$contract_home"
1508
+ echo "Post-sign signed companion CLI contract returned invalid JSON." >&2
1509
+ exit 1
1510
+ fi
1511
+ "$RM_EXECUTABLE" -rf "$contract_home"
1512
+ }
1513
+
1514
+ verify_hardened_helper
1515
+ run_signed_helper_contract
1516
+ if [ "$MODE" != "debug" ]; then
1517
+ verify_source_unchanged
1518
+ run_bun "$SOURCE_PACKAGE_ROOT/scripts/macos_artifact.ts" provenance \
1519
+ --app "$APP_DIR" \
1520
+ --source-sha "$SOURCE_SHA" \
1521
+ --team-id "$EXPECTED_TEAM_ID" \
1522
+ --package-root "$PACKAGE_ROOT" \
1523
+ --artifact-policy "$ARTIFACT_POLICY" \
1524
+ --approved-target "$APPROVED_TARGET" \
1525
+ --approved-target-identity-kind "$APPROVED_TARGET_IDENTITY_KIND" \
1526
+ --approved-target-identity-sha256 "$APPROVED_TARGET_IDENTITY_SHA256" \
1527
+ --builder-identity-kind "$BUILDER_IDENTITY_KIND" \
1528
+ --builder-identity-sha256 "$BUILDER_IDENTITY_SHA256"
1529
+ PROVENANCE_FILE="$RESOURCES/recordings-build-provenance.json"
1530
+ if [ -e "$PROVENANCE_FILE" ] || [ -L "$PROVENANCE_FILE" ]; then
1531
+ normalize_unsigned_app_data_file_mode "$PROVENANCE_FILE" "App build provenance"
1532
+ elif [ "$HOST_PLATFORM" = "Darwin" ]; then
1533
+ echo "App build provenance was not emitted: $PROVENANCE_FILE" >&2
1534
+ exit 1
1535
+ fi
1536
+ fi
1537
+ verify_app_bundle_modes "$APP_DIR"
1538
+ run_codesign "${APP_SIGN_ARGUMENTS[@]}" \
1539
+ --entitlements "$APP_ENTITLEMENTS" \
1540
+ "$APP_DIR"
1541
+
1542
+ verify_signed_code() {
1543
+ local code_path="$1"
1544
+ local label="$2"
1545
+ local details
1546
+ local authority
1547
+ local team_id
1548
+ local timestamp
1549
+ run_codesign --verify --strict --verbose=2 "$code_path"
1550
+ details="$(run_codesign -d --verbose=4 "$code_path" 2>&1)"
1551
+ authority="$(printf '%s\n' "$details" | run_sensitive_tool "$AWK_EXECUTABLE" -F= '/^Authority=/ { print $2; exit }')"
1552
+ team_id="$(printf '%s\n' "$details" | run_sensitive_tool "$AWK_EXECUTABLE" -F= '/^TeamIdentifier=/ { print $2; exit }')"
1553
+ timestamp="$(printf '%s\n' "$details" | run_sensitive_tool "$AWK_EXECUTABLE" -F= '/^Timestamp=/ { print $2; exit }')"
1554
+ if [[ "$authority" != "Developer ID Application:"* ]]; then
1555
+ echo "$label is not signed by a Developer ID Application authority." >&2
1556
+ exit 1
1557
+ fi
1558
+ if [ "$team_id" != "$EXPECTED_TEAM_ID" ]; then
1559
+ echo "$label TeamIdentifier ${team_id:-missing} does not match ${EXPECTED_TEAM_ID}." >&2
1560
+ exit 1
1561
+ fi
1562
+ if ! has_hardened_runtime_flag "$details"; then
1563
+ echo "$label is missing hardened runtime signing." >&2
1564
+ exit 1
1565
+ fi
1566
+ case "$timestamp" in
1567
+ ''|none|None|NONE)
1568
+ echo "$label is missing a trusted signing timestamp." >&2
1569
+ exit 1
1570
+ ;;
1571
+ esac
1572
+ }
1573
+
1574
+ run_codesign --verify --deep --strict --verbose=2 "$APP_DIR"
1575
+ "$ENV_EXECUTABLE" -i \
1576
+ HOME="$OPERATOR_HOME" \
1577
+ PATH="$SANITIZED_PATH" \
1578
+ TMPDIR="$BUILD_WORK_DIR" \
1579
+ SSH_CONNECTION="${SSH_CONNECTION:-}" \
1580
+ ${SMOKE_TEST_ENVIRONMENT[0]+"${SMOKE_TEST_ENVIRONMENT[@]}"} \
1581
+ "$BASH_EXECUTABLE" "$SMOKE_SCRIPT" "$APP_DIR" "$BUN_EXECUTABLE"
1582
+
1583
+ publish_app_output() {
1584
+ local source_tree_digest
1585
+ local published_tree_digest
1586
+ if [ "$MODE" = "release" ]; then
1587
+ [ ! -e "$OUTPUT_APP_DIR" ] && [ ! -L "$OUTPUT_APP_DIR" ] || {
1588
+ echo "Release staging already contains an app output; refusing replacement." >&2
1589
+ exit 1
1590
+ }
1591
+ else
1592
+ "$RM_EXECUTABLE" -rf "$OUTPUT_APP_DIR"
1593
+ fi
1594
+ run_sensitive_tool "$DITTO_EXECUTABLE" "$APP_DIR" "$OUTPUT_APP_DIR"
1595
+ if [ "$HOST_PLATFORM" = "Darwin" ]; then
1596
+ verify_app_bundle_modes "$OUTPUT_APP_DIR"
1597
+ require_app_tree_without_extended_acl "$OUTPUT_APP_DIR"
1598
+ source_tree_digest="$(run_bun "$SOURCE_PACKAGE_ROOT/scripts/macos_artifact.ts" tree-digest --path "$APP_DIR")"
1599
+ published_tree_digest="$(run_bun "$SOURCE_PACKAGE_ROOT/scripts/macos_artifact.ts" tree-digest --path "$OUTPUT_APP_DIR")"
1600
+ if [ "$published_tree_digest" != "$source_tree_digest" ]; then
1601
+ echo "Published app copy does not match the signed source tree." >&2
1602
+ exit 1
1603
+ fi
1604
+ run_codesign --verify --deep --strict --verbose=2 "$OUTPUT_APP_DIR"
1605
+ fi
1606
+ }
1607
+
1608
+ verify_complete_release_staging() {
1609
+ local output
1610
+ for output in \
1611
+ "$OUTPUT_APP_DIR" \
1612
+ "$FINAL_ARCHIVE" \
1613
+ "$FINAL_MANIFEST" \
1614
+ "$NOTARY_SUBMISSION" \
1615
+ "$NOTARY_LOG"; do
1616
+ [ -e "$output" ] && [ ! -L "$output" ] || {
1617
+ echo "Release staging is incomplete or unsafe: $output" >&2
1618
+ exit 1
1619
+ }
1620
+ done
1621
+ [ -d "$OUTPUT_APP_DIR" ] || { echo "Release staging app is not a directory." >&2; exit 1; }
1622
+ for output in "$FINAL_ARCHIVE" "$FINAL_MANIFEST" "$NOTARY_SUBMISSION" "$NOTARY_LOG"; do
1623
+ [ -f "$output" ] && [ -s "$output" ] || {
1624
+ echo "Release staging file is empty or not regular: $output" >&2
1625
+ exit 1
1626
+ }
1627
+ "$CHMOD_EXECUTABLE" 0444 "$output"
1628
+ done
1629
+ if [ "$HOST_PLATFORM" = "Darwin" ]; then
1630
+ local manifest_archive_digest manifest_notary_log_digest manifest_submitted_digest
1631
+ manifest_archive_digest="$(run_release_sensitive_tool "$PLUTIL" -extract archive.sha256 raw -o - \
1632
+ "$FINAL_MANIFEST")"
1633
+ manifest_notary_log_digest="$(run_release_sensitive_tool "$PLUTIL" -extract notarization.log_sha256 raw -o - \
1634
+ "$FINAL_MANIFEST")"
1635
+ manifest_submitted_digest="$(run_release_sensitive_tool "$PLUTIL" -extract notarization.submitted_archive_sha256 raw -o - \
1636
+ "$FINAL_MANIFEST")"
1637
+ [ "$manifest_archive_digest" = "$(run_release_sensitive_tool "$SHASUM_EXECUTABLE" -a 256 \
1638
+ "$FINAL_ARCHIVE" | run_release_sensitive_tool "$AWK_EXECUTABLE" '{ print $1 }')" ] || {
1639
+ echo "Release manifest no longer binds the finalized ZIP in staging." >&2
1640
+ exit 1
1641
+ }
1642
+ [ "$manifest_notary_log_digest" = "$(run_release_sensitive_tool "$SHASUM_EXECUTABLE" -a 256 \
1643
+ "$NOTARY_LOG" | run_release_sensitive_tool "$AWK_EXECUTABLE" '{ print $1 }')" ] || {
1644
+ echo "Release manifest no longer binds the accepted notary log in staging." >&2
1645
+ exit 1
1646
+ }
1647
+ [ "$manifest_submitted_digest" = "$NOTARY_ARCHIVE_SHA256" ] || {
1648
+ echo "Release manifest no longer binds the locally submitted archive digest." >&2
1649
+ exit 1
1650
+ }
1651
+ if [ "$RELEASE_SUBTYPE" = "initial-bootstrap" ]; then
1652
+ local package_set="$OUTPUT_BUILD_DIR/${ARTIFACT_BASENAME}-updater.release"
1653
+ local pkg="$OUTPUT_BUILD_DIR/${ARTIFACT_BASENAME}-updater.pkg"
1654
+ local pkg_digest="$OUTPUT_BUILD_DIR/${ARTIFACT_BASENAME}-updater.pkg.sha256"
1655
+ [ -d "$package_set" ] && [ ! -L "$package_set" ] || {
1656
+ echo "Initial-bootstrap staging is missing the canonical updater package set." >&2
1657
+ exit 1
1658
+ }
1659
+ run_bun "$SOURCE_PACKAGE_ROOT/scripts/macos_artifact.ts" assert-release-publication-complete \
1660
+ --destination "$package_set" \
1661
+ --output-root "$OUTPUT_BUILD_DIR" \
1662
+ --publication-identity-sha256 "$PACKAGE_PUBLICATION_IDENTITY_SHA256"
1663
+ for output in \
1664
+ "$pkg" \
1665
+ "$OUTPUT_BUILD_DIR/${ARTIFACT_BASENAME}-updater.notary-submit.json" \
1666
+ "$OUTPUT_BUILD_DIR/${ARTIFACT_BASENAME}-updater.notary-log.json" \
1667
+ "$pkg_digest" \
1668
+ "$OUTPUT_BUILD_DIR/${ARTIFACT_BASENAME}-updater.bootstrap-envelope.json" \
1669
+ "$OUTPUT_BUILD_DIR/${ARTIFACT_BASENAME}-updater.compatible-cohort.json" \
1670
+ "$OUTPUT_BUILD_DIR/${ARTIFACT_BASENAME}-updater.compatible-cohort.json.sha256"; do
1671
+ [ -f "$output" ] && [ ! -L "$output" ] && [ -s "$output" ] || {
1672
+ echo "Initial-bootstrap staging is missing a finalized package output: $output" >&2
1673
+ exit 1
1674
+ }
1675
+ [ "$("$STAT_EXECUTABLE" -f '%Lp' "$output")" = "444" ] || {
1676
+ echo "Finalized initial-bootstrap output is not read-only: $output" >&2
1677
+ exit 1
1678
+ }
1679
+ done
1680
+ [ ! -e "$OUTPUT_BUILD_DIR/${ARTIFACT_BASENAME}.update-envelope.json" ] && \
1681
+ [ ! -L "$OUTPUT_BUILD_DIR/${ARTIFACT_BASENAME}.update-envelope.json" ] || {
1682
+ echo "Initial-bootstrap staging must not contain an app-update envelope." >&2
1683
+ exit 1
1684
+ }
1685
+ local published_pkg_digest
1686
+ published_pkg_digest="$(run_release_sensitive_tool "$AWK_EXECUTABLE" 'NR == 1 { print $1 }' \
1687
+ "$pkg_digest")"
1688
+ [ "$published_pkg_digest" = "$(run_release_sensitive_tool "$SHASUM_EXECUTABLE" -a 256 \
1689
+ "$pkg" | run_release_sensitive_tool "$AWK_EXECUTABLE" '{ print $1 }')" ] || {
1690
+ echo "Initial-bootstrap PKG digest does not match the finalized PKG." >&2
1691
+ exit 1
1692
+ }
1693
+ else
1694
+ local update_envelope="$OUTPUT_BUILD_DIR/${ARTIFACT_BASENAME}.update-envelope.json"
1695
+ [ -f "$update_envelope" ] && [ ! -L "$update_envelope" ] && [ -s "$update_envelope" ] && \
1696
+ [ "$("$STAT_EXECUTABLE" -f '%Lp' "$update_envelope")" = "444" ] || {
1697
+ echo "App-update staging is missing its single finalized update envelope." >&2
1698
+ exit 1
1699
+ }
1700
+ for output in \
1701
+ "$OUTPUT_BUILD_DIR/${ARTIFACT_BASENAME}-updater.release" \
1702
+ "$OUTPUT_BUILD_DIR/${ARTIFACT_BASENAME}-updater.pkg" \
1703
+ "$OUTPUT_BUILD_DIR/${ARTIFACT_BASENAME}-updater.bootstrap-envelope.json" \
1704
+ "$OUTPUT_BUILD_DIR/${ARTIFACT_BASENAME}-updater.compatible-cohort.json"; do
1705
+ [ ! -e "$output" ] && [ ! -L "$output" ] || {
1706
+ echo "App-update staging contains a forbidden bootstrap or root-cohort output: $output" >&2
1707
+ exit 1
1708
+ }
1709
+ done
1710
+ fi
1711
+ fi
1712
+ }
1713
+
1714
+ publish_complete_release_set() {
1715
+ local output_alias
1716
+ local -a publication_arguments=()
1717
+ verify_complete_release_staging
1718
+ verify_source_unchanged
1719
+ [ ! -e "$RELEASE_FINAL_DIR" ] && [ ! -L "$RELEASE_FINAL_DIR" ] || {
1720
+ echo "Release destination appeared before immutable publication." >&2
1721
+ exit 1
1722
+ }
1723
+ for output_alias in "${RELEASE_OUTPUT_ALIASES[@]}"; do
1724
+ [ -f "$RELEASE_STAGING_DIR/$output_alias" ] || continue
1725
+ publication_arguments+=(--alias "$output_alias")
1726
+ done
1727
+ if [ "$RELEASE_SUBTYPE" = "initial-bootstrap" ]; then
1728
+ publication_arguments+=(
1729
+ --nested-publication
1730
+ "${ARTIFACT_BASENAME}-updater.release=$PACKAGE_PUBLICATION_IDENTITY_SHA256"
1731
+ )
1732
+ fi
1733
+ run_bun "$SOURCE_PACKAGE_ROOT/scripts/macos_artifact.ts" prepare-release-publication \
1734
+ --staging "$RELEASE_STAGING_DIR" \
1735
+ --destination "$RELEASE_FINAL_DIR" \
1736
+ --reservation "$RELEASE_RESERVATION" \
1737
+ --publication-identity-sha256 "$RELEASE_PUBLICATION_IDENTITY_SHA256" \
1738
+ "${publication_arguments[@]}"
1739
+ run_bun "$SOURCE_PACKAGE_ROOT/scripts/macos_artifact.ts" publish-release-directory \
1740
+ --staging "$RELEASE_STAGING_DIR" \
1741
+ --destination "$RELEASE_FINAL_DIR"
1742
+ RELEASE_DIRECTORY_PUBLISHED=1
1743
+ RELEASE_STAGING_DIR=""
1744
+ OUTPUT_BUILD_DIR="$RELEASE_FINAL_DIR"
1745
+ OUTPUT_APP_DIR="$OUTPUT_BUILD_DIR/Recordings.app"
1746
+ run_bun "$SOURCE_PACKAGE_ROOT/scripts/macos_artifact.ts" complete-release-publication \
1747
+ --destination "$RELEASE_FINAL_DIR" \
1748
+ --reservation "$RELEASE_RESERVATION" \
1749
+ --output-root "$RELEASE_OUTPUT_ROOT" \
1750
+ --publication-identity-sha256 "$RELEASE_PUBLICATION_IDENTITY_SHA256"
1751
+ run_bun "$SOURCE_PACKAGE_ROOT/scripts/macos_artifact.ts" assert-release-publication-complete \
1752
+ --destination "$RELEASE_FINAL_DIR" \
1753
+ --output-root "$RELEASE_OUTPUT_ROOT" \
1754
+ --publication-identity-sha256 "$RELEASE_PUBLICATION_IDENTITY_SHA256"
1755
+ RELEASE_RESERVATION_OWNED=0
1756
+ FINAL_ARCHIVE="$RELEASE_FINAL_DIR/${ARTIFACT_BASENAME}.zip"
1757
+ FINAL_MANIFEST="$RELEASE_FINAL_DIR/${ARTIFACT_BASENAME}.manifest.json"
1758
+ NOTARY_SUBMISSION="$RELEASE_FINAL_DIR/${ARTIFACT_BASENAME}.notary-submit.json"
1759
+ NOTARY_LOG="$RELEASE_FINAL_DIR/${ARTIFACT_BASENAME}.notary-log.json"
1760
+ }
1761
+
1762
+ if [ "$MODE" = "debug" ]; then
1763
+ verify_source_unchanged
1764
+ publish_app_output
1765
+ verify_source_unchanged
1766
+ echo "Built non-distributable debug app: $OUTPUT_APP_DIR"
1767
+ exit 0
1768
+ fi
1769
+
1770
+ if [ "$MODE" = "local" ]; then
1771
+ ARTIFACT_BASENAME="Recordings-${VERSION}-macos-${APPROVED_TARGET}-local-only"
1772
+ FINAL_ARCHIVE="$OUTPUT_BUILD_DIR/${ARTIFACT_BASENAME}.zip"
1773
+ FINAL_MANIFEST="$OUTPUT_BUILD_DIR/${ARTIFACT_BASENAME}.manifest.json"
1774
+ "$RM_EXECUTABLE" -f "$FINAL_ARCHIVE" "$FINAL_MANIFEST"
1775
+ run_codesign --verify --deep --strict --all-architectures --verbose=2 "$APP_DIR"
1776
+ "$DITTO_EXECUTABLE" -c -k --sequesterRsrc --keepParent "$APP_DIR" "$FINAL_ARCHIVE"
1777
+ verify_source_unchanged
1778
+ run_bun "$SOURCE_PACKAGE_ROOT/scripts/macos_artifact.ts" finalize-local \
1779
+ --app "$APP_DIR" \
1780
+ --source-sha "$SOURCE_SHA" \
1781
+ --archive "$FINAL_ARCHIVE" \
1782
+ --manifest "$FINAL_MANIFEST" \
1783
+ --package-root "$PACKAGE_ROOT" \
1784
+ --approved-target "$APPROVED_TARGET" \
1785
+ --approved-target-identity-kind "$APPROVED_TARGET_IDENTITY_KIND" \
1786
+ --approved-target-identity-sha256 "$APPROVED_TARGET_IDENTITY_SHA256"
1787
+ publish_app_output
1788
+ verify_source_unchanged
1789
+ echo "Built immutable local-only app artifact: $FINAL_ARCHIVE"
1790
+ echo "Built local-only artifact manifest: $FINAL_MANIFEST"
1791
+ echo "This artifact is not notarized and is approved only for ${APPROVED_TARGET}."
1792
+ exit 0
1793
+ fi
1794
+
1795
+ verify_signed_code "$HELPERS/recordings" "Companion CLI"
1796
+ verify_signed_code "$APP_DIR" "Recordings.app"
1797
+ ARTIFACT_BASENAME="Recordings-${VERSION}-macos-${RELEASE_SUBTYPE}"
1798
+ NOTARY_ARCHIVE="$OUTPUT_BUILD_DIR/${ARTIFACT_BASENAME}-notarization.zip"
1799
+ FINAL_ARCHIVE="$OUTPUT_BUILD_DIR/${ARTIFACT_BASENAME}.zip"
1800
+ FINAL_MANIFEST="$OUTPUT_BUILD_DIR/${ARTIFACT_BASENAME}.manifest.json"
1801
+ NOTARY_SUBMISSION="$OUTPUT_BUILD_DIR/${ARTIFACT_BASENAME}.notary-submit.json"
1802
+ NOTARY_LOG="$OUTPUT_BUILD_DIR/${ARTIFACT_BASENAME}.notary-log.json"
1803
+
1804
+ for release_staging_output in \
1805
+ "$NOTARY_ARCHIVE" \
1806
+ "$FINAL_ARCHIVE" \
1807
+ "$FINAL_MANIFEST" \
1808
+ "$NOTARY_SUBMISSION" \
1809
+ "$NOTARY_LOG"; do
1810
+ [ ! -e "$release_staging_output" ] && [ ! -L "$release_staging_output" ] || {
1811
+ echo "Release staging output already exists; refusing replacement: $release_staging_output" >&2
1812
+ exit 1
1813
+ }
1814
+ done
1815
+ run_release_sensitive_tool "$DITTO_EXECUTABLE" -c -k --sequesterRsrc --keepParent "$APP_DIR" "$NOTARY_ARCHIVE"
1816
+ NOTARY_ARCHIVE_SHA256="$(run_release_sensitive_tool "$SHASUM_EXECUTABLE" -a 256 "$NOTARY_ARCHIVE" | run_release_sensitive_tool "$AWK_EXECUTABLE" '{print $1}')"
1817
+ run_xcrun notarytool submit "$NOTARY_ARCHIVE" \
1818
+ --keychain-profile "$NOTARY_PROFILE" \
1819
+ --wait --output-format json >"$NOTARY_SUBMISSION"
1820
+ NOTARY_ID="$("$ENV_EXECUTABLE" -i \
1821
+ HOME="$BUILD_HOME" \
1822
+ PATH="$SANITIZED_PATH" \
1823
+ TMPDIR="$BUILD_WORK_DIR" \
1824
+ NOTARY_SUBMISSION_JSON="$(run_release_sensitive_tool "$CAT_EXECUTABLE" "$NOTARY_SUBMISSION")" \
1825
+ "$BUN_EXECUTABLE" -e '
1826
+ const value = JSON.parse(process.env.NOTARY_SUBMISSION_JSON ?? "null");
1827
+ if (value?.status !== "Accepted" || typeof value?.id !== "string" || !value.id) process.exit(1);
1828
+ process.stdout.write(value.id);
1829
+ ')" || {
1830
+ echo "Notarization submission was not accepted or omitted its submission ID." >&2
1831
+ exit 1
1832
+ }
1833
+ run_xcrun notarytool log "$NOTARY_ID" \
1834
+ --keychain-profile "$NOTARY_PROFILE" >"$NOTARY_LOG"
1835
+ if ! run_bun "$SOURCE_PACKAGE_ROOT/scripts/macos_artifact.ts" assert-notary-log \
1836
+ --notary-log "$NOTARY_LOG" \
1837
+ --submission-id "$NOTARY_ID" \
1838
+ --submitted-archive-sha256 "$NOTARY_ARCHIVE_SHA256"; then
1839
+ echo "Accepted notarization log contains a rejected status or reported issues." >&2
263
1840
  exit 1
264
1841
  fi
1842
+ "$RM_EXECUTABLE" -f "$NOTARY_ARCHIVE"
1843
+ run_xcrun stapler staple "$APP_DIR"
1844
+ run_xcrun stapler validate "$APP_DIR"
1845
+ run_release_sensitive_tool "$SPCTL_EXECUTABLE" --assess --type execute --verbose=2 "$APP_DIR"
1846
+ run_release_sensitive_tool "$SYSPOLICY_CHECK_EXECUTABLE" distribution "$APP_DIR"
1847
+ verify_signed_code "$HELPERS/recordings" "Companion CLI"
1848
+ verify_signed_code "$APP_DIR" "Recordings.app"
1849
+ run_codesign --verify --deep --strict --verbose=2 "$APP_DIR"
1850
+
1851
+ run_release_sensitive_tool "$DITTO_EXECUTABLE" -c -k --sequesterRsrc --keepParent "$APP_DIR" "$FINAL_ARCHIVE"
1852
+ verify_source_unchanged
1853
+ run_bun "$SOURCE_PACKAGE_ROOT/scripts/macos_artifact.ts" finalize \
1854
+ --app "$APP_DIR" \
1855
+ --source-sha "$SOURCE_SHA" \
1856
+ --archive "$FINAL_ARCHIVE" \
1857
+ --manifest "$FINAL_MANIFEST" \
1858
+ --package-root "$PACKAGE_ROOT" \
1859
+ --team-id "$EXPECTED_TEAM_ID" \
1860
+ --notary-log "$NOTARY_LOG" \
1861
+ --notary-submission-id "$NOTARY_ID" \
1862
+ --submitted-archive-sha256 "$NOTARY_ARCHIVE_SHA256"
1863
+
1864
+ designated_requirement() {
1865
+ local code_path="$1"
1866
+ local requirement
1867
+ requirement="$(run_codesign -d -r- "$code_path" 2>&1 | \
1868
+ run_release_sensitive_tool "$SED_EXECUTABLE" -n 's/^designated => //p' | \
1869
+ run_release_sensitive_tool "$HEAD_EXECUTABLE" -n 1)"
1870
+ [ -n "$requirement" ] || {
1871
+ echo "Signed component is missing its designated requirement: $code_path" >&2
1872
+ return 1
1873
+ }
1874
+ printf '%s\n' "$requirement"
1875
+ }
1876
+
1877
+ build_app_update_envelope() {
1878
+ local payload="$BUILD_WORK_DIR/app-update-envelope-payload.json"
1879
+ local update_envelope="$OUTPUT_BUILD_DIR/${ARTIFACT_BASENAME}.update-envelope.json"
1880
+ [ "$HOST_PLATFORM" = "Darwin" ] || [ -n "$TEST_SWIFT_EXECUTABLE" ] || {
1881
+ echo "App-update release envelopes can only be built on macOS." >&2
1882
+ return 1
1883
+ }
1884
+ [ -f "$COMPATIBLE_COHORT_SNAPSHOT" ] && [ ! -L "$COMPATIBLE_COHORT_SNAPSHOT" ] || {
1885
+ echo "App-update compatible-cohort snapshot is missing or unsafe." >&2
1886
+ return 1
1887
+ }
1888
+ [ ! -e "$update_envelope" ] && [ ! -L "$update_envelope" ] || {
1889
+ echo "App-update envelope output already exists; refusing replacement." >&2
1890
+ return 1
1891
+ }
1892
+ run_bun "$RELEASE_LIFECYCLE_HELPER" write-update-payload \
1893
+ --app-archive "$FINAL_ARCHIVE" \
1894
+ --application-designated-requirement "$(designated_requirement "$APP_DIR")" \
1895
+ --compatible-cohort-manifest "$COMPATIBLE_COHORT_SNAPSHOT" \
1896
+ --envelope-public-key "$ENVELOPE_PUBLIC_KEY_SNAPSHOT" \
1897
+ --expires-at-utc "$ENVELOPE_EXPIRES_AT_UTC" \
1898
+ --key-epoch "$KEY_EPOCH" \
1899
+ --manifest "$FINAL_MANIFEST" \
1900
+ --output "$payload" \
1901
+ --release-sequence "$RELEASE_SEQUENCE" \
1902
+ --source-sha "$SOURCE_SHA" \
1903
+ --team-id "$EXPECTED_TEAM_ID" \
1904
+ --update-client "$UPDATE_CLIENT" \
1905
+ --update-client-designated-requirement "$(designated_requirement "$UPDATE_CLIENT")" \
1906
+ --version "$VERSION"
1907
+ run_release_sensitive_tool "$ENVELOPE_SIGNER" \
1908
+ --payload "$payload" \
1909
+ --private-key "$ENVELOPE_PRIVATE_KEY" \
1910
+ --public-key "$ENVELOPE_PUBLIC_KEY_SNAPSHOT" \
1911
+ --output "$update_envelope"
1912
+ [ -f "$update_envelope" ] && [ ! -L "$update_envelope" ] && [ -s "$update_envelope" ] || {
1913
+ echo "Envelope signer did not emit exactly one app-update sidecar." >&2
1914
+ return 1
1915
+ }
1916
+ "$CHMOD_EXECUTABLE" 0444 "$update_envelope"
1917
+ }
1918
+
1919
+ verify_source_unchanged
1920
+
1921
+ compute_package_publication_identity() {
1922
+ local app_archive_sha256 manifest_sha256 broker_sha256 verifier_sha256
1923
+ local bootstrap_preflight_sha256 envelope_public_key_sha256 envelope_signer_sha256
1924
+ if [ "$HOST_PLATFORM" != "Darwin" ] && [ -n "$TEST_SWIFT_EXECUTABLE" ]; then
1925
+ PACKAGE_PUBLICATION_IDENTITY_SHA256="$(run_bun \
1926
+ "$SOURCE_PACKAGE_ROOT/scripts/macos_artifact.ts" release-publication-identity \
1927
+ --component "release_kind=initial-bootstrap-updater" \
1928
+ --component "source_sha=$SOURCE_SHA" \
1929
+ --component "version=$VERSION" \
1930
+ --component "fixture_mode=non-darwin-contract")"
1931
+ [[ "$PACKAGE_PUBLICATION_IDENTITY_SHA256" =~ ^[a-f0-9]{64}$ ]] || {
1932
+ echo "Updater package publication identity is invalid." >&2
1933
+ return 1
1934
+ }
1935
+ return
1936
+ fi
1937
+ app_archive_sha256="$(run_release_sensitive_tool "$SHASUM_EXECUTABLE" -a 256 \
1938
+ "$FINAL_ARCHIVE" | run_release_sensitive_tool "$AWK_EXECUTABLE" '{ print $1 }')"
1939
+ manifest_sha256="$(run_release_sensitive_tool "$SHASUM_EXECUTABLE" -a 256 \
1940
+ "$FINAL_MANIFEST" | run_release_sensitive_tool "$AWK_EXECUTABLE" '{ print $1 }')"
1941
+ broker_sha256="$(run_release_sensitive_tool "$SHASUM_EXECUTABLE" -a 256 \
1942
+ "$UPDATE_BROKER" | run_release_sensitive_tool "$AWK_EXECUTABLE" '{ print $1 }')"
1943
+ verifier_sha256="$(run_release_sensitive_tool "$SHASUM_EXECUTABLE" -a 256 \
1944
+ "$ARTIFACT_VERIFIER" | run_release_sensitive_tool "$AWK_EXECUTABLE" '{ print $1 }')"
1945
+ bootstrap_preflight_sha256="$(run_release_sensitive_tool "$SHASUM_EXECUTABLE" -a 256 \
1946
+ "$BOOTSTRAP_PREFLIGHT_VERIFIER" | run_release_sensitive_tool "$AWK_EXECUTABLE" '{ print $1 }')"
1947
+ envelope_public_key_sha256="$ENVELOPE_PUBLIC_KEY_SHA256"
1948
+ envelope_signer_sha256="$(run_release_sensitive_tool "$SHASUM_EXECUTABLE" -a 256 \
1949
+ "$ENVELOPE_SIGNER" | run_release_sensitive_tool "$AWK_EXECUTABLE" '{ print $1 }')"
1950
+ PACKAGE_PUBLICATION_IDENTITY_SHA256="$(run_bun \
1951
+ "$SOURCE_PACKAGE_ROOT/scripts/macos_artifact.ts" release-publication-identity \
1952
+ --component "release_kind=initial-bootstrap-updater" \
1953
+ --component "source_sha=$SOURCE_SHA" \
1954
+ --component "version=$VERSION" \
1955
+ --component "team_id=$EXPECTED_TEAM_ID" \
1956
+ --component "installer_identity=$INSTALLER_IDENTITY" \
1957
+ --component "notary_profile=$NOTARY_PROFILE" \
1958
+ --component "release_sequence=$RELEASE_SEQUENCE" \
1959
+ --component "key_epoch=$KEY_EPOCH" \
1960
+ --component "expires_at_utc=$ENVELOPE_EXPIRES_AT_UTC" \
1961
+ --component "app_archive_sha256=$app_archive_sha256" \
1962
+ --component "manifest_sha256=$manifest_sha256" \
1963
+ --component "broker_sha256=$broker_sha256" \
1964
+ --component "artifact_verifier_sha256=$verifier_sha256" \
1965
+ --component "bootstrap_preflight_sha256=$bootstrap_preflight_sha256" \
1966
+ --component "envelope_public_key_sha256=$envelope_public_key_sha256" \
1967
+ --component "envelope_signer_sha256=$envelope_signer_sha256")"
1968
+ [[ "$PACKAGE_PUBLICATION_IDENTITY_SHA256" =~ ^[a-f0-9]{64}$ ]] || {
1969
+ echo "Updater package publication identity is invalid." >&2
1970
+ return 1
1971
+ }
1972
+ }
1973
+
1974
+ if [ "$RELEASE_SUBTYPE" = "initial-bootstrap" ]; then
1975
+ compute_package_publication_identity
1976
+ run_release_sensitive_tool "$BASH_EXECUTABLE" \
1977
+ "$SOURCE_PACKAGE_ROOT/packaging/macos/build_release_pkg.sh" \
1978
+ --source-root "$SOURCE_PACKAGE_ROOT" \
1979
+ --app "$APP_DIR" \
1980
+ --app-archive "$FINAL_ARCHIVE" \
1981
+ --artifact-basename "$ARTIFACT_BASENAME" \
1982
+ --manifest "$FINAL_MANIFEST" \
1983
+ --broker "$UPDATE_BROKER" \
1984
+ --verifier "$ARTIFACT_VERIFIER" \
1985
+ --public-key "$ENVELOPE_PUBLIC_KEY_SNAPSHOT" \
1986
+ --envelope-private-key "$ENVELOPE_PRIVATE_KEY" \
1987
+ --envelope-signer "$ENVELOPE_SIGNER" \
1988
+ --bootstrap-preflight-verifier "$BOOTSTRAP_PREFLIGHT_VERIFIER" \
1989
+ --bun-executable "$BUN_EXECUTABLE" \
1990
+ --version "$VERSION" \
1991
+ --source-sha "$SOURCE_SHA" \
1992
+ --team-id "$EXPECTED_TEAM_ID" \
1993
+ --installer-identity "$INSTALLER_IDENTITY" \
1994
+ --notary-profile "$NOTARY_PROFILE" \
1995
+ --release-sequence "$RELEASE_SEQUENCE" \
1996
+ --key-epoch "$KEY_EPOCH" \
1997
+ --expires-at-utc "$ENVELOPE_EXPIRES_AT_UTC" \
1998
+ --publication-identity-sha256 "$PACKAGE_PUBLICATION_IDENTITY_SHA256" \
1999
+ --output-dir "$OUTPUT_BUILD_DIR"
2000
+ else
2001
+ build_app_update_envelope
2002
+ fi
265
2003
 
266
- echo "✓ Built $APP_DIR"
267
- echo ""
268
- echo "To install to ~/.hasna/recordings/:"
269
- echo " cp -r $APP_DIR ~/.hasna/recordings/Recordings.app"
270
- echo ""
271
- echo "To run:"
272
- echo " open $APP_DIR"
2004
+ publish_app_output
2005
+ publish_complete_release_set
2006
+ verify_source_unchanged
2007
+ echo "Built immutable app artifact: $FINAL_ARCHIVE"
2008
+ echo "Built artifact manifest: $FINAL_MANIFEST"
2009
+ echo "Captured accepted notarization log: $NOTARY_LOG"