@hasna/recordings 0.2.10 → 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 +350 -35
  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 +1922 -77
  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 +1990 -29
  167. package/src/native/Recordings/RecordingsLib/MenuBarPopover.swift +0 -380
@@ -1,48 +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
- echo "Building Recordings.app ($MODE)..."
12
- swift build -c "$MODE" --product App
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
397
+ fi
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
+ "$@"
534
+ fi
535
+ }
536
+
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
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")
660
+ fi
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
789
+ fi
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
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
802
+ fi
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
831
+ fi
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
857
+ fi
858
+ }
859
+
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"
874
+ }
13
875
 
14
- # Create .app bundle
15
- BUILD_DIR=".build/$MODE"
16
- APP_DIR="$BUILD_DIR/Recordings.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
1050
+
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"
17
1081
  CONTENTS="$APP_DIR/Contents"
18
1082
  MACOS="$CONTENTS/MacOS"
19
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"
1087
+
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
20
1172
 
21
- rm -rf "$APP_DIR"
22
- mkdir -p "$MACOS" "$RESOURCES"
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")"
23
1239
 
24
- # Copy binary
25
- cp "$BUILD_DIR/App" "$MACOS/Recordings"
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
+ }
26
1266
 
27
- # Copy Info.plist
28
- cp RecordingsLib/Info.plist "$CONTENTS/Info.plist"
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
+ }
29
1338
 
30
- # Copy SwiftPM resource bundles used by Bundle.module.
31
- for bundle in "$BUILD_DIR"/*.resources "$BUILD_DIR"/*.bundle .build/*/"$MODE"/*.resources .build/*/"$MODE"/*.bundle; do
1339
+ if [ "$MODE" = "release" ]; then
1340
+ reserve_release_output
1341
+ fi
1342
+
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
32
1344
  [ -e "$bundle" ] || continue
33
- rm -rf "$RESOURCES/$(basename "$bundle")"
34
- ditto "$bundle" "$RESOURCES/$(basename "$bundle")"
1345
+ "$RM_EXECUTABLE" -rf "$RESOURCES/$("$BASENAME_EXECUTABLE" "$bundle")"
1346
+ run_sensitive_tool "$DITTO_EXECUTABLE" "$bundle" "$RESOURCES/$("$BASENAME_EXECUTABLE" "$bundle")"
35
1347
  done
36
1348
 
37
- # Copy entitlements (for codesigning)
38
- if [ -f RecordingsLib/Recordings.entitlements ]; then
39
- codesign --force --sign - --entitlements RecordingsLib/Recordings.entitlements "$APP_DIR" 2>/dev/null || true
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)
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
1367
+
1368
+ if [ "$MODE" = "release" ]; then
1369
+ run_codesign "${HELPER_SIGN_ARGUMENTS[@]}" \
1370
+ --entitlements "$HELPER_ENTITLEMENTS" \
1371
+ "$HELPERS/recordings"
1372
+ else
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
1840
+ exit 1
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
40
2002
  fi
41
2003
 
42
- echo "✓ Built $APP_DIR"
43
- echo ""
44
- echo "To install to ~/.hasna/recordings/:"
45
- echo " cp -r $APP_DIR ~/.hasna/recordings/Recordings.app"
46
- echo ""
47
- echo "To run:"
48
- 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"