@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.
- package/README.md +56 -6
- package/contracts/v1/fixtures.json +1206 -0
- package/dist/cli/index.js +28 -7
- package/dist/contracts/hosted-v1.d.ts +105 -0
- package/dist/contracts/hosted-v1.d.ts.map +1 -0
- package/dist/contracts/hosted-v1.js +41 -0
- package/dist/contracts/stream-v1.d.ts +117 -0
- package/dist/contracts/stream-v1.d.ts.map +1 -0
- package/dist/contracts/stream-v1.js +35 -0
- package/dist/hosted/index.d.ts +58 -0
- package/dist/hosted/index.d.ts.map +1 -0
- package/dist/hosted/index.js +266 -0
- package/dist/hosted/transport.d.ts +36 -0
- package/dist/hosted/transport.d.ts.map +1 -0
- package/dist/hosted-v1-aavn7ktb.js +4114 -0
- package/dist/hosted-v1-gdr9extc.js +84 -0
- package/dist/index.js +27 -6
- package/dist/mcp/index.js +27 -6
- package/dist/server/index.js +27 -6
- package/dist/storage.js +27 -6
- package/docs/hosted-sdk.md +71 -0
- package/docs/wire-contracts.md +24 -0
- package/package.json +27 -6
- package/scripts/ci-linux-suite.ts +23 -13
- package/scripts/macos_artifact.ts +40 -33
- package/scripts/native/prebuilds/darwin-universal/recordings_fs_guard.node +0 -0
- package/scripts/native/recordings_fs_guard.c +36 -4
- package/scripts/native-core-receipt.py +171 -0
- package/scripts/native_fs_guard.ts +2 -0
- package/scripts/release-suite-gate.ts +227 -150
- package/scripts/resolve_tailscale_cli.sh +24 -3
- package/src/native/Recordings/RecordingsLib/BlockingOperation.swift +29 -0
- package/src/native/Recordings/RecordingsLib/Info.plist +2 -2
- package/src/native/Recordings/RecordingsLib/ProjectStore.swift +4 -4
- package/src/native/Recordings/RecordingsLib/RecordingEngine.swift +228 -60
- package/src/native/Recordings/RecordingsLib/RecordingPasteTarget.swift +114 -0
- package/src/native/Recordings/RecordingsLib/RecordingProvider.swift +13 -3
- package/src/native/Recordings/RecordingsTests/BlockingOperationTests.swift +85 -0
- package/src/native/Recordings/RecordingsTests/CLIRunnerTests.swift +441 -72
- package/src/native/Recordings/RecordingsTests/PipeClosureFixture.swift +215 -0
- package/src/native/Recordings/RecordingsTests/ProjectStoreTests.swift +10 -10
- package/src/native/Recordings/RecordingsTests/RecordingEngineDeliveryTests.swift +1 -1
- package/src/native/Recordings/RecordingsTests/RecordingFrozenPasteTargetTests.swift +50 -0
- package/src/native/Recordings/RecordingsTests/RecordingPasteTargetTrackerTests.swift +48 -0
- package/src/native/Recordings/RecordingsTests/RecordingProviderTests.swift +115 -2
- package/src/native/Recordings/RecordingsTests/RecordingStartTimingTests.swift +93 -21
- package/src/native/Recordings/RecordingsTests/TestHomeDirectory.swift +5 -1
- package/src/native/Recordings/build.sh +2 -1
- package/dist/__tests__/helpers/installer-guard-execution.d.ts +0 -22
- package/dist/__tests__/helpers/installer-guard-execution.d.ts.map +0 -1
- package/dist/__tests__/helpers/installer-preflight.d.ts +0 -22
- package/dist/__tests__/helpers/installer-preflight.d.ts.map +0 -1
- package/dist/__tests__/helpers/native-fs-guard.d.ts +0 -2
- package/dist/__tests__/helpers/native-fs-guard.d.ts.map +0 -1
- package/dist/__tests__/helpers/source-assertions.d.ts +0 -171
- package/dist/__tests__/helpers/source-assertions.d.ts.map +0 -1
- package/dist/__tests__/preload.d.ts +0 -2
- 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
|
|
177
|
+
func recorderStartDoesNotWaitOnSelectionCapture() async throws {
|
|
178
|
+
let capture = BlockedSelectionCapture()
|
|
179
|
+
defer { capture.release() }
|
|
143
180
|
let recorder = FakePCMRecorder()
|
|
144
|
-
let engine = makeStartableEngine(recorder: recorder) {
|
|
145
|
-
|
|
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
|
-
|
|
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
|
|
206
|
+
func stopAwaitsFrozenStartContext() async throws {
|
|
207
|
+
let capture = BlockedSelectionCapture()
|
|
208
|
+
defer { capture.release() }
|
|
172
209
|
let recorder = FakePCMRecorder()
|
|
173
|
-
let engine = makeStartableEngine(recorder: recorder) {
|
|
174
|
-
|
|
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
|
-
|
|
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
|
|
292
|
+
func cancelDuringPendingCapture() async throws {
|
|
293
|
+
let capture = BlockedSelectionCapture()
|
|
294
|
+
defer { capture.release() }
|
|
224
295
|
let recorder = FakePCMRecorder()
|
|
225
|
-
let engine = makeStartableEngine(recorder: recorder) {
|
|
226
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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", "
|
|
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 +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 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"preload.d.ts","sourceRoot":"","sources":["../../src/__tests__/preload.ts"],"names":[],"mappings":""}
|