@hasna/recordings 0.2.14 → 0.3.2

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 (123) hide show
  1. package/Dockerfile.package +4 -5
  2. package/README.md +113 -8
  3. package/bun.lock +2 -2
  4. package/dist/__tests__/helpers/installer-guard-execution.d.ts +22 -0
  5. package/dist/__tests__/helpers/installer-guard-execution.d.ts.map +1 -0
  6. package/dist/__tests__/helpers/installer-preflight.d.ts +22 -0
  7. package/dist/__tests__/helpers/installer-preflight.d.ts.map +1 -0
  8. package/dist/__tests__/helpers/native-fs-guard.d.ts.map +1 -1
  9. package/dist/__tests__/helpers/source-assertions.d.ts +169 -0
  10. package/dist/__tests__/helpers/source-assertions.d.ts.map +1 -0
  11. package/dist/__tests__/preload.d.ts +1 -4
  12. package/dist/__tests__/preload.d.ts.map +1 -1
  13. package/dist/cli/desktop-snapshot.d.ts +20 -0
  14. package/dist/cli/desktop-snapshot.d.ts.map +1 -0
  15. package/dist/cli/index.js +2094 -280
  16. package/dist/cli/macos-permissions.d.ts +72 -0
  17. package/dist/cli/macos-permissions.d.ts.map +1 -1
  18. package/dist/cli/macos-shortcut.d.ts +139 -0
  19. package/dist/cli/macos-shortcut.d.ts.map +1 -0
  20. package/dist/cli/trigger-probe.d.ts +172 -0
  21. package/dist/cli/trigger-probe.d.ts.map +1 -0
  22. package/dist/db/database.d.ts +9 -0
  23. package/dist/db/database.d.ts.map +1 -1
  24. package/dist/http/client.d.ts +19 -7
  25. package/dist/http/client.d.ts.map +1 -1
  26. package/dist/index.d.ts +3 -1
  27. package/dist/index.d.ts.map +1 -1
  28. package/dist/index.js +635 -50
  29. package/dist/lib/capture-probe.d.ts +216 -0
  30. package/dist/lib/capture-probe.d.ts.map +1 -0
  31. package/dist/lib/config.d.ts.map +1 -1
  32. package/dist/lib/macos-bundle.d.ts +50 -0
  33. package/dist/lib/macos-bundle.d.ts.map +1 -0
  34. package/dist/lib/persistence-probe.d.ts +208 -0
  35. package/dist/lib/persistence-probe.d.ts.map +1 -0
  36. package/dist/lib/recorder.d.ts.map +1 -1
  37. package/dist/lib/retired-deployment-modes.d.ts +24 -0
  38. package/dist/lib/retired-deployment-modes.d.ts.map +1 -0
  39. package/dist/lib/transcriber.d.ts +60 -0
  40. package/dist/lib/transcriber.d.ts.map +1 -1
  41. package/dist/mcp/index.js +141 -57
  42. package/dist/server/cloud-config.d.ts +25 -3
  43. package/dist/server/cloud-config.d.ts.map +1 -1
  44. package/dist/server/cloud-readiness.d.ts.map +1 -1
  45. package/dist/server/cloud.d.ts +8 -7
  46. package/dist/server/cloud.d.ts.map +1 -1
  47. package/dist/server/index.js +188 -80
  48. package/dist/server/migrate-command.d.ts.map +1 -1
  49. package/dist/server/serve.d.ts.map +1 -1
  50. package/dist/storage.d.ts +2 -2
  51. package/dist/storage.d.ts.map +1 -1
  52. package/dist/storage.js +109 -50
  53. package/dist/store.d.ts +1 -1
  54. package/dist/store.d.ts.map +1 -1
  55. package/dist/version.d.ts +1 -1
  56. package/dist/version.d.ts.map +1 -1
  57. package/package.json +25 -6
  58. package/scripts/ci-linux-suite.ts +403 -0
  59. package/scripts/ci-native-build.ts +402 -0
  60. package/scripts/enforce_identity_migration.sh +110 -0
  61. package/scripts/generate-sdk.ts +1 -1
  62. package/scripts/install_macos_app.sh +192 -31
  63. package/scripts/macos_artifact.ts +323 -43
  64. package/scripts/native/realtime-settle-probe/README.md +39 -0
  65. package/scripts/native/realtime-settle-probe/main.swift +135 -0
  66. package/scripts/policy/local-only-approved-targets.txt +21 -0
  67. package/scripts/read_local_only_targets.sh +203 -0
  68. package/scripts/scan-artifact.ts +40 -0
  69. package/scripts/set-version.ts +145 -0
  70. package/scripts/smoke_macos_app.sh +62 -16
  71. package/scripts/vacuity-manifest-gen.ts +96 -0
  72. package/scripts/vacuity-manifests/enhancement-screen-mirror.tsv +44 -0
  73. package/scripts/vacuity-manifests/install-chain.tsv +13 -0
  74. package/scripts/vacuity-manifests/must-survive-controls.tsv +10 -0
  75. package/scripts/vacuity-manifests/reorder.tsv +4 -0
  76. package/scripts/vacuity-manifests/source-side.tsv +15 -0
  77. package/scripts/vacuity-manifests/variable-operands.tsv +16 -0
  78. package/scripts/vacuity-manifests/version-sites.tsv +61 -0
  79. package/scripts/vacuity-mutation-battery.ts +266 -0
  80. package/src/native/Recordings/App/MenuBarStatusView.swift +23 -11
  81. package/src/native/Recordings/App/RecordingsApp.swift +37 -1
  82. package/src/native/Recordings/App/RuntimeSmoke.swift +5 -1
  83. package/src/native/Recordings/App/SidebarView.swift +1 -1
  84. package/src/native/Recordings/Package.swift +24 -5
  85. package/src/native/Recordings/RecordingsLib/EnhancementScreen.swift +91 -0
  86. package/src/native/Recordings/RecordingsLib/FnKeyMonitor.swift +27 -4
  87. package/src/native/Recordings/RecordingsLib/Info.plist +2 -2
  88. package/src/native/Recordings/RecordingsLib/MenuBarPresentation.swift +56 -3
  89. package/src/native/Recordings/RecordingsLib/NativePCMRecorder.swift +13 -0
  90. package/src/native/Recordings/RecordingsLib/PasteDeliveryVerification.swift +460 -0
  91. package/src/native/Recordings/RecordingsLib/PermissionRequestLaunchPlan.swift +21 -1
  92. package/src/native/Recordings/RecordingsLib/RealtimeTranscriptionClient.swift +41 -7
  93. package/src/native/Recordings/RecordingsLib/RecordingAttemptAlert.swift +36 -0
  94. package/src/native/Recordings/RecordingsLib/RecordingEngine.swift +1071 -75
  95. package/src/native/Recordings/RecordingsLib/SettingsView.swift +47 -3
  96. package/src/native/Recordings/RecordingsLib/VoiceShortcuts.swift +11 -6
  97. package/src/native/Recordings/RecordingsTests/BarOnlyLaunchPlanTests.swift +99 -0
  98. package/src/native/Recordings/RecordingsTests/CLIRunnerTests.swift +3 -3
  99. package/src/native/Recordings/RecordingsTests/EnhancementScreenTests.swift +191 -0
  100. package/src/native/Recordings/RecordingsTests/MenuBarPresentationTests.swift +144 -4
  101. package/src/native/Recordings/RecordingsTests/PasteDeliveryVerificationTests.swift +463 -0
  102. package/src/native/Recordings/RecordingsTests/PasteTargetTests.swift +33 -20
  103. package/src/native/Recordings/RecordingsTests/ProjectStoreTests.swift +1 -1
  104. package/src/native/Recordings/RecordingsTests/RealtimeSettleBenchmark.swift +200 -0
  105. package/src/native/Recordings/RecordingsTests/RealtimeSettleBudgetTests.swift +80 -0
  106. package/src/native/Recordings/RecordingsTests/RealtimeTranscriptionTests.swift +22 -4
  107. package/src/native/Recordings/RecordingsTests/RecordingEngineDeliveryTests.swift +12 -6
  108. package/src/native/Recordings/RecordingsTests/RecordingEngineHomeIsolationTests.swift +67 -0
  109. package/src/native/Recordings/RecordingsTests/RecordingStartGateTests.swift +12 -3
  110. package/src/native/Recordings/RecordingsTests/RecordingStartTimingTests.swift +207 -11
  111. package/src/native/Recordings/RecordingsTests/SpeechIntentTests.swift +17 -6
  112. package/src/native/Recordings/RecordingsTests/TestHomeDirectory.swift +23 -0
  113. package/src/native/Recordings/Updater/Broker/ApplicationProcessQuiescence.swift +7 -1
  114. package/src/native/Recordings/Updater/Broker/AtomicActivation.swift +10 -10
  115. package/src/native/Recordings/Updater/Broker/BrokerMain.swift +5 -1
  116. package/src/native/Recordings/Updater/Broker/HostOSProductVersion.swift +1 -0
  117. package/src/native/Recordings/Updater/Broker/PeerIdentity.swift +59 -3
  118. package/src/native/Recordings/Updater/Protocol/UpdateProtocol.swift +21 -2
  119. package/src/native/Recordings/Updater/ProtocolTests/ExpectationIntegrityTests.swift +60 -0
  120. package/src/native/Recordings/Updater/ProtocolTests/UpdateXPCInterfaceTests.swift +113 -0
  121. package/src/native/Recordings/Updater/VerifierLauncher/RecordingsVerifierLauncher.c +106 -5
  122. package/src/native/Recordings/build.sh +226 -20
  123. package/scripts/native/prebuilds/darwin-universal/recordings_fs_guard.node +0 -0
