@hasna/recordings 0.4.0 → 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 (58) hide show
  1. package/README.md +56 -6
  2. package/contracts/v1/fixtures.json +1206 -0
  3. package/dist/cli/index.js +28 -7
  4. package/dist/contracts/hosted-v1.d.ts +105 -0
  5. package/dist/contracts/hosted-v1.d.ts.map +1 -0
  6. package/dist/contracts/hosted-v1.js +41 -0
  7. package/dist/contracts/stream-v1.d.ts +117 -0
  8. package/dist/contracts/stream-v1.d.ts.map +1 -0
  9. package/dist/contracts/stream-v1.js +35 -0
  10. package/dist/hosted/index.d.ts +58 -0
  11. package/dist/hosted/index.d.ts.map +1 -0
  12. package/dist/hosted/index.js +266 -0
  13. package/dist/hosted/transport.d.ts +36 -0
  14. package/dist/hosted/transport.d.ts.map +1 -0
  15. package/dist/hosted-v1-aavn7ktb.js +4114 -0
  16. package/dist/hosted-v1-gdr9extc.js +84 -0
  17. package/dist/index.js +27 -6
  18. package/dist/mcp/index.js +27 -6
  19. package/dist/server/index.js +27 -6
  20. package/dist/storage.js +27 -6
  21. package/docs/hosted-sdk.md +71 -0
  22. package/docs/wire-contracts.md +24 -0
  23. package/package.json +27 -6
  24. package/scripts/ci-linux-suite.ts +23 -13
  25. package/scripts/macos_artifact.ts +40 -33
  26. package/scripts/native/prebuilds/darwin-universal/recordings_fs_guard.node +0 -0
  27. package/scripts/native/recordings_fs_guard.c +36 -4
  28. package/scripts/native-core-receipt.py +171 -0
  29. package/scripts/native_fs_guard.ts +2 -0
  30. package/scripts/release-suite-gate.ts +227 -150
  31. package/scripts/resolve_tailscale_cli.sh +24 -3
  32. package/src/native/Recordings/RecordingsLib/BlockingOperation.swift +29 -0
  33. package/src/native/Recordings/RecordingsLib/Info.plist +2 -2
  34. package/src/native/Recordings/RecordingsLib/ProjectStore.swift +4 -4
  35. package/src/native/Recordings/RecordingsLib/RecordingEngine.swift +228 -60
  36. package/src/native/Recordings/RecordingsLib/RecordingPasteTarget.swift +114 -0
  37. package/src/native/Recordings/RecordingsLib/RecordingProvider.swift +13 -3
  38. package/src/native/Recordings/RecordingsTests/BlockingOperationTests.swift +85 -0
  39. package/src/native/Recordings/RecordingsTests/CLIRunnerTests.swift +441 -72
  40. package/src/native/Recordings/RecordingsTests/PipeClosureFixture.swift +215 -0
  41. package/src/native/Recordings/RecordingsTests/ProjectStoreTests.swift +10 -10
  42. package/src/native/Recordings/RecordingsTests/RecordingEngineDeliveryTests.swift +1 -1
  43. package/src/native/Recordings/RecordingsTests/RecordingFrozenPasteTargetTests.swift +50 -0
  44. package/src/native/Recordings/RecordingsTests/RecordingPasteTargetTrackerTests.swift +48 -0
  45. package/src/native/Recordings/RecordingsTests/RecordingProviderTests.swift +115 -2
  46. package/src/native/Recordings/RecordingsTests/RecordingStartTimingTests.swift +93 -21
  47. package/src/native/Recordings/RecordingsTests/TestHomeDirectory.swift +5 -1
  48. package/src/native/Recordings/build.sh +2 -1
  49. package/dist/__tests__/helpers/installer-guard-execution.d.ts +0 -22
  50. package/dist/__tests__/helpers/installer-guard-execution.d.ts.map +0 -1
  51. package/dist/__tests__/helpers/installer-preflight.d.ts +0 -22
  52. package/dist/__tests__/helpers/installer-preflight.d.ts.map +0 -1
  53. package/dist/__tests__/helpers/native-fs-guard.d.ts +0 -2
  54. package/dist/__tests__/helpers/native-fs-guard.d.ts.map +0 -1
  55. package/dist/__tests__/helpers/source-assertions.d.ts +0 -171
  56. package/dist/__tests__/helpers/source-assertions.d.ts.map +0 -1
  57. package/dist/__tests__/preload.d.ts +0 -2
  58. package/dist/__tests__/preload.d.ts.map +0 -1
