@hasna/recordings 0.2.11 → 0.2.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (167) hide show
  1. package/README.md +349 -69
  2. package/bun.lock +3 -0
  3. package/dist/__tests__/helpers/native-fs-guard.d.ts +2 -0
  4. package/dist/__tests__/helpers/native-fs-guard.d.ts.map +1 -0
  5. package/dist/cli/index.js +1728 -320
  6. package/dist/cli/macos-permissions.d.ts +13 -0
  7. package/dist/cli/macos-permissions.d.ts.map +1 -0
  8. package/dist/cli/options.d.ts +12 -0
  9. package/dist/cli/options.d.ts.map +1 -1
  10. package/dist/db/pg-migrations.d.ts.map +1 -1
  11. package/dist/db/recordings.d.ts +2 -1
  12. package/dist/db/recordings.d.ts.map +1 -1
  13. package/dist/db/remote-storage.d.ts +5 -1
  14. package/dist/db/remote-storage.d.ts.map +1 -1
  15. package/dist/index.d.ts +2 -2
  16. package/dist/index.d.ts.map +1 -1
  17. package/dist/index.js +628 -147
  18. package/dist/lib/bun-runtime.d.ts +11 -0
  19. package/dist/lib/bun-runtime.d.ts.map +1 -0
  20. package/dist/lib/config.d.ts +8 -1
  21. package/dist/lib/config.d.ts.map +1 -1
  22. package/dist/lib/enhancer.d.ts +10 -2
  23. package/dist/lib/enhancer.d.ts.map +1 -1
  24. package/dist/lib/install-maintenance.d.ts +24 -0
  25. package/dist/lib/install-maintenance.d.ts.map +1 -0
  26. package/dist/lib/machine.d.ts +2 -0
  27. package/dist/lib/machine.d.ts.map +1 -0
  28. package/dist/lib/recorder.d.ts +2 -1
  29. package/dist/lib/recorder.d.ts.map +1 -1
  30. package/dist/lib/recording-create-identity.d.ts +13 -0
  31. package/dist/lib/recording-create-identity.d.ts.map +1 -0
  32. package/dist/lib/release-install-policy.d.ts +31 -0
  33. package/dist/lib/release-install-policy.d.ts.map +1 -0
  34. package/dist/lib/transcriber.d.ts.map +1 -1
  35. package/dist/mcp/index.d.ts.map +1 -1
  36. package/dist/mcp/index.js +977 -194
  37. package/dist/sdk/index.js +5 -1
  38. package/dist/sdk/v1.generated.d.ts +4 -0
  39. package/dist/sdk/v1.generated.d.ts.map +1 -1
  40. package/dist/server/cloud-config.d.ts +9 -0
  41. package/dist/server/cloud-config.d.ts.map +1 -0
  42. package/dist/server/cloud-readiness.d.ts +10 -0
  43. package/dist/server/cloud-readiness.d.ts.map +1 -0
  44. package/dist/server/cloud.d.ts +9 -18
  45. package/dist/server/cloud.d.ts.map +1 -1
  46. package/dist/server/index.js +1654 -304
  47. package/dist/server/migrate-command.d.ts +11 -0
  48. package/dist/server/migrate-command.d.ts.map +1 -0
  49. package/dist/server/openapi.d.ts +22 -0
  50. package/dist/server/openapi.d.ts.map +1 -1
  51. package/dist/server/repo.d.ts +8 -1
  52. package/dist/server/repo.d.ts.map +1 -1
  53. package/dist/server/serve.d.ts +6 -1
  54. package/dist/server/serve.d.ts.map +1 -1
  55. package/dist/server/v1.d.ts.map +1 -1
  56. package/dist/storage.js +515 -80
  57. package/dist/store.d.ts +3 -1
  58. package/dist/store.d.ts.map +1 -1
  59. package/dist/types/index.d.ts +10 -0
  60. package/dist/types/index.d.ts.map +1 -1
  61. package/dist/version.d.ts +1 -1
  62. package/package.json +7 -4
  63. package/packaging/macos/Empty.entitlements +5 -0
  64. package/packaging/macos/Library/LaunchDaemons/com.hasna.recordings.updater.plist +34 -0
  65. package/packaging/macos/Verifier.entitlements +10 -0
  66. package/packaging/macos/artifact-verifier.sb +33 -0
  67. package/packaging/macos/build_release_pkg.sh +872 -0
  68. package/packaging/macos/managed_bootstrap.sh +623 -0
  69. package/packaging/macos/pkgutil_fingerprint.awk +37 -0
  70. package/packaging/macos/release_lifecycle.ts +463 -0
  71. package/packaging/macos/scripts/postinstall +565 -0
  72. package/packaging/macos/scripts/preinstall +297 -0
  73. package/scripts/build_companion_cli.sh +337 -0
  74. package/scripts/build_native_fs_guard.sh +59 -0
  75. package/scripts/generate-sdk.ts +19 -1
  76. package/scripts/install_macos_app.sh +1919 -98
  77. package/scripts/macos_artifact.ts +5316 -0
  78. package/scripts/migrate.ts +38 -10
  79. package/scripts/native/prebuilds/darwin-universal/recordings_fs_guard.node +0 -0
  80. package/scripts/native/recordings_fs_guard.c +1167 -0
  81. package/scripts/native_fs_guard.ts +158 -0
  82. package/scripts/release-guard.ts +20 -1
  83. package/scripts/resolve_tailscale_cli.sh +389 -0
  84. package/scripts/smoke_macos_app.sh +573 -0
  85. package/src/native/Recordings/App/ContentView.swift +83 -0
  86. package/src/native/Recordings/App/MenuBarStatusView.swift +102 -0
  87. package/src/native/Recordings/App/RecordWorkspaceView.swift +468 -0
  88. package/src/native/Recordings/App/RecordingDetailView.swift +138 -0
  89. package/src/native/Recordings/App/RecordingsApp.swift +367 -44
  90. package/src/native/Recordings/App/RecordingsListView.swift +130 -0
  91. package/src/native/Recordings/App/RecordingsStore.swift +206 -0
  92. package/src/native/Recordings/App/RuntimeSmoke.swift +158 -0
  93. package/src/native/Recordings/App/SidebarView.swift +212 -0
  94. package/src/native/Recordings/App/Theme.swift +87 -0
  95. package/src/native/Recordings/Package.resolved +5 -5
  96. package/src/native/Recordings/Package.swift +62 -2
  97. package/src/native/Recordings/RecordingsLib/AccessibilityPromptGate.swift +104 -0
  98. package/src/native/Recordings/RecordingsLib/BrandAssets.swift +0 -32
  99. package/src/native/Recordings/RecordingsLib/ChromeSurface.swift +17 -0
  100. package/src/native/Recordings/RecordingsLib/Info.plist +7 -3
  101. package/src/native/Recordings/RecordingsLib/MenuBarPresentation.swift +37 -0
  102. package/src/native/Recordings/RecordingsLib/NativeAppDiagnostics.swift +32 -2
  103. package/src/native/Recordings/RecordingsLib/NativeMachineIdentity.swift +24 -0
  104. package/src/native/Recordings/RecordingsLib/NativePCMRecorder.swift +262 -23
  105. package/src/native/Recordings/RecordingsLib/OpenAIAPIKeyStore.swift +56 -0
  106. package/src/native/Recordings/RecordingsLib/PermissionRequestLaunchPlan.swift +56 -0
  107. package/src/native/Recordings/RecordingsLib/ProjectStore.swift +321 -25
  108. package/src/native/Recordings/RecordingsLib/RealtimeTranscriptionClient.swift +605 -90
  109. package/src/native/Recordings/RecordingsLib/Recording.swift +141 -0
  110. package/src/native/Recordings/RecordingsLib/RecordingEngine.swift +3704 -376
  111. package/src/native/Recordings/RecordingsLib/RecordingStartControlPresentation.swift +29 -0
  112. package/src/native/Recordings/RecordingsLib/RecordingsCLI.entitlements +10 -0
  113. package/src/native/Recordings/RecordingsLib/RecordingsCLI.swift +222 -0
  114. package/src/native/Recordings/RecordingsLib/SettingsView.swift +45 -11
  115. package/src/native/Recordings/RecordingsLib/SpeechIntent.swift +331 -0
  116. package/src/native/Recordings/RecordingsLib/SpeechIntentClassifier.swift +232 -0
  117. package/src/native/Recordings/RecordingsLib/VoiceShortcuts.swift +17 -7
  118. package/src/native/Recordings/RecordingsTests/CLIRunnerTests.swift +1343 -0
  119. package/src/native/Recordings/RecordingsTests/MenuBarPresentationTests.swift +98 -0
  120. package/src/native/Recordings/RecordingsTests/NativeAppDiagnosticsTests.swift +20 -0
  121. package/src/native/Recordings/RecordingsTests/NativePCMRecorderTests.swift +370 -1
  122. package/src/native/Recordings/RecordingsTests/PasteTargetTests.swift +987 -1
  123. package/src/native/Recordings/RecordingsTests/ProjectStoreTests.swift +385 -0
  124. package/src/native/Recordings/RecordingsTests/RealtimeTranscriptionTests.swift +835 -8
  125. package/src/native/Recordings/RecordingsTests/RecordingBridgeTests.swift +180 -0
  126. package/src/native/Recordings/RecordingsTests/RecordingEngineDeliveryTests.swift +760 -0
  127. package/src/native/Recordings/RecordingsTests/RecordingStartControlPresentationTests.swift +42 -0
  128. package/src/native/Recordings/RecordingsTests/RecordingStartGateTests.swift +264 -0
  129. package/src/native/Recordings/RecordingsTests/RecordingStartTimingTests.swift +129 -0
  130. package/src/native/Recordings/RecordingsTests/SpeechIntentTests.swift +600 -0
  131. package/src/native/Recordings/RecordingsTests/TranscriptResolutionTests.swift +62 -1
  132. package/src/native/Recordings/RecordingsTests/TranscriptionResultIdentityTests.swift +21 -0
  133. package/src/native/Recordings/Updater/BootstrapPreflight/BootstrapPreflightMain.swift +739 -0
  134. package/src/native/Recordings/Updater/Broker/ActivationRecoveryPolicy.swift +254 -0
  135. package/src/native/Recordings/Updater/Broker/ApplicationNamespace.swift +463 -0
  136. package/src/native/Recordings/Updater/Broker/ApplicationProcessQuiescence.swift +55 -0
  137. package/src/native/Recordings/Updater/Broker/ArtifactIngest.swift +341 -0
  138. package/src/native/Recordings/Updater/Broker/AtomicActivation.swift +477 -0
  139. package/src/native/Recordings/Updater/Broker/BrokerMain.swift +624 -0
  140. package/src/native/Recordings/Updater/Broker/CanonicalTreeCopy.swift +63 -0
  141. package/src/native/Recordings/Updater/Broker/CodeValidation.swift +561 -0
  142. package/src/native/Recordings/Updater/Broker/DarwinACLValidator.swift +66 -0
  143. package/src/native/Recordings/Updater/Broker/HostOSProductVersion.swift +50 -0
  144. package/src/native/Recordings/Updater/Broker/InstallJournal.swift +362 -0
  145. package/src/native/Recordings/Updater/Broker/InstallRecovery.swift +662 -0
  146. package/src/native/Recordings/Updater/Broker/MonotonicState.swift +456 -0
  147. package/src/native/Recordings/Updater/Broker/PeerIdentity.swift +274 -0
  148. package/src/native/Recordings/Updater/Broker/VerifierRunner.swift +81 -0
  149. package/src/native/Recordings/Updater/BrokerTests/ActivationRecoveryPolicyTests.swift +259 -0
  150. package/src/native/Recordings/Updater/BrokerTests/CanonicalReleaseOrderTests.swift +21 -0
  151. package/src/native/Recordings/Updater/Client/ClientMain.swift +119 -0
  152. package/src/native/Recordings/Updater/Protocol/BoundedProcess.swift +159 -0
  153. package/src/native/Recordings/Updater/Protocol/CandidateMetadataPolicy.swift +214 -0
  154. package/src/native/Recordings/Updater/Protocol/HostOSVersionPolicy.swift +55 -0
  155. package/src/native/Recordings/Updater/Protocol/MonotonicReleasePolicy.swift +152 -0
  156. package/src/native/Recordings/Updater/Protocol/ReleaseEnvelope.swift +229 -0
  157. package/src/native/Recordings/Updater/Protocol/UpdateProtocol.swift +119 -0
  158. package/src/native/Recordings/Updater/ProtocolTests/BoundedProcessRunnerTests.swift +60 -0
  159. package/src/native/Recordings/Updater/ProtocolTests/CandidateMetadataPolicyTests.swift +168 -0
  160. package/src/native/Recordings/Updater/ProtocolTests/HostOSVersionPolicyTests.swift +62 -0
  161. package/src/native/Recordings/Updater/ProtocolTests/MonotonicReleasePolicyTests.swift +172 -0
  162. package/src/native/Recordings/Updater/ProtocolTests/ReleaseEnvelopeValidationTests.swift +65 -0
  163. package/src/native/Recordings/Updater/Signer/SignerMain.swift +210 -0
  164. package/src/native/Recordings/Updater/VerifierLauncher/RecordingsVerifierLauncher.c +725 -0
  165. package/src/native/Recordings/Updater/VerifierLauncher/include/RecordingsVerifierLauncher.h +33 -0
  166. package/src/native/Recordings/build.sh +1973 -236
  167. package/src/native/Recordings/RecordingsLib/MenuBarPopover.swift +0 -380