@@ -146,6 +146,66 @@ require_absolute_canonical_owned_home() {
146
146
  }
147
147
  }
148
148
 
149
+ # Every accepted argument, because until now there was no usage output at all and an
150
+ # unrecognized argument printed only "Unknown argument". That mattered most for
151
+ # --allow-adhoc-identity-migration: the identity-migration gate refuses a local-only
152
+ # reinstall by default, and an operator repairing a station had no way to discover the
153
+ # approval flag except by reading the refusal on stderr or the script itself.
154
+ print_usage() {
155
+ cat <<'USAGE'
156
+ Usage: install_macos_app.sh --artifact <zip> --manifest <json> [options]
157
+
158
+ Installs a Recordings macOS app artifact transactionally. Normally invoked through
159
+ `recordings app install`, which forwards these arguments.
160
+
161
+ Artifact and version:
162
+ --artifact <path> Finalized artifact archive to install
163
+ --manifest <path> Artifact manifest describing it
164
+ --manifest-sha256 <sha256> Authenticated digest of the manifest
165
+ --expected-source-sha <sha> Approved 40-character source commit
166
+ --expected-version <version> Version the artifact must declare
167
+ --expected-team-id <team-id> Required Apple Developer team identifier
168
+
169
+ Target selection:
170
+ --expected-hostname <name> Require this live short hostname before mutating
171
+ --variant <full|bar> Artifact variant the operator intends to install;
172
+ the manifest variant is verified against this at
173
+ install time (default: full)
174
+ --artifact-policy <release|local-only>
175
+ Trust policy to evaluate the artifact under
176
+ --approved-target <name> Approved target for a local-only artifact
177
+ --approved-target-identity-kind <kind>
178
+ hardware_uuid_sha256 or tailscale_node_id_sha256
179
+ --approved-target-identity-sha256 <sha256>
180
+ Authenticated digest of the target identity
181
+
182
+ Identity migration. Replacing an installed app with one signed by a different identity
183
+ voids the Microphone and Accessibility grants the installed app holds; macOS keys those
184
+ grants to code identity, and they cannot be restored by the installer. Both approvals are
185
+ one-shot and deliberately distinct: approving a release signer rotation is not approving
186
+ an ad-hoc replacement of a certificate-rooted install.
187
+ --allow-signing-identity-migration Approve one reviewed release signer change
188
+ --expected-old-identity-sha256 <sha256>
189
+ Exact installed identity approved for migration
190
+ --expected-new-identity-sha256 <sha256>
191
+ Exact candidate identity approved for migration
192
+ --allow-adhoc-identity-migration Approve replacing an installed app with an ad-hoc
193
+ signed local-only build. Required for essentially
194
+ every local-only reinstall, including a repair
195
+ install of the same version, because each ad-hoc
196
+ rebuild produces a new CDHash and is therefore a
197
+ real identity migration. Accepts that Microphone
198
+ and Accessibility must be granted again afterwards.
199
+ --acknowledge-local-signing-and-permissions
200
+ Acknowledge local-only ad-hoc signing up front
201
+
202
+ Launch and help:
203
+ --launch Launch the app after installing
204
+ --launch-timeout <seconds> Seconds to wait for the launch to settle
205
+ --help, -h Print this message and exit 0
206
+ USAGE
207
+ }
208
+
149
209
  ARTIFACT_PATH=""
