@hasna/recordings 0.3.2 → 0.3.9
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.
- package/README.md +38 -34
- package/bun.lock +96 -36
- package/dist/__tests__/helpers/source-assertions.d.ts +6 -4
- package/dist/__tests__/helpers/source-assertions.d.ts.map +1 -1
- package/dist/cli/index.js +1059 -219
- package/dist/cli/macos-shortcut.d.ts +2 -2
- package/dist/db/pg-migrations.d.ts +1 -1
- package/dist/http/client.d.ts +6 -14
- package/dist/http/client.d.ts.map +1 -1
- package/dist/index.js +1971 -175
- package/dist/lib/capture-probe.d.ts +3 -3
- package/dist/lib/capture-probe.d.ts.map +1 -1
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/macos-bundle.d.ts +1 -1
- package/dist/lib/release-install-policy.d.ts +21 -0
- package/dist/lib/release-install-policy.d.ts.map +1 -1
- package/dist/mcp/index.js +978 -142
- package/dist/sdk/index.d.ts +2 -2
- package/dist/sdk/index.js +7 -3
- package/dist/sdk/v1.generated.d.ts.map +1 -1
- package/dist/server/cloud-config.d.ts +4 -19
- package/dist/server/cloud-config.d.ts.map +1 -1
- package/dist/server/cloud.d.ts +1 -1
- package/dist/server/cloud.d.ts.map +1 -1
- package/dist/server/index.js +217 -299
- package/dist/server/serve.d.ts.map +1 -1
- package/dist/storage.d.ts +1 -1
- package/dist/storage.d.ts.map +1 -1
- package/dist/storage.js +1881 -90
- package/dist/store.d.ts.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.d.ts.map +1 -1
- package/package.json +4 -3
- package/packaging/macos/build_release_pkg.sh +10 -4
- package/packaging/macos/managed_bootstrap.sh +4 -4
- package/packaging/macos/scripts/postinstall +2 -2
- package/packaging/macos/scripts/preinstall +2 -0
- package/scripts/generate-sdk.ts +17 -16
- package/scripts/install_macos_app.sh +22 -22
- package/scripts/macos_artifact.ts +48 -39
- package/scripts/migrate.ts +2 -2
- package/scripts/release-suite-gate.ts +174 -0
- package/scripts/set-version.ts +5 -5
- package/scripts/smoke_macos_app.sh +21 -21
- package/scripts/vacuity-manifests/version-sites.tsv +8 -4
- package/src/native/Recordings/RecordingsLib/Info.plist +3 -3
- package/src/native/Recordings/RecordingsTests/CLIRunnerTests.swift +2 -2
- package/src/native/Recordings/RecordingsTests/RecordingStartGateTests.swift +1 -1
- package/src/native/Recordings/Updater/BootstrapPreflight/BootstrapPreflightMain.swift +1 -1
- package/src/native/Recordings/Updater/Broker/ApplicationNamespace.swift +2 -2
- package/src/native/Recordings/Updater/Broker/BrokerMain.swift +1 -1
- package/src/native/Recordings/Updater/Broker/CanonicalTreeCopy.swift +1 -1
- package/src/native/Recordings/Updater/Broker/InstallJournal.swift +1 -1
- package/src/native/Recordings/Updater/BrokerTests/ActivationRecoveryPolicyTests.swift +2 -2
- package/src/native/Recordings/Updater/Protocol/UpdateProtocol.swift +1 -1
- package/src/native/Recordings/Updater/VerifierLauncher/RecordingsVerifierLauncher.c +2 -2
- package/src/native/Recordings/build.sh +14 -20
- package/dist/lib/retired-deployment-modes.d.ts +0 -24
- package/dist/lib/retired-deployment-modes.d.ts.map +0 -1
package/dist/store.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EACV,SAAS,EACT,oBAAoB,EACpB,eAAe,EACf,KAAK,EACL,OAAO,EACR,MAAM,kBAAkB,CAAC;AAI1B,OAAO,EAAqC,KAAK,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAUzF,eAAO,MAAM,GAAG,eAAe,CAAC;AAEhC,YAAY,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAEtD,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAClC;AAED,MAAM,WAAW,KAAK;IACpB,QAAQ,CAAC,IAAI,EAAE,QAAQ,GAAG,MAAM,CAAC;IACjC,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAGhC,eAAe,CAAC,KAAK,EAAE,oBAAoB,EAAE,cAAc,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC;IAC1F,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC,CAAC;IACpD,cAAc,CAAC,MAAM,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC,CAAC;IAC/D,eAAe,CAAC,CAAC,MAAM,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC5D,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC,CAAC;IAChF,eAAe,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC9C,iBAAiB,IAAI,OAAO,CAAC,cAAc,CAAC,CAAC;IAG7C,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC;IACjF,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC;IAClD,UAAU,IAAI,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC;IAC/B,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC;IACxD,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC;IAGjF,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACpF,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CAAC;IACtD,YAAY,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC;IAGnC,YAAY,CAAC,KAAK,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACnD;AA4QD,wBAAsB,oBAAoB,CACxC,KAAK,EAAE,KAAK,EACZ,MAAM,CAAC,EAAE,eAAe,GACvB,OAAO,CAAC,MAAM,CAAC,CA0BjB;AAID;;;;;GAKG;AACH,wBAAgB,QAAQ,CAAC,GAAG,GAAE,MAAM,CAAC,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAe,GAAG,KAAK,CAMzG;AAED,2CAA2C;AAC3C,wBAAgB,YAAY,IAAI,IAAI,CAEnC"}
|
package/dist/version.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const VERSION
|
|
1
|
+
export declare const VERSION: string;
|
|
2
2
|
//# sourceMappingURL=version.d.ts.map
|
package/dist/version.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAMA,eAAO,MAAM,OAAO,QAAsB,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hasna/recordings",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.9",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Speech-to-text recording tool with MCP and CLI — records, transcribes, and optionally enhances text using AI",
|
|
6
6
|
"repository": {
|
|
@@ -63,7 +63,8 @@
|
|
|
63
63
|
"version:check": "bun run scripts/set-version.ts --check",
|
|
64
64
|
"prepack": "bun run prepack:platform-gate && bun run version:check && bun run build && bun run verify:release && bun run scan:artifact",
|
|
65
65
|
"prepack:platform-gate": "bash -c 'if [ \"$(uname -s)\" = Darwin ]; then bun run build:native-fs-guard; else echo \"WARN: native fs-guard build skipped on $(uname -s) — dry-run/CI pack; publish recordings from macOS\"; fi'",
|
|
66
|
-
"prepublishOnly": "bun run typecheck && bun run
|
|
66
|
+
"prepublishOnly": "bun run typecheck && bun run release-suite-gate",
|
|
67
|
+
"release-suite-gate": "bun run scripts/release-suite-gate.ts",
|
|
67
68
|
"typecheck:shortcut-contract": "tsc --noEmit -p tsconfig.test.json"
|
|
68
69
|
},
|
|
69
70
|
"files": [
|
|
@@ -83,7 +84,7 @@
|
|
|
83
84
|
"LICENSE"
|
|
84
85
|
],
|
|
85
86
|
"dependencies": {
|
|
86
|
-
"@hasna/contracts": "0.
|
|
87
|
+
"@hasna/contracts": "0.13.4",
|
|
87
88
|
"@hasna/events": "0.1.11",
|
|
88
89
|
"@modelcontextprotocol/sdk": "^1.12.1",
|
|
89
90
|
"chalk": "^5.4.1",
|
|
@@ -222,8 +222,14 @@ done
|
|
|
222
222
|
case "$VERSION" in
|
|
223
223
|
''|*[!0-9A-Za-z._+-]*) echo "Package version is missing or invalid." >&2; exit 2 ;;
|
|
224
224
|
esac
|
|
225
|
-
|
|
226
|
-
|
|
225
|
+
# The artifact basename is derived from the canonical bundle identity, never hardcoded:
|
|
226
|
+
# build.sh emits ${APP_BASENAME}-${VERSION}-macos-${RELEASE_SUBTYPE} with APP_BASENAME
|
|
227
|
+
# taken from the bundle name (currently HasnaRecordings.app). The legacy
|
|
228
|
+
# Recordings-* form was dropped with the bundle rename and must not be re-required.
|
|
229
|
+
[ -n "$APP" ] && [ -d "$APP" ] || { echo "Package app bundle path is missing or invalid." >&2; exit 2; }
|
|
230
|
+
expected_basename="$(/usr/bin/basename "$APP" .app)-${VERSION}-macos-initial-bootstrap"
|
|
231
|
+
[ "$ARTIFACT_BASENAME" = "$expected_basename" ] || {
|
|
232
|
+
echo "Initial-bootstrap package artifact basename is missing or mode-incompatible (expected ${expected_basename})." >&2
|
|
227
233
|
exit 2
|
|
228
234
|
}
|
|
229
235
|
[[ "$SOURCE_SHA" =~ ^[0-9a-f]{40}$ ]] || { echo "Package source SHA must be an exact commit." >&2; exit 2; }
|
|
@@ -527,7 +533,7 @@ PACKAGE_BOOTSTRAP_ID="$(/usr/bin/uuidgen | /usr/bin/tr '[:upper:]' '[:lower:]')"
|
|
|
527
533
|
"$SCRIPTS"
|
|
528
534
|
require_no_extended_acl "$PACKAGE_STAGE_DIR"
|
|
529
535
|
|
|
530
|
-
/usr/bin/ditto "$APP" "$ROOT/Applications/
|
|
536
|
+
/usr/bin/ditto "$APP" "$ROOT/Applications/HasnaRecordings.app"
|
|
531
537
|
/bin/cp "$BROKER" "$ROOT/Library/PrivilegedHelperTools/com.hasna.recordings.updater"
|
|
532
538
|
/bin/cp "$VERIFIER" "$ROOT/Library/PrivilegedHelperTools/com.hasna.recordings.artifact-verifier"
|
|
533
539
|
/bin/cp "$PUBLIC_KEY" "$ROOT/Library/Application Support/Hasna/Recordings/Trust/envelope-keys/${KEY_EPOCH}.raw"
|
|
@@ -576,7 +582,7 @@ require_tree_without_extended_acl "$SCRIPTS"
|
|
|
576
582
|
"$ROOT/Library/LaunchDaemons/com.hasna.recordings.updater.plist"
|
|
577
583
|
/usr/bin/plutil -lint "$ROOT/Library/LaunchDaemons/com.hasna.recordings.updater.plist" >/dev/null
|
|
578
584
|
|
|
579
|
-
STAGED_APP="$ROOT/Applications/
|
|
585
|
+
STAGED_APP="$ROOT/Applications/HasnaRecordings.app"
|
|
580
586
|
STAGED_BROKER="$ROOT/Library/PrivilegedHelperTools/com.hasna.recordings.updater"
|
|
581
587
|
STAGED_CLIENT="$STAGED_APP/Contents/Helpers/recordings-update-client"
|
|
582
588
|
STAGED_VERIFIER="$ROOT/Library/PrivilegedHelperTools/com.hasna.recordings.artifact-verifier"
|
|
@@ -173,7 +173,7 @@ validate_target_ancestry() {
|
|
|
173
173
|
|
|
174
174
|
validate_target_ancestry
|
|
175
175
|
|
|
176
|
-
CLIENT="/Applications/
|
|
176
|
+
CLIENT="/Applications/HasnaRecordings.app/Contents/Helpers/recordings-update-client"
|
|
177
177
|
STATE="/var/db/com.hasna.recordings.updater/release-state.json"
|
|
178
178
|
AUTHORIZATION_ROOT="/private/var/db/com.hasna.recordings.bootstrap-authorization"
|
|
179
179
|
AUTHORIZATION_JOURNAL="${AUTHORIZATION_ROOT}/journal"
|
|
@@ -182,7 +182,7 @@ COHORT_PATHS=(
|
|
|
182
182
|
"/Library/PrivilegedHelperTools/com.hasna.recordings.updater"
|
|
183
183
|
"/Library/PrivilegedHelperTools/com.hasna.recordings.artifact-verifier"
|
|
184
184
|
"/Library/LaunchDaemons/com.hasna.recordings.updater.plist"
|
|
185
|
-
"/Applications/
|
|
185
|
+
"/Applications/HasnaRecordings.app"
|
|
186
186
|
"$CLIENT"
|
|
187
187
|
"/Library/Application Support/Hasna/Recordings/Trust"
|
|
188
188
|
"/Library/Application Support/Hasna/Recordings/Bootstrap"
|
|
@@ -304,7 +304,7 @@ PAYLOAD="$EXPANDED/Payload"
|
|
|
304
304
|
PREFLIGHT="$EXPANDED/Scripts/recordings-bootstrap-preflight"
|
|
305
305
|
EXPANDED_PREINSTALL="$EXPANDED/Scripts/preinstall"
|
|
306
306
|
EXPANDED_POSTINSTALL="$EXPANDED/Scripts/postinstall"
|
|
307
|
-
PAYLOAD_CLIENT="$PAYLOAD/Applications/
|
|
307
|
+
PAYLOAD_CLIENT="$PAYLOAD/Applications/HasnaRecordings.app/Contents/Helpers/recordings-update-client"
|
|
308
308
|
[ -d "$PAYLOAD" ] && [ ! -L "$PAYLOAD" ] && \
|
|
309
309
|
[ -x "$PREFLIGHT" ] && [ ! -L "$PREFLIGHT" ] && \
|
|
310
310
|
[ -x "$EXPANDED_PREINSTALL" ] && [ ! -L "$EXPANDED_PREINSTALL" ] && \
|
|
@@ -512,7 +512,7 @@ cohort_expected_mode() {
|
|
|
512
512
|
"/Library/PrivilegedHelperTools/com.hasna.recordings.updater"|\
|
|
513
513
|
"/Library/PrivilegedHelperTools/com.hasna.recordings.artifact-verifier") printf '555\n' ;;
|
|
514
514
|
"/Library/LaunchDaemons/com.hasna.recordings.updater.plist") printf '444\n' ;;
|
|
515
|
-
"/Applications/
|
|
515
|
+
"/Applications/HasnaRecordings.app"|"$CLIENT") printf '755\n' ;;
|
|
516
516
|
"/Library/Application Support/Hasna/Recordings/Trust") printf '700-or-755\n' ;;
|
|
517
517
|
"/Library/Application Support/Hasna/Recordings/Bootstrap"|\
|
|
518
518
|
"/Library/Application Support/Hasna/Recordings/Updates"|\
|
|
@@ -11,9 +11,9 @@ PACKAGE_BOOTSTRAP_ID="__RECORDINGS_PACKAGE_BOOTSTRAP_ID__"
|
|
|
11
11
|
AUTHORIZATION_ROOT="/private/var/db/com.hasna.recordings.bootstrap-authorization"
|
|
12
12
|
AUTHORIZATION_JOURNAL="${AUTHORIZATION_ROOT}/journal"
|
|
13
13
|
BROKER="/Library/PrivilegedHelperTools/com.hasna.recordings.updater"
|
|
14
|
-
CLIENT="/Applications/
|
|
14
|
+
CLIENT="/Applications/HasnaRecordings.app/Contents/Helpers/recordings-update-client"
|
|
15
15
|
VERIFIER="/Library/PrivilegedHelperTools/com.hasna.recordings.artifact-verifier"
|
|
16
|
-
APP="/Applications/
|
|
16
|
+
APP="/Applications/HasnaRecordings.app"
|
|
17
17
|
STATE_ROOT="/Library/Application Support/Hasna/Recordings/Updates"
|
|
18
18
|
TRUST_ROOT="/Library/Application Support/Hasna/Recordings/Trust"
|
|
19
19
|
BOOTSTRAP_ROOT="/Library/Application Support/Hasna/Recordings/Bootstrap"
|
|
@@ -248,6 +248,8 @@ COHORT_PATHS=(
|
|
|
248
248
|
"/Library/PrivilegedHelperTools/com.hasna.recordings.updater:555"
|
|
249
249
|
"/Library/PrivilegedHelperTools/com.hasna.recordings.artifact-verifier:555"
|
|
250
250
|
"/Library/LaunchDaemons/com.hasna.recordings.updater.plist:444"
|
|
251
|
+
"/Applications/HasnaRecordings.app:755"
|
|
252
|
+
"/Applications/HasnaRecordings.app/Contents/Helpers/recordings-update-client:755"
|
|
251
253
|
"/Applications/Recordings.app:755"
|
|
252
254
|
"/Applications/Recordings.app/Contents/Helpers/recordings-update-client:755"
|
|
253
255
|
"/Library/Application Support/Hasna/Recordings/Trust:700-or-755"
|
package/scripts/generate-sdk.ts
CHANGED
|
@@ -20,23 +20,24 @@ const { code, operations, warnings } = generateSdkFromOpenApi(spec as never, {
|
|
|
20
20
|
apiKeyHeader: "x-api-key",
|
|
21
21
|
});
|
|
22
22
|
|
|
23
|
-
// @hasna/contracts 0.
|
|
24
|
-
//
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
23
|
+
// @hasna/contracts 0.13.1 emits array-aware query serialization natively —
|
|
24
|
+
// per-item append, null/undefined items skipped — which is exactly OpenAPI's
|
|
25
|
+
// form+explode semantics the 0.8.4-era transform used to add by hand. Assert
|
|
26
|
+
// the measured 0.13.1 shape so a future serializer change fails generation
|
|
27
|
+
// loudly instead of silently altering request semantics.
|
|
28
|
+
const arrayQuerySerialization =
|
|
29
|
+
" if (value === undefined || value === null) continue;\n" +
|
|
30
|
+
" if (Array.isArray(value)) {\n" +
|
|
31
|
+
" for (const item of value) {\n" +
|
|
32
|
+
" if (item !== undefined && item !== null) url.searchParams.append(key, String(item));\n" +
|
|
33
|
+
" }\n" +
|
|
34
|
+
" } else {\n" +
|
|
35
|
+
" url.searchParams.set(key, String(value));\n" +
|
|
36
|
+
" }";
|
|
37
|
+
if (!code.includes(arrayQuerySerialization)) {
|
|
38
|
+
throw new Error("Generated SDK query serializer changed; update the serializer shape assertion");
|
|
29
39
|
}
|
|
30
|
-
const compatibleCode = code
|
|
31
|
-
scalarQuerySerialization,
|
|
32
|
-
[
|
|
33
|
-
" if (Array.isArray(value)) {",
|
|
34
|
-
" for (const item of value) url.searchParams.append(key, String(item));",
|
|
35
|
-
" } else if (value !== undefined && value !== null) {",
|
|
36
|
-
" url.searchParams.set(key, String(value));",
|
|
37
|
-
" }",
|
|
38
|
-
].join("\n"),
|
|
39
|
-
);
|
|
40
|
+
const compatibleCode = code;
|
|
40
41
|
|
|
41
42
|
const header =
|
|
42
43
|
"// AUTO-GENERATED by scripts/generate-sdk.ts from src/server/openapi.ts — DO NOT EDIT BY HAND.\n" +
|
|
@@ -126,18 +126,18 @@ require_absolute_canonical_owned_home() {
|
|
|
126
126
|
local canonical
|
|
127
127
|
case "$candidate" in
|
|
128
128
|
/*) ;;
|
|
129
|
-
*) echo "
|
|
129
|
+
*) echo "HasnaRecordings.app installation requires HOME to be an absolute canonical path." >&2; exit 1 ;;
|
|
130
130
|
esac
|
|
131
131
|
[ -d "$candidate" ] && [ ! -L "$candidate" ] || {
|
|
132
|
-
echo "
|
|
132
|
+
echo "HasnaRecordings.app installation requires HOME to be an absolute canonical path to a non-symlink directory." >&2
|
|
133
133
|
exit 1
|
|
134
134
|
}
|
|
135
135
|
canonical="$(cd "$candidate" 2>/dev/null && builtin pwd -P)" || {
|
|
136
|
-
echo "
|
|
136
|
+
echo "HasnaRecordings.app installation could not resolve HOME canonically." >&2
|
|
137
137
|
exit 1
|
|
138
138
|
}
|
|
139
139
|
[ "$candidate" = "$canonical" ] || {
|
|
140
|
-
echo "
|
|
140
|
+
echo "HasnaRecordings.app installation requires HOME to be an absolute canonical path." >&2
|
|
141
141
|
exit 1
|
|
142
142
|
}
|
|
143
143
|
[ "$("$STAT_EXECUTABLE" -f '%u' "$candidate")" = "$("$ID_EXECUTABLE" -u)" ] || {
|
|
@@ -352,7 +352,7 @@ require_absolute_executable "BUN_EXECUTABLE" "$BUN_EXECUTABLE"
|
|
|
352
352
|
require_absolute_canonical_owned_home
|
|
353
353
|
|
|
354
354
|
if [ "$("$UNAME_EXECUTABLE" -s)" != "Darwin" ]; then
|
|
355
|
-
echo "
|
|
355
|
+
echo "HasnaRecordings.app installation is only supported on macOS." >&2
|
|
356
356
|
exit 1
|
|
357
357
|
fi
|
|
358
358
|
|
|
@@ -546,7 +546,7 @@ fi
|
|
|
546
546
|
"$BUN_EXECUTABLE" "$ARTIFACT_TOOL" native-fs-guard-check
|
|
547
547
|
DATA_DIR="${HOME}/.hasna/recordings"
|
|
548
548
|
DATA_PARENT="$("$DIRNAME_EXECUTABLE" "$DATA_DIR")"
|
|
549
|
-
MANIFEST_BUNDLE_NAME="
|
|
549
|
+
MANIFEST_BUNDLE_NAME="HasnaRecordings.app"
|
|
550
550
|
APP_DEST="${HOME}/Applications/${MANIFEST_BUNDLE_NAME}"
|
|
551
551
|
APP_PARENT="$("$DIRNAME_EXECUTABLE" "$APP_DEST")"
|
|
552
552
|
ROLLBACK_DIR="${DATA_DIR}/rollbacks"
|
|
@@ -794,7 +794,7 @@ acquire_install_lock() {
|
|
|
794
794
|
if kill -0 "$owner_pid" 2>/dev/null; then
|
|
795
795
|
actual_start="$("$PS_EXECUTABLE" -o lstart= -p "$owner_pid" 2>/dev/null | "$TR_EXECUTABLE" -s ' ' | "$SED_EXECUTABLE" 's/^ //;s/ $//' || true)"
|
|
796
796
|
if [ -z "$owner_start" ] || [ -z "$actual_start" ] || [ "$owner_start" = "$actual_start" ]; then
|
|
797
|
-
echo "Another
|
|
797
|
+
echo "Another HasnaRecordings.app installer owns the active install lock." >&2
|
|
798
798
|
exit 1
|
|
799
799
|
fi
|
|
800
800
|
fi
|
|
@@ -807,17 +807,17 @@ acquire_install_lock() {
|
|
|
807
807
|
now="$("$DATE_EXECUTABLE" +%s)"
|
|
808
808
|
lock_age=$((now - $("$STAT_EXECUTABLE" -f '%m' "$LOCK_DIR")))
|
|
809
809
|
if [ "$lock_age" -lt "$stale_seconds" ]; then
|
|
810
|
-
echo "
|
|
810
|
+
echo "HasnaRecordings.app install lock is incomplete and too recent to reclaim safely." >&2
|
|
811
811
|
exit 1
|
|
812
812
|
fi
|
|
813
813
|
fi
|
|
814
814
|
stale_claim="${LOCK_DIR}.stale.$$"
|
|
815
815
|
if ! "$MV_EXECUTABLE" "$LOCK_DIR" "$stale_claim" 2>/dev/null; then
|
|
816
|
-
echo "
|
|
816
|
+
echo "HasnaRecordings.app install lock changed during stale-owner recovery." >&2
|
|
817
817
|
exit 1
|
|
818
818
|
fi
|
|
819
819
|
"$RM_EXECUTABLE" -rf "$stale_claim"
|
|
820
|
-
"$MKDIR_EXECUTABLE" -m 700 "$LOCK_DIR" || { echo "Could not acquire
|
|
820
|
+
"$MKDIR_EXECUTABLE" -m 700 "$LOCK_DIR" || { echo "Could not acquire HasnaRecordings.app install lock." >&2; exit 1; }
|
|
821
821
|
fi
|
|
822
822
|
LOCK_OWNED=1
|
|
823
823
|
local start
|
|
@@ -1263,13 +1263,13 @@ stop_uncommitted_candidate() {
|
|
|
1263
1263
|
done <<< "$remaining"
|
|
1264
1264
|
"$SLEEP_EXECUTABLE" 0.1
|
|
1265
1265
|
[ -z "$(pids_for_exact_executable "$expected")" ] || {
|
|
1266
|
-
echo "Uncommitted
|
|
1266
|
+
echo "Uncommitted HasnaRecordings.app process could not be stopped before recovery." >&2
|
|
1267
1267
|
return 1
|
|
1268
1268
|
}
|
|
1269
1269
|
}
|
|
1270
1270
|
|
|
1271
1271
|
if [ -f "$JOURNAL_PATH" ]; then
|
|
1272
|
-
echo "Recovering incomplete
|
|
1272
|
+
echo "Recovering incomplete HasnaRecordings.app installation transaction." >&2
|
|
1273
1273
|
if ! RECOVER_WAS_RUNNING="$("$BUN_EXECUTABLE" "$ARTIFACT_TOOL" journal-get --journal "$JOURNAL_PATH" --field was_running)" || \
|
|
1274
1274
|
! RECOVER_PHASE="$("$BUN_EXECUTABLE" "$ARTIFACT_TOOL" journal-get --journal "$JOURNAL_PATH" --field phase)" || \
|
|
1275
1275
|
! RECOVER_RUNNING_APP_PATHS_JSON="$("$BUN_EXECUTABLE" "$ARTIFACT_TOOL" journal-get --journal "$JOURNAL_PATH" --field prior_running_app_paths)"; then
|
|
@@ -1376,7 +1376,7 @@ if ! "$BUN_EXECUTABLE" -e '
|
|
|
1376
1376
|
if ((actual[i] ?? 0) < (minimum[i] ?? 0)) process.exit(1);
|
|
1377
1377
|
}
|
|
1378
1378
|
' "$CURRENT_MACOS" "$MINIMUM_MACOS"; then
|
|
1379
|
-
echo "
|
|
1379
|
+
echo "HasnaRecordings.app requires macOS ${MINIMUM_MACOS} or later; target is ${CURRENT_MACOS}." >&2
|
|
1380
1380
|
exit 1
|
|
1381
1381
|
fi
|
|
1382
1382
|
TARGET_ARCH="$("$UNAME_EXECUTABLE" -m)"
|
|
@@ -1386,7 +1386,7 @@ MANIFEST_ARCHITECTURES="$("$BUN_EXECUTABLE" "$ARTIFACT_TOOL" manifest-get \
|
|
|
1386
1386
|
--field architectures)"
|
|
1387
1387
|
case " ${MANIFEST_ARCHITECTURES} " in
|
|
1388
1388
|
*" ${TARGET_ARCH} "*) ;;
|
|
1389
|
-
*) echo "
|
|
1389
|
+
*) echo "HasnaRecordings.app artifact does not support target architecture ${TARGET_ARCH}." >&2; exit 1 ;;
|
|
1390
1390
|
esac
|
|
1391
1391
|
|
|
1392
1392
|
verify_secure_parent "$APP_PARENT"
|
|
@@ -1412,7 +1412,7 @@ add_unique_app() {
|
|
|
1412
1412
|
local existing
|
|
1413
1413
|
if [ -e "$candidate" ] || [ -L "$candidate" ]; then
|
|
1414
1414
|
[ -d "$candidate" ] && [ ! -L "$candidate" ] || {
|
|
1415
|
-
echo "
|
|
1415
|
+
echo "HasnaRecordings.app path is not a secure directory: ${candidate}" >&2
|
|
1416
1416
|
exit 1
|
|
1417
1417
|
}
|
|
1418
1418
|
fi
|
|
@@ -1524,7 +1524,7 @@ stop_old_processes() {
|
|
|
1524
1524
|
"$SLEEP_EXECUTABLE" 0.1
|
|
1525
1525
|
done
|
|
1526
1526
|
if [ "$remaining" -ne 0 ]; then
|
|
1527
|
-
echo "Existing
|
|
1527
|
+
echo "Existing HasnaRecordings.app process did not stop before replacement." >&2
|
|
1528
1528
|
exit 1
|
|
1529
1529
|
fi
|
|
1530
1530
|
}
|
|
@@ -1545,7 +1545,7 @@ quiesce_old_processes() {
|
|
|
1545
1545
|
fi
|
|
1546
1546
|
"$SLEEP_EXECUTABLE" 0.1
|
|
1547
1547
|
done
|
|
1548
|
-
echo "Existing
|
|
1548
|
+
echo "Existing HasnaRecordings.app processes did not remain quiescent before replacement." >&2
|
|
1549
1549
|
exit 1
|
|
1550
1550
|
}
|
|
1551
1551
|
|
|
@@ -1727,7 +1727,7 @@ for existing_app in ${DISCOVERED_APPS[@]+"${DISCOVERED_APPS[@]}"}; do
|
|
|
1727
1727
|
MANAGEABLE_APPS+=("$existing_app")
|
|
1728
1728
|
;;
|
|
1729
1729
|
*)
|
|
1730
|
-
echo "Duplicate
|
|
1730
|
+
echo "Duplicate HasnaRecordings.app at ${existing_app} is outside the transactional user install paths; archive or remove it before installing." >&2
|
|
1731
1731
|
exit 1
|
|
1732
1732
|
;;
|
|
1733
1733
|
esac
|
|
@@ -1954,7 +1954,7 @@ for ((refresh_attempt = 1; refresh_attempt <= 5; refresh_attempt++)); do
|
|
|
1954
1954
|
fi
|
|
1955
1955
|
done
|
|
1956
1956
|
[ "$STOPPED_REFRESH_STABLE" -eq 1 ] || {
|
|
1957
|
-
echo "Existing
|
|
1957
|
+
echo "Existing HasnaRecordings.app writers did not remain quiescent around the stopped-state refresh." >&2
|
|
1958
1958
|
exit 1
|
|
1959
1959
|
}
|
|
1960
1960
|
|
|
@@ -2004,7 +2004,7 @@ for ((move_index = 0; move_index < MOVED_ORIGINAL_COUNT; move_index++)); do
|
|
|
2004
2004
|
existing_app="${MOVED_ORIGINALS[$move_index]}"
|
|
2005
2005
|
moved_path="${MOVED_PATHS[$move_index]}"
|
|
2006
2006
|
[ "$("$BUN_EXECUTABLE" "$ARTIFACT_TOOL" tree-digest --path "$existing_app")" = "${MOVED_DIGESTS[$move_index]}" ] || {
|
|
2007
|
-
echo "Installed
|
|
2007
|
+
echo "Installed HasnaRecordings.app changed after transactional backup planning." >&2
|
|
2008
2008
|
exit 1
|
|
2009
2009
|
}
|
|
2010
2010
|
RECORDINGS_TEST_INSTALLER_PID="$$" \
|
|
@@ -2117,7 +2117,7 @@ if [ "$LAUNCH_APP" -eq 1 ] || [ "$was_running" -eq 1 ]; then
|
|
|
2117
2117
|
fi
|
|
2118
2118
|
|
|
2119
2119
|
if [ "$ARTIFACT_POLICY" = "local_only" ]; then
|
|
2120
|
-
echo "Installed local-only
|
|
2120
|
+
echo "Installed local-only HasnaRecordings.app for ${APPROVED_TARGET}; this artifact is ad-hoc signed and non-notarized."
|
|
2121
2121
|
if [ "$identity_migration" -eq 1 ]; then
|
|
2122
2122
|
# Not a maybe: the designated requirement did change, under an explicit approval.
|
|
2123
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."
|
|
@@ -2127,5 +2127,5 @@ if [ "$ARTIFACT_POLICY" = "local_only" ]; then
|
|
|
2127
2127
|
elif [ "$identity_migration" -eq 1 ]; then
|
|
2128
2128
|
echo "Installed a new signing identity; macOS will require one-time permission approval for this migration."
|
|
2129
2129
|
else
|
|
2130
|
-
echo "Installed verified
|
|
2130
|
+
echo "Installed verified HasnaRecordings.app release artifact: ${APP_DEST}"
|
|
2131
2131
|
fi
|
|
@@ -183,8 +183,8 @@ export type BuildProvenance = {
|
|
|
183
183
|
|
|
184
184
|
export type MacOSArtifactManifest = BuildProvenance & {
|
|
185
185
|
artifact_type: "recordings-macos-app";
|
|
186
|
-
/// The top-level bundle directory name inside the artifact (
|
|
187
|
-
/// full app
|
|
186
|
+
/// The top-level bundle directory name inside the artifact (HasnaRecordings.app for
|
|
187
|
+
/// both the full app and the bar variant per the fleet naming rule).
|
|
188
188
|
bundle_name: string;
|
|
189
189
|
app_sha256: string;
|
|
190
190
|
binding: {
|
|
@@ -843,7 +843,7 @@ function manifestVariant(manifest: MacOSArtifactManifest): ArtifactVariant {
|
|
|
843
843
|
}
|
|
844
844
|
|
|
845
845
|
function manifestBundleName(manifest: MacOSArtifactManifest): string {
|
|
846
|
-
return manifest.bundle_name ?? "
|
|
846
|
+
return manifest.bundle_name ?? "HasnaRecordings.app";
|
|
847
847
|
}
|
|
848
848
|
|
|
849
849
|
function manifestTargetIdentityKind(manifest: MacOSArtifactManifest): OperatorTargetIdentityKind {
|
|
@@ -1094,23 +1094,23 @@ function assertCanonicalZipEntries(entries: Array<{ name: string; isDirectory: b
|
|
|
1094
1094
|
const components = logicalPath.split("/");
|
|
1095
1095
|
if (
|
|
1096
1096
|
!logicalPath ||
|
|
1097
|
-
components[0] !== "
|
|
1097
|
+
components[0] !== "HasnaRecordings.app" ||
|
|
1098
1098
|
components.some((component) => !component || component === "." || component === "..")
|
|
1099
1099
|
) {
|
|
1100
|
-
throw new Error("release ZIP contains an entry outside the canonical
|
|
1100
|
+
throw new Error("release ZIP contains an entry outside the canonical HasnaRecordings.app tree");
|
|
1101
1101
|
}
|
|
1102
1102
|
const collisionKey = canonicalZipCollisionKey(logicalPath);
|
|
1103
1103
|
if (logicalPaths.has(collisionKey)) {
|
|
1104
1104
|
throw new Error("release ZIP contains duplicate, file/directory, case-fold, or Unicode-colliding entries");
|
|
1105
1105
|
}
|
|
1106
1106
|
logicalPaths.set(collisionKey, { name: logicalPath, isDirectory: entry.isDirectory });
|
|
1107
|
-
if (logicalPath === "
|
|
1108
|
-
if (!entry.isDirectory) throw new Error("release ZIP
|
|
1107
|
+
if (logicalPath === "HasnaRecordings.app") {
|
|
1108
|
+
if (!entry.isDirectory) throw new Error("release ZIP HasnaRecordings.app root is not a directory entry");
|
|
1109
1109
|
rootDirectoryCount += 1;
|
|
1110
1110
|
}
|
|
1111
1111
|
}
|
|
1112
1112
|
if (rootDirectoryCount !== 1) {
|
|
1113
|
-
throw new Error("release ZIP must contain exactly one canonical
|
|
1113
|
+
throw new Error("release ZIP must contain exactly one canonical HasnaRecordings.app root entry");
|
|
1114
1114
|
}
|
|
1115
1115
|
for (const { name } of logicalPaths.values()) {
|
|
1116
1116
|
const components = name.split("/");
|
|
@@ -1426,7 +1426,7 @@ export function withPrivatelyExtractedArchiveApp<T>(
|
|
|
1426
1426
|
operation: (appPath: string) => T,
|
|
1427
1427
|
platformArchiveTool = "/usr/bin/ditto",
|
|
1428
1428
|
expectedArchiveSha256?: string,
|
|
1429
|
-
expectedBundleName = "
|
|
1429
|
+
expectedBundleName = "HasnaRecordings.app",
|
|
1430
1430
|
): T {
|
|
1431
1431
|
const privateRoot = mkdtempSync(join(tmpdir(), "recordings-artifact-extract-"));
|
|
1432
1432
|
chmodSync(privateRoot, 0o700);
|
|
@@ -1751,8 +1751,8 @@ export function assertManifestShape(manifest: MacOSArtifactManifest): void {
|
|
|
1751
1751
|
manifest.container?.type !== "zip" ||
|
|
1752
1752
|
JSON.stringify(manifest.container.install_locations) !== JSON.stringify(
|
|
1753
1753
|
artifactPolicy === "release"
|
|
1754
|
-
? ["/Applications/
|
|
1755
|
-
: ["~/Applications/
|
|
1754
|
+
? ["/Applications/HasnaRecordings.app"]
|
|
1755
|
+
: ["~/Applications/HasnaRecordings.app"],
|
|
1756
1756
|
)
|
|
1757
1757
|
) {
|
|
1758
1758
|
throw new Error("manifest has an unexpected container install policy");
|
|
@@ -2516,7 +2516,7 @@ export function assertVersionTransition(
|
|
|
2516
2516
|
assertManifestShape(manifest);
|
|
2517
2517
|
if (compareVersions(manifest.bundle_version, installedVersion) < 0) {
|
|
2518
2518
|
throw new Error(
|
|
2519
|
-
`refusing to downgrade
|
|
2519
|
+
`refusing to downgrade HasnaRecordings.app from ${installedVersion} to ${manifest.bundle_version}`,
|
|
2520
2520
|
);
|
|
2521
2521
|
}
|
|
2522
2522
|
if (compareVersions(manifest.bundle_version, installedVersion) === 0) {
|
|
@@ -2626,8 +2626,6 @@ function verifyActiveApp(
|
|
|
2626
2626
|
const environment: NodeJS.ProcessEnv = {
|
|
2627
2627
|
HOME: contractHome,
|
|
2628
2628
|
PATH: "/usr/bin:/bin:/usr/sbin:/sbin",
|
|
2629
|
-
HASNA_RECORDINGS_CLIENT_STORE: "sqlite",
|
|
2630
|
-
RECORDINGS_CLIENT_STORE: "sqlite",
|
|
2631
2629
|
HASNA_RECORDINGS_DB_PATH: join(contractHome, "recordings.db"),
|
|
2632
2630
|
RECORDINGS_AUDIO_DIR: join(contractHome, "audio"),
|
|
2633
2631
|
};
|
|
@@ -2983,7 +2981,7 @@ function readJournal(path: string): InstallJournal {
|
|
|
2983
2981
|
if (resolve(path) !== resolve(join(expectedParent, ".Recordings-install-transaction.json"))) {
|
|
2984
2982
|
throw new Error("install transaction journal is outside the expected app parent");
|
|
2985
2983
|
}
|
|
2986
|
-
if (resolve(journal.app_destination) !== resolve(join(expectedParent, "
|
|
2984
|
+
if (resolve(journal.app_destination) !== resolve(join(expectedParent, "HasnaRecordings.app"))) {
|
|
2987
2985
|
throw new Error("install transaction journal has an unexpected app destination");
|
|
2988
2986
|
}
|
|
2989
2987
|
const expectedDataDir = resolve(join(dirname(expectedParent), ".hasna", "recordings"));
|
|
@@ -3001,7 +2999,9 @@ function readJournal(path: string): InstallJournal {
|
|
|
3001
2999
|
return (
|
|
3002
3000
|
resolved === resolve(journal.app_destination) ||
|
|
3003
3001
|
resolved === resolve(join(journal.data_dir, "Recordings.app")) ||
|
|
3004
|
-
resolved
|
|
3002
|
+
resolved === resolve(join(journal.data_dir, "HasnaRecordings.app")) ||
|
|
3003
|
+
resolved.startsWith(`${resolve(journal.app_parent)}/Recordings.app.`) ||
|
|
3004
|
+
resolved.startsWith(`${resolve(journal.app_parent)}/HasnaRecordings.app.`)
|
|
3005
3005
|
);
|
|
3006
3006
|
};
|
|
3007
3007
|
if (!journal.originals.every((entry) => allowedOriginal(entry.path))) {
|
|
@@ -3939,7 +3939,7 @@ function openRecoveryCapabilities(
|
|
|
3939
3939
|
const homePath = resolve(dirname(appParent));
|
|
3940
3940
|
if (
|
|
3941
3941
|
basename(appParent) !== "Applications" ||
|
|
3942
|
-
resolve(journal.app_destination) !== resolve(join(appParent, "
|
|
3942
|
+
resolve(journal.app_destination) !== resolve(join(appParent, "HasnaRecordings.app")) ||
|
|
3943
3943
|
resolve(journal.data_dir) !== resolve(join(homePath, ".hasna", "recordings")) ||
|
|
3944
3944
|
dirname(resolve(journal.transaction_dir)) !== appParent
|
|
3945
3945
|
) {
|
|
@@ -4342,9 +4342,10 @@ export function publishInstallCandidate(
|
|
|
4342
4342
|
const destination = resolve(destinationPath);
|
|
4343
4343
|
const stagingParentPath = dirname(staging);
|
|
4344
4344
|
const stagingParentLeaf = basename(stagingParentPath);
|
|
4345
|
+
const bundleLeaf = basename(journal.app_destination);
|
|
4345
4346
|
if (
|
|
4346
4347
|
destination !== resolve(journal.app_destination) ||
|
|
4347
|
-
basename(staging) !==
|
|
4348
|
+
basename(staging) !== bundleLeaf ||
|
|
4348
4349
|
dirname(stagingParentPath) !== resolve(journal.app_parent) ||
|
|
4349
4350
|
!/^\.Recordings-install\.[A-Za-z0-9]+$/.test(stagingParentLeaf)
|
|
4350
4351
|
) {
|
|
@@ -4367,12 +4368,12 @@ export function publishInstallCandidate(
|
|
|
4367
4368
|
stagingParent,
|
|
4368
4369
|
"candidate staging parent",
|
|
4369
4370
|
);
|
|
4370
|
-
retained = guard.openDirAt(stagingParent,
|
|
4371
|
-
assertNativeBinding(guard, stagingParent,
|
|
4371
|
+
retained = guard.openDirAt(stagingParent, bundleLeaf);
|
|
4372
|
+
assertNativeBinding(guard, stagingParent, bundleLeaf, retained, "staged candidate");
|
|
4372
4373
|
if (nativeTreeDigest(guard, retained) !== expectedDigest) {
|
|
4373
4374
|
throw new Error("staged candidate does not match durable journal evidence");
|
|
4374
4375
|
}
|
|
4375
|
-
if (guard.statAt(capabilities.applications,
|
|
4376
|
+
if (guard.statAt(capabilities.applications, bundleLeaf) !== null) {
|
|
4376
4377
|
throw new Error("candidate destination already exists");
|
|
4377
4378
|
}
|
|
4378
4379
|
fsyncRetainedTree(guard, retained, uid, "staged candidate");
|
|
@@ -4384,8 +4385,8 @@ export function publishInstallCandidate(
|
|
|
4384
4385
|
stagingParent,
|
|
4385
4386
|
"candidate staging parent",
|
|
4386
4387
|
);
|
|
4387
|
-
assertNativeBinding(guard, stagingParent,
|
|
4388
|
-
if (guard.statAt(capabilities.applications,
|
|
4388
|
+
assertNativeBinding(guard, stagingParent, bundleLeaf, retained, "staged candidate");
|
|
4389
|
+
if (guard.statAt(capabilities.applications, bundleLeaf) !== null) {
|
|
4389
4390
|
throw new Error("candidate destination appeared before publication");
|
|
4390
4391
|
}
|
|
4391
4392
|
installTransitionTestPoint(
|
|
@@ -4395,10 +4396,10 @@ export function publishInstallCandidate(
|
|
|
4395
4396
|
);
|
|
4396
4397
|
guard.renameHandleNoReplaceAt(
|
|
4397
4398
|
stagingParent,
|
|
4398
|
-
|
|
4399
|
+
bundleLeaf,
|
|
4399
4400
|
retained,
|
|
4400
4401
|
capabilities.applications,
|
|
4401
|
-
|
|
4402
|
+
bundleLeaf,
|
|
4402
4403
|
);
|
|
4403
4404
|
installTransitionTestPoint(
|
|
4404
4405
|
"publish-candidate",
|
|
@@ -4409,11 +4410,11 @@ export function publishInstallCandidate(
|
|
|
4409
4410
|
assertNativeBinding(
|
|
4410
4411
|
guard,
|
|
4411
4412
|
capabilities.applications,
|
|
4412
|
-
|
|
4413
|
+
bundleLeaf,
|
|
4413
4414
|
retained,
|
|
4414
4415
|
"published candidate",
|
|
4415
4416
|
);
|
|
4416
|
-
if (guard.statAt(stagingParent,
|
|
4417
|
+
if (guard.statAt(stagingParent, bundleLeaf) !== null) {
|
|
4417
4418
|
throw new Error("candidate staging leaf was recreated during publication");
|
|
4418
4419
|
}
|
|
4419
4420
|
guard.fsyncHandle(capabilities.applications);
|
|
@@ -4459,7 +4460,8 @@ function cleanupInstallCandidateStaging(
|
|
|
4459
4460
|
staging,
|
|
4460
4461
|
"candidate staging root",
|
|
4461
4462
|
);
|
|
4462
|
-
const
|
|
4463
|
+
const bundleLeaf = basename(journal.app_destination);
|
|
4464
|
+
const candidateMetadata = guard.statAt(staging, bundleLeaf);
|
|
4463
4465
|
if (candidateMetadata !== null) {
|
|
4464
4466
|
if (journal.schema_version !== 9 || !journal.candidate_tree_sha256) {
|
|
4465
4467
|
throw new Error("legacy journal cannot authenticate candidate staging cleanup");
|
|
@@ -4467,7 +4469,7 @@ function cleanupInstallCandidateStaging(
|
|
|
4467
4469
|
const candidate = openProvenDirectoryAt(
|
|
4468
4470
|
guard,
|
|
4469
4471
|
staging,
|
|
4470
|
-
|
|
4472
|
+
bundleLeaf,
|
|
4471
4473
|
journal.candidate_tree_sha256,
|
|
4472
4474
|
"staged candidate",
|
|
4473
4475
|
);
|
|
@@ -4475,7 +4477,7 @@ function cleanupInstallCandidateStaging(
|
|
|
4475
4477
|
quarantineRemoveRetainedAt(
|
|
4476
4478
|
guard,
|
|
4477
4479
|
staging,
|
|
4478
|
-
|
|
4480
|
+
bundleLeaf,
|
|
4479
4481
|
candidate,
|
|
4480
4482
|
"staged candidate",
|
|
4481
4483
|
);
|
|
@@ -4567,14 +4569,20 @@ function originalDestinationCapability(
|
|
|
4567
4569
|
): { parent: NativeHandle; leaf: string } {
|
|
4568
4570
|
const resolved = resolve(path);
|
|
4569
4571
|
if (resolved === resolve(journal.app_destination)) {
|
|
4570
|
-
return { parent: capabilities.applications, leaf:
|
|
4572
|
+
return { parent: capabilities.applications, leaf: basename(journal.app_destination) };
|
|
4571
4573
|
}
|
|
4572
|
-
if (
|
|
4573
|
-
|
|
4574
|
+
if (
|
|
4575
|
+
dirname(resolved) === resolve(journal.data_dir) &&
|
|
4576
|
+
(basename(resolved) === "Recordings.app" || basename(resolved) === "HasnaRecordings.app")
|
|
4577
|
+
) {
|
|
4578
|
+
// Install site inside the state data directory: the legacy pre-rename
|
|
4579
|
+
// Recordings.app and the renamed HasnaRecordings.app are both recognized and
|
|
4580
|
+
// preserved, never treated as the canonical Applications destination.
|
|
4581
|
+
return { parent: capabilities.data, leaf: basename(resolved) };
|
|
4574
4582
|
}
|
|
4575
4583
|
if (
|
|
4576
4584
|
dirname(resolved) === resolve(journal.app_parent) &&
|
|
4577
|
-
/^Recordings\.app\.[A-Za-z0-9._-]+$/.test(basename(resolved))
|
|
4585
|
+
/^(?:Hasna)?Recordings\.app\.[A-Za-z0-9._-]+$/.test(basename(resolved))
|
|
4578
4586
|
) {
|
|
4579
4587
|
return { parent: capabilities.applications, leaf: basename(resolved) };
|
|
4580
4588
|
}
|
|
@@ -5005,24 +5013,25 @@ function recoverJournal(path: string): void {
|
|
|
5005
5013
|
const canonicalOriginal = journal.originals.find(
|
|
5006
5014
|
(entry) => resolve(entry.path) === resolve(journal.app_destination),
|
|
5007
5015
|
);
|
|
5016
|
+
const destinationLeaf = basename(journal.app_destination);
|
|
5008
5017
|
const canonicalDestinationDigest = canonicalOriginal
|
|
5009
|
-
? nativeTreeDigestAt(guard, applications,
|
|
5018
|
+
? nativeTreeDigestAt(guard, applications, destinationLeaf)
|
|
5010
5019
|
: null;
|
|
5011
5020
|
const canonicalAlreadyRestored = canonicalOriginal !== undefined &&
|
|
5012
5021
|
canonicalDestinationDigest === canonicalOriginal.sha256;
|
|
5013
5022
|
if (
|
|
5014
5023
|
["candidate-moving", "candidate-installed", "activated", "launching"].includes(journal.phase) &&
|
|
5015
|
-
!canonicalAlreadyRestored && guard.statAt(applications,
|
|
5024
|
+
!canonicalAlreadyRestored && guard.statAt(applications, destinationLeaf) !== null
|
|
5016
5025
|
) {
|
|
5017
5026
|
if (journal.schema_version !== 9 || !journal.candidate_tree_sha256) {
|
|
5018
5027
|
throw new Error("legacy recovery journal cannot prove the uncommitted candidate tree");
|
|
5019
5028
|
}
|
|
5020
|
-
recoveryTestBarrier("before-candidate-remove",
|
|
5029
|
+
recoveryTestBarrier("before-candidate-remove", destinationLeaf);
|
|
5021
5030
|
assertNativeBinding(guard, capabilities.home, "Applications", applications, "Applications");
|
|
5022
5031
|
const candidate = openProvenDirectoryAt(
|
|
5023
5032
|
guard,
|
|
5024
5033
|
applications,
|
|
5025
|
-
|
|
5034
|
+
destinationLeaf,
|
|
5026
5035
|
journal.candidate_tree_sha256,
|
|
5027
5036
|
"uncommitted candidate",
|
|
5028
5037
|
);
|
|
@@ -5030,7 +5039,7 @@ function recoverJournal(path: string): void {
|
|
|
5030
5039
|
quarantineRemoveRetainedAt(
|
|
5031
5040
|
guard,
|
|
5032
5041
|
applications,
|
|
5033
|
-
|
|
5042
|
+
destinationLeaf,
|
|
5034
5043
|
candidate,
|
|
5035
5044
|
"uncommitted candidate",
|
|
5036
5045
|
);
|