@hasna/recordings 0.3.13 → 0.5.0

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 (148) hide show
  1. package/README.md +279 -43
  2. package/bun.lock +37 -38
  3. package/contracts/v1/fixtures.json +1206 -0
  4. package/dist/cli/index.js +1909 -619
  5. package/dist/cli/macos-shortcut.d.ts +2 -2
  6. package/dist/contracts/hosted-v1.d.ts +105 -0
  7. package/dist/contracts/hosted-v1.d.ts.map +1 -0
  8. package/dist/contracts/hosted-v1.js +41 -0
  9. package/dist/contracts/stream-v1.d.ts +117 -0
  10. package/dist/contracts/stream-v1.d.ts.map +1 -0
  11. package/dist/contracts/stream-v1.js +35 -0
  12. package/dist/db/database.d.ts.map +1 -1
  13. package/dist/db/pg-migrations.d.ts.map +1 -1
  14. package/dist/db/recordings.d.ts +6 -0
  15. package/dist/db/recordings.d.ts.map +1 -1
  16. package/dist/hosted/index.d.ts +58 -0
  17. package/dist/hosted/index.d.ts.map +1 -0
  18. package/dist/hosted/index.js +266 -0
  19. package/dist/hosted/transport.d.ts +36 -0
  20. package/dist/hosted/transport.d.ts.map +1 -0
  21. package/dist/hosted-v1-aavn7ktb.js +4114 -0
  22. package/dist/hosted-v1-gdr9extc.js +84 -0
  23. package/dist/http/client.d.ts +183 -22
  24. package/dist/http/client.d.ts.map +1 -1
  25. package/dist/index.d.ts +3 -3
  26. package/dist/index.d.ts.map +1 -1
  27. package/dist/index.js +2879 -2288
  28. package/dist/lib/audio-artifact-storage.d.ts +98 -0
  29. package/dist/lib/audio-artifact-storage.d.ts.map +1 -0
  30. package/dist/lib/capture-probe.d.ts +3 -3
  31. package/dist/lib/capture-probe.d.ts.map +1 -1
  32. package/dist/lib/config.d.ts.map +1 -1
  33. package/dist/lib/local-opt-in.d.ts +132 -0
  34. package/dist/lib/local-opt-in.d.ts.map +1 -0
  35. package/dist/lib/macos-bundle.d.ts +1 -1
  36. package/dist/lib/persistence-probe.d.ts +56 -17
  37. package/dist/lib/persistence-probe.d.ts.map +1 -1
  38. package/dist/lib/transcriber.d.ts.map +1 -1
  39. package/dist/mcp/index.d.ts.map +1 -1
  40. package/dist/mcp/index.js +1713 -459
  41. package/dist/sdk/index.d.ts +13 -5
  42. package/dist/sdk/index.d.ts.map +1 -1
  43. package/dist/sdk/index.js +904 -1
  44. package/dist/sdk/resolve.d.ts +53 -0
  45. package/dist/sdk/resolve.d.ts.map +1 -0
  46. package/dist/sdk/v1.generated.d.ts +6 -0
  47. package/dist/sdk/v1.generated.d.ts.map +1 -1
  48. package/dist/server/client-ip.d.ts +39 -0
  49. package/dist/server/client-ip.d.ts.map +1 -0
  50. package/dist/server/cloud-config.d.ts +12 -0
  51. package/dist/server/cloud-config.d.ts.map +1 -1
  52. package/dist/server/cloud-readiness.d.ts.map +1 -1
  53. package/dist/server/cloud.d.ts +20 -2
  54. package/dist/server/cloud.d.ts.map +1 -1
  55. package/dist/server/index.js +590 -287
  56. package/dist/server/migrate-command.d.ts +1 -1
  57. package/dist/server/migrate-command.d.ts.map +1 -1
  58. package/dist/server/openapi.d.ts +21 -0
  59. package/dist/server/openapi.d.ts.map +1 -1
  60. package/dist/server/repo.d.ts.map +1 -1
  61. package/dist/server/serve.d.ts.map +1 -1
  62. package/dist/storage.d.ts +2 -2
  63. package/dist/storage.d.ts.map +1 -1
  64. package/dist/storage.js +1358 -874
  65. package/dist/store.d.ts +12 -3
  66. package/dist/store.d.ts.map +1 -1
  67. package/dist/types/index.d.ts +15 -0
  68. package/dist/types/index.d.ts.map +1 -1
  69. package/docs/hosted-sdk.md +71 -0
  70. package/docs/wire-contracts.md +24 -0
  71. package/package.json +33 -11
  72. package/packaging/macos/build_release_pkg.sh +3 -3
  73. package/packaging/macos/managed_bootstrap.sh +4 -4
  74. package/packaging/macos/scripts/postinstall +2 -2
  75. package/packaging/macos/scripts/preinstall +2 -2
  76. package/scripts/build_companion_cli.sh +7 -1
  77. package/scripts/ci-linux-suite.ts +23 -13
  78. package/scripts/install_macos_app.sh +27 -24
  79. package/scripts/macos_artifact.ts +60 -49
  80. package/scripts/migrate.ts +4 -20
  81. package/scripts/native/prebuilds/darwin-universal/recordings_fs_guard.node +0 -0
  82. package/scripts/native/recordings_fs_guard.c +36 -4
  83. package/scripts/native-core-receipt.py +171 -0
  84. package/scripts/native_fs_guard.ts +2 -0
  85. package/scripts/release-suite-gate.ts +227 -150
  86. package/scripts/resolve_tailscale_cli.sh +24 -3
  87. package/scripts/smoke_macos_app.sh +21 -21
  88. package/src/native/Recordings/App/ContentView.swift +53 -68
  89. package/src/native/Recordings/App/MenuBarStatusView.swift +171 -61
  90. package/src/native/Recordings/App/RecordWorkspaceView.swift +47 -449
  91. package/src/native/Recordings/App/RecorderSettingsView.swift +75 -0
  92. package/src/native/Recordings/App/RecordingDetailView.swift +3 -4
  93. package/src/native/Recordings/App/RecordingsApp.swift +185 -9
  94. package/src/native/Recordings/App/RecordingsListView.swift +60 -112
  95. package/src/native/Recordings/App/RecordingsStore.swift +124 -109
  96. package/src/native/Recordings/App/Theme.swift +73 -61
  97. package/src/native/Recordings/Package.swift +1 -0
  98. package/src/native/Recordings/RecordingsLib/BlockingOperation.swift +29 -0
  99. package/src/native/Recordings/RecordingsLib/CaptureMonitor.swift +65 -0
  100. package/src/native/Recordings/RecordingsLib/Info.plist +3 -3
  101. package/src/native/Recordings/RecordingsLib/OpenAIAPIKeyStore.swift +75 -21
  102. package/src/native/Recordings/RecordingsLib/ProjectStore.swift +4 -4
  103. package/src/native/Recordings/RecordingsLib/Recording.swift +14 -2
  104. package/src/native/Recordings/RecordingsLib/RecordingEngine.swift +539 -89
  105. package/src/native/Recordings/RecordingsLib/RecordingGlassBackground.swift +34 -0
  106. package/src/native/Recordings/RecordingsLib/RecordingPasteTarget.swift +114 -0
  107. package/src/native/Recordings/RecordingsLib/RecordingProvider.swift +112 -0
  108. package/src/native/Recordings/RecordingsLib/RecordingsCLI.swift +17 -10
  109. package/src/native/Recordings/RecordingsLib/ServiceAPIConfiguration.swift +107 -0
  110. package/src/native/Recordings/RecordingsLib/ServiceConnectionView.swift +76 -0
  111. package/src/native/Recordings/RecordingsLib/SettingsView.swift +45 -149
  112. package/src/native/Recordings/RecordingsLib/SettingsWindowController.swift +48 -0
  113. package/src/native/Recordings/RecordingsTests/BlockingOperationTests.swift +85 -0
  114. package/src/native/Recordings/RecordingsTests/CLIRunnerTests.swift +454 -76
  115. package/src/native/Recordings/RecordingsTests/OpenAIAPIKeyStoreTests.swift +43 -38
  116. package/src/native/Recordings/RecordingsTests/PipeClosureFixture.swift +215 -0
  117. package/src/native/Recordings/RecordingsTests/ProjectStoreTests.swift +10 -10
  118. package/src/native/Recordings/RecordingsTests/RecorderSurfaceTests.swift +60 -0
  119. package/src/native/Recordings/RecordingsTests/RecordingDateTests.swift +26 -0
  120. package/src/native/Recordings/RecordingsTests/RecordingEngineDeliveryTests.swift +1 -1
  121. package/src/native/Recordings/RecordingsTests/RecordingFrozenPasteTargetTests.swift +50 -0
  122. package/src/native/Recordings/RecordingsTests/RecordingPasteTargetTrackerTests.swift +48 -0
  123. package/src/native/Recordings/RecordingsTests/RecordingProviderTests.swift +308 -0
  124. package/src/native/Recordings/RecordingsTests/RecordingStartGateTests.swift +1 -1
  125. package/src/native/Recordings/RecordingsTests/RecordingStartTimingTests.swift +123 -21
  126. package/src/native/Recordings/RecordingsTests/ServiceAPIConfigurationTests.swift +75 -0
  127. package/src/native/Recordings/RecordingsTests/SettingsWindowControllerTests.swift +28 -0
  128. package/src/native/Recordings/RecordingsTests/TestHomeDirectory.swift +5 -1
  129. package/src/native/Recordings/Updater/BootstrapPreflight/BootstrapPreflightMain.swift +1 -1
  130. package/src/native/Recordings/Updater/Broker/ApplicationNamespace.swift +2 -2
  131. package/src/native/Recordings/Updater/Broker/BrokerMain.swift +1 -1
  132. package/src/native/Recordings/Updater/Broker/CanonicalTreeCopy.swift +1 -1
  133. package/src/native/Recordings/Updater/Broker/InstallJournal.swift +1 -1
  134. package/src/native/Recordings/Updater/BrokerTests/ActivationRecoveryPolicyTests.swift +2 -2
  135. package/src/native/Recordings/Updater/Protocol/UpdateProtocol.swift +1 -1
  136. package/src/native/Recordings/Updater/VerifierLauncher/RecordingsVerifierLauncher.c +2 -2
  137. package/src/native/Recordings/build.sh +8 -7
  138. package/dist/__tests__/helpers/installer-guard-execution.d.ts +0 -22
  139. package/dist/__tests__/helpers/installer-guard-execution.d.ts.map +0 -1
  140. package/dist/__tests__/helpers/installer-preflight.d.ts +0 -22
  141. package/dist/__tests__/helpers/installer-preflight.d.ts.map +0 -1
  142. package/dist/__tests__/helpers/native-fs-guard.d.ts +0 -2
  143. package/dist/__tests__/helpers/native-fs-guard.d.ts.map +0 -1
  144. package/dist/__tests__/helpers/source-assertions.d.ts +0 -171
  145. package/dist/__tests__/helpers/source-assertions.d.ts.map +0 -1
  146. package/dist/__tests__/preload.d.ts +0 -2
  147. package/dist/__tests__/preload.d.ts.map +0 -1
  148. package/src/native/Recordings/App/SidebarView.swift +0 -212
