@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
@@ -83,7 +83,7 @@ fi
83
83
  readonly RELEASE_SUBTYPE
84
84
  RELEASE_ARCHITECTURES=(arm64 x86_64)
85
85
 
86
- CODESIGN_IDENTITY="${RECORDINGS_CODESIGN_IDENTITY:-}"
86
+ CODESIGN_IDENTITY="${RECORDINGS_CODESIGN_IDENTITY:-${HASNA_CODESIGN_IDENTITY:-}}"
87
87
  EXPECTED_TEAM_ID="${RECORDINGS_EXPECTED_TEAM_IDENTIFIER:-}"
88
88
  NOTARY_PROFILE="${RECORDINGS_NOTARY_KEYCHAIN_PROFILE:-}"
89
89
  INSTALLER_IDENTITY="${RECORDINGS_INSTALLER_CODESIGN_IDENTITY:-}"
@@ -96,6 +96,41 @@ COMPATIBLE_COHORT_MANIFEST="${RECORDINGS_RELEASE_COMPATIBLE_COHORT_MANIFEST:-}"
96
96
  LOCAL_APPROVED_TARGET="${RECORDINGS_LOCAL_APPROVED_TARGET:-}"
97
97
  LOCAL_APPROVED_TARGET_IDENTITY_KIND="${RECORDINGS_LOCAL_APPROVED_TARGET_IDENTITY_KIND:-}"
98
98
  LOCAL_APPROVED_TARGET_IDENTITY_SHA256="${RECORDINGS_LOCAL_APPROVED_TARGET_IDENTITY_SHA256:-}"
99
+ VARIANT="${RECORDINGS_VARIANT:-full}"
100
+ case "$VARIANT" in
101
+ full|bar) ;;
102
+ *)
103
+ echo "RECORDINGS_VARIANT must be full or bar; got: $VARIANT" >&2
104
+ exit 2
105
+ ;;
106
+ esac
107
+ if [ "$MODE" = "release" ] && [ "$VARIANT" = "bar" ]; then
108
+ # The bar variant is a deliberate product change (no workspace window, accessory
109
+ # launch); a release that carries it must be explicitly marked so a default release
110
+ # can never silently emit a bar artifact.
111
+ if [ "${RECORDINGS_RELEASE_BAR_VARIANT_MARKED:-0}" != "1" ]; then
112
+ echo "release-mode bar-variant builds require RECORDINGS_RELEASE_BAR_VARIANT_MARKED=1 to proceed explicitly." >&2
113
+ exit 2
114
+ fi
115
+ fi
116
+ readonly VARIANT
117
+ VARIANT_SWIFT_FLAGS=()
118
+ if [ "$VARIANT" = "bar" ]; then
119
+ VARIANT_SWIFT_FLAGS=(-Xswiftc -DRECORDINGS_BAR_ONLY)
120
+ fi
121
+ # Fleet naming rule (knowledge k_msxd5rz3_jfvl3i): every Hasna macOS app is
122
+ # Hasna<Name>.app with 'Hasna' at the beginning. The full app keeps its historical
123
+ # Recordings.app name (its rename is the rule's recorded follow-up decision); the
124
+ # bar-only artifact is named per the rule NOW — HasnaRecordings.app. The 'bar' is the
125
+ # variant, never a separate app name: bundle identifier stays com.hasna.recordings so
126
+ # TCC keeps keying on bundle id + signing identity, and the executable inside the
127
+ # bundle stays "Recordings".
128
+ APP_BUNDLE_NAME="Recordings.app"
129
+ if [ "$VARIANT" = "bar" ]; then
130
+ APP_BUNDLE_NAME="HasnaRecordings.app"
131
+ fi
132
+ readonly APP_BUNDLE_NAME
133
+ APP_BASENAME="${APP_BUNDLE_NAME%.app}"
99
134
  PLIST_BUDDY="$(select_executable "/usr/libexec/PlistBuddy" "${RECORDINGS_TEST_PLIST_BUDDY_EXECUTABLE:-${PLIST_BUDDY:-}}")"