150
210
  MANIFEST_PATH=""
151
211
  EXPECTED_TEAM_ID="${RECORDINGS_EXPECTED_TEAM_IDENTIFIER:-}"
@@ -157,11 +217,13 @@ EXPECTED_NEW_IDENTITY_SHA256=""
157
217
  EXPECTED_HOSTNAME=""
158
218
  EXPECTED_HOSTNAME_SET=0
159
219
  ARTIFACT_POLICY="release"
220
+ INSTALL_VARIANT="full"
160
221
  APPROVED_TARGET="fleet"
161
222
  APPROVED_TARGET_IDENTITY_KIND=""
162
223
  APPROVED_TARGET_IDENTITY_SHA256="none"
163
224
  ACKNOWLEDGE_LOCAL_SIGNING_AND_PERMISSIONS=0
164
225
  ALLOW_IDENTITY_MIGRATION=0
226
+ ALLOW_ADHOC_IDENTITY_MIGRATION=0
165
227
  LAUNCH_APP=0
166
228
  LAUNCH_TIMEOUT_SECONDS="${RECORDINGS_LAUNCH_TIMEOUT_SECONDS:-10}"
167
229
 
@@ -196,6 +258,16 @@ while [ "$#" -gt 0 ]; do
196
258
  EXPECTED_HOSTNAME_SET=1
197
259
  shift 2
198
260
  ;;
261
+ --variant)
262
+ case "${2:-}" in
263
+ full|bar) INSTALL_VARIANT="${2}" ;;
264
+ *)
265
+ echo "Install variant must be full or bar." >&2
266
+ exit 2
267
+ ;;
268
+ esac
269
+ shift 2
270
+ ;;
199
271
  --artifact-policy)
200
272
  case "${2:-}" in
201
273
  release) ARTIFACT_POLICY="release" ;;
@@ -232,6 +304,12 @@ while [ "$#" -gt 0 ]; do
232
304
  ALLOW_IDENTITY_MIGRATION=1