@@ -61,9 +61,24 @@ private final class FakePCMRecorder: PCMRecordingSource, @unchecked Sendable {
61
61
  @MainActor
62
62
  private func makeStartableEngine(
63
63
  recorder: FakePCMRecorder,
64
+ intentDetectionEnabled: Bool = false,
64
65
  selectionCapture: @escaping @Sendable (pid_t) -> AccessibilitySelectionToken? = { _ in nil }
65
66
  ) -> RecordingEngine {
66
- let engine = RecordingEngine(homePath: makeIsolatedTestHome("start-timing-tests"))
67
+ let engine = RecordingEngine(homePath: makeIsolatedTestHome("start-timing-tests"), installsGlobalHandlers: false)
68
+ // The legacy engine persists this setting. Keep the explicit mode on this instance,
69
+ // restoring the exact application-domain value before another MainActor test can run.
70
+ // This factory and the restoration are synchronous: there is no suspension in between.
71
+ let defaults = UserDefaults.standard
72
+ let domain = Bundle.main.bundleIdentifier ?? ProcessInfo.processInfo.processName
73
+ let priorIntent = defaults.persistentDomain(forName: domain)?["intentDetectionEnabled"]
74
+ defer {
75
+ if let priorIntent {
76
+ defaults.set(priorIntent, forKey: "intentDetectionEnabled")
77
+ } else {
78
+ defaults.removeObject(forKey: "intentDetectionEnabled")
79
+ }
80
+ }
81
+ engine.intentDetectionEnabled = intentDetectionEnabled
67
82
  engine.openAIAPIKeyProvider = { "" }
68
83
  engine.microphoneAuthorization = { .authorized }
69
84
  engine.accessibilityTrustCheck = { true }
@@ -82,6 +97,27 @@ private func makeStartableEngine(
82
97
  return engine
83
98
  }
84
99
 
100
+ /// An entered callback is essential evidence: with intent detection disabled the engine
101
+ /// correctly skips selection capture, so merely installing a blocked closure proves nothing.
102
+ private final class BlockedSelectionCapture: @unchecked Sendable {
103
+ private let lock = NSLock()
104
+ private let gate = DispatchSemaphore(value: 0)
105
+ private var capturedPIDs: [pid_t] = []
106
+ private var returned = false
107
+
108
+ var pids: [pid_t] { lock.withLock { capturedPIDs } }
109
+ var isPending: Bool { lock.withLock { !capturedPIDs.isEmpty && !returned } }
110
+
111
+ func capture(_ pid: pid_t) -> AccessibilitySelectionToken? {
112
+ lock.withLock { capturedPIDs.append(pid) }
113
+ gate.wait()
114
+ lock.withLock { returned = true }
115
+ return AccessibilitySelectionToken.unsafeTestToken(selectedText: "frozen words")
116
+ }
117
+
118
+ func release() { gate.signal() }
119
+ }
120
+
85
121
  @MainActor
86
122
  private func waitUntil(
87
123
  timeout: TimeInterval = 5,
@@ -138,14 +174,12 @@ private func expectEmptyAttemptDisclosed(
138
174
  @MainActor
139
175
  struct RecordingStartTimingTests {
140
176
  @Test("the recorder starts on keydown even while the AX selection capture is blocked")
141
- func recorderStartDoesNotWaitOnSelectionCapture() async {
142
- let captureGate = DispatchSemaphore(value: 0)
177
+ func recorderStartDoesNotWaitOnSelectionCapture() async throws {
178
+ let capture = BlockedSelectionCapture()
179
+ defer { capture.release() }
143
180
  let recorder = FakePCMRecorder()
144
- let engine = makeStartableEngine(recorder: recorder) { _ in
145
- // Simulates a beachballing target app: the capture IPC hangs far longer than
146
- // any acceptable start budget.
147
- captureGate.wait()
148
- return nil
181
+ let engine = makeStartableEngine(recorder: recorder, intentDetectionEnabled: true) { pid in
182
+ capture.capture(pid)
149
183
  }
150
184
 
151
185
  // `startRecording` runs synchronously on the MainActor through recorder start.
@@ -157,25 +191,29 @@ struct RecordingStartTimingTests {
157
191
  #expect(engine.isWarmingUpCapture, "start() returning is warm-up, not captured audio")
158
192
  #expect(engine.captureIsActive)
159
193
  #expect(engine.flowPhase == .listening)
194
+ try #require(await waitUntil { capture.isPending })
195
+ #expect(capture.pids == [99_999])
160
196
 
161
197
  #expect(await confirmCapture(engine, recorder))
162
198
  #expect(!engine.isWarmingUpCapture)
163
199
 
164
- captureGate.signal()
200
+ capture.release()
165
201
  engine.cancelRecording()
166
202
  #expect(engine.flowPhase == .idle)
167
203
  }
168
204
 
169
205
  @Test("stopping waits for the generation-bound start context instead of dropping the frozen target")
170
- func stopAwaitsFrozenStartContext() async {
171
- let captureGate = DispatchSemaphore(value: 0)
206
+ func stopAwaitsFrozenStartContext() async throws {
207
+ let capture = BlockedSelectionCapture()
208
+ defer { capture.release() }
172
209
  let recorder = FakePCMRecorder()
173
- let engine = makeStartableEngine(recorder: recorder) { _ in
174
- captureGate.wait()
175
- return AccessibilitySelectionToken.unsafeTestToken(selectedText: "frozen words")
210
+ let engine = makeStartableEngine(recorder: recorder, intentDetectionEnabled: true) { pid in
211
+ capture.capture(pid)
176
212
  }
177
213
 
178
214
  engine.startRecording(trigger: .manual)
215
+ try #require(await waitUntil { capture.isPending })
216
+ #expect(capture.pids == [99_999])
179
217
  #expect(await confirmCapture(engine, recorder))
180
218
  engine.stopAndTranscribe()
181
219
  #expect(engine.isTranscribing)
@@ -183,9 +221,10 @@ struct RecordingStartTimingTests {
183
221
  // The pipeline must hold in finalizing while the frozen context is unresolved —
184
222
  // it may not deliver without the selection frozen at start.
185
223
  try? await Task.sleep(for: .milliseconds(150))
224
+ #expect(capture.isPending)
186
225
  #expect(engine.flowPhase == .finalizing)
187
226
 
188
- captureGate.signal()
227
+ capture.release()
189
228
  // No audio was produced by the fake recorder, so the pipeline ends in the
190
229
  // fail-closed no-audio state — importantly, only after the context resolved.
191
230
  #expect(await waitUntil {
@@ -196,6 +235,37 @@ struct RecordingStartTimingTests {
196
235
  #expect(engine.canStartRecording)
197
236
  }
198
237
 
238
+ @Test("intent detection disabled skips selection capture and can finish the empty attempt")
239
+ func disabledIntentDoesNotWaitForSelection() async throws {
240
+ let capture = BlockedSelectionCapture()
241
+ defer { capture.release() }
242
+ let recorder = FakePCMRecorder()
243
+ let engine = makeStartableEngine(recorder: recorder, intentDetectionEnabled: false) { pid in
244
+ capture.capture(pid)
245
+ }
246
+ engine.startRecording(trigger: .manual)
247
+ try #require(await confirmCapture(engine, recorder))
248
+ engine.stopAndTranscribe()
249
+ try #require(await waitUntil { !engine.isTranscribing })
250
+ #expect(capture.pids.isEmpty)
251
+ #expect(engine.flowPhase == .failed("No audio captured"))
252
+ #expect(engine.canStartRecording)
253
+ }
254
+
255
+ @Test("fixture intent modes stay on their engine without changing the process preference")
256
+ func fixtureIntentModeIsInstanceScoped() {
257
+ let defaults = UserDefaults.standard
258
+ let domain = Bundle.main.bundleIdentifier ?? ProcessInfo.processInfo.processName
259
+ let before = defaults.persistentDomain(forName: domain)?["intentDetectionEnabled"] as? NSObject
260
+ let enabled = makeStartableEngine(recorder: FakePCMRecorder(), intentDetectionEnabled: true)
261
+ #expect(enabled.intentDetectionEnabled)
262
+ #expect((defaults.persistentDomain(forName: domain)?["intentDetectionEnabled"] as? NSObject) == before)
263
+ let disabled = makeStartableEngine(recorder: FakePCMRecorder())
264
+ #expect(!disabled.intentDetectionEnabled)
265
+ #expect(enabled.intentDetectionEnabled)
266
+ #expect((defaults.persistentDomain(forName: domain)?["intentDetectionEnabled"] as? NSObject) == before)
267
+ }
268
+
199
269
  @Test("slow microphone shutdown keeps the UI responsive and finishes draining before transcription")
200
270
  func stopDoesNotBlockMainActor() async {
201
271
  let stopGate = DispatchSemaphore(value: 0)
@@ -219,19 +289,21 @@ struct RecordingStartTimingTests {
219
289
  }
220
290
 
221
291
  @Test("a released key before the capture resolves still cancels cleanly")
222
- func cancelDuringPendingCapture() async {
223
- let captureGate = DispatchSemaphore(value: 0)
292
+ func cancelDuringPendingCapture() async throws {
293
+ let capture = BlockedSelectionCapture()
294
+ defer { capture.release() }
224
295
  let recorder = FakePCMRecorder()
225
- let engine = makeStartableEngine(recorder: recorder) { _ in
226
- captureGate.wait()
227
- return nil
296
+ let engine = makeStartableEngine(recorder: recorder, intentDetectionEnabled: true) { pid in
297
+ capture.capture(pid)
228
298
  }
229
299
  engine.startRecording(trigger: .manual)
300
+ try #require(await waitUntil { capture.isPending })
301
+ #expect(capture.pids == [99_999])
230
302
  #expect(await confirmCapture(engine, recorder))
231
303
  engine.cancelRecording()
232
304
  #expect(!engine.isRecording)
233
305
  #expect(engine.flowPhase == .idle)
234
- captureGate.signal()
306
+ capture.release()
235
307
  #expect(engine.canStartRecording)
236
308
  }
237
309
  }
@@ -16,7 +16,11 @@ import Foundation
16
16
  /// back only the engine and so have no scope to remove it from; `CLIRunnerTests` instead
17
17
  /// passes the temp home it already creates and already deletes.
18
18
  func makeIsolatedTestHome(_ label: String) -> String {
19
- let url = FileManager.default.temporaryDirectory
19
+ // Foundation can ignore TMPDIR under the Darwin test-bundle launcher. An
20
+ // explicit fixture root keeps confined tests inside their owned write scope.
21
+ let root = ProcessInfo.processInfo.environment["RECORDINGS_TEST_TMP_ROOT"]
22
+ .map { URL(fileURLWithPath: $0, isDirectory: true) } ?? FileManager.default.temporaryDirectory
23
+ let url = root
20
24
  .appendingPathComponent("recordings-\(label)-\(UUID().uuidString)")
21
25
  try? FileManager.default.createDirectory(at: url, withIntermediateDirectories: true)
22
26
  return url.path
@@ -505,7 +505,8 @@ generate_and_verify_native_fs_guard() {
505
505
  import { createRequire } from "node:module";
506
506
  const addon = createRequire(import.meta.url)(process.argv[1]);
507
507
  const expected = [
508
- "chmodHandle", "close", "copyRegularNoReplaceAt", "fsyncHandle",
508
+ "chmodHandle", "close", "copyRegularNoReplaceAt", "duplicateDirectoryDescriptor",
509
+ "fsyncHandle", "handleHasNoExtendedAcl",
509
510
  "linkNoReplaceAt", "mkdirAt", "openDirAt", "openRegularAt", "openTrustedHome",
510
511
  "readDir", "readRegularAt", "removeTreeAt", "removeTreeHandleAt",
511
512
  "renameHandleNoReplaceAt", "renameNoReplaceAt", "renameReplaceAt",
@@ -1,22 +0,0 @@
1
- export declare const EXISTING_IDENTITY_SHA256: string;
2
- export declare const CANDIDATE_IDENTITY_SHA256: string;
3
- export declare const MANIFEST_SHA256: string;
4
- export type ArtifactPolicy = "local-only" | "release";
5
- export type IncompatibleDirection = "installed-requirement-vs-candidate" | "candidate-requirement-vs-installed" | "both";
6
- export type GuardExecutionOptions = {
7
- identityMigration: boolean;
8
- incompatibleDirection?: IncompatibleDirection;
9
- artifactPolicy?: ArtifactPolicy;
10
- extraArguments?: string[];
11
- };
12
- export type GuardExecutionResult = {
13
- exitCode: number;
14
- stdout: string;
15
- stderr: string;
16
- reachedTransaction: boolean;
17
- codesignInvocations: string[];
18
- bunInvocations: string[];
19
- unstubbedInvocations: string[];
20
- };
21
- export declare function runInstallerToIdentityGuard(options: GuardExecutionOptions): GuardExecutionResult;
22
- //# sourceMappingURL=installer-guard-execution.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"installer-guard-execution.d.ts","sourceRoot":"","sources":["../../../src/__tests__/helpers/installer-guard-execution.ts"],"names":[],"mappings":"AA+DA,eAAO,MAAM,wBAAwB,QAAiB,CAAC;AACvD,eAAO,MAAM,yBAAyB,QAAiB,CAAC;AACxD,eAAO,MAAM,eAAe,QAAiB,CAAC;AAsB9C,MAAM,MAAM,cAAc,GAAG,YAAY,GAAG,SAAS,CAAC;AAuFtD,MAAM,MAAM,qBAAqB,GAE7B,oCAAoC,GAGpC,oCAAoC,GAEpC,MAAM,CAAC;AAEX,MAAM,MAAM,qBAAqB,GAAG;IAKlC,iBAAiB,EAAE,OAAO,CAAC;IAG3B,qBAAqB,CAAC,EAAE,qBAAqB,CAAC;IAK9C,cAAc,CAAC,EAAE,cAAc,CAAC;IAEhC,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;CAC3B,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IAKf,kBAAkB,EAAE,OAAO,CAAC;IAG5B,mBAAmB,EAAE,MAAM,EAAE,CAAC;IAG9B,cAAc,EAAE,MAAM,EAAE,CAAC;IAMzB,oBAAoB,EAAE,MAAM,EAAE,CAAC;CAChC,CAAC;AAEF,wBAAgB,2BAA2B,CACzC,OAAO,EAAE,qBAAqB,GAC7B,oBAAoB,CA2StB"}
@@ -1,22 +0,0 @@
1
- export declare const POLICY_RELATIVE_PATH = "scripts/policy/local-only-approved-targets.txt";
2
- export declare const READER_RELATIVE_PATH = "scripts/read_local_only_targets.sh";
3
- export declare const IDENTITY_GUARD_RELATIVE_PATH = "scripts/enforce_identity_migration.sh";
4
- export declare const readRepositoryFile: (relativePath: string) => string;
5
- export type InstallerPreflightOptions = {
6
- artifactPolicy?: "release" | "local-only";
7
- approvedTarget?: string;
8
- hostname?: string;
9
- extraArguments?: string[];
10
- policyContents?: string | null;
11
- removeReader?: boolean;
12
- removeIdentityGuard?: boolean;
13
- identityGuardContents?: string;
14
- symlinkIdentityGuard?: boolean;
15
- environment?: Record<string, string>;
16
- };
17
- export declare function runInstallerPreflight(options?: InstallerPreflightOptions): {
18
- exitCode: number;
19
- stdout: string;
20
- stderr: string;
21
- };
22
- //# sourceMappingURL=installer-preflight.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"installer-preflight.d.ts","sourceRoot":"","sources":["../../../src/__tests__/helpers/installer-preflight.ts"],"names":[],"mappings":"AAOA,eAAO,MAAM,oBAAoB,mDAAmD,CAAC;AACrF,eAAO,MAAM,oBAAoB,uCAAuC,CAAC;AACzE,eAAO,MAAM,4BAA4B,0CAA0C,CAAC;AAEpF,eAAO,MAAM,kBAAkB,GAAI,cAAc,MAAM,KAAG,MACA,CAAC;AAc3D,MAAM,MAAM,yBAAyB,GAAG;IACtC,cAAc,CAAC,EAAE,SAAS,GAAG,YAAY,CAAC;IAC1C,cAAc,CAAC,EAAE,MAAM,CAAC;IAGxB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAG9B,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAG/B,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAG/B,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACtC,CAAC;AAEF,wBAAgB,qBAAqB,CACnC,OAAO,GAAE,yBAA8B,GACtC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CA8GtD"}
@@ -1,2 +0,0 @@
1
- export declare function ensureNativeFsGuardAddon(repositoryRoot?: string): string;
2
- //# sourceMappingURL=native-fs-guard.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"native-fs-guard.d.ts","sourceRoot":"","sources":["../../../src/__tests__/helpers/native-fs-guard.ts"],"names":[],"mappings":"AA2CA,wBAAgB,wBAAwB,CAAC,cAAc,SAAuC,GAAG,MAAM,CActG"}
@@ -1,171 +0,0 @@
1
- /**
2
- * Shared assertions for contract tests that read source text.
3
- *
4
- * These live here rather than inside one suite because the defect they exist to prevent is
5
- * repo-wide: a sweep of every `*.test.ts` found 40 ordering assertions written as
6
- * `indexOf(a) < indexOf(b)`, across 11 files. `indexOf` answers -1 when the needle is absent and
7
- * `-1 < anything` is true, so such an assertion PASSES when the thing being ordered is DELETED.
8
- * The same hole exists in `.slice(indexOf(...), indexOf(...))` region bounds, where a -1 silently
9
- * slices from the end of the file or to its start — a region assertion over the wrong text, or over
10
- * none of it, reads exactly like a satisfied one.
11
- *
12
- * ---------------------------------------------------------------------------------------------
13
- * BEFORE YOU BUILD A MUTATION BATTERY: three suites are RED ON A CONTENDED STATION, not on Linux.
14
- *
15
- * A mutation battery is evidence only when its clean control is GREEN. Include a suite that was
16
- * already failing and the run was non-zero before you changed anything, so every mutation "looks
17
- * caught" and every verdict is manufactured. That already produced one wrong all-clear here.
18
- *
19
- * These are the three suites it happens to. NO PASS/FAIL SPLIT IS RECORDED FOR THEM ON PURPOSE —
20
- * see below. `macos-app-lifecycle.test.ts` has 140 tests, `native-app-companion-contract.test.ts`
21
- * has 14, `config.test.ts` has 44, and how many of those fail is a property of the MACHINE:
22
- *
23
- * src/__tests__/macos-app-lifecycle.test.ts
24
- * src/__tests__/native-app-companion-contract.test.ts
25
- * src/__tests__/config.test.ts
26
- *
27
- * CORRECTED 2026-07-27, and the correction is the useful part. This block used to call them
28
- * "PERMANENTLY RED on Linux" and attribute fixed environmental causes — BSD `stat -f`, a fixture
29
- * port reading `NaN`, a `getDataDir` HOME-ancestor assumption. The first CI run this repository
30
- * ever had (run 30302342895, ubuntu-24.04) re-ran all three on a clean single-tenant runner and
31
- * every one of them PASSED:
32
- *
33
- * macos-app-lifecycle 140 pass / 0 fail (358.85s)
34
- * native-app-companion-contract 14 pass / 0 fail ( 4.50s)
35
- * config 44 pass / 0 fail ( 0.10s)
36
- *
37
- * So the cause is not the platform. Measure on a quiet machine, or in CI, before recording a suite
38
- * as red. There were TWO independent station-local causes, and every earlier version of this comment
39
- * named only one of them. In a 92-fail run of `macos-app-lifecycle.test.ts` on this station the
40
- * failure messages broke down as 38 × `Home ancestor has an unexpected owner.`, 22 × FIFO
41
- * synchronisation timeout, 24 × ENOENT on a fixture marker. The first cause is now fixed in the
42
- * fixture and only the second remains; do not collapse them into one, because a quiet machine with
43
- * `FORCE_COLOR` set still showed all 38 aborts, so "measure on a quiet machine" was necessary and
44
- * not sufficient:
45
- *
46
- * 1. `FORCE_COLOR` — **FIXED IN THE FIXTURE, no longer a live cause.** The `stat` stub answered
47
- * `%u` by shelling out to `bun -e '… console.log(statSync(…).uid)'`
48
- * (`macos-app-lifecycle.test.ts:217`) and spread `...Bun.env` into the installer, so with
49
- * `FORCE_COLOR` set Bun COLOURED the number: the installer compared `\e[0m\e[33m1000\e[0m`
50
- * against `id -u`'s `1000` at `install_macos_app.sh:143` and aborted before reaching any gate.
51
- * `NO_COLOR=1` did NOT help — FORCE_COLOR wins in Bun. The stub now writes bare integers with
52
- * `process.stdout.write` and `unset FORCE_COLOR`s its own children, which takes that message
53
- * from 38 to 0 in place, positive-controlled (the same grep still finds 38 in the pre-fix log).
54
- * Nothing about ancestor MODE was ever involved: `verify_secure_parent` and
55
- * `verify_safe_home_ancestor` each `stat` only the one path handed to them, the sole call is
56
- * `verify_safe_home_ancestor "$HOME"`, and the stub hardcodes every `%Lp` answer anyway.
57
- * Kept here rather than deleted because it is the reason this file's split moved, and because
58
- * the same trap recurs in any stub that parses `console.log` of a NUMBER: only strings are
59
- * left uncoloured.
60
- * 2. CONTENTION. The station routinely runs several full recordings suites at once out of
61
- * different worktrees, and this suite scans a shared /tmp — the hazard this very comment warns
62
- * about below. Those were FIFO timeouts at an internal 5000ms budget; the FIFO helper now uses
63
- * the suite's configured timeout too, so a raised test budget can distinguish load sensitivity
64
- * from a regression. With `FORCE_COLOR` unset the residual failures scaled with load: 132 pass /
65
- * 8 fail at load ~20, 114 pass / 26 fail at load 44-60 on the same commit, which is why no split
66
- * belongs here either. GitHub Actions sets neither `FORCE_COLOR` nor a competing suite, which
67
- * is why both causes were absent from the only clean measurement.
68
- *
69
- * WHY NO SPLIT IS RECORDED. Every split ever written here has gone stale, including two written as
70
- * corrections. On one unchanged tree, three consecutive runs measured 48/92, 48/92, 49/91; the
71
- * single test that flips is `runtime smoke timeout does not wait forever on a live open process`
72
- * (`macos-app-lifecycle.test.ts:3594`), which races a hardcoded internal `Bun.sleep(2_000)` that no
73
- * `--timeout` flag reaches either. So a count comparison across two trees shows a phantom delta
74
- * from this file alone — which is the concrete reason for the rule below: compare failing test
75
- * NAMES, never counts. Two trees whose failing NAME SETS are identical are identical regardless of
76
- * what the totals say.
77
- *
78
- * `@hasna/events` MUST resolve 0.1.11, as `bun.lock` pins it. A plain `bun install` pulls 0.1.14,
79
- * which dropped a shipped CLI command inside the patch range and fails `cli.test.ts` — and it can
80
- * drift back mid-session, so re-check it before quoting any cross-tree comparison.
81
- *
82
- * Corollary, also corrected: this repo NOW HAS CI. The repo-root
83
- * `.github/workflows/ci.yml` gates the whole TypeScript suite with no exemptions on every
84
- * pull request, so these suites are no longer gated only by somebody remembering to run
85
- * them. The Swift/C half compiles in CI through the repo-root
86
- * `.github/workflows/recordings-macos.yml` (`verify:ci-native`); see
87
- * `.github/native-known-errors.txt` for what that gate means.
88
- * Compare failing test NAMES, never counts — the suite is nondeterministic at the margin.
89
- * ---------------------------------------------------------------------------------------------
90
- */
91
- /**
92
- * Assert `first` appears before `second`, requiring BOTH to exist.
93
- *
94
- * Use this instead of comparing two `indexOf` results directly. `firstMatch: "last"` selects
95
- * `lastIndexOf` for the first operand, which has the identical -1 hole.
96
- */
97
- export declare function expectOrder(haystack: string, first: string, second: string, options?: {
98
- firstMatch?: "first" | "last";
99
- }): void;
100
- /**
101
- * Slice between two markers, requiring both to exist and the region to be non-trivial.
102
- *
103
- * The length floor matters as much as the -1 checks: `slice(-1)` yields a ONE-CHARACTER string, not
104
- * an empty one, so a `expect(region.length).toBeGreaterThan(0)` control passes on a region that
105
- * contains nothing worth asserting about. Any `not.toContain` over such a region is vacuous.
106
- */
107
- export declare function sliceBetween(source: string, open: string, close: string, options?: {
108
- minimumLength?: number;
109
- }): string;
110
- /**
111
- * Assert a marker occurs exactly once before slicing on it.
112
- *
113
- * A duplicated end marker silently extends a region: `let myPID = ProcessInfo…` occurs twice in
114
- * `RecordingEngine.swift`, so a region bounded by it could stretch ~127 KB and be satisfied by
115
- * copies of the needle from an unrelated function.
116
- */
117
- export declare function sliceBetweenUnique(source: string, open: string, close: string): string;
118
- /** Strip Swift line comments so an assertion about code is not defeated by prose. */
119
- export declare function withoutComments(source: string): string;
120
- /**
121
- * Index just past the `close` matching the `open` at `openIndex`, skipping comments and literals.
122
- *
123
- * Needed because `lastIndexOf("}")` is not brace matching, and the difference is a live defect: an
124
- * early exit written between a decision and its use —
125
- *
126
- * } // the decision table closes here
127
- * guard stillOwnsChangeCount else { return }
128
- * if shouldRestore { … }
129
- *
130
- * — puts a NEARER `}` (the `else` block's) between the two, so a "nothing between them" check
131
- * measured from the last brace saw only whitespace and passed. Counting braces naively fails the
132
- * other way: one `}` inside a string literal, such as `log("settlement }")`, cancels a real opener.
133
- * Both were measured surviving at EXIT=0.
134
- */
135
- export declare function matchingDelimiterIndex(source: string, openIndex: number, open: string, close: string): number;
136
- export declare function withoutAnyComments(source: string): string;
137
- /**
138
- * The arms of a Swift `switch` body, as a mapping from each matched case to its expression.
139
- *
140
- * Order- and grouping-independent, which matters because pinning an arm by its exact TEXT gets both
141
- * directions wrong. It false-positives on a pure reorder — `.deliveredUnverified, .deliveryNotObserved`
142
- * is the same table and failed — and it misses a real defect: splitting one outcome out of a group
143
- * into its own arm with a different expression leaves the pinned needle intact, so
144
- * `.targetUnavailable` could be given `false` while the six-outcome needle still matched.
145
- *
146
- * `body` is the text between the switch's braces, comments already stripped by the caller.
147
- */
148
- export declare function switchArmsByOutcome(body: string): Map<string, string>;
149
- /**
150
- * Evaluate a Swift boolean condition for a given binding of its identifiers.
151
- *
152
- * Folded in from PR #43, which pinned the clipboard-restore guard by evaluating it for BOTH values
153
- * of the decision rather than comparing its text. That is the half worth keeping: an exact
154
- * `toBe("shouldRestore")` on a captured condition kills the inversion, but it also fails on
155
- * `(shouldRestore)` and on a trailing comment — reporting refactors as defects while proving
156
- * nothing about behaviour.
157
- *
158
- * Deliberately narrow and fail-CLOSED: `true`, `false`, `!x`, whole-expression parentheses, and
159
- * identifiers present in `env`. Anything else throws. An added disjunct
160
- * (`shouldRestore || stillOwnsChangeCount`) is an unevaluatable expression, not a passing one —
161
- * which is the behaviour that matters, because that disjunct is exactly how the
162
- * transcript-destroying defect gets reintroduced. Extend the evaluator when a new shape is
163
- * legitimate; do not loosen the assertion.
164
- */
165
- export declare function evaluateSwiftCondition(condition: string, env: Record<string, boolean>): boolean;
166
- /**
167
- * Every Swift source under a root, so an absence claim can be made about the app rather than about
168
- * whichever files were on the reviewer's mind.
169
- */
170
- export declare function swiftSourcesUnder(root: string): Array<[path: string, source: string]>;
171
- //# sourceMappingURL=source-assertions.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"source-assertions.d.ts","sourceRoot":"","sources":["../../../src/__tests__/helpers/source-assertions.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyFG;AAEH;;;;;GAKG;AACH,wBAAgB,WAAW,CACzB,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACd,OAAO,GAAE;IAAE,UAAU,CAAC,EAAE,OAAO,GAAG,MAAM,CAAA;CAAO,GAC9C,IAAI,CAON;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAC1B,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,EACb,OAAO,GAAE;IAAE,aAAa,CAAC,EAAE,MAAM,CAAA;CAAO,GACvC,MAAM,CAWR;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAMtF;AAED,qFAAqF;AACrF,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAKtD;AAuED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,GACZ,MAAM,CA0BR;AAED,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAoEzD;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAyBrE;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,sBAAsB,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAiC/F;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,KAAK,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC,CAIrF"}
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=preload.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"preload.d.ts","sourceRoot":"","sources":["../../src/__tests__/preload.ts"],"names":[],"mappings":""}