100
135
  PLUTIL="$(select_executable "/usr/bin/plutil" "${RECORDINGS_TEST_PLUTIL_EXECUTABLE:-${PLUTIL:-}}")"
101
136
  BUN_EXECUTABLE="${BUN_EXECUTABLE:-}"
@@ -134,6 +169,48 @@ LIPO_EXECUTABLE="$(select_executable "/usr/bin/lipo" "${RECORDINGS_TEST_LIPO_EXE
134
169
  STAT_EXECUTABLE="$(select_executable "/usr/bin/stat" "${RECORDINGS_TEST_STAT_EXECUTABLE:-}")"
135
170
  TEST_GIT_EXECUTABLE="$(select_executable "" "${RECORDINGS_TEST_GIT_EXECUTABLE:-}")"
136
171
  TEST_SWIFT_EXECUTABLE="$(select_executable "" "${RECORDINGS_TEST_SWIFT_EXECUTABLE:-}")"
172
+ SECURITY_EXECUTABLE="$(select_executable "/usr/bin/security" "${RECORDINGS_TEST_SECURITY_EXECUTABLE:-}")"
173
+
174
+ # Developer ID signing identity support. The identity comes from RECORDINGS_CODESIGN_IDENTITY
175
+ # (legacy) or HASNA_CODESIGN_IDENTITY (canonical name), and when neither is supplied the
176
+ # keychain is consulted with `security find-identity -v -p codesigning` and the first
177
+ # "Developer ID Application:" identity is used. Signing NEVER silently falls back to ad-hoc
178
+ # when a Developer ID was requested: release builds always require one, and a local build
179
+ # with RECORDINGS_SIGNING_REQUIRED=1 fails loudly when none exists.
180
+ discover_developer_id_identity() {
181
+ local listing
182
+ listing="$("$SECURITY_EXECUTABLE" find-identity -v -p codesigning 2>/dev/null)" || return 0
183
+ printf '%s\n' "$listing" | "$AWK_EXECUTABLE" -F'"' '/Developer ID Application:/ { print $2; exit }'
184
+ }
185
+
186
+ derive_team_id() {
187
+ local identity="$1"
188
+ local team_id
189
+ team_id="$(printf '%s\n' "$identity" | "$SED_EXECUTABLE" -n 's/.*(\([A-Z0-9]\{10\}\))$/\1/p')"
190
+ if ! [[ "$team_id" =~ ^[A-Z0-9]{10}$ ]]; then
191
+ echo "Could not derive a 10-character Developer ID team from the signing identity: $identity" >&2
192
+ return 1
193
+ fi
194
+ printf '%s\n' "$team_id"
195
+ }
196
+
197
+ resolve_signing_identity() {
198
+ # Called when no explicit identity was supplied. Fails loudly when a Developer ID
199
+ # was requested but none can be found; the caller decides whether one was requested.
200
+ if [ "$HOST_PLATFORM" != "Darwin" ]; then
201
+ return 0
202
+ fi
203
+ local discovered
204
+ discovered="$(discover_developer_id_identity)"
205
+ if [ -z "$discovered" ]; then
206
+ echo "Developer ID signing was requested but no Developer ID Application identity found in the keychain; never silently ad-hoc when a Developer ID is requested." >&2
207
+ return 1
208
+ fi
209
+ CODESIGN_IDENTITY="$discovered"
210
+ if [ -z "$EXPECTED_TEAM_ID" ]; then
211
+ EXPECTED_TEAM_ID="$(derive_team_id "$CODESIGN_IDENTITY")" || return 1
212
+ fi
213
+ }
137
214
 
138
215
  require_executable() {
139
216
  local label="$1"
@@ -253,6 +330,7 @@ require_executable "ID_EXECUTABLE" "$ID_EXECUTABLE"
253
330
  require_executable "STAT_EXECUTABLE" "$STAT_EXECUTABLE"
254
331
  if [ "$HOST_PLATFORM" = "Darwin" ]; then
255
332
  require_executable "LIPO_EXECUTABLE" "$LIPO_EXECUTABLE"
333
+ require_executable "SECURITY_EXECUTABLE" "$SECURITY_EXECUTABLE"
256
334
  fi
257
335
  require_bun_executable "$BUN_EXECUTABLE"
258
336
  if [ "$MODE" = "release" ]; then
@@ -380,6 +458,8 @@ if [ "$SOURCE_PACKAGE_ROOT" != "$PACKAGE_ROOT" ]; then
380
458
  "$SOURCE_PACKAGE_ROOT/scripts/native_fs_guard.ts" \
381
459
  "$SOURCE_PACKAGE_ROOT/scripts/build_native_fs_guard.sh" \
382
460
  "$SOURCE_PACKAGE_ROOT/scripts/native/recordings_fs_guard.c" \
461
+ "$SOURCE_PACKAGE_ROOT/scripts/policy/local-only-approved-targets.txt" \
462
+ "$SOURCE_PACKAGE_ROOT/scripts/read_local_only_targets.sh" \
383
463
  "$SOURCE_PACKAGE_ROOT/packaging/macos/build_release_pkg.sh" \
384
464
  "$SOURCE_PACKAGE_ROOT/packaging/macos/release_lifecycle.ts" \
385
465
  "$SOURCE_PACKAGE_ROOT/packaging/macos/Verifier.entitlements" \
@@ -543,11 +623,23 @@ run_codesign() {
543
623
  }
544
624
 
545
625
  run_swift() {
626
+ # VARIANT_SWIFT_FLAGS is empty for every full build (the default). A bare
627
+ # "${arr[@]}" on an EMPTY array under `set -u` aborts bash < 4.4 — macOS
628
+ # /bin/bash is 3.2.57 — with `unbound variable`, which killed every build the
629
+ # moment the flags existed. The ${arr[0]+"${arr[@]}"} guard is the script-wide
630
+ # idiom (see run_bun, run_xcrun, run_release_sensitive_tool); do not reintroduce
631
+ # a bare expansion here.
632
+ #
633
+ # The flags are arguments TO swift (after the executable), never before it:
634
+ # placed before the executable they would be consumed by /usr/bin/env as its
635
+ # own options and the build would die with `env: '-Xswiftc': No such file`.
546
636
  "$ENV_EXECUTABLE" -i \
547
637
  HOME="$BUILD_HOME" \
548
638
  PATH="$SANITIZED_PATH" \
549
639
  TMPDIR="$BUILD_WORK_DIR" \
550
- "$SWIFT_EXECUTABLE" "$@"
640
+ "$SWIFT_EXECUTABLE" \
641
+ ${VARIANT_SWIFT_FLAGS[0]+"${VARIANT_SWIFT_FLAGS[@]}"} \
642
+ "$@"
551
643
  }
552
644
 
553
645
  run_lipo() {
@@ -937,8 +1029,19 @@ BUILDER_IDENTITY_KIND="none"
937
1029
  BUILDER_IDENTITY_SHA256="none"
938
1030
  if [ "$MODE" = "release" ]; then
939
1031
  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
1032
+ # No explicit identity: discover the Developer ID Application identity from the
1033
+ # keychain. Release signing is never optional, so "requested but not found" fails
1034
+ # loudly instead of degrading to an ad-hoc signature.
1035
+ if [ "$HOST_PLATFORM" = "Darwin" ]; then
1036
+ resolve_signing_identity || {
1037
+ echo "Release builds require a Developer ID Application identity in RECORDINGS_CODESIGN_IDENTITY or HASNA_CODESIGN_IDENTITY." >&2
1038
+ exit 1
1039
+ }
1040
+ fi
1041
+ if [ -z "$CODESIGN_IDENTITY" ] || [ "$CODESIGN_IDENTITY" = "-" ]; then
1042
+ echo "Release builds require RECORDINGS_CODESIGN_IDENTITY or HASNA_CODESIGN_IDENTITY for a Developer ID Application identity." >&2
1043
+ exit 1
1044
+ fi
942
1045
  fi
943
1046
  if [ -z "$EXPECTED_TEAM_ID" ]; then
944
1047
  echo "Release builds require RECORDINGS_EXPECTED_TEAM_IDENTIFIER to pin the Developer ID team." >&2
@@ -991,8 +1094,34 @@ if [ "$MODE" = "release" ]; then
991
1094
  fi
992
1095
  fi
993
1096
  elif [ "$MODE" = "local" ]; then
994
- if [ "$LOCAL_APPROVED_TARGET" != "station06" ]; then
995
- echo "Local-only builds currently require RECORDINGS_LOCAL_APPROVED_TARGET=station06." >&2
1097
+ # Same policy file and same sourced reader the installer uses. The artifact tool
1098
+ # re-implements the same rules in TypeScript and re-checks the target later, so
1099
+ # agreement between the two implementations rests on their shared contract test,
1100
+ # not on shared code.
1101
+ #
1102
+ # Both resolve from $SOURCE_PACKAGE_ROOT — the `git archive` of $SOURCE_SHA — and not
1103
+ # from $PACKAGE_ROOT. They used to read $PACKAGE_ROOT, which is the mutable working
1104
+ # tree, so this gate validated a different file from the one the artifact was built
1105
+ # from: with the archived policy listing only station06, a working-tree policy listing
1106
+ # "attacker" got "attacker" approved, and station06 refused. Every other build input
1107
+ # is resolved from the snapshot for exactly this reason (see the required-snapshot-input
1108
+ # loop above, which already demands both of these paths inside the archive), and a gate
1109
+ # that checks a file the artifact does not contain is not a gate.
1110
+ LOCAL_TARGET_POLICY="${SOURCE_PACKAGE_ROOT}/scripts/policy/local-only-approved-targets.txt"
1111
+ LOCAL_TARGET_READER="${SOURCE_PACKAGE_ROOT}/scripts/read_local_only_targets.sh"
1112
+ [ -f "$LOCAL_TARGET_READER" ] && [ ! -L "$LOCAL_TARGET_READER" ] || {
1113
+ echo "Packaged local-only approved target reader is missing." >&2
1114
+ exit 1
1115
+ }
1116
+ # shellcheck source=/dev/null
1117
+ . "$LOCAL_TARGET_READER"
1118
+ LOCAL_APPROVED_TARGET_MATCHED=0
1119
+ LOCAL_APPROVED_TARGET_LIST=""
1120
+ read_local_only_targets \
1121
+ "$LOCAL_TARGET_POLICY" LOCAL_APPROVED_TARGET_LIST LOCAL_APPROVED_TARGET_MATCHED \
1122
+ "$LOCAL_APPROVED_TARGET" || exit 1
1123
+ if [ "$LOCAL_APPROVED_TARGET_MATCHED" -ne 1 ]; then
1124
+ echo "Local-only builds require an approved RECORDINGS_LOCAL_APPROVED_TARGET (${LOCAL_APPROVED_TARGET_LIST})." >&2
996
1125
  exit 1
997
1126
  fi
998
1127
  if ! [[ "$LOCAL_APPROVED_TARGET_IDENTITY_SHA256" =~ ^[a-f0-9]{64}$ ]]; then
@@ -1031,13 +1160,75 @@ elif [ "$MODE" = "local" ]; then
1031
1160
  echo "Local-only artifacts must be built on a different authenticated Tailscale node." >&2
1032
1161
  exit 1
1033
1162
  fi
1034
- CODESIGN_IDENTITY="-"
1035
- EXPECTED_TEAM_ID="ADHOC"
1163
+ # Signing identity for a local-station artifact.
1164
+ #
1165
+ # This used to be an unconditional `CODESIGN_IDENTITY="-"`, and that was the defect
1166
+ # behind "the Fn key does nothing". Hold-to-talk is a CGEventTap (RecordingsLib/
1167
+ # FnKeyMonitor.swift), macOS only creates that tap for a process with Accessibility,
1168
+ # and TCC keys every grant to bundle identifier PLUS code-signing identity. An ad-hoc
1169
+ # signature has no identity to key on beyond a CDHash that changes on every rebuild,
1170
+ # so each reinstall orphaned the grant, AXIsProcessTrusted() stayed false and the app
1171
+ # logged "fn monitor start ok=false" forever. Ad-hoc is therefore not a neutral default
1172
+ # here — it structurally prevents the permission the feature needs from ever sticking.
1173
+ #
1174
+ # So when a Developer ID Application identity is supplied, sign with it and pin its
1175
+ # team. That is the same stable identity a release uses; what still separates this from
1176
+ # a release is notarization, the single-target binding, and the builder attestation
1177
+ # above, none of which are relaxed. With no identity supplied the old ad-hoc behaviour
1178
+ # is kept verbatim, because a Mac without the signing key must still be able to build.
1179
+ if [ -z "$CODESIGN_IDENTITY" ] || [ "$CODESIGN_IDENTITY" = "-" ]; then
1180
+ if [ "${RECORDINGS_SIGNING_REQUIRED:-0}" = "1" ]; then
1181
+ # A Developer ID was explicitly requested for this local build: discover it
1182
+ # from the keychain and fail loudly when none exists.
1183
+ resolve_signing_identity || exit 1
1184
+ elif [ "$HOST_PLATFORM" = "Darwin" ]; then
1185
+ # The DEFAULT route is the keychain: `security find-identity` and use the
1186
+ # Developer ID Application identity when one exists. That is the stable
1187
+ # signing identity TCC grants key on — an ad-hoc signature has no identity
1188
+ # to key on beyond a CDHash that changes on every rebuild, which orphans
1189
+ # the Accessibility grant and breaks the Fn hold-to-talk path. A Mac
1190
+ # without the signing key still builds ad-hoc (the old behaviour), but the
1191
+ # fallback is LOUD, never silent, and RECORDINGS_SIGNING_REQUIRED=1 above
1192
+ # turns it into a hard failure.
1193
+ discovered_identity="$(discover_developer_id_identity)"
1194
+ if [ -n "$discovered_identity" ]; then
1195
+ CODESIGN_IDENTITY="$discovered_identity"
1196
+ if [ -z "$EXPECTED_TEAM_ID" ]; then
1197
+ EXPECTED_TEAM_ID="$(derive_team_id "$CODESIGN_IDENTITY")" || exit 1
1198
+ fi
1199
+ else
1200
+ echo "WARNING: no Developer ID Application identity found in the keychain; local build falls back to ad-hoc signing (set RECORDINGS_SIGNING_REQUIRED=1 to fail instead)." >&2
1201
+ fi
1202
+ fi
1203
+ fi
1204
+ if [ -n "$CODESIGN_IDENTITY" ] && [ "$CODESIGN_IDENTITY" != "-" ]; then
1205
+ if [[ "$CODESIGN_IDENTITY" != "Developer ID Application:"* ]]; then
1206
+ echo "Local-station signing requires a Developer ID Application identity in RECORDINGS_CODESIGN_IDENTITY." >&2
1207
+ exit 1
1208
+ fi
1209
+ if [ -z "$EXPECTED_TEAM_ID" ]; then
1210
+ echo "Local-station Developer ID signing requires RECORDINGS_EXPECTED_TEAM_IDENTIFIER to pin the team." >&2
1211
+ exit 1
1212
+ fi
1213
+ if [ "$EXPECTED_TEAM_ID" = "ADHOC" ]; then
1214
+ echo "RECORDINGS_EXPECTED_TEAM_IDENTIFIER=ADHOC is not a Developer ID team." >&2
1215
+ exit 1
1216
+ fi
1217
+ LOCAL_SIGNING_MODE="developer_id"
1218
+ else
1219
+ CODESIGN_IDENTITY="-"
1220
+ EXPECTED_TEAM_ID="ADHOC"
1221
+ LOCAL_SIGNING_MODE="ad_hoc"
1222
+ fi
1036
1223
  ARTIFACT_POLICY="local_only"
1037
1224
  APPROVED_TARGET="$LOCAL_APPROVED_TARGET"
1038
1225
  APPROVED_TARGET_IDENTITY_KIND="$LOCAL_APPROVED_TARGET_IDENTITY_KIND"
1039
1226
  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
1227
+ if [ "$LOCAL_SIGNING_MODE" = "developer_id" ]; then
1228
+ echo "WARNING: local-only artifacts are Developer ID signed, non-notarized, and restricted to ${APPROVED_TARGET}." >&2
1229
+ else
1230
+ echo "WARNING: local-only artifacts are ad-hoc signed, non-notarized, and restricted to ${APPROVED_TARGET}." >&2
1231
+ fi
1041
1232
  echo "WARNING: installing can change code identity and require Microphone or Accessibility reauthorization." >&2
1042
1233
  else
1043
1234
  BUILD_CONFIGURATION="debug"
@@ -1075,9 +1266,9 @@ if [ "$MODE" = "release" ]; then
1075
1266
  else
1076
1267
  OUTPUT_BUILD_DIR="$SCRIPT_DIR/.build/$BUILD_CONFIGURATION"
1077
1268
  fi
1078
- OUTPUT_APP_DIR="$OUTPUT_BUILD_DIR/Recordings.app"
1269
+ OUTPUT_APP_DIR="$OUTPUT_BUILD_DIR/$APP_BUNDLE_NAME"
1079
1270
  SWIFT_PRODUCT_DIR="$SWIFT_SCRATCH_PATH/$BUILD_CONFIGURATION"
1080
- APP_DIR="$BUILD_WORK_DIR/Recordings.app"
1271
+ APP_DIR="$BUILD_WORK_DIR/$APP_BUNDLE_NAME"
1081
1272
  CONTENTS="$APP_DIR/Contents"
1082
1273
  MACOS="$CONTENTS/MacOS"
1083
1274
  RESOURCES="$CONTENTS/Resources"
@@ -1235,6 +1426,16 @@ if [ "$MODE" = "release" ] && [ "$HOST_PLATFORM" = "Darwin" ]; then
1235
1426
  fi
1236
1427
  fi
1237
1428
  "$CP_EXECUTABLE" "$SOURCE_NATIVE_DIR/RecordingsLib/Info.plist" "$CONTENTS/Info.plist"
1429
+ if [ "$VARIANT" = "bar" ]; then
1430
+ # The bar is an accessory app: LSUIElement keeps it out of the Dock and the app
1431
+ # switcher, and the app's window-declaring control flow (RECORDINGS_BAR_ONLY)
1432
+ # keeps the workspace window absent. The bundle identifier stays com.hasna.recordings
1433
+ # so TCC grants continue to key on bundle id + signing identity, and the bundle is
1434
+ # named per the fleet rule (knowledge k_msxd5rz3_jfvl3i) — HasnaRecordings.app with
1435
+ # the display name "Hasna Recordings"; 'Hasna' at the beginning of the name.
1436
+ "$PLIST_BUDDY" -c 'Add :LSUIElement bool true' "$CONTENTS/Info.plist"
1437
+ "$PLIST_BUDDY" -c 'Set :CFBundleDisplayName Hasna Recordings' "$CONTENTS/Info.plist"
1438
+ fi
1238
1439
  VERSION="$("$PLIST_BUDDY" -c 'Print :CFBundleShortVersionString' "$CONTENTS/Info.plist")"
1239
1440
 
1240
1441
  compute_release_publication_identity() {
@@ -1248,6 +1449,7 @@ compute_release_publication_identity() {
1248
1449
  --component "release_sequence=$RELEASE_SEQUENCE"
1249
1450
  --component "key_epoch=$KEY_EPOCH"
1250
1451
  --component "expires_at_utc=$ENVELOPE_EXPIRES_AT_UTC"
1452
+ --component "variant=$VARIANT"
1251
1453
  )
1252
1454
  identity_arguments+=(--component "envelope_public_key_sha256=$ENVELOPE_PUBLIC_KEY_SHA256")
1253
1455
  if [ "$RELEASE_SUBTYPE" = "initial-bootstrap" ]; then
@@ -1266,7 +1468,7 @@ compute_release_publication_identity() {
1266
1468
 
1267
1469
  reserve_release_output() {
1268
1470
  local release_set_basename reserved_output
1269
- release_set_basename="Recordings-${VERSION}-macos-${RELEASE_SUBTYPE}"
1471
+ release_set_basename="${APP_BASENAME}-${VERSION}-macos-${RELEASE_SUBTYPE}"
1270
1472
  compute_release_publication_identity
1271
1473
  RELEASE_OUTPUT_ROOT="$OUTPUT_BUILD_DIR"
1272
1474
  RELEASE_FINAL_DIR="$RELEASE_OUTPUT_ROOT/${release_set_basename}.release"
@@ -1333,7 +1535,7 @@ reserve_release_output() {
1333
1535
  RELEASE_STAGING_DIR="$($MKTEMP_EXECUTABLE -d "$RELEASE_OUTPUT_ROOT/.${release_set_basename}.staging.XXXXXX")"
1334
1536
  "$CHMOD_EXECUTABLE" 0700 "$RELEASE_STAGING_DIR"
1335
1537
  OUTPUT_BUILD_DIR="$RELEASE_STAGING_DIR"
1336
- OUTPUT_APP_DIR="$OUTPUT_BUILD_DIR/Recordings.app"
1538
+ OUTPUT_APP_DIR="$OUTPUT_BUILD_DIR/$APP_BUNDLE_NAME"
1337
1539
  }
1338
1540
 
1339
1541
  if [ "$MODE" = "release" ]; then
@@ -1468,8 +1670,8 @@ run_signed_helper_contract() {
1468
1670
  HOME="$contract_home"
1469
1671
  PATH="/usr/bin:/bin:/usr/sbin:/sbin"
1470
1672
  TMPDIR="$contract_home"
1471
- HASNA_RECORDINGS_STORAGE_MODE="local"
1472
- RECORDINGS_STORAGE_MODE="local"
1673
+ HASNA_RECORDINGS_CLIENT_STORE="sqlite"
1674
+ RECORDINGS_CLIENT_STORE="sqlite"
1473
1675
  HASNA_RECORDINGS_DB_PATH="$contract_home/recordings.db"
1474
1676
  RECORDINGS_AUDIO_DIR="$contract_home/audio"
1475
1677
  )
@@ -1521,6 +1723,7 @@ if [ "$MODE" != "debug" ]; then
1521
1723
  --team-id "$EXPECTED_TEAM_ID" \
1522
1724
  --package-root "$PACKAGE_ROOT" \
1523
1725
  --artifact-policy "$ARTIFACT_POLICY" \
1726
+ --variant "$VARIANT" \
1524
1727
  --approved-target "$APPROVED_TARGET" \
1525
1728
  --approved-target-identity-kind "$APPROVED_TARGET_IDENTITY_KIND" \
1526
1729
  --approved-target-identity-sha256 "$APPROVED_TARGET_IDENTITY_SHA256" \
@@ -1578,7 +1781,7 @@ run_codesign --verify --deep --strict --verbose=2 "$APP_DIR"
1578
1781
  TMPDIR="$BUILD_WORK_DIR" \
1579
1782
  SSH_CONNECTION="${SSH_CONNECTION:-}" \
1580
1783
  ${SMOKE_TEST_ENVIRONMENT[0]+"${SMOKE_TEST_ENVIRONMENT[@]}"} \
1581
- "$BASH_EXECUTABLE" "$SMOKE_SCRIPT" "$APP_DIR" "$BUN_EXECUTABLE"
1784
+ "$BASH_EXECUTABLE" "$SMOKE_SCRIPT" "$APP_DIR" "$BUN_EXECUTABLE" --variant "$VARIANT"
1582
1785
 
1583
1786
  publish_app_output() {
1584
1787
  local source_tree_digest
@@ -1742,7 +1945,7 @@ publish_complete_release_set() {
1742
1945
  RELEASE_DIRECTORY_PUBLISHED=1
1743
1946
  RELEASE_STAGING_DIR=""
1744
1947
  OUTPUT_BUILD_DIR="$RELEASE_FINAL_DIR"
1745
- OUTPUT_APP_DIR="$OUTPUT_BUILD_DIR/Recordings.app"
1948
+ OUTPUT_APP_DIR="$OUTPUT_BUILD_DIR/$APP_BUNDLE_NAME"
1746
1949
  run_bun "$SOURCE_PACKAGE_ROOT/scripts/macos_artifact.ts" complete-release-publication \
1747
1950
  --destination "$RELEASE_FINAL_DIR" \
1748
1951
  --reservation "$RELEASE_RESERVATION" \
@@ -1768,7 +1971,7 @@ if [ "$MODE" = "debug" ]; then
1768
1971
  fi
1769
1972
 
1770
1973
  if [ "$MODE" = "local" ]; then
1771
- ARTIFACT_BASENAME="Recordings-${VERSION}-macos-${APPROVED_TARGET}-local-only"
1974
+ ARTIFACT_BASENAME="${APP_BASENAME}-${VERSION}-macos-${APPROVED_TARGET}-local-only"
1772
1975
  FINAL_ARCHIVE="$OUTPUT_BUILD_DIR/${ARTIFACT_BASENAME}.zip"
1773
1976
  FINAL_MANIFEST="$OUTPUT_BUILD_DIR/${ARTIFACT_BASENAME}.manifest.json"
1774
1977
  "$RM_EXECUTABLE" -f "$FINAL_ARCHIVE" "$FINAL_MANIFEST"
@@ -1781,9 +1984,11 @@ if [ "$MODE" = "local" ]; then
1781
1984
  --archive "$FINAL_ARCHIVE" \
1782
1985
  --manifest "$FINAL_MANIFEST" \
1783
1986
  --package-root "$PACKAGE_ROOT" \
1987
+ --variant "$VARIANT" \
1784
1988
  --approved-target "$APPROVED_TARGET" \
1785
1989
  --approved-target-identity-kind "$APPROVED_TARGET_IDENTITY_KIND" \
1786
- --approved-target-identity-sha256 "$APPROVED_TARGET_IDENTITY_SHA256"
1990
+ --approved-target-identity-sha256 "$APPROVED_TARGET_IDENTITY_SHA256" \
1991
+ --expected-team-id "$EXPECTED_TEAM_ID"
1787
1992
  publish_app_output
1788
1993
  verify_source_unchanged
1789
1994
  echo "Built immutable local-only app artifact: $FINAL_ARCHIVE"
@@ -1794,7 +1999,7 @@ fi
1794
1999
 
1795
2000
  verify_signed_code "$HELPERS/recordings" "Companion CLI"
1796
2001
  verify_signed_code "$APP_DIR" "Recordings.app"
1797
- ARTIFACT_BASENAME="Recordings-${VERSION}-macos-${RELEASE_SUBTYPE}"
2002
+ ARTIFACT_BASENAME="${APP_BASENAME}-${VERSION}-macos-${RELEASE_SUBTYPE}"
1798
2003
  NOTARY_ARCHIVE="$OUTPUT_BUILD_DIR/${ARTIFACT_BASENAME}-notarization.zip"
1799
2004
  FINAL_ARCHIVE="$OUTPUT_BUILD_DIR/${ARTIFACT_BASENAME}.zip"
1800
2005
  FINAL_MANIFEST="$OUTPUT_BUILD_DIR/${ARTIFACT_BASENAME}.manifest.json"
@@ -1857,6 +2062,7 @@ run_bun "$SOURCE_PACKAGE_ROOT/scripts/macos_artifact.ts" finalize \
1857
2062
  --manifest "$FINAL_MANIFEST" \
1858
2063
  --package-root "$PACKAGE_ROOT" \
1859
2064
  --team-id "$EXPECTED_TEAM_ID" \
2065
+ --variant "$VARIANT" \
1860
2066
  --notary-log "$NOTARY_LOG" \
1861
2067
  --notary-submission-id "$NOTARY_ID" \
1862
2068
  --submitted-archive-sha256 "$NOTARY_ARCHIVE_SHA256"