233
305
  shift
234
306
  ;;
307
+ # Distinct from --allow-signing-identity-migration: approving a release signer
308
+ # rotation is not approving an ad-hoc replacement of a certificate-rooted install.
309
+ --allow-adhoc-identity-migration)
310
+ ALLOW_ADHOC_IDENTITY_MIGRATION=1
311
+ shift
312
+ ;;
235
313
  --launch)
236
314
  LAUNCH_APP=1
237
315
  shift
@@ -240,8 +318,13 @@ while [ "$#" -gt 0 ]; do
240
318
  LAUNCH_TIMEOUT_SECONDS="${2:-}"
241
319
  shift 2
242
320
  ;;
321
+ --help | -h)
322
+ print_usage
323
+ exit 0
324
+ ;;
243
325
  *)
244
326
  echo "Unknown argument: $1" >&2
327
+ echo "Run with --help for the accepted arguments." >&2
245
328
  exit 2
246
329
  ;;
247
330
  esac
@@ -310,6 +393,19 @@ PACKAGE_ROOT="$(cd "$("$DIRNAME_EXECUTABLE" "${BASH_SOURCE[0]}")/.." && pwd)"
310
393
  ARTIFACT_TOOL="${PACKAGE_ROOT}/scripts/macos_artifact.ts"
311
394
  RUNTIME_SMOKE="${PACKAGE_ROOT}/scripts/smoke_macos_app.sh"
312
395
  TAILSCALE_RESOLVER="${PACKAGE_ROOT}/scripts/resolve_tailscale_cli.sh"
396
+ # Sourced here, before either policy branch, so a missing or unusable guard denies every
397
+ # install rather than only the ones that happen to reach it.
398
+ IDENTITY_MIGRATION_GUARD="${PACKAGE_ROOT}/scripts/enforce_identity_migration.sh"
399
+ [ -f "$IDENTITY_MIGRATION_GUARD" ] && [ ! -L "$IDENTITY_MIGRATION_GUARD" ] || {
400
+ echo "Packaged identity-migration guard is missing." >&2
401
+ exit 2
402
+ }
403
+ # shellcheck source=/dev/null
404
+ . "$IDENTITY_MIGRATION_GUARD"
405
+ declare -F recordings_enforce_identity_migration >/dev/null || {
406
+ echo "Packaged identity-migration guard does not define its enforcement function." >&2
407
+ exit 2
408
+ }
313
409
  if [ "$ARTIFACT_POLICY" = "release" ]; then
314
410
  if [ -z "$EXPECTED_TEAM_ID" ]; then
315
411
  echo "Release install requires --expected-team-id or RECORDINGS_EXPECTED_TEAM_IDENTIFIER." >&2
@@ -331,13 +427,36 @@ if [ "$ARTIFACT_POLICY" = "release" ]; then
331
427
  echo "Local-only acknowledgment cannot be supplied for a release artifact." >&2
332
428
  exit 2
333
429
  fi
430
+ if [ "$ALLOW_ADHOC_IDENTITY_MIGRATION" -eq 1 ]; then
431
+ echo "Ad-hoc identity-migration approval is not valid for a release artifact; use --allow-signing-identity-migration with the exact old/new identity digests." >&2
432
+ exit 2
433
+ fi
334
434
  else
335
- if [ -n "$EXPECTED_TEAM_ID" ]; then
336
- echo "Local-only artifacts do not accept --expected-team-id or a release TeamIdentifier environment value." >&2
435
+ # A local-station artifact may be Developer ID signed under a pinned team, because ad-hoc
436
+ # signing cannot hold a macOS TCC grant: TCC keys Accessibility to bundle id plus signing
437
+ # identity, and an ad-hoc CDHash changes on every rebuild, which is what silently killed the
438
+ # Fn/Globe hold-to-talk CGEventTap. A supplied team must therefore be a real Apple team, and
439
+ # it is matched against the manifest below exactly as a release team is.
440
+ if [ -n "$EXPECTED_TEAM_ID" ] && ! [[ "$EXPECTED_TEAM_ID" =~ ^[A-Z0-9]{10}$ ]]; then
441
+ echo "Local-only --expected-team-id must be a 10-character Developer ID team identifier." >&2
337
442
  exit 2
338
443
  fi
339
- if [ "$APPROVED_TARGET" != "station06" ]; then
340
- echo "Local-only install is currently restricted to --approved-target station06." >&2
444
+ # The approved local-only targets are policy data shared with the artifact tool
445
+ # and the builder, parsed by one sourced reader so all three agree.
446
+ LOCAL_TARGET_POLICY="${PACKAGE_ROOT}/scripts/policy/local-only-approved-targets.txt"
447
+ LOCAL_TARGET_READER="${PACKAGE_ROOT}/scripts/read_local_only_targets.sh"
448
+ [ -f "$LOCAL_TARGET_READER" ] && [ ! -L "$LOCAL_TARGET_READER" ] || {
449
+ echo "Packaged local-only approved target reader is missing." >&2
450
+ exit 2
451
+ }
452
+ # shellcheck source=/dev/null
453
+ . "$LOCAL_TARGET_READER"
454
+ APPROVED_TARGET_MATCHED=0
455
+ APPROVED_TARGET_LIST=""
456
+ read_local_only_targets \
457
+ "$LOCAL_TARGET_POLICY" APPROVED_TARGET_LIST APPROVED_TARGET_MATCHED "$APPROVED_TARGET" || exit 2
458
+ if [ "$APPROVED_TARGET_MATCHED" -ne 1 ]; then
459
+ echo "Local-only install requires an approved --approved-target (${APPROVED_TARGET_LIST})." >&2
341
460
  exit 2
