@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
@@ -31,6 +31,7 @@ import {
31
31
  unlinkSync,
32
32
  } from "node:fs";
33
33
  import { tmpdir } from "node:os";
34
+ import { fileURLToPath } from "node:url";
34
35
  import { crc32, inflateRawSync } from "node:zlib";
35
36
  import {
36
37
  nativeFsGuard,
@@ -57,8 +58,100 @@ export const BUNDLE_ID = "com.hasna.recordings";
57
58
  export const PROVENANCE_FILENAME = "recordings-build-provenance.json";
58
59
  export const RELEASE_APPROVED_TARGET = "fleet";
59
60
  export const LEGACY_LOCAL_TARGET_IDENTITY_KIND = "hardware_uuid_sha256";
61
+ export const LOCAL_ONLY_APPROVED_TARGETS_POLICY_PATH = join(
62
+ dirname(fileURLToPath(import.meta.url)),
63
+ "policy",
64
+ "local-only-approved-targets.txt",
65
+ );
66
+
67
+ // The approved local-only targets are policy data, not code: one file is read by
68
+ // this validator and, through scripts/read_local_only_targets.sh, by both shell entry
69
+ // points, so a target is declared once. The shell reader is a separate implementation
70
+ // of these rules; equivalence is enforced by an executing contract test, not by shared
71
+ // code. Parsing stays deliberately strict — a malformed policy fails closed rather than
72
+ // silently widening the allowlist.
73
+ //
74
+ // This is an operator-integrity control, NOT an authorization boundary. The file sits in
75
+ // the same package root as the guards that read it, so anyone able to edit it can edit
76
+ // those guards. Widening it still grants nothing on its own: the install additionally
77
+ // requires a matching `hostname -s`, a matching authenticated Tailscale node digest, and
78
+ // a matching authenticated manifest SHA-256.
79
+ export function localOnlyApprovedTargets(
80
+ policyPath: string = LOCAL_ONLY_APPROVED_TARGETS_POLICY_PATH,
81
+ ): string[] {
82
+ // lstat, not readFileSync alone: readFileSync FOLLOWS a symlink, and the shell reader
83
+ // refuses one outright (`[ -L ]`). That divergence was real and it favoured the
84
+ // attacker — a policy symlinked at a widened allowlist was rejected by the shell gate
85
+ // and silently honoured here. Refusing every non-regular file matches the shell reader
86
+ // and keeps the wording it prints, so both sides fail closed identically.
87
+ let policyStats;
88
+ try {
89
+ policyStats = lstatSync(policyPath);
90
+ } catch {
91
+ throw new Error("local-only approved target policy is missing");
92
+ }
93
+ if (!policyStats.isFile()) {
94
+ throw new Error("local-only approved target policy is missing");
95
+ }
96
+ // An unreadable (mode 000) policy passes the lstat above and then threw a raw EACCES from
97
+ // here, while the shell reader emitted bash's own "Permission denied" and no reader
98
+ // message. Same fail-closed outcome, two unrecognizable errors; both now say this.
99
+ let rawContents: string;
100
+ try {
101
+ rawContents = readFileSync(policyPath, "utf8");
102
+ } catch {
103
+ throw new Error("local-only approved target policy is not readable");
104
+ }
105
+ // Reject a NUL anywhere, in the same order and with the same wording as the shell
106
+ // reader's pre-scan. This reader had no NUL check at all and only ever refused one that
107
+ // happened to land inside a would-be hostname: "# comment\0" was dropped as a comment,
108
+ // so "# comment\0\nstation03\n" was refused by the shell gate and ACCEPTED here.
109
+ if (rawContents.includes("\u0000")) {
110
+ throw new Error("local-only approved target policy contains a NUL byte");
111
+ }
112
+ // Strip a BOM at offset 0 only. A U+FEFF anywhere else is a zero-width no-break space,
113
+ // not a byte-order mark; the shell reader used to strip one per line and therefore
114
+ // accepted a BOM on line 2 that this reader rejected.
115
+ const contents = rawContents.replace(/^/, "");
116
+ const targets = contents
117
+ .split("\n")
118
+ // Deliberately NOT String.trim(): trim() also strips U+FEFF and U+00A0, and the
119
+ // shell reader trims neither, so a trailing NBSP or BOM would be accepted here and
120
+ // rejected there. ASCII space and tab only, matching read_local_only_targets.sh
121
+ // under its pinned LC_ALL=C; everything else is left for the hostname shape below.
122
+ .map((line) => line.replace(/\r$/, "").replace(/^[\t ]+|[\t ]+$/g, ""))
123
+ .filter((line) => line.length > 0 && !line.startsWith("#"));
124
+ if (targets.length === 0) {
125
+ throw new Error("local-only approved target policy lists no targets");
126
+ }
127
+ for (const target of targets) {
128
+ if (!/^[a-z][a-z0-9-]{1,30}[a-z0-9]$/.test(target)) {
129
+ throw new Error(`local-only approved target policy has an invalid target name: ${target}`);
130
+ }
131
+ }
132
+ if (new Set(targets).size !== targets.length) {
133
+ throw new Error("local-only approved target policy has duplicate targets");
134
+ }
135
+ if (targets.includes(RELEASE_APPROVED_TARGET)) {
136
+ throw new Error("local-only approved target policy must not list the release fleet target");
137
+ }
138
+ return targets;
139
+ }
140
+
141
+ export function isLocalOnlyApprovedTarget(
142
+ target: string | undefined,
143
+ policyPath?: string,
144
+ ): boolean {
145
+ if (target === undefined) return false;
146
+ return localOnlyApprovedTargets(policyPath).includes(target);
147
+ }
148
+
149
+ function localOnlyApprovedTargetsMessage(policyPath?: string): string {
150
+ return localOnlyApprovedTargets(policyPath).join(", ");
151
+ }
60
152
 
61
153
  export type ArtifactPolicy = "release" | "local_only";
154
+ export type ArtifactVariant = "full" | "bar";
62
155
  export type TargetIdentityKind =
63
156
  | typeof LEGACY_LOCAL_TARGET_IDENTITY_KIND
64
157
  | "tailscale_node_id_sha256";
@@ -67,6 +160,7 @@ type OperatorTargetIdentityKind = TargetIdentityKind | "none";
67
160
  export type BuildProvenance = {
68
161
  schema_version: 3 | 4;
69
162
  artifact_policy?: "local_only";
163
+ variant?: ArtifactVariant;
70
164
  approved_target?: string;
71
165
  approved_target_identity_kind?: TargetIdentityKind;
72
166
  approved_target_identity_sha256?: string;
@@ -89,13 +183,16 @@ export type BuildProvenance = {
89
183
 
90
184
  export type MacOSArtifactManifest = BuildProvenance & {
91
185
  artifact_type: "recordings-macos-app";
186
+ /// The top-level bundle directory name inside the artifact (Recordings.app for the
187
+ /// full app, HasnaRecordings.app for the bar variant per the fleet naming rule).
188
+ bundle_name: string;
92
189
  app_sha256: string;
93
190
  binding: {
94
191
  bundle_tree_sha256: string;
95
192
  };
96
193
  provenance_sha256: string;
97
194
  signing: {
98
- mode?: "ad_hoc";
195
+ mode?: "ad_hoc" | "developer_id";
99
196
  authority: string;
100
197
  team_id: string;
101
198
  trusted_timestamp: string;
@@ -118,7 +215,7 @@ export type MacOSArtifactManifest = BuildProvenance & {
118
215
  };
119
216
  container: {
120
217
  type: "zip";
121
- install_locations: ["/Applications/Recordings.app"] | ["~/Applications/Recordings.app"];
218
+ install_locations: [string];
122
219
  };
123
220
  nested_code_policy: {
124
221
  allowlist_sha256: string;
@@ -460,14 +557,43 @@ export function parseDesignatedRequirement(output: string): string {
460
557
  return requirement;
461
558
  }
462
559
 
560
+ /**
561
+ * A local-station artifact is signed one of two ways, and the expected team identifier is
562
+ * what says which: the sentinel `ADHOC`, or a real 10-character Apple team.
563
+ *
564
+ * Developer ID signing exists on this path because ad-hoc signing cannot hold a macOS TCC
565
+ * grant. TCC keys Accessibility (and Microphone) to bundle id plus signing identity, and an
566
+ * ad-hoc signature offers only a CDHash that changes on every rebuild, so the hold-to-talk
567
+ * CGEventTap in FnKeyMonitor.swift lost its permission on each reinstall. Ad-hoc remains
568
+ * supported for builders that hold no signing key.
569
+ */
570
+ export function isLocalOnlySigningTeam(expectedTeamId: string): boolean {
571
+ return expectedTeamId === "ADHOC" || /^[A-Z0-9]{10}$/.test(expectedTeamId);
572
+ }
573
+
574
+ export function localOnlySigningMode(expectedTeamId: string): "ad_hoc" | "developer_id" {
575
+ if (expectedTeamId === "ADHOC") return "ad_hoc";
576
+ if (!/^[A-Z0-9]{10}$/.test(expectedTeamId)) {
577
+ throw new Error(
578
+ "local-only Developer ID signing requires ADHOC or a 10-character Apple team identifier",
579
+ );
580
+ }
581
+ return "developer_id";
582
+ }
583
+
463
584
  export function designatedRequirementForPolicy(
464
585
  output: string,
465
586
  artifactPolicy: ArtifactPolicy,
466
587
  adHocSignatureVerified = false,
588
+ developerIdSignatureVerified = false,
467
589
  ): string {
468
590
  if (artifactPolicy === "release") return parseDesignatedRequirement(output);
591
+ // A Developer ID local-station artifact always carries a real designated requirement, and
592
+ // that requirement is the whole point of signing it: it is the stable identity a TCC grant
593
+ // attaches to. Never let it degrade to the ad-hoc sentinel.
594
+ if (developerIdSignatureVerified) return parseDesignatedRequirement(output);
469
595
  if (!adHocSignatureVerified) {
470
- throw new Error("local-only designated requirement evidence requires verified ad-hoc signing");
596
+ throw new Error("local-only designated requirement evidence requires verified signing");
471
597
  }
472
598
  try {
473
599
  return parseDesignatedRequirement(output);
@@ -558,11 +684,24 @@ function signingEvidence(
558
684
  if (!timestamp || timestamp.toLowerCase() === "none") {
559
685
  throw new Error(`${codePath} is missing a trusted signing timestamp`);
560
686
  }
687
+ } else if (localOnlySigningMode(expectedTeamId) === "developer_id") {
688
+ // Developer ID signed but deliberately NOT notarized: the notarization, single-target
689
+ // binding and builder attestation are what still separate this from a release. Only the
690
+ // signing identity is shared, and only because TCC cannot key a durable Accessibility
691
+ // grant to anything weaker (see localOnlySigningMode).
692
+ if (!authority.startsWith("Developer ID Application:")) {
693
+ throw new Error(`${codePath} is not signed by a Developer ID Application authority`);
694
+ }
695
+ if (teamId !== expectedTeamId) {
696
+ throw new Error(`${codePath} TeamIdentifier ${teamId || "missing"} does not match ${expectedTeamId}`);
697
+ }
698
+ if (rawSignature.toLowerCase() === "adhoc") {
699
+ throw new Error(`${codePath} is ad-hoc signed but a Developer ID team was pinned`);
700
+ }
701
+ // A trusted timestamp is a release-only claim; local-station builds never pass --timestamp.
702
+ timestamp = rawTimestamp && rawTimestamp.toLowerCase() !== "none" ? rawTimestamp : "none";
561
703
  } else {
562
704
  mode = "ad_hoc";
563
- if (expectedTeamId !== "ADHOC") {
564
- throw new Error("local-only code verification requires the ADHOC signing identity");
565
- }
566
705
  if (rawSignature.toLowerCase() !== "adhoc" || authority) {
567
706
  throw new Error(`${codePath} is not consistently ad-hoc signed for local-only use`);
568
707
  }
@@ -587,6 +726,7 @@ function signingEvidence(
587
726
  requirementOutput,
588
727
  artifactPolicy,
589
728
  mode === "ad_hoc",
729
+ artifactPolicy !== "release" && mode === "developer_id",
590
730
  );
591
731
  const entitlements = canonicalEntitlements(codePath);
592
732
  if (entitlements !== canonicalJson(expectedEntitlements)) {
@@ -690,6 +830,22 @@ function manifestPolicy(manifest: MacOSArtifactManifest): ArtifactPolicy {
690
830
  return manifest.schema_version === LOCAL_ARTIFACT_SCHEMA_VERSION ? "local_only" : "release";
691
831
  }
692
832
 
833
+ /**
834
+ * The variant an artifact was built as. Older manifests predate the field; they are
835
+ * full artifacts by definition ("full" was the only variant that existed).
836
+ */
837
+ function manifestVariant(manifest: MacOSArtifactManifest): ArtifactVariant {
838
+ const value = manifest.variant ?? "full";
839
+ if (value !== "full" && value !== "bar") {
840
+ throw new Error(`manifest variant is unsupported: ${value}`);
841
+ }
842
+ return value;
843
+ }
844
+
845
+ function manifestBundleName(manifest: MacOSArtifactManifest): string {
846
+ return manifest.bundle_name ?? "Recordings.app";
847
+ }
848
+
693
849
  function manifestTargetIdentityKind(manifest: MacOSArtifactManifest): OperatorTargetIdentityKind {
694
850
  if (manifestPolicy(manifest) === "release") return "none";
695
851
  return manifest.approved_target_identity_kind ?? LEGACY_LOCAL_TARGET_IDENTITY_KIND;
@@ -1270,6 +1426,7 @@ export function withPrivatelyExtractedArchiveApp<T>(
1270
1426
  operation: (appPath: string) => T,
1271
1427
  platformArchiveTool = "/usr/bin/ditto",
1272
1428
  expectedArchiveSha256?: string,
1429
+ expectedBundleName = "Recordings.app",
1273
1430
  ): T {
1274
1431
  const privateRoot = mkdtempSync(join(tmpdir(), "recordings-artifact-extract-"));
1275
1432
  chmodSync(privateRoot, 0o700);
@@ -1314,13 +1471,17 @@ export function withPrivatelyExtractedArchiveApp<T>(
1314
1471
  inspectZipArchive(pinnedArchivePath);
1315
1472
  run(platformArchiveTool, ["-x", "-k", pinnedArchivePath, extractionRoot]);
1316
1473
  const rootEntries = readdirSync(extractionRoot);
1317
- if (rootEntries.length !== 1 || rootEntries[0] !== "Recordings.app") {
1318
- throw new Error("release archive must contain exactly one top-level Recordings.app");
1474
+ if (rootEntries.length !== 1 || rootEntries[0] !== expectedBundleName) {
1475
+ throw new Error(
1476
+ `release archive must contain exactly one top-level ${expectedBundleName}`,
1477
+ );
1319
1478
  }
1320
- const extractedAppPath = join(extractionRoot, "Recordings.app");
1479
+ const extractedAppPath = join(extractionRoot, expectedBundleName);
1321
1480
  const appDetails = lstatSync(extractedAppPath);
1322
1481
  if (appDetails.isSymbolicLink() || !appDetails.isDirectory()) {
1323
- throw new Error("release archive top-level Recordings.app must be a regular directory");
1482
+ throw new Error(
1483
+ `release archive top-level ${expectedBundleName} must be a regular directory`,
1484
+ );
1324
1485
  }
1325
1486
  assertRegularArchiveTree(extractedAppPath);
1326
1487
  return operation(extractedAppPath);
@@ -1343,6 +1504,7 @@ export function extractVerifiedArchiveToStaging(
1343
1504
  expectedApprovedTarget: string,
1344
1505
  expectedApprovedTargetIdentitySha256: string,
1345
1506
  expectedApprovedTargetIdentityKind: OperatorTargetIdentityKind,
1507
+ expectedVariant: ArtifactVariant = "full",
1346
1508
  platformArchiveTool = "/usr/bin/ditto",
1347
1509
  ): void {
1348
1510
  if (resolve(stagingTarget) !== stagingTarget) {
@@ -1375,16 +1537,19 @@ export function extractVerifiedArchiveToStaging(
1375
1537
  withPrivatelyExtractedArchiveApp(
1376
1538
  archivePath,
1377
1539
  (appPath) => {
1378
- const targetAppPath = join(stagingTarget, "Recordings.app");
1540
+ const targetAppPath = join(stagingTarget, manifestBundleName(manifest));
1379
1541
  renameSync(appPath, targetAppPath);
1380
1542
  assertRegularArchiveTree(targetAppPath);
1381
1543
  const entries = readdirSync(stagingTarget);
1382
- if (entries.length !== 1 || entries[0] !== "Recordings.app") {
1383
- throw new Error("archive extraction did not produce exactly Recordings.app in staging");
1544
+ if (entries.length !== 1 || entries[0] !== manifestBundleName(manifest)) {
1545
+ throw new Error(
1546
+ `archive extraction did not produce exactly ${manifestBundleName(manifest)} in staging`,
1547
+ );
1384
1548
  }
1385
1549
  },
1386
1550
  platformArchiveTool,
1387
1551
  manifest.archive.sha256,
1552
+ manifestBundleName(manifest),
1388
1553
  );
1389
1554
  }
1390
1555
 
@@ -1412,6 +1577,20 @@ export function assertManifestShape(manifest: MacOSArtifactManifest): void {
1412
1577
  }
1413
1578
  if (manifest.artifact_type !== "recordings-macos-app") throw new Error("unexpected artifact type");
1414
1579
  if (manifest.bundle_id !== BUNDLE_ID) throw new Error("unexpected bundle identifier");
1580
+ if (
1581
+ manifest.bundle_name !== undefined &&
1582
+ (typeof manifest.bundle_name !== "string" ||
1583
+ !/^[A-Za-z0-9][A-Za-z0-9 .-]*\.app$/.test(manifest.bundle_name))
1584
+ ) {
1585
+ throw new Error("manifest bundle name must be a *.app bundle directory name");
1586
+ }
1587
+ if (
1588
+ manifest.variant !== undefined &&
1589
+ manifest.variant !== "full" &&
1590
+ manifest.variant !== "bar"
1591
+ ) {
1592
+ throw new Error(`manifest variant is unsupported: ${manifest.variant}`);
1593
+ }
1415
1594
  const artifactPolicy = manifestPolicy(manifest);
1416
1595
  for (const [label, value] of [
1417
1596
  ["bundle version", manifest.bundle_version],
@@ -1510,7 +1689,7 @@ export function assertManifestShape(manifest: MacOSArtifactManifest): void {
1510
1689
  } else {
1511
1690
  if (
1512
1691
  manifest.artifact_policy !== "local_only" ||
1513
- manifest.approved_target !== "station06" ||
1692
+ !isLocalOnlyApprovedTarget(manifest.approved_target) ||
1514
1693
  (manifest.approved_target_identity_kind !== undefined &&
1515
1694
  manifest.approved_target_identity_kind !== "tailscale_node_id_sha256") ||
1516
1695
  (manifest.approved_target_identity_kind === undefined) !==
@@ -1523,20 +1702,38 @@ export function assertManifestShape(manifest: MacOSArtifactManifest): void {
1523
1702
  !isHex(manifest.builder_identity_sha256, 64) ||
1524
1703
  manifest.builder_identity_sha256 === manifest.approved_target_identity_sha256
1525
1704
  ) {
1526
- throw new Error("local-only schema v3 requires exact station06 name and machine identity");
1705
+ throw new Error(
1706
+ "local-only schema v3 requires an approved target name " +
1707
+ `(${localOnlyApprovedTargetsMessage()}) and machine identity`,
1708
+ );
1527
1709
  }
1528
- if (
1529
- manifest.non_notarized !== true ||
1530
- manifest.team_id !== "ADHOC" ||
1531
- manifest.signing.mode !== "ad_hoc" ||
1532
- manifest.signing.authority !== "adhoc" ||
1533
- manifest.signing.helper_authority !== "adhoc" ||
1534
- manifest.signing.team_id !== "ADHOC" ||
1535
- manifest.signing.helper_team_id !== "ADHOC" ||
1536
- manifest.signing.trusted_timestamp !== "none" ||
1537
- manifest.signing.helper_trusted_timestamp !== "none"
1710
+ // A local-station manifest is internally consistent in exactly one of two shapes: fully
1711
+ // ad-hoc, or fully Developer ID under one pinned team. Mixing them (e.g. a Developer ID
1712
+ // app with an ad-hoc helper) would leave the bundle without one stable identity for TCC
1713
+ // to key on, which is the failure this whole path exists to prevent.
1714
+ if (manifest.non_notarized !== true || !isLocalOnlySigningTeam(manifest.team_id)) {
1715
+ throw new Error("local-only manifest requires a non-notarized ADHOC or Developer ID team");
1716
+ }
1717
+ if (manifest.team_id === "ADHOC") {
1718
+ if (
1719
+ manifest.signing.mode !== "ad_hoc" ||
1720
+ manifest.signing.authority !== "adhoc" ||
1721
+ manifest.signing.helper_authority !== "adhoc" ||
1722
+ manifest.signing.team_id !== "ADHOC" ||
1723
+ manifest.signing.helper_team_id !== "ADHOC" ||
1724
+ manifest.signing.trusted_timestamp !== "none" ||
1725
+ manifest.signing.helper_trusted_timestamp !== "none"
1726
+ ) {
1727
+ throw new Error("local-only manifest requires consistent ad-hoc signing evidence");
1728
+ }
1729
+ } else if (
1730
+ manifest.signing.mode !== "developer_id" ||
1731
+ !manifest.signing.authority.startsWith("Developer ID Application:") ||
1732
+ !manifest.signing.helper_authority.startsWith("Developer ID Application:") ||
1733
+ manifest.signing.team_id !== manifest.team_id ||
1734
+ manifest.signing.helper_team_id !== manifest.team_id
1538
1735
  ) {
1539
- throw new Error("local-only manifest requires consistent ad-hoc signing evidence");
1736
+ throw new Error("local-only manifest requires consistent Developer ID signing evidence");
1540
1737
  }
1541
1738
  if (
1542
1739
  manifest.notarization.status !== "Not Submitted" ||
@@ -1602,6 +1799,7 @@ export function verifyArchiveManifest(
1602
1799
  expectedApprovedTarget: string = RELEASE_APPROVED_TARGET,
1603
1800
  expectedApprovedTargetIdentitySha256: string = "none",
1604
1801
  expectedApprovedTargetIdentityKind: OperatorTargetIdentityKind = LEGACY_LOCAL_TARGET_IDENTITY_KIND,
1802
+ expectedVariant: ArtifactVariant = "full",
1605
1803
  ): MacOSArtifactManifest {
1606
1804
  if (!expectedTeamId) throw new Error("expected Team ID is required");
1607
1805
  const manifest = readAuthenticatedManifest<MacOSArtifactManifest>(
@@ -1612,6 +1810,11 @@ export function verifyArchiveManifest(
1612
1810
  if (manifestPolicy(manifest) !== expectedPolicy) {
1613
1811
  throw new Error("manifest artifact policy does not match the explicit operator selection");
1614
1812
  }
1813
+ if (manifestVariant(manifest) !== expectedVariant) {
1814
+ throw new Error(
1815
+ `manifest variant ${manifestVariant(manifest)} does not match the explicit operator selection ${expectedVariant}`,
1816
+ );
1817
+ }
1615
1818
  const actualApprovedTarget = manifest.approved_target ?? RELEASE_APPROVED_TARGET;
1616
1819
  if (actualApprovedTarget !== expectedApprovedTarget) {
1617
1820
  throw new Error("manifest approved target does not match the exact operator-approved target");
@@ -1664,6 +1867,7 @@ function assertProvenanceMatchesManifest(
1664
1867
  "builder_identity_kind",
1665
1868
  "builder_identity_sha256",
1666
1869
  "non_notarized",
1870
+ "variant",
1667
1871
  "bundle_id",
1668
1872
  "bundle_version",
1669
1873
  "git_sha",
@@ -1836,11 +2040,17 @@ export function verifyExtractedApp(
1836
2040
  expectedApprovedTarget: string = RELEASE_APPROVED_TARGET,
1837
2041
  expectedApprovedTargetIdentitySha256: string = "none",
1838
2042
  expectedApprovedTargetIdentityKind: OperatorTargetIdentityKind = LEGACY_LOCAL_TARGET_IDENTITY_KIND,
2043
+ expectedVariant: ArtifactVariant = "full",
1839
2044
  ): MacOSArtifactManifest {
1840
2045
  const manifest = readAuthenticatedManifest<MacOSArtifactManifest>(
1841
2046
  manifestPath,
1842
2047
  expectedManifestSha256,
1843
2048
  );
2049
+ if (manifestVariant(manifest) !== expectedVariant) {
2050
+ throw new Error(
2051
+ `manifest variant ${manifestVariant(manifest)} does not match the explicit operator selection ${expectedVariant}`,
2052
+ );
2053
+ }
1844
2054
  verifyAppAgainstManifest(
1845
2055
  appPath,
1846
2056
  manifest,
@@ -1910,6 +2120,7 @@ function verifySuppliedAndArchivedApps(
1910
2120
  },
1911
2121
  "/usr/bin/ditto",
1912
2122
  manifest.archive.sha256,
2123
+ manifestBundleName(manifest),
1913
2124
  );
1914
2125
  if (sha256ArchiveFile(archivePath) !== manifest.archive.sha256) {
1915
2126
  throw new Error("release archive changed during exact-byte verification");
@@ -1927,6 +2138,7 @@ function writeProvenance(
1927
2138
  approvedTargetIdentitySha256: string,
1928
2139
  builderIdentityKind: OperatorTargetIdentityKind,
1929
2140
  builderIdentitySha256: string,
2141
+ variant: ArtifactVariant,
1930
2142
  ): void {
1931
2143
  const executablePath = join(appPath, "Contents", "MacOS", "Recordings");
1932
2144
  const helperPath = join(appPath, "Contents", "Helpers", "recordings");
@@ -1936,6 +2148,7 @@ function writeProvenance(
1936
2148
  artifactPolicy === "release"
1937
2149
  ? RELEASE_ARTIFACT_SCHEMA_VERSION
1938
2150
  : LOCAL_ARTIFACT_SCHEMA_VERSION,
2151
+ variant,
1939
2152
  bundle_id: plistValue(appPath, "CFBundleIdentifier"),
1940
2153
  bundle_version: plistValue(appPath, "CFBundleShortVersionString"),
1941
2154
  bundle_build_version: plistValue(appPath, "CFBundleVersion"),
@@ -1962,15 +2175,19 @@ function writeProvenance(
1962
2175
  throw new Error("release provenance has an invalid target or Team ID");
1963
2176
  }
1964
2177
  } else if (
1965
- expectedTeamId !== "ADHOC" ||
1966
- approvedTarget !== "station06" ||
2178
+ // ADHOC or a pinned Developer ID team; localOnlySigningMode rejects anything else.
2179
+ !isLocalOnlySigningTeam(expectedTeamId) ||
2180
+ !isLocalOnlyApprovedTarget(approvedTarget) ||
1967
2181
  approvedTargetIdentityKind !== "tailscale_node_id_sha256" ||
1968
2182
  builderIdentityKind !== "tailscale_node_id_sha256" ||
1969
2183
  !isHex(approvedTargetIdentitySha256, 64) ||
1970
2184
  !isHex(builderIdentitySha256, 64) ||
1971
2185
  approvedTargetIdentitySha256 === builderIdentitySha256
1972
2186
  ) {
1973
- throw new Error("new local-only provenance requires ADHOC and a Tailscale node-bound station06 identity");
2187
+ throw new Error(
2188
+ "new local-only provenance requires ADHOC or a pinned Developer ID team and a Tailscale " +
2189
+ `node-bound approved-target identity (${localOnlyApprovedTargetsMessage()})`,
2190
+ );
1974
2191
  } else {
1975
2192
  provenance.artifact_policy = "local_only";
1976
2193
  provenance.approved_target = approvedTarget;
@@ -1994,6 +2211,7 @@ function finalizeArtifact(
1994
2211
  notaryLogPath: string,
1995
2212
  notarySubmissionId: string,
1996
2213
  submittedArchiveSha256: string,
2214
+ expectedVariant: ArtifactVariant = "full",
1997
2215
  ): void {
1998
2216
  assertCurrentSourceRevision(packageRoot, expectedSourceSha);
1999
2217
  const executablePath = join(appPath, "Contents", "MacOS", "Recordings");
@@ -2003,6 +2221,14 @@ function finalizeArtifact(
2003
2221
  if (provenance.git_sha !== expectedSourceSha) {
2004
2222
  throw new Error("embedded provenance does not match the pinned source SHA");
2005
2223
  }
2224
+ if (expectedVariant !== "full" && expectedVariant !== "bar") {
2225
+ throw new Error(`finalize variant is unsupported: ${expectedVariant}`);
2226
+ }
2227
+ if ((provenance.variant ?? "full") !== expectedVariant) {
2228
+ throw new Error(
2229
+ `finalize variant ${expectedVariant} does not match the embedded provenance variant`,
2230
+ );
2231
+ }
2006
2232
  assertExpectedCodeLayout(appPath);
2007
2233
  const outerSigning = signingEvidence(appPath, expectedTeamId, APP_ENTITLEMENTS, executablePath);
2008
2234
  const helperSigning = signingEvidence(helperPath, expectedTeamId, HELPER_ENTITLEMENTS);
@@ -2015,9 +2241,14 @@ function finalizeArtifact(
2015
2241
  const notaryLog = JSON.parse(notaryLogSnapshot.toString("utf8")) as unknown;
2016
2242
  assertAcceptedNotaryLog(notaryLog, notarySubmissionId, submittedArchiveSha256);
2017
2243
  const items = nestedItems(appPath, expectedTeamId, "release", outerSigning, helperSigning);
2244
+ const bundleName = basename(appPath);
2245
+ if (!bundleName.endsWith(".app")) {
2246
+ throw new Error(`release app bundle must be named *.app: ${appPath}`);
2247
+ }
2018
2248
  const manifest: MacOSArtifactManifest = {
2019
2249
  ...provenance,
2020
2250
  artifact_type: "recordings-macos-app",
2251
+ bundle_name: bundleName,
2021
2252
  app_sha256: sha256File(executablePath),
2022
2253
  binding: { bundle_tree_sha256: treeDigest(appPath) },
2023
2254
  provenance_sha256: sha256File(embeddedPath),
@@ -2044,7 +2275,7 @@ function finalizeArtifact(
2044
2275
  },
2045
2276
  container: {
2046
2277
  type: "zip",
2047
- install_locations: ["/Applications/Recordings.app"],
2278
+ install_locations: [`/Applications/${bundleName}`],
2048
2279
  },
2049
2280
  nested_code_policy: {
2050
2281
  allowlist_sha256: nestedPolicyDigest(items),
@@ -2120,11 +2351,17 @@ function finalizeLocalArtifact(
2120
2351
  approvedTarget: string,
2121
2352
  approvedTargetIdentityKind: TargetIdentityKind,
2122
2353
  approvedTargetIdentitySha256: string,
2354
+ expectedTeamId: string = "ADHOC",
2355
+ expectedVariant: ArtifactVariant = "full",
2123
2356
  ): void {
2124
2357
  assertCurrentSourceRevision(packageRoot, expectedSourceSha);
2125
2358
  if (approvedTargetIdentityKind !== "tailscale_node_id_sha256") {
2126
2359
  throw new Error("new local-only artifacts require a Tailscale node ID identity hash");
2127
2360
  }
2361
+ if (expectedVariant !== "full" && expectedVariant !== "bar") {
2362
+ throw new Error(`finalize variant is unsupported: ${expectedVariant}`);
2363
+ }
2364
+ const localSigningMode = localOnlySigningMode(expectedTeamId);
2128
2365
  const executablePath = join(appPath, "Contents", "MacOS", "Recordings");
2129
2366
  const helperPath = join(appPath, "Contents", "Helpers", "recordings");
2130
2367
  const embeddedPath = provenancePath(appPath);
@@ -2137,34 +2374,44 @@ function finalizeLocalArtifact(
2137
2374
  provenance.approved_target_identity_sha256 !== approvedTargetIdentitySha256 ||
2138
2375
  provenance.builder_identity_kind !== "tailscale_node_id_sha256" ||
2139
2376
  provenance.non_notarized !== true ||
2140
- provenance.team_id !== "ADHOC"
2377
+ provenance.team_id !== expectedTeamId
2141
2378
  ) {
2142
2379
  throw new Error("embedded provenance is not approved for this local-only target");
2143
2380
  }
2381
+ if ((provenance.variant ?? "full") !== expectedVariant) {
2382
+ throw new Error(
2383
+ `finalize variant ${expectedVariant} does not match the embedded provenance variant`,
2384
+ );
2385
+ }
2144
2386
  assertExpectedCodeLayout(appPath);
2145
2387
  const outerSigning = signingEvidence(
2146
2388
  appPath,
2147
- "ADHOC",
2389
+ expectedTeamId,
2148
2390
  APP_ENTITLEMENTS,
2149
2391
  executablePath,
2150
2392
  "local_only",
2151
2393
  );
2152
2394
  const helperSigning = signingEvidence(
2153
2395
  helperPath,
2154
- "ADHOC",
2396
+ expectedTeamId,
2155
2397
  HELPER_ENTITLEMENTS,
2156
2398
  helperPath,
2157
2399
  "local_only",
2158
2400
  );
2159
- const items = nestedItems(appPath, "ADHOC", "local_only", outerSigning, helperSigning);
2401
+ const items = nestedItems(appPath, expectedTeamId, "local_only", outerSigning, helperSigning);
2402
+ const bundleName = basename(appPath);
2403
+ if (!bundleName.endsWith(".app")) {
2404
+ throw new Error(`local app bundle must be named *.app: ${appPath}`);
2405
+ }
2160
2406
  const manifest: MacOSArtifactManifest = {
2161
2407
  ...provenance,
2162
2408
  artifact_type: "recordings-macos-app",
2409
+ bundle_name: bundleName,
2163
2410
  app_sha256: sha256File(executablePath),
2164
2411
  binding: { bundle_tree_sha256: treeDigest(appPath) },
2165
2412
  provenance_sha256: sha256File(embeddedPath),
2166
2413
  signing: {
2167
- mode: "ad_hoc",
2414
+ mode: localSigningMode,
2168
2415
  authority: outerSigning.authority,
2169
2416
  team_id: outerSigning.teamId,
2170
2417
  trusted_timestamp: outerSigning.timestamp,
@@ -2187,7 +2434,7 @@ function finalizeLocalArtifact(
2187
2434
  },
2188
2435
  container: {
2189
2436
  type: "zip",
2190
- install_locations: ["~/Applications/Recordings.app"],
2437
+ install_locations: [`~/Applications/${bundleName}`],
2191
2438
  },
2192
2439
  nested_code_policy: {
2193
2440
  allowlist_sha256: nestedPolicyDigest(items),
@@ -2208,7 +2455,7 @@ function finalizeLocalArtifact(
2208
2455
  appPath,
2209
2456
  archivePath,
2210
2457
  manifest,
2211
- "ADHOC",
2458
+ expectedTeamId,
2212
2459
  "local_only",
2213
2460
  approvedTarget,
2214
2461
  approvedTargetIdentitySha256,
@@ -2301,11 +2548,19 @@ function assertInstallTransition(
2301
2548
  assertVersionTransition(installedVersion, installedSource, manifest);
2302
2549
  }
2303
2550
 
2304
- function requirementDigest(appPath: string, artifactPolicy: ArtifactPolicy): void {
2551
+ function requirementDigest(
2552
+ appPath: string,
2553
+ artifactPolicy: ArtifactPolicy,
2554
+ expectedTeamId: string = "ADHOC",
2555
+ ): void {
2305
2556
  if (artifactPolicy === "local_only") {
2557
+ // The pinned team decides which local signing shape is verified. Hard-coding "ADHOC" here
2558
+ // rejected every correctly Developer-ID-signed local-station bundle, and because
2559
+ // install_macos_app.sh calls this for EVERY local install under `set -euo pipefail`, that
2560
+ // rejection aborted the install outright — making a properly signed build uninstallable.
2306
2561
  const evidence = signingEvidence(
2307
2562
  appPath,
2308
- "ADHOC",
2563
+ expectedTeamId,
2309
2564
  APP_ENTITLEMENTS,
2310
2565
  join(appPath, "Contents", "MacOS", "Recordings"),
2311
2566
  "local_only",
@@ -2349,6 +2604,7 @@ function verifyActiveApp(
2349
2604
  expectedApprovedTarget: string,
2350
2605
  expectedApprovedTargetIdentitySha256: string,
2351
2606
  expectedApprovedTargetIdentityKind: OperatorTargetIdentityKind,
2607
+ expectedVariant: ArtifactVariant = "full",
2352
2608
  ): void {
2353
2609
  const manifest = verifyExtractedApp(
2354
2610
  appPath,
@@ -2359,6 +2615,7 @@ function verifyActiveApp(
2359
2615
  expectedApprovedTarget,
2360
2616
  expectedApprovedTargetIdentitySha256,
2361
2617
  expectedApprovedTargetIdentityKind,
2618
+ expectedVariant,
2362
2619
  );
2363
2620
  const helperPath = join(appPath, "Contents", "Helpers", "recordings");
2364
2621
  if (companionVersion(helperPath) !== manifest.companion.version) {
@@ -2369,8 +2626,8 @@ function verifyActiveApp(
2369
2626
  const environment: NodeJS.ProcessEnv = {
2370
2627
  HOME: contractHome,
2371
2628
  PATH: "/usr/bin:/bin:/usr/sbin:/sbin",
2372
- HASNA_RECORDINGS_STORAGE_MODE: "local",
2373
- RECORDINGS_STORAGE_MODE: "local",
2629
+ HASNA_RECORDINGS_CLIENT_STORE: "sqlite",
2630
+ RECORDINGS_CLIENT_STORE: "sqlite",
2374
2631
  HASNA_RECORDINGS_DB_PATH: join(contractHome, "recordings.db"),
2375
2632
  RECORDINGS_AUDIO_DIR: join(contractHome, "audio"),
2376
2633
  };
@@ -2702,7 +2959,7 @@ function readJournal(path: string): InstallJournal {
2702
2959
  journal.approved_target_identity_sha256 !== "none" ||
2703
2960
  journal.builder_identity_kind !== "none")) ||
2704
2961
  (journal.artifact_policy === "local_only" &&
2705
- (journal.approved_target !== "station06" ||
2962
+ (!isLocalOnlyApprovedTarget(journal.approved_target) ||
2706
2963
  !isTargetIdentityKind(journal.approved_target_identity_kind) ||
2707
2964
  !isTargetIdentityKind(journal.builder_identity_kind) ||
2708
2965
  journal.approved_target_identity_kind !== journal.builder_identity_kind ||
@@ -5021,6 +5278,9 @@ function manifestGet(path: string, expectedManifestSha256: string, field: string
5021
5278
  }
5022
5279
  else if (field === "approved_target_identity_sha256") console.log(manifest.approved_target_identity_sha256);
5023
5280
  else if (field === "builder_identity_kind") console.log(manifestBuilderIdentityKind(manifest));
5281
+ else if (field === "variant") console.log(manifestVariant(manifest));
5282
+ // Pre-variant manifests carry no bundle_name; they are full builds by definition.
5283
+ else if (field === "bundle_name") console.log(manifestBundleName(manifest));
5024
5284
  else throw new Error(`unsupported manifest field: ${field}`);
5025
5285
  }
5026
5286
 
@@ -5047,6 +5307,14 @@ function repeatedArguments(name: string): string[] {
5047
5307
  return values;
5048
5308
  }
5049
5309
 
5310
+ function variantArgument(defaultVariant: ArtifactVariant): ArtifactVariant {
5311
+ const value = optionalArgument("--variant") ?? defaultVariant;
5312
+ if (value !== "full" && value !== "bar") {
5313
+ throw new Error(`artifact variant must be full or bar; got: ${value}`);
5314
+ }
5315
+ return value;
5316
+ }
5317
+
5050
5318
  function artifactPolicyArgument(): ArtifactPolicy {
5051
5319
  const value = argument("--artifact-policy");
5052
5320
  if (value !== "release" && value !== "local_only") {
@@ -5109,6 +5377,7 @@ function main(): void {
5109
5377
  argument("--approved-target-identity-sha256"),
5110
5378
  builderIdentityKindArgument(policy),
5111
5379
  argument("--builder-identity-sha256"),
5380
+ variantArgument("full"),
5112
5381
  );
5113
5382
  } else if (command === "finalize") {
5114
5383
  const sourceSha = argument("--source-sha");
@@ -5123,6 +5392,7 @@ function main(): void {
5123
5392
  argument("--notary-log"),
5124
5393
  argument("--notary-submission-id"),
5125
5394
  argument("--submitted-archive-sha256"),
5395
+ variantArgument("full"),
5126
5396
  );
5127
5397
  } else if (command === "finalize-local") {
5128
5398
  const sourceSha = argument("--source-sha");
@@ -5135,6 +5405,8 @@ function main(): void {
5135
5405
  argument("--approved-target"),
5136
5406
  targetIdentityKindArgument(undefined, true),
5137
5407
  argument("--approved-target-identity-sha256"),
5408
+ optionalArgument("--expected-team-id") ?? "ADHOC",
5409
+ variantArgument("full"),
5138
5410
  );
5139
5411
  } else if (command === "verify-archive") {
5140
5412
  const teamId = argument("--team-id");
@@ -5150,6 +5422,7 @@ function main(): void {
5150
5422
  argument("--approved-target"),
5151
5423
  argument("--approved-target-identity-sha256"),
5152
5424
  targetIdentityKindArgument(policy),
5425
+ variantArgument("full"),
5153
5426
  );
5154
5427
  } else if (command === "extract-verified-archive") {
5155
5428
  const teamId = argument("--team-id");
@@ -5166,6 +5439,7 @@ function main(): void {
5166
5439
  argument("--approved-target"),
5167
5440
  argument("--approved-target-identity-sha256"),
5168
5441
  targetIdentityKindArgument(policy),
5442
+ variantArgument("full"),
5169
5443
  );
5170
5444
  } else if (command === "verify-app") {
5171
5445
  const teamId = argument("--team-id");
@@ -5179,6 +5453,7 @@ function main(): void {
5179
5453
  argument("--approved-target"),
5180
5454
  argument("--approved-target-identity-sha256"),
5181
5455
  targetIdentityKindArgument(policy),
5456
+ variantArgument("full"),
5182
5457
  );
5183
5458
  } else if (command === "verify-active") {
5184
5459
  const teamId = argument("--team-id");
@@ -5192,6 +5467,7 @@ function main(): void {
5192
5467
  argument("--approved-target"),
5193
5468
  argument("--approved-target-identity-sha256"),
5194
5469
  targetIdentityKindArgument(policy),
5470
+ variantArgument("full"),
5195
5471
  );
5196
5472
  } else if (command === "assert-release") {
5197
5473
  assertExpectedRelease(
@@ -5207,7 +5483,11 @@ function main(): void {
5207
5483
  argument("--manifest-sha256"),
5208
5484
  );
5209
5485
  } else if (command === "requirement-digest") {
5210
- requirementDigest(argument("--app"), artifactPolicyArgument());
5486
+ requirementDigest(
5487
+ argument("--app"),
5488
+ artifactPolicyArgument(),
5489
+ optionalArgument("--expected-team-id") ?? "ADHOC",
5490
+ );
5211
5491
  } else if (command === "tailscale-node-id-sha256") {
5212
5492
  console.log(tailscaleNodeIdSha256(readFileSync(0, "utf8"), argument("--expected-hostname")));
5213
5493
  } else if (command === "verify-filesystem-tree") {