package/dist/store.d.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  import type { Recording, CreateRecordingInput, RecordingFilter, Agent, Project } from "./types/index.js";
2
2
  import { type FeedbackInput } from "./db/feedback.js";
3
+ import { type RecordsClientResolveOptions } from "./http/client.js";
4
+ import { AudioArtifactStorage } from "./lib/audio-artifact-storage.js";
3
5
  export declare const APP = "recordings";
4
6
  export type { FeedbackInput } from "./db/feedback.js";
5
7
  export interface RecordingStats {
@@ -30,13 +32,20 @@ export interface Store {
30
32
  saveFeedback(input: FeedbackInput): Promise<void>;
31
33
  }
32
34
  export declare function countStoreRecordings(store: Store, filter?: RecordingFilter): Promise<number>;
35
+ /** Test helper: force the LocalStore's artifact kit (e.g. an in-memory bucket). */
36
+ export declare function __setLocalArtifactStorage(storage: AudioArtifactStorage | null): void;
37
+ /** Test helper: clear the artifact-storage override. */
38
+ export declare function __resetLocalArtifactStorage(): void;
33
39
  /**
34
40
  * Resolve the active Store from the environment. Cached per-process after first
35
41
  * resolution against `process.env`; pass an explicit `env` (e.g. in tests) to
36
- * bypass the cache. Throws if the cloud transport is requested but misconfigured
37
- * (URL/key mismatch) so callers never silently read the wrong dataset.
42
+ * bypass the cache. The hosted client re-resolves its credential on every
43
+ * request through the @hasna/contracts chain, so the cache never freezes a
44
+ * key. Throws a `REMOTE_API_*` failure when no credential resolves — the
45
+ * on-box file is reachable only through the explicit
46
+ * `HASNA_RECORDINGS_LOCAL=1` opt-in, never as a fallback.
38
47
  */
39
- export declare function getStore(env?: NodeJS.ProcessEnv | Record<string, string | undefined>): Store;
48
+ export declare function getStore(env?: NodeJS.ProcessEnv | Record<string, string | undefined>, options?: RecordsClientResolveOptions): Store;
40
49
  /** Test helper: clear the cached Store. */
41
50
  export declare function __resetStore(): void;
42
51
  //# sourceMappingURL=store.d.ts.map
@@ -1 +1 @@
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"}
1
+ {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,12 @@
1
1
  export interface Recording {
2
2
  id: string;
3
3
  audio_path: string | null;
4
+ /** Object key of the uploaded audio in the configured artifact bucket, when the upload happened. */
5
+ audio_object_key: string | null;
6
+ /** Lowercase hex sha-256 of the uploaded audio bytes; content-addressed storage identity. */
7
+ audio_sha256: string | null;
8
+ /** Byte size of the uploaded audio object. */
9
+ audio_bytes: number | null;
4
10
  raw_text: string;
5
11
  processed_text: string | null;
6
12
  processing_mode: ProcessingMode;
@@ -24,6 +30,9 @@ export interface CreateRecordingInput {
24
30
  /** Stable caller-owned identity for retrying one logical recording create. */
25
31
  id?: string;
26
32
  audio_path?: string;
33
+ audio_object_key?: string;
34
+ audio_sha256?: string;
35
+ audio_bytes?: number;
27
36
  raw_text: string;
28
37
  processed_text?: string;
29
38
  processing_mode?: ProcessingMode;
@@ -93,6 +102,12 @@ export interface RecordingsConfig {
93
102
  db_path: string;
94
103
  audio_dir: string;
95
104
  max_recording_seconds: number;
105
+ /** Artifact bucket for uploaded audio (HASNA_RECORDINGS_S3_BUCKET / RECORDINGS_S3_BUCKET). Empty = local-only. */
106
+ s3_bucket: string;
107
+ /** Object-key prefix inside the bucket; defaults to "recordings" when the bucket is set. */
108
+ s3_prefix: string;
109
+ /** AWS region for the S3 client; defaults to AWS_REGION, then us-east-1. */
110
+ s3_region: string;
96
111
  config_warnings?: string[];
97
112
  }
98
113
  export interface TranscriptionResult {
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,eAAe,EAAE,cAAc,CAAC;IAChC,UAAU,EAAE,MAAM,CAAC;IACnB,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,MAAM,cAAc,GAAG,KAAK,GAAG,UAAU,CAAC;AAEhD,MAAM,WAAW,oBAAoB;IACnC,8EAA8E;IAC9E,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,eAAe,CAAC,EAAE,cAAc,CAAC;IACjC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,eAAe,CAAC,EAAE,cAAc,CAAC;IACjC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAID,MAAM,WAAW,KAAK;IACpB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;CACtB;AAID,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;CACpB;AAID,MAAM,MAAM,kBAAkB,GAAG,KAAK,GAAG,MAAM,GAAG,QAAQ,CAAC;AAE3D,MAAM,WAAW,gBAAgB;IAC/B,cAAc,EAAE,MAAM,CAAC;IACvB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,mBAAmB,EAAE,MAAM,CAAC;IAC5B,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,4BAA4B,CAAC,EAAE,MAAM,CAAC;IACtC,iBAAiB,EAAE,MAAM,CAAC;IAC1B,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,KAAK,GAAG,KAAK,GAAG,KAAK,GAAG,MAAM,CAAC;IAC7C,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,MAAM,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,oBAAoB,CAAC,EAAE,kBAAkB,CAAC;IAC1C,YAAY,EAAE,OAAO,CAAC;IACtB,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,kBAAkB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC3C,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,qBAAqB,EAAE,MAAM,CAAC;IAC9B,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;CAC5B;AAID,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAID,qBAAa,sBAAuB,SAAQ,KAAK;gBACnC,EAAE,EAAE,MAAM;CAIvB;AAED,qBAAa,cAAe,SAAQ,KAAK;gBAC3B,OAAO,EAAE,MAAM;CAI5B;AAED,qBAAa,kBAAmB,SAAQ,KAAK;gBAC/B,OAAO,EAAE,MAAM;CAI5B;AAED,qBAAa,gBAAiB,SAAQ,KAAK;gBAC7B,OAAO,EAAE,MAAM;CAI5B"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,71 @@
1
+ # Hosted SDK
2
+
3
+ `@hasna/recordings/hosted` provides `HostedRecordingsClient` for hosted recording
4
+ metadata, account and paste-history operations. The existing `./sdk` retains its
5
+ separate legacy API. This additive client neither translates legacy fields nor
6
+ records audio, opens a login browser, manages credentials or starts a speech
7
+ provider. Bun is the tested runtime; browser compatibility is not claimed.
8
+
9
+ ```ts
10
+ import { HostedRecordingsClient, recordingCursor } from "@hasna/recordings/hosted";
11
+
12
+ const client = new HostedRecordingsClient({
13
+ apiBase: configuration.completeV1Base,
14
+ credentialProvider: ({ apiBase, signal }) => session.accessTokenFor(apiBase, signal),
15
+ });
16
+ const { recordings } = await client.listRecordings({ limit: 25 });
17
+ if (recordings.length === 25) {
18
+ const next = await client.listRecordings({ limit: 25, ...recordingCursor(recordings.at(-1)!) });
19
+ }
20
+ ```
21
+
22
+ The caller supplies a complete API base ending in `/v1`, including any prefix.
23
+ Remote bases require HTTPS; HTTP requires an explicit exact loopback address.
24
+ The client has no default endpoint or ambient environment, disk or Keychain
25
+ credential lookup. Public `health()` and `version()` never request credentials;
26
+ all other methods request a fresh caller-supplied Bearer credential. Injected
27
+ `fetch` is trusted and must honor manual redirects and AbortSignal. No request
28
+ follows a redirect, sends cookies or retries automatically.
29
+
30
+ `bootstrap()` sends an empty object; the server derives account/profile authority
31
+ from the authenticated session. `account()` reads that account and `ready()`
32
+ checks authenticated readiness. The caller owns OAuth, refresh and credential
33
+ storage. `logout()` revokes the current server session family; the caller must
34
+ also clear its stored credentials.
35
+
36
+ `listRecordings`, `getRecording`, `saveRecording` and `renameRecording` use
37
+ camelCase values: `id`, optional `sessionId`, `title`, `transcript`, and
38
+ `durationMs`. Duration is milliseconds. `deleteRecording` returns
39
+ `{state:'pending'}` for accepted cleanup or `{state:'removed'}` for completed
40
+ deletion. Pending is not proof that audio has been purged. Repeat explicitly;
41
+ reads may return the `recording_deleted` error after a permanent tombstone.
42
+
43
+ `listPasteReceipts`, `savePasteReceipt`, `deletePasteReceipt` and
44
+ `clearPasteHistory` use the hosted paste contract. An input `occurredAt` must be
45
+ UTC ending in `Z`; omit it for server time. `client_reported` receipt evidence
46
+ does not mean the server observed the target. Empty text remains valid when
47
+ retention is disabled. Recordings and receipts can contain private text; callers
48
+ should avoid logging responses.
49
+
50
+ Both lists require `before` and `beforeId` together or neither. `recordingCursor`
51
+ selects the last row's `createdAt` and ID; `pasteCursor` selects its `occurredAt`
52
+ and ID. There is no inferred total or automatic pagination. A full page can be
53
+ the last page. Inputs are validated before credentials or fetch; future JSON
54
+ response fields are preserved. Advertised metadata must include a compatible
55
+ version and all required capabilities; entirely absent metadata remains legacy
56
+ compatible. This is stricter than an older hosted adapter that accepted empty
57
+ capability advertisements.
58
+
59
+ The default 20-second deadline includes credential acquisition and the entire
60
+ response body. Caller cancellation has the same scope. Encoded JSON requests
61
+ are limited to 1 MiB before credentials are requested. Responses are limited to
62
+ 4 MiB of decoded bytes by default, configurable from 1 KiB to 32 MiB; compressed
63
+ wire Content-Length is not compared with decoded size. The transport bounds its
64
+ growing buffer, not total process memory. Error bodies are cancelled unread.
65
+
66
+ `RecordingsSDKError` contains a fixed code/message and optional HTTP status and
67
+ validated UUID request ID. It retains no body, endpoint, token, server message or
68
+ original cause. A timed-out mutation may already have committed: reconcile using
69
+ the known recording or receipt ID instead of blindly retrying. This JSON client
70
+ does not implement audio upload/download, WebSocket sessions, native app control,
71
+ tenant storage, usage admission or provider execution.
@@ -0,0 +1,24 @@
1
+ # Hosted recording wire contracts
2
+
3
+ The additive `@hasna/recordings/contracts/hosted-v1` and `@hasna/recordings/contracts/stream-v1` exports describe hosted recording metadata and PCM streaming. They perform no I/O, choose no endpoint, read no credentials and implement no provider or storage backend. Existing `./sdk`, CLI, MCP, serve, projects and native provider interfaces keep their existing behavior. The hosted camelCase API is distinct from the legacy SDK's snake_case API. Qualified timestamps require seconds; numeric offsets require a colon and valid hour/minute ranges.
4
+
5
+ ```ts
6
+ import { recordingInputParser } from "@hasna/recordings/contracts/hosted-v1";
7
+ import { streamEventParser } from "@hasna/recordings/contracts/stream-v1";
8
+
9
+ const request = recordingInputParser.parse(input);
10
+ const event = streamEventParser.safeParse(decodedEvent);
11
+ if (!event.success) handleInvalidContract(event.error.code);
12
+ ```
13
+
14
+ Every parser exposes an explicit `ContractParser<T>` interface with plain DTOs. `parse` throws a fixed `ContractValidationError`; `safeParse` returns `{success: true, data}` or `{success: false, error: {code: "invalid_contract"}}`. Neither error retains rejected data, schema-library internals or a cause. Consumers using different validation libraries can use these interfaces without exchanging Zod types.
15
+
16
+ Inputs reject unknown fields. Recording input has a UUID, title, transcript and duration in milliseconds; caller input cannot supply trusted provenance. Titles are trimmed. Paste input uses UTC timestamps ending in `Z`; output timestamps may have offsets. A paste receipt's `client_reported` evidence does not mean the server observed the target. Pagination requires `before` and `beforeId` together, or neither; either cursor component alone is invalid. Responses preserve extra JSON fields, including nested fields, while still checking known fields.
17
+
18
+ Hosted wire version `1.0` is independent of package and local-control versions. Metadata may be entirely absent for legacy v1. Once advertised, version and capabilities must both be present: major `1`, bounded minor, unique valid capability names, and all four required capabilities. Future compatible minors and additional capabilities are accepted. No incompatible advertisement downgrades to legacy mode. Bootstrap and version response parsers enforce these rules too. This intentionally tightens the previous hosted SDK's permissive metadata parsing to match service/native negotiation; it does not modify the legacy public SDK.
19
+
20
+ Streaming controls are strict. `parseStreamControlJSON` also enforces the existing 20,000-byte UTF-8 control-message ceiling. `pcmFrameParser` accepts only nonempty even-length PCM16 frames, from 2 through 24,000 bytes, without copying or converting them. The format is 24 kHz mono signed little-endian PCM16. Stream event parsers validate known event types, bounds, final capture identity and complete advertised metadata. New fields are allowed; unknown event types are rejected. Legacy final events may omit segments/provenance/recordingId, and legacy ready events may omit metadata/window fields.
21
+
22
+ These are value contracts, not a session state machine. The consumer must bind events to its current capture, require ready before sending, enforce sequence ordering, cumulative/buffer/deadline limits, require input finish before final delivery, validate renewal expiry against its clock, and require ready metadata when bootstrap advertised it. `acceptsAudioAcknowledgement` is a pure check against the consumer's sent and acknowledged byte counts; it does not update those counts. The consumer owns consent, authorization, cancellation and error display. Error-event messages are untrusted data and must not be logged as validation diagnostics.
23
+
24
+ Fictional positive and negative interoperability vectors are exported as `@hasna/recordings/contracts/fixtures/v1`. They freeze current input/output differences explicitly. Contract additions do not repoint or replace any legacy API. A subsequent reviewed package release and consumer adoption are required; introducing these modules does not publish them or change a deployed service.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/recordings",
3
- "version": "0.3.13",
3
+ "version": "0.5.0",
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": {
@@ -26,6 +26,19 @@
26
26
  "./sdk": {
27
27
  "import": "./dist/sdk/index.js",
28
28
  "types": "./dist/sdk/index.d.ts"
29
+ },
30
+ "./contracts/hosted-v1": {
31
+ "import": "./dist/contracts/hosted-v1.js",
32
+ "types": "./dist/contracts/hosted-v1.d.ts"
33
+ },
34
+ "./contracts/stream-v1": {
35
+ "import": "./dist/contracts/stream-v1.js",
36
+ "types": "./dist/contracts/stream-v1.d.ts"
37
+ },
38
+ "./contracts/fixtures/v1": "./contracts/v1/fixtures.json",
39
+ "./hosted": {
40
+ "import": "./dist/hosted/index.js",
41
+ "types": "./dist/hosted/index.d.ts"
29
42
  }
30
43
  },
31
44
  "engines": {
@@ -34,20 +47,20 @@
34
47
  "scripts": {
35
48
  "clean": "rm -rf dist",
36
49
  "build": "bun run clean && bun run build:cli && bun run build:mcp && bun run build:serve && bun run build:lib && tsc --emitDeclarationOnly --outDir dist",
37
- "build:cli": "bun build src/cli/index.ts --target=bun --outfile=dist/cli/index.js --external=commander --external=chalk --external=openai",
38
- "build:mcp": "bun build src/mcp/index.ts --target=bun --outfile=dist/mcp/index.js --external=@modelcontextprotocol/sdk --external=openai",
39
- "build:serve": "bun build src/server/index.ts --target=bun --outfile=dist/server/index.js --external=@modelcontextprotocol/sdk --external=@hasna/contracts --external=openai --external=pg",
40
- "build:lib": "bun build src/index.ts src/storage.ts src/sdk/index.ts --target=bun --outdir=dist --external=openai",
50
+ "build:cli": "bun build src/cli/index.ts --target=bun --outfile=dist/cli/index.js --external=commander --external=chalk --external=openai --external=@aws-sdk/client-s3",
51
+ "build:mcp": "bun build src/mcp/index.ts --target=bun --outfile=dist/mcp/index.js --external=@modelcontextprotocol/sdk --external=openai --external=@aws-sdk/client-s3",
52
+ "build:serve": "bun build src/server/index.ts --target=bun --outfile=dist/server/index.js --external=@modelcontextprotocol/sdk --external=@hasna/contracts --external=openai --external=pg --external=@aws-sdk/client-s3",
53
+ "build:lib": "bun build src/index.ts src/storage.ts src/sdk/index.ts --target=bun --outdir=dist --external=openai --external=@aws-sdk/client-s3 && bun run build:contracts",
41
54
  "build:native-fs-guard": "/bin/bash scripts/build_native_fs_guard.sh",
42
55
  "generate:sdk": "bun run scripts/generate-sdk.ts",
43
56
  "migrate": "bun run scripts/migrate.ts",
44
57
  "typecheck": "tsc --noEmit",
45
58
  "typecheck:tcc-contract": "tsc --noEmit -p tsconfig.tcc-contract.json",
46
- "test": "bun test",
59
+ "test": "bun scripts/release-suite-gate.ts --all",
47
60
  "verify:ci-suite": "bun scripts/ci-linux-suite.ts --check",
48
61
  "verify:ci-suite:run": "bun scripts/ci-linux-suite.ts --verify-run",
49
62
  "verify:ci-native": "bun scripts/ci-native-build.ts",
50
- "test:gated": "bun scripts/ci-linux-suite.ts --check && RECORDINGS_TEST_TIMEOUT_MS=\"${RECORDINGS_TEST_TIMEOUT_MS:-120000}\" bun test --timeout \"$RECORDINGS_TEST_TIMEOUT_MS\" $(bun scripts/ci-linux-suite.ts --gated)",
63
+ "test:gated": "bun scripts/release-suite-gate.ts",
51
64
  "test:vacuity-battery": "bun scripts/vacuity-manifest-gen.ts > \"${TMPDIR:-/tmp}/vacuity-corrupt-sites.tsv\" && bun scripts/vacuity-mutation-battery.ts \"${TMPDIR:-/tmp}/vacuity-corrupt-sites.tsv\"",
52
65
  "test:vacuity-battery:source": "bun scripts/vacuity-mutation-battery.ts scripts/vacuity-manifests/source-side.tsv",
53
66
  "test:vacuity-battery:chain": "bun scripts/vacuity-mutation-battery.ts scripts/vacuity-manifests/install-chain.tsv",
@@ -65,11 +78,16 @@
65
78
  "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
79
  "prepublishOnly": "bun run typecheck && bun run release-suite-gate",
67
80
  "release-suite-gate": "bun run scripts/release-suite-gate.ts",
68
- "typecheck:shortcut-contract": "tsc --noEmit -p tsconfig.test.json"
81
+ "typecheck:shortcut-contract": "tsc --noEmit -p tsconfig.test.json",
82
+ "build:contracts": "bun build src/contracts/hosted-v1.ts src/contracts/stream-v1.ts src/hosted/index.ts --target=bun --root src --outdir=dist --splitting --external=@hasna/contracts"
69
83
  },
70
84
  "files": [
71
85
  "dist/",
86
+ "!dist/__tests__/",
72
87
  "scripts/",
88
+ "!scripts/test-recorder-fixtures.ts",
89
+ "!scripts/**/__pycache__/",
90
+ "!scripts/**/*.pyc",
73
91
  "Dockerfile.package",
74
92
  "bun.lock",
75
93
  "src/native/Recordings/App/",
@@ -81,10 +99,14 @@
81
99
  "src/native/Recordings/build.sh",
82
100
  "packaging/macos/",
83
101
  "README.md",
84
- "LICENSE"
102
+ "LICENSE",
103
+ "contracts/v1/fixtures.json",
104
+ "docs/wire-contracts.md",
105
+ "docs/hosted-sdk.md"
85
106
  ],
86
107
  "dependencies": {
87
- "@hasna/contracts": "0.14.1",
108
+ "@aws-sdk/client-s3": "^3.1007.0",
109
+ "@hasna/contracts": "1.0.2",
88
110
  "@hasna/events": "0.1.11",
89
111
  "@modelcontextprotocol/sdk": "^1.12.1",
90
112
  "chalk": "^5.4.1",
@@ -94,7 +116,7 @@
94
116
  "zod": "^3.24.2"
95
117
  },
96
118
  "devDependencies": {
97
- "@types/bun": "^1.2.5",
119
+ "@types/bun": "1.3.14",
98
120
  "@types/pg": "^8.11.11",
99
121
  "node-api-headers": "1.9.0",
100
122
  "typescript": "^5.8.2"
@@ -224,7 +224,7 @@ case "$VERSION" in
224
224
  esac
225
225
  # The artifact basename is derived from the canonical bundle identity, never hardcoded:
226
226
  # build.sh emits ${APP_BASENAME}-${VERSION}-macos-${RELEASE_SUBTYPE} with APP_BASENAME
227
- # taken from the bundle name (currently HasnaRecordings.app). The legacy
227
+ # taken from the bundle name (currently Hasna Recordings.app). The legacy
228
228
  # Recordings-* form was dropped with the bundle rename and must not be re-required.
229
229
  [ -n "$APP" ] && [ -d "$APP" ] || { echo "Package app bundle path is missing or invalid." >&2; exit 2; }
230
230
  expected_basename="$(/usr/bin/basename "$APP" .app)-${VERSION}-macos-initial-bootstrap"
@@ -533,7 +533,7 @@ PACKAGE_BOOTSTRAP_ID="$(/usr/bin/uuidgen | /usr/bin/tr '[:upper:]' '[:lower:]')"
533
533
  "$SCRIPTS"
534
534
  require_no_extended_acl "$PACKAGE_STAGE_DIR"
535
535
 
536
- /usr/bin/ditto "$APP" "$ROOT/Applications/HasnaRecordings.app"
536
+ /usr/bin/ditto "$APP" "$ROOT/Applications/Hasna Recordings.app"
537
537
  /bin/cp "$BROKER" "$ROOT/Library/PrivilegedHelperTools/com.hasna.recordings.updater"
538
538
  /bin/cp "$VERIFIER" "$ROOT/Library/PrivilegedHelperTools/com.hasna.recordings.artifact-verifier"
539
539
  /bin/cp "$PUBLIC_KEY" "$ROOT/Library/Application Support/Hasna/Recordings/Trust/envelope-keys/${KEY_EPOCH}.raw"
@@ -582,7 +582,7 @@ require_tree_without_extended_acl "$SCRIPTS"
582
582
  "$ROOT/Library/LaunchDaemons/com.hasna.recordings.updater.plist"
583
583
  /usr/bin/plutil -lint "$ROOT/Library/LaunchDaemons/com.hasna.recordings.updater.plist" >/dev/null
584
584
 
585
- STAGED_APP="$ROOT/Applications/HasnaRecordings.app"
585
+ STAGED_APP="$ROOT/Applications/Hasna Recordings.app"
586
586
  STAGED_BROKER="$ROOT/Library/PrivilegedHelperTools/com.hasna.recordings.updater"
587
587
  STAGED_CLIENT="$STAGED_APP/Contents/Helpers/recordings-update-client"
588
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/HasnaRecordings.app/Contents/Helpers/recordings-update-client"
176
+ CLIENT="/Applications/Hasna Recordings.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/HasnaRecordings.app"
185
+ "/Applications/Hasna Recordings.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/HasnaRecordings.app/Contents/Helpers/recordings-update-client"
307
+ PAYLOAD_CLIENT="$PAYLOAD/Applications/Hasna Recordings.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/HasnaRecordings.app"|"$CLIENT") printf '755\n' ;;
515
+ "/Applications/Hasna Recordings.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/HasnaRecordings.app/Contents/Helpers/recordings-update-client"
14
+ CLIENT="/Applications/Hasna Recordings.app/Contents/Helpers/recordings-update-client"
15
15
  VERIFIER="/Library/PrivilegedHelperTools/com.hasna.recordings.artifact-verifier"
16
- APP="/Applications/HasnaRecordings.app"
16
+ APP="/Applications/Hasna Recordings.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,8 +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
+ "/Applications/Hasna Recordings.app:755"
252
+ "/Applications/Hasna Recordings.app/Contents/Helpers/recordings-update-client:755"
253
253
  "/Applications/Recordings.app:755"
254
254
  "/Applications/Recordings.app/Contents/Helpers/recordings-update-client:755"
255
255
  "/Library/Application Support/Hasna/Recordings/Trust:700-or-755"
@@ -151,7 +151,13 @@ COMPILED_ARM64="$COMPILE_DIR/recordings.arm64"
151
151
  COMPILED_X86_64="$COMPILE_DIR/recordings.x86_64"
152
152
  "$MKDIR_EXECUTABLE" -p "$BUILD_HOME" "$BUN_CACHE" "$STAGED_ROOT" "$COMPILE_DIR"
153
153
  "$CP_EXECUTABLE" "$ROOT/package.json" "$ROOT/bun.lock" "$ROOT/bunfig.toml" "$ROOT/tsconfig.json" "$STAGED_ROOT/"
154
- "$CP_EXECUTABLE" -R "$ROOT/src" "$STAGED_ROOT/src"
154
+ "$MKDIR_EXECUTABLE" -p "$STAGED_ROOT/src"
155
+ # The CLI has no Swift inputs. Walking src/native also walks SwiftPM's live
156
+ # .build cache, whose temporary files can disappear during a native build.
157
+ for source_entry in "$ROOT/src/"*; do
158
+ [ "$source_entry" != "$ROOT/src/native" ] || continue
159
+ "$CP_EXECUTABLE" -R "$source_entry" "$STAGED_ROOT/src/"
160
+ done
155
161
  if [ -d "$ROOT/migrations" ]; then
156
162
  "$CP_EXECUTABLE" -R "$ROOT/migrations" "$STAGED_ROOT/migrations"
157
163
  fi
@@ -29,6 +29,7 @@
29
29
  *
30
30
  * Usage:
31
31
  * bun scripts/ci-linux-suite.ts --check validate the quarantine file, print counts
32
+ * bun scripts/ci-linux-suite.ts --all every discovered tracked test, including quarantine
32
33
  * bun scripts/ci-linux-suite.ts --gated newline-separated gated files
33
34
  * bun scripts/ci-linux-suite.ts --quarantined newline-separated quarantined files
34
35
  * bun scripts/ci-linux-suite.ts --verify-run <log> assert a gated run honoured the partition
@@ -41,6 +42,13 @@ import { spawnSync } from "node:child_process";
41
42
  export const QUARANTINE_FILE = ".github/linux-quarantine.txt";
42
43
 
43
44
  const TEST_SUFFIX = ".test.ts";
45
+ const BUN_TEST_SUFFIX = /[._](?:test|spec)\.(?:[cm]?[jt]sx?)$/;
46
+
47
+ function supportedTestFiles(paths: string[]): string[] {
48
+ const unsupported = paths.filter(path => BUN_TEST_SUFFIX.test(path) && !path.endsWith(TEST_SUFFIX));
49
+ if (unsupported.length) throw new Error(`unsupported test suffix; extend discovery and JUnit contracts before adding:\n${unsupported.join("\n")}`);
50
+ return paths.filter(path => path.endsWith(TEST_SUFFIX));
51
+ }
44
52
 
45
53
  /** Directories never worth walking. `.build` holds SwiftPM output, which can be enormous. */
46
54
  const SKIPPED_DIRS = new Set([".git", "node_modules", ".build", "dist"]);
@@ -92,7 +100,7 @@ export function entriesMissingReason(text: string): string[] {
92
100
  * in the working tree.
93
101
  */
94
102
  export function testFilesFromGit(repoRoot: string): string[] {
95
- const result = spawnSync("git", ["-C", repoRoot, "ls-files", "-z", `*${TEST_SUFFIX}`], {
103
+ const result = spawnSync("git", ["-C", repoRoot, "ls-files", "-z"], {
96
104
  encoding: "utf8",
97
105
  });
98
106
  if (result.status !== 0) {
@@ -100,7 +108,7 @@ export function testFilesFromGit(repoRoot: string): string[] {
100
108
  }
101
109
  // -z because a path may legally contain a newline, and a newline-split enumeration would report
102
110
  // one such path as two missing files — a disagreement that reads as a broken walker.
103
- return result.stdout.split("\0").filter((path) => path.length > 0).sort();
111
+ return supportedTestFiles(result.stdout.split("\0").filter((path) => path.length > 0)).sort();
104
112
  }
105
113
 
106
114
  /**
@@ -118,13 +126,13 @@ export function testFilesFromWalk(repoRoot: string): string[] {
118
126
  if (entry.isDirectory()) {
119
127
  if (SKIPPED_DIRS.has(entry.name)) continue;
120
128
  walk(absolute);
121
- } else if (entry.isFile() && entry.name.endsWith(TEST_SUFFIX)) {
129
+ } else if (entry.isFile()) {
122
130
  found.push(relative(repoRoot, absolute).split(sep).join("/"));
123
131
  }
124
132
  }
125
133
  };
126
134
  walk(repoRoot);
127
- return found.sort();
135
+ return supportedTestFiles(found).sort();
128
136
  }
129
137
 
130
138
  export type DiscoveryDisagreement = { trackedNotOnDisk: string[]; onDiskUntracked: string[] };
@@ -257,7 +265,7 @@ function fail(message: string): never {
257
265
  process.exit(1);
258
266
  }
259
267
 
260
- function loadPartition(repoRoot: string): Partition & { discovered: string[] } {
268
+ function loadPartition(repoRoot: string, requireNonemptyGated = true): Partition & { discovered: string[] } {
261
269
  const fromGit = testFilesFromGit(repoRoot);
262
270
  const { trackedNotOnDisk, onDiskUntracked } = compareDiscovery(fromGit, testFilesFromWalk(repoRoot));
263
271
  if (trackedNotOnDisk.length > 0) {
@@ -293,16 +301,14 @@ function loadPartition(repoRoot: string): Partition & { discovered: string[] } {
293
301
  } catch (error) {
294
302
  fail(error instanceof Error ? error.message : String(error));
295
303
  }
296
- if (split.gated.length === 0) {
304
+ if (requireNonemptyGated && split.gated.length === 0) {
297
305
  fail(
298
306
  `Every discovered test file is quarantined by ${QUARANTINE_FILE}. The gate would run nothing.`,
299
307
  );
300
308
  }
301
- // The workflow expands `--gated` unquoted so the runner receives one argument per file, which is
302
- // only safe while no path contains whitespace. Asserting it here keeps that assumption in the
303
- // same place as the list, instead of leaving a shell-quoting bug to be discovered by a suite that
304
- // silently stopped being gated.
305
- const whitespace = split.gated.filter((path) => /\s/.test(path));
309
+ // Keep the established newline/shell-safe manifest contract in both selections.
310
+ // Quarantined paths are still executable inputs to the explicit all-tests mode.
311
+ const whitespace = fromGit.filter((path) => /\s/.test(path));
306
312
  if (whitespace.length > 0) {
307
313
  fail(
308
314
  "Test paths containing whitespace cannot be passed through unquoted word splitting:\n" +
@@ -318,8 +324,12 @@ function main(argv: string[]): void {
318
324
  fail(`Run this from the repository root; ${QUARANTINE_FILE} is not there.`);
319
325
  }
320
326
  const mode = argv[0];
321
- const { gated, quarantined, discovered } = loadPartition(repoRoot);
327
+ const { gated, quarantined, discovered } = loadPartition(repoRoot, mode !== "--all");
322
328
 
329
+ if (mode === "--all") {
330
+ console.log(discovered.join("\n"));
331
+ return;
332
+ }
323
333
  if (mode === "--gated") {
324
334
  console.log(gated.join("\n"));
325
335
  return;
@@ -395,7 +405,7 @@ function main(argv: string[]): void {
395
405
  }
396
406
  return;
397
407
  }
398
- fail("usage: --check | --gated | --quarantined | --verify-run <junit.xml>");
408
+ fail("usage: --check | --all | --gated | --quarantined | --verify-run <junit.xml>");
399
409
  }
400
410
 
401
411
  if (import.meta.main) {