342
461
  fi
343
462
  if ! [[ "$APPROVED_TARGET_IDENTITY_SHA256" =~ ^[a-f0-9]{64}$ ]]; then
@@ -386,12 +505,16 @@ else
386
505
  exit 1
387
506
  fi
388
507
  if [ "$ALLOW_IDENTITY_MIGRATION" -eq 1 ] || [ -n "$EXPECTED_OLD_IDENTITY_SHA256" ] || [ -n "$EXPECTED_NEW_IDENTITY_SHA256" ]; then
389
- echo "Release identity-migration flags are not valid for local-only artifacts." >&2
508
+ echo "Release identity-migration flags are not valid for local-only artifacts; an ad-hoc replacement is approved with --allow-adhoc-identity-migration." >&2
390
509
  exit 2
391
510
  fi
392
- EXPECTED_TEAM_ID="ADHOC"
511
+ if [ -z "$EXPECTED_TEAM_ID" ]; then
512
+ EXPECTED_TEAM_ID="ADHOC"
513
+ fi
393
514
  echo "WARNING: installing a non-notarized local-only artifact approved only for ${APPROVED_TARGET}." >&2
394
- echo "WARNING: code identity can change; Microphone or Accessibility may require manual reauthorization." >&2
515
+ if [ "$EXPECTED_TEAM_ID" = "ADHOC" ]; then
516
+ echo "WARNING: code identity can change; Microphone or Accessibility may require manual reauthorization." >&2
517
+ fi
395
518
  fi
396
519
  if [ "$ARTIFACT_POLICY" = "release" ] && [ "$ALLOW_IDENTITY_MIGRATION" -eq 1 ] && \
397
520
  { [ -z "$EXPECTED_OLD_IDENTITY_SHA256" ] || [ -z "$EXPECTED_NEW_IDENTITY_SHA256" ]; }; then
@@ -423,7 +546,8 @@ fi
423
546
  "$BUN_EXECUTABLE" "$ARTIFACT_TOOL" native-fs-guard-check
424
547
  DATA_DIR="${HOME}/.hasna/recordings"
425
548
  DATA_PARENT="$("$DIRNAME_EXECUTABLE" "$DATA_DIR")"
426
- APP_DEST="${HOME}/Applications/Recordings.app"
549
+ MANIFEST_BUNDLE_NAME="Recordings.app"
550
+ APP_DEST="${HOME}/Applications/${MANIFEST_BUNDLE_NAME}"
427
551
  APP_PARENT="$("$DIRNAME_EXECUTABLE" "$APP_DEST")"
428
552
  ROLLBACK_DIR="${DATA_DIR}/rollbacks"
429
553
  JOURNAL_PATH="${APP_PARENT}/.Recordings-install-transaction.json"
@@ -1214,6 +1338,7 @@ MANIFEST_SNAPSHOT="${WORK_DIR}/$("$BASENAME_EXECUTABLE" "$MANIFEST_PATH")"
1214
1338
  --source-sha "$EXPECTED_SOURCE_SHA" \
1215
1339
  --version "$EXPECTED_VERSION" \
1216
1340
  --artifact-policy "$ARTIFACT_POLICY" \
1341
+ --variant "$INSTALL_VARIANT" \
1217
1342
  --approved-target "$APPROVED_TARGET" \
1218
1343
  --approved-target-identity-kind "$APPROVED_TARGET_IDENTITY_KIND" \
1219
1344
  --approved-target-identity-sha256 "$APPROVED_TARGET_IDENTITY_SHA256"
@@ -1221,6 +1346,22 @@ MANIFEST_BUILDER_IDENTITY_KIND="$("$BUN_EXECUTABLE" "$ARTIFACT_TOOL" manifest-ge
1221
1346
  --manifest "$MANIFEST_SNAPSHOT" \
1222
1347
  --manifest-sha256 "$EXPECTED_MANIFEST_SHA256" \
1223
1348
  --field builder_identity_kind)"