@@ -0,0 +1,565 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ umask 077
4
+
5
+ export PATH=/usr/bin:/bin:/usr/sbin:/sbin
6
+ export LC_ALL=C
7
+ export LANG=C
8
+ export TZ=UTC0
9
+
10
+ PACKAGE_BOOTSTRAP_ID="__RECORDINGS_PACKAGE_BOOTSTRAP_ID__"
11
+ AUTHORIZATION_ROOT="/private/var/db/com.hasna.recordings.bootstrap-authorization"
12
+ AUTHORIZATION_JOURNAL="${AUTHORIZATION_ROOT}/journal"
13
+ BROKER="/Library/PrivilegedHelperTools/com.hasna.recordings.updater"
14
+ CLIENT="/Applications/Recordings.app/Contents/Helpers/recordings-update-client"
15
+ VERIFIER="/Library/PrivilegedHelperTools/com.hasna.recordings.artifact-verifier"
16
+ APP="/Applications/Recordings.app"
17
+ STATE_ROOT="/Library/Application Support/Hasna/Recordings/Updates"
18
+ TRUST_ROOT="/Library/Application Support/Hasna/Recordings/Trust"
19
+ BOOTSTRAP_ROOT="/Library/Application Support/Hasna/Recordings/Bootstrap"
20
+ RELEASE_SEQUENCE_FILE="${BOOTSTRAP_ROOT}/release-sequence"
21
+ KEY_EPOCH_FILE="${BOOTSTRAP_ROOT}/key-epoch"
22
+ SOURCE_SHA_FILE="${BOOTSTRAP_ROOT}/source-sha"
23
+ BROKER_POLICY="${TRUST_ROOT}/broker-policy.json"
24
+ VERIFIER_SANDBOX="${TRUST_ROOT}/artifact-verifier.sb"
25
+ BOOTSTRAP_MARKER="${TRUST_ROOT}/bootstrap-marker.json"
26
+ LAUNCHD_PLIST="/Library/LaunchDaemons/com.hasna.recordings.updater.plist"
27
+ VERIFIER_USER="_recordingsverify"
28
+ VERIFIER_GROUP="_recordingsverify"
29
+
30
+ fail() {
31
+ echo "Recordings updater bootstrap failed: $*" >&2
32
+ exit 1
33
+ }
34
+
35
+ path_exists_or_symlink() {
36
+ [ -e "$1" ] || [ -L "$1" ]
37
+ }
38
+
39
+ require_no_extended_acl() {
40
+ local path="$1"
41
+ local acl_listing acl_status
42
+ acl_listing="$(/bin/ls -lade "$path")" || fail "could not inspect extended ACLs on $path"
43
+ if /usr/bin/printf '%s\n' "$acl_listing" | /usr/bin/awk '
44
+ NR == 1 && length($1) == 11 && substr($1, 11, 1) == "+" { found = 1 }
45
+ NR > 1 && $1 ~ /^[0-9]+:$/ { found = 1 }
46
+ END { exit found ? 0 : 1 }
47
+ '; then
48
+ fail "unexpected extended ACL on package trust path: $path"
49
+ else
50
+ acl_status=$?
51
+ [ "$acl_status" -eq 1 ] || fail "could not evaluate extended ACLs on $path"
52
+ fi
53
+ }
54
+
55
+ read_journal_value() {
56
+ local key="$1"
57
+ /usr/bin/awk -F= -v key="$key" '
58
+ $1 == key { count += 1; value = substr($0, length(key) + 2) }
59
+ END { if (count != 1) exit 1; print value }
60
+ ' "$AUTHORIZATION_JOURNAL"
61
+ }
62
+
63
+ require_authorization_journal() {
64
+ path_exists_or_symlink "$AUTHORIZATION_ROOT" && \
65
+ [ -d "$AUTHORIZATION_ROOT" ] && [ ! -L "$AUTHORIZATION_ROOT" ] && \
66
+ [ "$(/usr/bin/stat -f '%u:%g:%Lp' "$AUTHORIZATION_ROOT")" = "0:0:700" ] || \
67
+ fail "authorization evidence is missing or unsafe"
68
+ [ -f "$AUTHORIZATION_JOURNAL" ] && [ ! -L "$AUTHORIZATION_JOURNAL" ] && \
69
+ [ "$(/usr/bin/stat -f '%u:%g:%Lp' "$AUTHORIZATION_JOURNAL")" = "0:0:400" ] && \
70
+ [ "$(/usr/bin/stat -f '%l' "$AUTHORIZATION_JOURNAL")" = "1" ] && \
71
+ [ "$(/usr/bin/wc -l <"$AUTHORIZATION_JOURNAL" | /usr/bin/tr -d ' ')" = "6" ] || \
72
+ fail "authorization journal is missing or unsafe"
73
+ require_no_extended_acl "$AUTHORIZATION_ROOT"
74
+ require_no_extended_acl "$AUTHORIZATION_JOURNAL"
75
+ [ "$(read_journal_value schema_version)" = "1" ] || fail "authorization journal schema is invalid"
76
+ JOURNAL_PHASE="$(read_journal_value phase)"
77
+ JOURNAL_PACKAGE_SHA256="$(read_journal_value package_sha256)"
78
+ JOURNAL_PACKAGE_BOOTSTRAP_ID="$(read_journal_value package_bootstrap_id)"
79
+ JOURNAL_INSTALLER_TEAM_ID="$(read_journal_value installer_team_id)"
80
+ JOURNAL_INSTALLER_CERTIFICATE_SHA256="$(read_journal_value installer_certificate_sha256)"
81
+ [ "$JOURNAL_PHASE" = "installer-started" ] || fail "authorization journal is not in Installer repair phase"
82
+ [[ "$JOURNAL_PACKAGE_SHA256" =~ ^[0-9a-f]{64}$ ]] && \
83
+ [[ "$JOURNAL_PACKAGE_BOOTSTRAP_ID" =~ ^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$ ]] && \
84
+ [[ "$JOURNAL_INSTALLER_TEAM_ID" =~ ^[A-Z0-9]{10}$ ]] && \
85
+ [[ "$JOURNAL_INSTALLER_CERTIFICATE_SHA256" =~ ^[0-9a-f]{64}$ ]] || \
86
+ fail "authorization journal values are malformed"
87
+ [ "$JOURNAL_PACKAGE_BOOTSTRAP_ID" = "$PACKAGE_BOOTSTRAP_ID" ] || \
88
+ fail "authorization journal belongs to a different authenticated package"
89
+ }
90
+
91
+ require_tree_without_extended_acl() {
92
+ local tree="$1"
93
+ local acl_listing acl_status
94
+ require_no_extended_acl "$tree"
95
+ [ -d "$tree" ] || return 0
96
+ acl_listing="$(/bin/ls -laeR "$tree")" || fail "could not recursively inspect extended ACLs on $tree"
97
+ if /usr/bin/printf '%s\n' "$acl_listing" | /usr/bin/awk '
98
+ length($1) == 11 && substr($1, 11, 1) == "+" { found = 1 }
99
+ $1 ~ /^[0-9]+:$/ { found = 1 }
100
+ END { exit found ? 0 : 1 }
101
+ '; then
102
+ fail "unexpected extended ACL inside package-owned tree: $tree"
103
+ else
104
+ acl_status=$?
105
+ [ "$acl_status" -eq 1 ] || fail "could not evaluate recursive extended ACLs on $tree"
106
+ fi
107
+ }
108
+
109
+ # Prove every recursive mutation target before chmod/chown can traverse it.
110
+ # Recursive mutation roots are under root-owned, non-writable ancestry, so once
111
+ # every entry is root-owned and non-writable an unprivileged process cannot
112
+ # replace a validated path before the immediately following mutation. The app
113
+ # is validated with the same structure policy but is never recursively mutated,
114
+ # preserving the exact root:admin /Applications compatibility boundary.
115
+ package_entry_structure_is_safe() {
116
+ local entry="$1"
117
+ local expected_device="$2"
118
+ local mode owner links device
119
+ [ ! -L "$entry" ] || return 1
120
+ if [ -d "$entry" ]; then
121
+ :
122
+ elif [ -f "$entry" ]; then
123
+ links="$(/usr/bin/stat -f '%l' "$entry")" || return 1
124
+ [ "$links" = "1" ] || return 1
125
+ else
126
+ return 1
127
+ fi
128
+ owner="$(/usr/bin/stat -f '%u' "$entry")" || return 1
129
+ [ "$owner" = "0" ] || return 1
130
+ device="$(/usr/bin/stat -f '%d' "$entry")" || return 1
131
+ [ "$device" = "$expected_device" ] || return 1
132
+ mode="$(/usr/bin/stat -f '%Lp' "$entry")" || return 1
133
+ [ $((8#$mode & 8#022)) -eq 0 ] || return 1
134
+ }
135
+
136
+ require_safe_package_tree_structure() {
137
+ local tree="$1"
138
+ local entry listing tree_device invalid=0
139
+ [ -d "$tree" ] && [ ! -L "$tree" ] || \
140
+ fail "package-owned tree is missing, linked, or not a directory: $tree"
141
+ listing="$(/usr/bin/mktemp -t recordings-package-tree)" || \
142
+ fail "could not allocate package-tree inspection state"
143
+ tree_device="$(/usr/bin/stat -f '%d' "$tree")" || {
144
+ /bin/rm -f "$listing"
145
+ fail "could not inspect package-owned tree device: $tree"
146
+ }
147
+ if ! /usr/bin/find -x "$tree" -print0 >"$listing"; then
148
+ /bin/rm -f "$listing"
149
+ fail "could not enumerate package-owned tree: $tree"
150
+ fi
151
+ while IFS= read -r -d '' entry; do
152
+ if ! package_entry_structure_is_safe "$entry" "$tree_device"; then
153
+ invalid=1
154
+ break
155
+ fi
156
+ done <"$listing"
157
+ /bin/rm -f "$listing"
158
+ [ "$invalid" -eq 0 ] || \
159
+ fail "package-owned tree contains a link, hard link, special file, unsafe owner, or writable entry: $tree"
160
+ }
161
+
162
+ require_safe_package_leaf_structure() {
163
+ local leaf="$1"
164
+ local leaf_device
165
+ leaf_device="$(/usr/bin/stat -f '%d' "$leaf")" || \
166
+ fail "could not inspect package-owned leaf device: $leaf"
167
+ [ -f "$leaf" ] && package_entry_structure_is_safe "$leaf" "$leaf_device" || \
168
+ fail "package-owned leaf is linked, hard-linked, special, unsafely owned, or writable: $leaf"
169
+ }
170
+
171
+ require_root_owned_nonwritable_directory() {
172
+ local directory="$1"
173
+ local mode
174
+ [ -d "$directory" ] && [ ! -L "$directory" ] && \
175
+ [ "$(/usr/bin/stat -f '%u' "$directory")" = 0 ] || \
176
+ fail "target ancestry is missing, linked, or not root-owned: $directory"
177
+ mode="$(/usr/bin/stat -f '%Lp' "$directory")"
178
+ [ $((8#$mode & 8#022)) -eq 0 ] || \
179
+ fail "target ancestry is group/other writable: $directory"
180
+ require_no_extended_acl "$directory"
181
+ }
182
+
183
+ # Standard macOS permits the admin group to install app bundles. Executable
184
+ # authority remains the pinned Developer ID/audit-token policy, so accept only
185
+ # that exact OS topology and reject every ACL or broader mode.
186
+ require_managed_applications_directory() {
187
+ local mode group_id
188
+ [ -d "/Applications" ] && [ ! -L "/Applications" ] && \
189
+ [ "$(/usr/bin/stat -f '%u' "/Applications")" = 0 ] || \
190
+ fail "canonical /Applications directory is missing, linked, or not root-owned"
191
+ require_no_extended_acl "/Applications"
192
+ mode="$(/usr/bin/stat -f '%Lp' "/Applications")"
193
+ group_id="$(/usr/bin/stat -f '%g' "/Applications")"
194
+ case "$mode:$group_id" in
195
+ 755:*|775:80) ;;
196
+ *) fail "/Applications must be ACL-free mode 0755 or root:admin mode 0775" ;;
197
+ esac
198
+ }
199
+
200
+ require_expected_var_link() {
201
+ local var_target
202
+ [ -L "/var" ] || fail "canonical macOS /var system link is missing"
203
+ var_target="$(/usr/bin/readlink "/var")" || fail "could not inspect the macOS /var system link"
204
+ [ "$var_target" = "private/var" ] && \
205
+ [ "$(cd "/var" && /bin/pwd -P)" = "/private/var" ] || \
206
+ fail "macOS /var system link is noncanonical"
207
+ }
208
+
209
+ validate_existing_mutation_root() {
210
+ local mutation_root="$1"
211
+ if path_exists_or_symlink "$mutation_root"; then
212
+ [ -d "$mutation_root" ] && [ ! -L "$mutation_root" ] && \
213
+ [ "$(/usr/bin/stat -f '%u:%g:%Lp' "$mutation_root")" = "0:0:700" ] || \
214
+ fail "interrupted package mutation root is linked or has ownership/mode drift: $mutation_root"
215
+ require_safe_package_tree_structure "$mutation_root"
216
+ require_tree_without_extended_acl "$mutation_root"
217
+ fi
218
+ }
219
+
220
+ [ "$(/usr/bin/id -u)" = "0" ] || fail "postinstall must run as root through Installer or MDM"
221
+
222
+ require_authorization_journal
223
+ require_expected_var_link
224
+ require_managed_applications_directory
225
+ for required_ancestor in \
226
+ "/" \
227
+ "/Library" \
228
+ "/Library/Application Support" \
229
+ "/Library/Application Support/Hasna" \
230
+ "/Library/Application Support/Hasna/Recordings" \
231
+ "/Library/PrivilegedHelperTools" \
232
+ "/Library/LaunchDaemons" \
233
+ "/private" \
234
+ "/private/var" \
235
+ "/private/var/db"; do
236
+ require_root_owned_nonwritable_directory "$required_ancestor"
237
+ done
238
+ for mutation_root in "$STATE_ROOT" "/var/db/com.hasna.recordings.updater"; do
239
+ validate_existing_mutation_root "$mutation_root"
240
+ done
241
+
242
+ for required in "$BROKER" "$CLIENT" "$VERIFIER" "$BROKER_POLICY" "$VERIFIER_SANDBOX" "$BOOTSTRAP_MARKER" "$LAUNCHD_PLIST" \
243
+ "$RELEASE_SEQUENCE_FILE" "$KEY_EPOCH_FILE" "$SOURCE_SHA_FILE"; do
244
+ [ -e "$required" ] || fail "package payload is missing $required"
245
+ [ ! -L "$required" ] || fail "package payload contains a symbolic-link trust boundary: $required"
246
+ require_no_extended_acl "$required"
247
+ done
248
+ release_sequence="$(/bin/cat "$RELEASE_SEQUENCE_FILE")"
249
+ key_epoch="$(/bin/cat "$KEY_EPOCH_FILE")"
250
+ source_sha="$(/bin/cat "$SOURCE_SHA_FILE")"
251
+ [[ "$release_sequence" =~ ^[1-9][0-9]*$ ]] || fail "bootstrap release sequence is invalid"
252
+ [[ "$key_epoch" =~ ^[1-9][0-9]*$ ]] || fail "bootstrap key epoch is invalid"
253
+ [[ "$source_sha" =~ ^[0-9a-f]{40}$ ]] || fail "bootstrap source commit is invalid"
254
+ PUBLIC_KEY="${TRUST_ROOT}/envelope-keys/${key_epoch}.raw"
255
+ [ -f "$PUBLIC_KEY" ] && [ ! -L "$PUBLIC_KEY" ] || fail "package payload is missing the epoch release-envelope key"
256
+ [ -d "$APP" ] && [ ! -L "$APP" ] || fail "package payload is missing the canonical app bundle"
257
+ [ "$(/usr/bin/stat -f '%z' "$PUBLIC_KEY")" = "32" ] || fail "release-envelope public key must be exactly 32 raw Ed25519 bytes"
258
+ for installed_package_tree in "$APP" "$TRUST_ROOT" "$BOOTSTRAP_ROOT"; do
259
+ require_safe_package_tree_structure "$installed_package_tree"
260
+ done
261
+ require_tree_without_extended_acl "$APP"
262
+ for installed_package_leaf in "$BROKER" "$VERIFIER" "$LAUNCHD_PLIST"; do
263
+ require_safe_package_leaf_structure "$installed_package_leaf"
264
+ done
265
+
266
+ find_unused_directory_id() {
267
+ local candidate=499
268
+ local user_ids group_ids
269
+ user_ids="$(/usr/bin/dscl . -list /Users UniqueID)"
270
+ group_ids="$(/usr/bin/dscl . -list /Groups PrimaryGroupID)"
271
+ while [ "$candidate" -ge 400 ]; do
272
+ if ! /usr/bin/awk -v value="$candidate" '$2 == value { found=1 } END { exit found ? 0 : 1 }' <<<"$user_ids" && \
273
+ ! /usr/bin/awk -v value="$candidate" '$2 == value { found=1 } END { exit found ? 0 : 1 }' <<<"$group_ids"; then
274
+ printf '%s\n' "$candidate"
275
+ return 0
276
+ fi
277
+ candidate=$((candidate - 1))
278
+ done
279
+ return 1
280
+ }
281
+
282
+ read_directory_value() {
283
+ local record="$1"
284
+ local key="$2"
285
+ { /usr/bin/dscl . -read "$record" "$key" 2>/dev/null || true; } | \
286
+ /usr/bin/awk -v key="$key:" '$1 == key { $1=""; sub(/^ /, ""); print; exit }'
287
+ }
288
+
289
+ ensure_directory_value() {
290
+ local record="$1"
291
+ local key="$2"
292
+ local expected="$3"
293
+ local label="$4"
294
+ local actual
295
+ if /usr/bin/dscl . -read "$record" "$key" >/dev/null 2>&1; then
296
+ actual="$(read_directory_value "$record" "$key")"
297
+ [ "$actual" = "$expected" ] || fail "$label has an unexpected existing value"
298
+ else
299
+ /usr/bin/dscl . -create "$record" "$key" "$expected"
300
+ fi
301
+ }
302
+
303
+ validate_numeric_system_id() {
304
+ local label="$1"
305
+ local value="$2"
306
+ case "$value" in ''|*[!0-9]*) fail "$label is not numeric" ;; esac
307
+ [ "$value" -ge 400 ] && [ "$value" -le 499 ] || fail "$label is outside the reserved verifier range"
308
+ }
309
+
310
+ validate_verifier_group() {
311
+ local gid real_name password members member_ids
312
+ gid="$(read_directory_value "/Groups/${VERIFIER_GROUP}" PrimaryGroupID)"
313
+ real_name="$(read_directory_value "/Groups/${VERIFIER_GROUP}" RealName)"
314
+ password="$(read_directory_value "/Groups/${VERIFIER_GROUP}" Password)"
315
+ members="$(read_directory_value "/Groups/${VERIFIER_GROUP}" GroupMembership)"
316
+ member_ids="$(read_directory_value "/Groups/${VERIFIER_GROUP}" GroupMembers)"
317
+ validate_numeric_system_id "verifier group PrimaryGroupID" "$gid"
318
+ [ "$real_name" = "Recordings Artifact Verifier" ] || fail "existing verifier group has an unexpected RealName"
319
+ case "$password" in \**|\*\*\** ) ;; *) fail "existing verifier group is not password-disabled" ;; esac
320
+ [ -z "$members" ] && [ -z "$member_ids" ] || fail "verifier group must not grant supplementary membership"
321
+ printf '%s\n' "$gid"
322
+ }
323
+
324
+ validate_verifier_user() {
325
+ local expected_gid="$1"
326
+ local uid real_name password hidden
327
+ uid="$(read_directory_value "/Users/${VERIFIER_USER}" UniqueID)"
328
+ real_name="$(read_directory_value "/Users/${VERIFIER_USER}" RealName)"
329
+ password="$(read_directory_value "/Users/${VERIFIER_USER}" Password)"
330
+ hidden="$(read_directory_value "/Users/${VERIFIER_USER}" IsHidden)"
331
+ validate_numeric_system_id "verifier user UniqueID" "$uid"
332
+ [ "$real_name" = "Recordings Artifact Verifier" ] || fail "existing verifier account has an unexpected RealName"
333
+ case "$password" in \**|\*\*\** ) ;; *) fail "existing verifier account is not password-disabled" ;; esac
334
+ [ "$hidden" = "1" ] || fail "existing verifier account is not hidden"
335
+ [ "$(read_directory_value "/Users/${VERIFIER_USER}" UserShell)" = "/usr/bin/false" ] || \
336
+ fail "existing verifier account has an interactive shell"
337
+ [ "$(read_directory_value "/Users/${VERIFIER_USER}" NFSHomeDirectory)" = "/var/empty" ] || \
338
+ fail "existing verifier account has an unexpected home"
339
+ [ "$(read_directory_value "/Users/${VERIFIER_USER}" PrimaryGroupID)" = "$expected_gid" ] || \
340
+ fail "existing verifier account has an unexpected primary group"
341
+ }
342
+
343
+ if ! /usr/bin/dscl . -read "/Groups/${VERIFIER_GROUP}" >/dev/null 2>&1; then
344
+ /usr/bin/dscl . -create "/Groups/${VERIFIER_GROUP}"
345
+ fi
346
+ echo "Recordings updater will repair interrupted verifier group initialization when expected attributes are absent." >&2
347
+ ensure_directory_value "/Groups/${VERIFIER_GROUP}" RealName "Recordings Artifact Verifier" "verifier group RealName"
348
+ if /usr/bin/dscl . -read "/Groups/${VERIFIER_GROUP}" Password >/dev/null 2>&1; then
349
+ verifier_group_password="$(read_directory_value "/Groups/${VERIFIER_GROUP}" Password)"
350
+ case "$verifier_group_password" in \**|\*\*\** ) ;; *) fail "existing verifier group is not password-disabled" ;; esac
351
+ else
352
+ /usr/bin/dscl . -create "/Groups/${VERIFIER_GROUP}" Password "*"
353
+ fi
354
+ if /usr/bin/dscl . -read "/Groups/${VERIFIER_GROUP}" PrimaryGroupID >/dev/null 2>&1; then
355
+ verifier_gid="$(read_directory_value "/Groups/${VERIFIER_GROUP}" PrimaryGroupID)"
356
+ validate_numeric_system_id "verifier group PrimaryGroupID" "$verifier_gid"
357
+ else
358
+ verifier_gid="$(find_unused_directory_id)" || fail "no unused system account ID is available"
359
+ /usr/bin/dscl . -create "/Groups/${VERIFIER_GROUP}" PrimaryGroupID "$verifier_gid"
360
+ fi
361
+ verifier_gid="$(validate_verifier_group)"
362
+
363
+ if ! /usr/bin/dscl . -read "/Users/${VERIFIER_USER}" >/dev/null 2>&1; then
364
+ /usr/bin/dscl . -create "/Users/${VERIFIER_USER}"
365
+ fi
366
+ echo "Recordings updater will repair interrupted verifier account initialization when expected attributes are absent." >&2
367
+ ensure_directory_value "/Users/${VERIFIER_USER}" RealName "Recordings Artifact Verifier" "verifier account RealName"
368
+ if /usr/bin/dscl . -read "/Users/${VERIFIER_USER}" Password >/dev/null 2>&1; then
369
+ verifier_user_password="$(read_directory_value "/Users/${VERIFIER_USER}" Password)"
370
+ case "$verifier_user_password" in \**|\*\*\** ) ;; *) fail "existing verifier account is not password-disabled" ;; esac
371
+ else
372
+ /usr/bin/dscl . -create "/Users/${VERIFIER_USER}" Password "*"
373
+ fi
374
+ if /usr/bin/dscl . -read "/Users/${VERIFIER_USER}" UniqueID >/dev/null 2>&1; then
375
+ verifier_uid="$(read_directory_value "/Users/${VERIFIER_USER}" UniqueID)"
376
+ validate_numeric_system_id "verifier user UniqueID" "$verifier_uid"
377
+ else
378
+ verifier_uid="$(find_unused_directory_id)" || fail "no unused system account ID is available"
379
+ /usr/bin/dscl . -create "/Users/${VERIFIER_USER}" UniqueID "$verifier_uid"
380
+ fi
381
+ ensure_directory_value "/Users/${VERIFIER_USER}" PrimaryGroupID "$verifier_gid" "verifier account PrimaryGroupID"
382
+ ensure_directory_value "/Users/${VERIFIER_USER}" UserShell "/usr/bin/false" "verifier account UserShell"
383
+ ensure_directory_value "/Users/${VERIFIER_USER}" NFSHomeDirectory "/var/empty" "verifier account NFSHomeDirectory"
384
+ ensure_directory_value "/Users/${VERIFIER_USER}" IsHidden 1 "verifier account IsHidden"
385
+ validate_verifier_user "$verifier_gid"
386
+
387
+ /bin/mkdir -p \
388
+ "$STATE_ROOT/staging" \
389
+ "$STATE_ROOT/journal" \
390
+ "$STATE_ROOT/rollback" \
391
+ "$TRUST_ROOT/envelope-keys" \
392
+ "/var/db/com.hasna.recordings.updater"
393
+ for created_directory in \
394
+ "$STATE_ROOT" \
395
+ "$STATE_ROOT/staging" \
396
+ "$STATE_ROOT/journal" \
397
+ "$STATE_ROOT/rollback" \
398
+ "$TRUST_ROOT" \
399
+ "$TRUST_ROOT/envelope-keys" \
400
+ "$BOOTSTRAP_ROOT" \
401
+ "/var/db/com.hasna.recordings.updater"; do
402
+ require_root_owned_nonwritable_directory "$created_directory"
403
+ done
404
+
405
+ # Re-prove the complete recursive targets immediately before mutation. Strip
406
+ # ACLs one target at a time and revalidate that target before proceeding.
407
+ for package_owned_tree in \
408
+ "$STATE_ROOT" \
409
+ "$TRUST_ROOT" \
410
+ "$BOOTSTRAP_ROOT" \
411
+ "/var/db/com.hasna.recordings.updater"; do
412
+ require_safe_package_tree_structure "$package_owned_tree"
413
+ /bin/chmod -RN "$package_owned_tree"
414
+ require_tree_without_extended_acl "$package_owned_tree"
415
+ done
416
+ for package_owned_leaf in "$BROKER" "$VERIFIER" "$LAUNCHD_PLIST"; do
417
+ require_safe_package_leaf_structure "$package_owned_leaf"
418
+ /bin/chmod -N "$package_owned_leaf"
419
+ require_no_extended_acl "$package_owned_leaf"
420
+ done
421
+ /usr/sbin/chown -R root:wheel "$STATE_ROOT" "$TRUST_ROOT" "$BOOTSTRAP_ROOT" "/var/db/com.hasna.recordings.updater"
422
+ /usr/sbin/chown root:wheel "$BROKER" "$VERIFIER" "$PUBLIC_KEY" "$BROKER_POLICY" "$VERIFIER_SANDBOX" "$BOOTSTRAP_MARKER" "$LAUNCHD_PLIST"
423
+ /bin/chmod 0555 "$BROKER" "$VERIFIER"
424
+ [ "$(/usr/bin/stat -f '%Lp' "$CLIENT")" = "755" ] || fail "package update client mode is not 0755"
425
+ /bin/chmod 0700 "$STATE_ROOT" "$STATE_ROOT/staging" "$STATE_ROOT/journal" "$STATE_ROOT/rollback"
426
+ /bin/chmod 0700 "/var/db/com.hasna.recordings.updater"
427
+ /bin/chmod 0755 "$TRUST_ROOT"
428
+ /bin/chmod 0755 "$TRUST_ROOT/envelope-keys"
429
+ /bin/chmod 0444 "$PUBLIC_KEY" "$BROKER_POLICY" "$VERIFIER_SANDBOX" "$BOOTSTRAP_MARKER" "$LAUNCHD_PLIST"
430
+
431
+ for package_owned_tree in \
432
+ "$APP" \
433
+ "$STATE_ROOT" \
434
+ "$TRUST_ROOT" \
435
+ "$BOOTSTRAP_ROOT" \
436
+ "/var/db/com.hasna.recordings.updater"; do
437
+ require_tree_without_extended_acl "$package_owned_tree"
438
+ done
439
+ for package_owned_leaf in "$BROKER" "$VERIFIER" "$LAUNCHD_PLIST"; do
440
+ require_no_extended_acl "$package_owned_leaf"
441
+ done
442
+
443
+ /usr/bin/plutil -lint "$LAUNCHD_PLIST" >/dev/null
444
+ /usr/bin/plutil -lint "$BROKER_POLICY" >/dev/null
445
+ /usr/bin/plutil -lint "$BOOTSTRAP_MARKER" >/dev/null
446
+ EXPECTED_TEAM_ID="$(/usr/bin/plutil -extract signing_team_identifier raw -o - "$BROKER_POLICY")"
447
+ [[ "$EXPECTED_TEAM_ID" =~ ^[A-Z0-9]{10}$ ]] || fail "broker policy TeamIdentifier is invalid"
448
+ [ "$(/usr/bin/plutil -extract lifecycle raw -o - "$BROKER_POLICY")" = "bootstrap-v1-app-updates-only" ] || \
449
+ fail "broker policy lifecycle is unsupported"
450
+ [ "$(/usr/bin/plutil -extract root_maintenance_supported raw -o - "$BROKER_POLICY")" = "false" ] || \
451
+ fail "broker policy must disable root maintenance"
452
+ [ "$(/usr/bin/plutil -extract key_rotation_supported raw -o - "$BROKER_POLICY")" = "false" ] || \
453
+ fail "broker policy must disable key rotation"
454
+ [ "$(/usr/bin/plutil -extract initial_key_epoch raw -o - "$BROKER_POLICY")" = "$key_epoch" ] || \
455
+ fail "broker policy initial key epoch does not match the bootstrap"
456
+ [ "$(/usr/bin/plutil -extract allowed_key_epochs.0 raw -o - "$BROKER_POLICY")" = "$key_epoch" ] || \
457
+ fail "broker policy does not pin the bootstrap key epoch"
458
+ [ "$(/usr/bin/plutil -extract allowed_key_epochs json -o - "$BROKER_POLICY" | /usr/bin/tr -d '[:space:]')" = "[${key_epoch}]" ] || \
459
+ fail "broker policy authorizes unsupported key-epoch transitions"
460
+ [ "$(/usr/bin/plutil -extract lifecycle raw -o - "$BOOTSTRAP_MARKER")" = "bootstrap-v1-app-updates-only" ] || \
461
+ fail "bootstrap marker lifecycle is unsupported"
462
+ [ "$(/usr/bin/plutil -extract root_maintenance_supported raw -o - "$BOOTSTRAP_MARKER")" = "false" ] || \
463
+ fail "bootstrap marker must disable root maintenance"
464
+ [ "$(/usr/bin/plutil -extract key_rotation_supported raw -o - "$BOOTSTRAP_MARKER")" = "false" ] || \
465
+ fail "bootstrap marker must disable key rotation"
466
+
467
+ verify_entitlement_allowlist() {
468
+ local code_path="$1"
469
+ local policy="$2"
470
+ local entitlement_plist
471
+ entitlement_plist="$(/usr/bin/mktemp -t recordings-entitlements)"
472
+ if ! /usr/bin/codesign -d --entitlements :- "$code_path" >"$entitlement_plist" 2>/dev/null || \
473
+ [ ! -s "$entitlement_plist" ]; then
474
+ /bin/rm -f "$entitlement_plist"
475
+ [ "$policy" = "none" ] || fail "$code_path is missing required entitlements"
476
+ return 0
477
+ fi
478
+ /usr/bin/plutil -lint "$entitlement_plist" >/dev/null || fail "$code_path has malformed signed entitlements"
479
+ case "$policy" in
480
+ app)
481
+ [ "$(/usr/libexec/PlistBuddy -c 'Print :com.apple.security.app-sandbox' "$entitlement_plist")" = "false" ] || fail "$code_path app sandbox entitlement changed"
482
+ [ "$(/usr/libexec/PlistBuddy -c 'Print :com.apple.security.device.audio-input' "$entitlement_plist")" = "true" ] || fail "$code_path audio entitlement changed"
483
+ [ "$(/usr/libexec/PlistBuddy -c 'Print :com.apple.security.automation.apple-events' "$entitlement_plist")" = "true" ] || fail "$code_path automation entitlement changed"
484
+ /usr/libexec/PlistBuddy -c 'Delete :com.apple.security.app-sandbox' "$entitlement_plist"
485
+ /usr/libexec/PlistBuddy -c 'Delete :com.apple.security.device.audio-input' "$entitlement_plist"
486
+ /usr/libexec/PlistBuddy -c 'Delete :com.apple.security.automation.apple-events' "$entitlement_plist"
487
+ ;;
488
+ verifier)
489
+ [ "$(/usr/libexec/PlistBuddy -c 'Print :com.apple.security.cs.allow-jit' "$entitlement_plist")" = "true" ] || fail "$code_path JIT entitlement changed"
490
+ [ "$(/usr/libexec/PlistBuddy -c 'Print :com.apple.security.cs.allow-unsigned-executable-memory' "$entitlement_plist")" = "true" ] || fail "$code_path executable-memory entitlement changed"
491
+ /usr/libexec/PlistBuddy -c 'Delete :com.apple.security.cs.allow-jit' "$entitlement_plist"
492
+ /usr/libexec/PlistBuddy -c 'Delete :com.apple.security.cs.allow-unsigned-executable-memory' "$entitlement_plist"
493
+ ;;
494
+ none) ;;
495
+ *) fail "unknown entitlement policy" ;;
496
+ esac
497
+ remaining="$(/usr/libexec/PlistBuddy -c Print "$entitlement_plist" | /usr/bin/tr -d '[:space:]')"
498
+ /bin/rm -f "$entitlement_plist"
499
+ [ "$remaining" = "Dict{}" ] || fail "$code_path has entitlements outside its allowlist"
500
+ }
501
+
502
+ verify_release_code() {
503
+ local code_path="$1"
504
+ local expected_identifier="$2"
505
+ local entitlement_policy="$3"
506
+ local details authority team identifier timestamp flags
507
+ /usr/bin/codesign --verify --strict --all-architectures --verbose=2 "$code_path"
508
+ details="$(/usr/bin/codesign -d --verbose=4 "$code_path" 2>&1)"
509
+ authority="$(printf '%s\n' "$details" | /usr/bin/awk -F= '/^Authority=/ { print $2; exit }')"
510
+ team="$(printf '%s\n' "$details" | /usr/bin/awk -F= '/^TeamIdentifier=/ { print $2; exit }')"
511
+ identifier="$(printf '%s\n' "$details" | /usr/bin/awk -F= '/^Identifier=/ { print $2; exit }')"
512
+ timestamp="$(printf '%s\n' "$details" | /usr/bin/awk -F= '/^Timestamp=/ { print $2; exit }')"
513
+ flags="$(printf '%s\n' "$details" | /usr/bin/sed -n 's/^CodeDirectory .*flags=[^(]*(\([^)]*\)).*/\1/p' | /usr/bin/head -n 1)"
514
+ [[ "$authority" == "Developer ID Application:"* ]] || fail "$code_path is not Developer ID Application signed"
515
+ [ "$team" = "$EXPECTED_TEAM_ID" ] || fail "$code_path has an unexpected TeamIdentifier"
516
+ [ "$identifier" = "$expected_identifier" ] || fail "$code_path has an unexpected signing identifier"
517
+ case ",$flags," in *,runtime,*) ;; *) fail "$code_path lacks hardened runtime" ;; esac
518
+ case "$timestamp" in ''|none|None|NONE) fail "$code_path lacks a trusted timestamp" ;; esac
519
+ verify_entitlement_allowlist "$code_path" "$entitlement_policy"
520
+ }
521
+
522
+ verify_release_code "$BROKER" "com.hasna.recordings.updater" none
523
+ verify_release_code "$CLIENT" "com.hasna.recordings.update-client" none
524
+ verify_release_code "$VERIFIER" "com.hasna.recordings.artifact-verifier" verifier
525
+ verify_release_code "$APP" "com.hasna.recordings" app
526
+ /usr/bin/codesign --verify --deep --strict --all-architectures --verbose=2 "$APP"
527
+
528
+ # Close the ancestry-check-to-launch window after all package mutations and
529
+ # code validation. An ACL added anywhere in the launch path aborts bootstrap.
530
+ require_managed_applications_directory
531
+ for launch_ancestor in \
532
+ "/" \
533
+ "/Library" \
534
+ "/Library/Application Support" \
535
+ "/Library/Application Support/Hasna" \
536
+ "/Library/Application Support/Hasna/Recordings" \
537
+ "/Library/PrivilegedHelperTools" \
538
+ "/Library/LaunchDaemons" \
539
+ "/private" \
540
+ "/private/var" \
541
+ "/private/var/db"; do
542
+ require_root_owned_nonwritable_directory "$launch_ancestor"
543
+ done
544
+ require_expected_var_link
545
+ for launch_tree in "$APP" "$STATE_ROOT" "$TRUST_ROOT" "$BOOTSTRAP_ROOT" "/var/db/com.hasna.recordings.updater"; do
546
+ require_tree_without_extended_acl "$launch_tree"
547
+ done
548
+ for launch_leaf in "$BROKER" "$VERIFIER" "$LAUNCHD_PLIST"; do
549
+ require_no_extended_acl "$launch_leaf"
550
+ done
551
+
552
+ if ! /bin/launchctl print system/com.hasna.recordings.updater >/dev/null 2>&1; then
553
+ /bin/launchctl bootstrap system "$LAUNCHD_PLIST"
554
+ fi
555
+ /bin/launchctl enable system/com.hasna.recordings.updater
556
+ /bin/launchctl kickstart -k system/com.hasna.recordings.updater
557
+ /bin/launchctl print system/com.hasna.recordings.updater >/dev/null
558
+
559
+ # The external signed bootstrap envelope is generated only after the final PKG
560
+ # is stapled, because it binds that exact PKG digest. Managed deployment passes
561
+ # that sidecar to the broker after Installer succeeds. Postinstall deliberately
562
+ # leaves release-state.json absent; until bootstrap initialization completes the
563
+ # broker is status-only and rejects every update request.
564
+ [ ! -e "/var/db/com.hasna.recordings.updater/release-state.json" ] || \
565
+ fail "a bootstrap PKG must not overwrite existing anti-rollback state"