1349
+ AUTHENTICATED_BUNDLE_NAME="$("$BUN_EXECUTABLE" "$ARTIFACT_TOOL" manifest-get \
1350
+ --manifest "$MANIFEST_SNAPSHOT" \
1351
+ --manifest-sha256 "$EXPECTED_MANIFEST_SHA256" \
1352
+ --field bundle_name)"
1353
+ case "$AUTHENTICATED_BUNDLE_NAME" in
1354
+ *.app) ;;
1355
+ *)
1356
+ echo "Authenticated artifact manifest carries an invalid bundle name: ${AUTHENTICATED_BUNDLE_NAME}." >&2
1357
+ exit 1
1358
+ ;;
1359
+ esac
1360
+ if [ "$AUTHENTICATED_BUNDLE_NAME" != "$MANIFEST_BUNDLE_NAME" ]; then
1361
+ MANIFEST_BUNDLE_NAME="$AUTHENTICATED_BUNDLE_NAME"
1362
+ APP_DEST="${HOME}/Applications/${MANIFEST_BUNDLE_NAME}"
1363
+ APP_PARENT="$("$DIRNAME_EXECUTABLE" "$APP_DEST")"
1364
+ fi
1224
1365
 
1225
1366
  CURRENT_MACOS="$("$SW_VERS_EXECUTABLE" -productVersion)"
1226
1367
  MINIMUM_MACOS="$("$BUN_EXECUTABLE" "$ARTIFACT_TOOL" manifest-get \
@@ -1252,7 +1393,7 @@ verify_secure_parent "$APP_PARENT"
1252
1393
  verify_existing_state_root
1253
1394
 
1254
1395
  STAGING_DIR="$("$MKTEMP_EXECUTABLE" -d "${APP_PARENT}/.Recordings-install.XXXXXX")"
1255
- STAGED_APP="${STAGING_DIR}/Recordings.app"
1396
+ STAGED_APP="${STAGING_DIR}/${MANIFEST_BUNDLE_NAME}"
1256
1397
  "$BUN_EXECUTABLE" "$ARTIFACT_TOOL" verify-archive \
1257
1398
  --archive "$ARTIFACT_SNAPSHOT" \
1258
1399
  --manifest "$MANIFEST_SNAPSHOT" \
@@ -1261,6 +1402,7 @@ STAGED_APP="${STAGING_DIR}/Recordings.app"
1261
1402
  --source-sha "$EXPECTED_SOURCE_SHA" \
1262
1403
  --version "$EXPECTED_VERSION" \
1263
1404
  --artifact-policy "$ARTIFACT_POLICY" \
1405
+ --variant "$INSTALL_VARIANT" \
1264
1406
  --approved-target "$APPROVED_TARGET" \
1265
1407
  --approved-target-identity-kind "$APPROVED_TARGET_IDENTITY_KIND" \
1266
1408
  --approved-target-identity-sha256 "$APPROVED_TARGET_IDENTITY_SHA256"
@@ -1538,10 +1680,11 @@ write_journal() {
1538
1680
  --source-sha "$EXPECTED_SOURCE_SHA" \
1539
1681
  --version "$EXPECTED_VERSION" \
1540
1682
  --artifact-policy "$ARTIFACT_POLICY" \
1683
+ --variant "$INSTALL_VARIANT" \
1541
1684
  --approved-target "$APPROVED_TARGET" \
1542
1685
  --approved-target-identity-kind "$APPROVED_TARGET_IDENTITY_KIND" \
1543
1686
  --approved-target-identity-sha256 "$APPROVED_TARGET_IDENTITY_SHA256"
1544
- CANDIDATE_APP="$UNPACK_DIR/Recordings.app"
1687
+ CANDIDATE_APP="$UNPACK_DIR/${MANIFEST_BUNDLE_NAME}"
1545
1688
 
1546
1689
  "$BUN_EXECUTABLE" "$ARTIFACT_TOOL" verify-app \
1547
1690
  --app "$CANDIDATE_APP" \
@@ -1549,6 +1692,7 @@ CANDIDATE_APP="$UNPACK_DIR/Recordings.app"
1549
1692
  --manifest-sha256 "$EXPECTED_MANIFEST_SHA256" \
1550
1693
  --team-id "$EXPECTED_TEAM_ID" \
1551
1694
  --artifact-policy "$ARTIFACT_POLICY" \
1695
+ --variant "$INSTALL_VARIANT" \
1552
1696
  --approved-target "$APPROVED_TARGET" \
1553
1697
  --approved-target-identity-kind "$APPROVED_TARGET_IDENTITY_KIND" \
1554
1698
  --approved-target-identity-sha256 "$APPROVED_TARGET_IDENTITY_SHA256"
@@ -1562,10 +1706,12 @@ fi
1562
1706
  DISCOVERED_APPS=()
1563
1707
  add_unique_app "$APP_DEST"
1564
1708
  add_unique_app "${DATA_DIR}/Recordings.app"
1565
- for candidate in "${HOME}"/Applications/Recordings.app.*; do
1709
+ add_unique_app "${DATA_DIR}/HasnaRecordings.app"
1710
+ for candidate in "${HOME}"/Applications/Recordings.app.* "${HOME}"/Applications/HasnaRecordings.app.*; do
1566
1711
  add_unique_app "$candidate"
1567
1712
  done
1568
1713
  add_unique_app "/Applications/Recordings.app"
1714
+ add_unique_app "/Applications/HasnaRecordings.app"
1569
1715
  if [ -x "$MDFIND_EXECUTABLE" ]; then
1570
1716
  while IFS= read -r candidate; do
1571
1717
  [ -n "$candidate" ] || continue
@@ -1576,7 +1722,7 @@ fi
1576
1722
  MANAGEABLE_APPS=()
1577
1723
  for existing_app in ${DISCOVERED_APPS[@]+"${DISCOVERED_APPS[@]}"}; do
1578
1724
  case "$existing_app" in
1579
- "$APP_DEST"|"${DATA_DIR}/Recordings.app"|"${HOME}/Applications/Recordings.app."*)
1725
+ "$APP_DEST"|"${DATA_DIR}/Recordings.app"|"${DATA_DIR}/HasnaRecordings.app"|"${HOME}/Applications/Recordings.app."*|"${HOME}/Applications/HasnaRecordings.app."*)
1580
1726
  echo "Duplicate disposition: archive transactionally, remove after activation: ${existing_app}"
1581
1727
  MANAGEABLE_APPS+=("$existing_app")
1582
1728
  ;;
@@ -1621,7 +1767,10 @@ if [ "$ARTIFACT_POLICY" = "release" ] && [ -z "$candidate_requirement" ]; then
1621
1767
  exit 1
1622
1768
  fi
1623
1769
  identity_migration=0
1624
- candidate_identity_sha256="$("$BUN_EXECUTABLE" "$ARTIFACT_TOOL" requirement-digest --app "$CANDIDATE_APP" --artifact-policy "$ARTIFACT_POLICY")"
1770
+ # Pass the SAME pinned team already given to verify-archive and verify-app. Without it the
1771
+ # digest verifies the candidate against the ad-hoc shape, which rejects a correctly
1772
+ # Developer-ID-signed local-station bundle and, under `set -euo pipefail`, aborts the install.
1773
+ candidate_identity_sha256="$("$BUN_EXECUTABLE" "$ARTIFACT_TOOL" requirement-digest --app "$CANDIDATE_APP" --artifact-policy "$ARTIFACT_POLICY" --expected-team-id "$EXPECTED_TEAM_ID")"
1625
1774
  candidate_tree_sha256="$("$BUN_EXECUTABLE" "$ARTIFACT_TOOL" tree-digest --path "$CANDIDATE_APP")"
1626
1775
  [ "$candidate_identity_sha256" = "$("$BUN_EXECUTABLE" "$ARTIFACT_TOOL" manifest-get \
1627
1776
  --manifest "$MANIFEST_SNAPSHOT" \
@@ -1655,21 +1804,18 @@ for existing_app in ${MANAGEABLE_APPS[@]+"${MANAGEABLE_APPS[@]}"}; do
1655
1804
  identity_migration=1
1656
1805
  fi
1657
1806
  done
1658
- if [ "$ARTIFACT_POLICY" = "release" ] && [ "$identity_migration" -eq 1 ] && [ "$ALLOW_IDENTITY_MIGRATION" -ne 1 ]; then
1659
- echo "Candidate and existing app designated requirements are not mutually compatible; review the signer change and rerun once with --allow-signing-identity-migration." >&2
1660
- exit 1
1661
- fi
1662
- if [ "$ARTIFACT_POLICY" = "release" ] && [ "$identity_migration" -eq 1 ] && {
1663
- [ "$previous_identity_sha256" != "$EXPECTED_OLD_IDENTITY_SHA256" ] ||
1664
- [ "$candidate_identity_sha256" != "$EXPECTED_NEW_IDENTITY_SHA256" ];
1665
- }; then
1666
- echo "Signing identity migration does not match the exact operator-approved old/new identities." >&2
1667
- exit 1
1668
- fi
1669
- if [ "$ARTIFACT_POLICY" = "release" ] && [ "$identity_migration" -eq 0 ] && [ "$ALLOW_IDENTITY_MIGRATION" -eq 1 ]; then
1670
- echo "Identity migration approval was supplied but no identity migration is required." >&2
1671
- exit 1
1672
- fi
1807
+ # Enforced for every artifact policy. A local-only artifact is ad-hoc signed, so this is
1808
+ # the point at which an install would void the installed app's Microphone and
1809
+ # Accessibility grants; the printed warning further down is not a gate.
1810
+ recordings_enforce_identity_migration \
1811
+ "$ARTIFACT_POLICY" \
1812
+ "$identity_migration" \
1813
+ "$ALLOW_IDENTITY_MIGRATION" \
1814
+ "$ALLOW_ADHOC_IDENTITY_MIGRATION" \
1815
+ "$previous_identity_sha256" \
1816
+ "$candidate_identity_sha256" \
1817
+ "$EXPECTED_OLD_IDENTITY_SHA256" \
1818
+ "$EXPECTED_NEW_IDENTITY_SHA256" || exit 1
1673
1819
 
1674
1820
  TRANSACTION_DIR="$("$MKTEMP_EXECUTABLE" -d "${APP_PARENT}/.Recordings-transaction.XXXXXX")"
1675
1821
  TRANSACTION_NONCE="$("$BUN_EXECUTABLE" -e 'import { randomUUID } from "node:crypto"; process.stdout.write(randomUUID())')"
@@ -1738,6 +1884,7 @@ done
1738
1884
  --manifest-sha256 "$EXPECTED_MANIFEST_SHA256" \
1739
1885
  --team-id "$EXPECTED_TEAM_ID" \
1740
1886
  --artifact-policy "$ARTIFACT_POLICY" \
1887
+ --variant "$INSTALL_VARIANT" \
1741
1888
  --approved-target "$APPROVED_TARGET" \
1742
1889
  --approved-target-identity-kind "$APPROVED_TARGET_IDENTITY_KIND" \
1743
1890
  --approved-target-identity-sha256 "$APPROVED_TARGET_IDENTITY_SHA256"
@@ -1903,6 +2050,7 @@ write_journal candidate-installed
1903
2050
  --manifest-sha256 "$EXPECTED_MANIFEST_SHA256" \
1904
2051
  --team-id "$EXPECTED_TEAM_ID" \
1905
2052
  --artifact-policy "$ARTIFACT_POLICY" \
2053
+ --variant "$INSTALL_VARIANT" \
1906
2054
  --approved-target "$APPROVED_TARGET" \
1907
2055
  --approved-target-identity-kind "$APPROVED_TARGET_IDENTITY_KIND" \
1908
2056
  --approved-target-identity-sha256 "$APPROVED_TARGET_IDENTITY_SHA256"
@@ -1918,11 +2066,12 @@ fi
1918
2066
  --manifest-sha256 "$EXPECTED_MANIFEST_SHA256" \
1919
2067
  --team-id "$EXPECTED_TEAM_ID" \
1920
2068
  --artifact-policy "$ARTIFACT_POLICY" \
2069
+ --variant "$INSTALL_VARIANT" \
1921
2070
  --approved-target "$APPROVED_TARGET" \
1922
2071
  --approved-target-identity-kind "$APPROVED_TARGET_IDENTITY_KIND" \
1923
2072
  --approved-target-identity-sha256 "$APPROVED_TARGET_IDENTITY_SHA256"
1924
2073
  SMOKE_TOOL_PATH="/usr/bin:/bin:/usr/sbin:/sbin:$("$DIRNAME_EXECUTABLE" "$BUN_EXECUTABLE")"
1925
- PATH="$SMOKE_TOOL_PATH" "$RUNTIME_SMOKE" "$APP_DEST" "$BUN_EXECUTABLE"
2074
+ PATH="$SMOKE_TOOL_PATH" "$RUNTIME_SMOKE" "$APP_DEST" "$BUN_EXECUTABLE" --variant "$INSTALL_VARIANT"
1926
2075
  write_journal activated
1927
2076
 
1928
2077
  write_journal committed
@@ -1934,7 +2083,14 @@ STOPPED_RUNNING_APP=0
1934
2083
  if [ "$LAUNCH_APP" -eq 1 ] || [ "$was_running" -eq 1 ]; then
1935
2084
  EXPECTED_EXECUTABLE="$APP_DEST/Contents/MacOS/Recordings"
1936
2085
  RUNNING_EXECUTABLES+=("$EXPECTED_EXECUTABLE")
1937
- "$OPEN_EXECUTABLE" -n "$APP_DEST"
2086
+ if [ "$INSTALL_VARIANT" = "bar" ]; then
2087
+ # Bar installs relaunch with an explicit --bar-only so the launch record is
2088
+ # self-describing; the compile-time RECORDINGS_BAR_ONLY default makes the bar
2089
+ # windowless even without the argument.
2090
+ "$OPEN_EXECUTABLE" -n "$APP_DEST" --args --bar-only
2091
+ else
2092
+ "$OPEN_EXECUTABLE" -n "$APP_DEST"
2093
+ fi
1938
2094
  attempts=$((LAUNCH_TIMEOUT_SECONDS * 10))
1939
2095
  launched_pid=""
1940
2096
  launched_start_identity=""
@@ -1962,7 +2118,12 @@ fi
1962
2118
 
1963
2119
  if [ "$ARTIFACT_POLICY" = "local_only" ]; then
1964
2120
  echo "Installed local-only Recordings.app for ${APPROVED_TARGET}; this artifact is ad-hoc signed and non-notarized."
1965
- echo "Microphone or Accessibility may require manual reauthorization after this code-identity change."
2121
+ if [ "$identity_migration" -eq 1 ]; then
2122
+ # Not a maybe: the designated requirement did change, under an explicit approval.
2123
+ echo "Code identity changed under --allow-adhoc-identity-migration; the Microphone and Accessibility grants held by the replaced app no longer apply and must be reauthorized."
2124
+ else
2125
+ echo "Microphone or Accessibility may require manual reauthorization if this code identity differs from the last authorized one."
2126
+ fi
1966
2127
  elif [ "$identity_migration" -eq 1 ]; then
1967
2128
  echo "Installed a new signing identity; macOS will require one-time permission approval for this migration."
1968
2129
  else