@hasna/recordings 0.2.14 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (123) hide show
  1. package/Dockerfile.package +4 -5
  2. package/README.md +113 -8
  3. package/bun.lock +2 -2
  4. package/dist/__tests__/helpers/installer-guard-execution.d.ts +22 -0
  5. package/dist/__tests__/helpers/installer-guard-execution.d.ts.map +1 -0
  6. package/dist/__tests__/helpers/installer-preflight.d.ts +22 -0
  7. package/dist/__tests__/helpers/installer-preflight.d.ts.map +1 -0
  8. package/dist/__tests__/helpers/native-fs-guard.d.ts.map +1 -1
  9. package/dist/__tests__/helpers/source-assertions.d.ts +169 -0
  10. package/dist/__tests__/helpers/source-assertions.d.ts.map +1 -0
  11. package/dist/__tests__/preload.d.ts +1 -4
  12. package/dist/__tests__/preload.d.ts.map +1 -1
  13. package/dist/cli/desktop-snapshot.d.ts +20 -0
  14. package/dist/cli/desktop-snapshot.d.ts.map +1 -0
  15. package/dist/cli/index.js +2094 -280
  16. package/dist/cli/macos-permissions.d.ts +72 -0
  17. package/dist/cli/macos-permissions.d.ts.map +1 -1
  18. package/dist/cli/macos-shortcut.d.ts +139 -0
  19. package/dist/cli/macos-shortcut.d.ts.map +1 -0
  20. package/dist/cli/trigger-probe.d.ts +172 -0
  21. package/dist/cli/trigger-probe.d.ts.map +1 -0
  22. package/dist/db/database.d.ts +9 -0
  23. package/dist/db/database.d.ts.map +1 -1
  24. package/dist/http/client.d.ts +19 -7
  25. package/dist/http/client.d.ts.map +1 -1
  26. package/dist/index.d.ts +3 -1
  27. package/dist/index.d.ts.map +1 -1
  28. package/dist/index.js +635 -50
  29. package/dist/lib/capture-probe.d.ts +216 -0
  30. package/dist/lib/capture-probe.d.ts.map +1 -0
  31. package/dist/lib/config.d.ts.map +1 -1
  32. package/dist/lib/macos-bundle.d.ts +50 -0
  33. package/dist/lib/macos-bundle.d.ts.map +1 -0
  34. package/dist/lib/persistence-probe.d.ts +208 -0
  35. package/dist/lib/persistence-probe.d.ts.map +1 -0
  36. package/dist/lib/recorder.d.ts.map +1 -1
  37. package/dist/lib/retired-deployment-modes.d.ts +24 -0
  38. package/dist/lib/retired-deployment-modes.d.ts.map +1 -0
  39. package/dist/lib/transcriber.d.ts +60 -0
  40. package/dist/lib/transcriber.d.ts.map +1 -1
  41. package/dist/mcp/index.js +141 -57
  42. package/dist/server/cloud-config.d.ts +25 -3
  43. package/dist/server/cloud-config.d.ts.map +1 -1
  44. package/dist/server/cloud-readiness.d.ts.map +1 -1
  45. package/dist/server/cloud.d.ts +8 -7
  46. package/dist/server/cloud.d.ts.map +1 -1
  47. package/dist/server/index.js +188 -80
  48. package/dist/server/migrate-command.d.ts.map +1 -1
  49. package/dist/server/serve.d.ts.map +1 -1
  50. package/dist/storage.d.ts +2 -2
  51. package/dist/storage.d.ts.map +1 -1
  52. package/dist/storage.js +109 -50
  53. package/dist/store.d.ts +1 -1
  54. package/dist/store.d.ts.map +1 -1
  55. package/dist/version.d.ts +1 -1
  56. package/dist/version.d.ts.map +1 -1
  57. package/package.json +25 -6
  58. package/scripts/ci-linux-suite.ts +403 -0
  59. package/scripts/ci-native-build.ts +402 -0
  60. package/scripts/enforce_identity_migration.sh +110 -0
  61. package/scripts/generate-sdk.ts +1 -1
  62. package/scripts/install_macos_app.sh +192 -31
  63. package/scripts/macos_artifact.ts +323 -43
  64. package/scripts/native/realtime-settle-probe/README.md +39 -0
  65. package/scripts/native/realtime-settle-probe/main.swift +135 -0
  66. package/scripts/policy/local-only-approved-targets.txt +21 -0
  67. package/scripts/read_local_only_targets.sh +203 -0
  68. package/scripts/scan-artifact.ts +40 -0
  69. package/scripts/set-version.ts +145 -0
  70. package/scripts/smoke_macos_app.sh +62 -16
  71. package/scripts/vacuity-manifest-gen.ts +96 -0
  72. package/scripts/vacuity-manifests/enhancement-screen-mirror.tsv +44 -0
  73. package/scripts/vacuity-manifests/install-chain.tsv +13 -0
  74. package/scripts/vacuity-manifests/must-survive-controls.tsv +10 -0
  75. package/scripts/vacuity-manifests/reorder.tsv +4 -0
  76. package/scripts/vacuity-manifests/source-side.tsv +15 -0
  77. package/scripts/vacuity-manifests/variable-operands.tsv +16 -0
  78. package/scripts/vacuity-manifests/version-sites.tsv +61 -0
  79. package/scripts/vacuity-mutation-battery.ts +266 -0
  80. package/src/native/Recordings/App/MenuBarStatusView.swift +23 -11
  81. package/src/native/Recordings/App/RecordingsApp.swift +37 -1
  82. package/src/native/Recordings/App/RuntimeSmoke.swift +5 -1
  83. package/src/native/Recordings/App/SidebarView.swift +1 -1
  84. package/src/native/Recordings/Package.swift +24 -5
  85. package/src/native/Recordings/RecordingsLib/EnhancementScreen.swift +91 -0
  86. package/src/native/Recordings/RecordingsLib/FnKeyMonitor.swift +27 -4
  87. package/src/native/Recordings/RecordingsLib/Info.plist +2 -2
  88. package/src/native/Recordings/RecordingsLib/MenuBarPresentation.swift +56 -3
  89. package/src/native/Recordings/RecordingsLib/NativePCMRecorder.swift +13 -0
  90. package/src/native/Recordings/RecordingsLib/PasteDeliveryVerification.swift +460 -0
  91. package/src/native/Recordings/RecordingsLib/PermissionRequestLaunchPlan.swift +21 -1
  92. package/src/native/Recordings/RecordingsLib/RealtimeTranscriptionClient.swift +41 -7
  93. package/src/native/Recordings/RecordingsLib/RecordingAttemptAlert.swift +36 -0
  94. package/src/native/Recordings/RecordingsLib/RecordingEngine.swift +1071 -75
  95. package/src/native/Recordings/RecordingsLib/SettingsView.swift +47 -3
  96. package/src/native/Recordings/RecordingsLib/VoiceShortcuts.swift +11 -6
  97. package/src/native/Recordings/RecordingsTests/BarOnlyLaunchPlanTests.swift +99 -0
  98. package/src/native/Recordings/RecordingsTests/CLIRunnerTests.swift +3 -3
  99. package/src/native/Recordings/RecordingsTests/EnhancementScreenTests.swift +191 -0
  100. package/src/native/Recordings/RecordingsTests/MenuBarPresentationTests.swift +144 -4
  101. package/src/native/Recordings/RecordingsTests/PasteDeliveryVerificationTests.swift +463 -0
  102. package/src/native/Recordings/RecordingsTests/PasteTargetTests.swift +33 -20
  103. package/src/native/Recordings/RecordingsTests/ProjectStoreTests.swift +1 -1
  104. package/src/native/Recordings/RecordingsTests/RealtimeSettleBenchmark.swift +200 -0
  105. package/src/native/Recordings/RecordingsTests/RealtimeSettleBudgetTests.swift +80 -0
  106. package/src/native/Recordings/RecordingsTests/RealtimeTranscriptionTests.swift +22 -4
  107. package/src/native/Recordings/RecordingsTests/RecordingEngineDeliveryTests.swift +12 -6
  108. package/src/native/Recordings/RecordingsTests/RecordingEngineHomeIsolationTests.swift +67 -0
  109. package/src/native/Recordings/RecordingsTests/RecordingStartGateTests.swift +12 -3
  110. package/src/native/Recordings/RecordingsTests/RecordingStartTimingTests.swift +207 -11
  111. package/src/native/Recordings/RecordingsTests/SpeechIntentTests.swift +17 -6
  112. package/src/native/Recordings/RecordingsTests/TestHomeDirectory.swift +23 -0
  113. package/src/native/Recordings/Updater/Broker/ApplicationProcessQuiescence.swift +7 -1
  114. package/src/native/Recordings/Updater/Broker/AtomicActivation.swift +10 -10
  115. package/src/native/Recordings/Updater/Broker/BrokerMain.swift +5 -1
  116. package/src/native/Recordings/Updater/Broker/HostOSProductVersion.swift +1 -0
  117. package/src/native/Recordings/Updater/Broker/PeerIdentity.swift +59 -3
  118. package/src/native/Recordings/Updater/Protocol/UpdateProtocol.swift +21 -2
  119. package/src/native/Recordings/Updater/ProtocolTests/ExpectationIntegrityTests.swift +60 -0
  120. package/src/native/Recordings/Updater/ProtocolTests/UpdateXPCInterfaceTests.swift +113 -0
  121. package/src/native/Recordings/Updater/VerifierLauncher/RecordingsVerifierLauncher.c +106 -5
  122. package/src/native/Recordings/build.sh +226 -20
  123. package/scripts/native/prebuilds/darwin-universal/recordings_fs_guard.node +0 -0
@@ -0,0 +1,403 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * Partition the `bun test` suite into the Linux-GATED set and the Linux-QUARANTINED set, and prove
4
+ * afterwards that the run actually honoured the partition.
5
+ *
6
+ * Why this is a script with its own contract test rather than a glob in the workflow YAML:
7
+ *
8
+ * A gate that names its files explicitly stops covering anything added after it was written, and
9
+ * a gate that silently narrows its own file list is the same vacuity class this repo spent a day
10
+ * closing in its assertions — a green check over almost nothing looks exactly like a green check
11
+ * over everything. So discovery is DYNAMIC (a new `*.test.ts` is gated the moment it lands) and
12
+ * the narrowing is verified after the fact against the runner's own output.
13
+ *
14
+ * Three independent facts are cross-checked, because any one of them alone can fail silently:
15
+ *
16
+ * 1. `git ls-files` and a filesystem walk must agree on the set of test files. A walker with a
17
+ * broken suffix check, or a suite that was never `git add`ed, shows up as a disagreement
18
+ * instead of as a quietly smaller gate.
19
+ * 2. Every quarantine entry must name a DISCOVERED test file. A renamed or deleted suite cannot
20
+ * keep its exemption; the entry goes stale and `--check` fails.
21
+ * 3. After the gated run, the number of files the runner reports must equal the gated count, and
22
+ * no quarantined file may appear as an executed file. `bun test` treats its positional
23
+ * arguments as SUBSTRING filters rather than paths, so "I passed it a list" is not evidence
24
+ * that it ran that list — only its own output is.
25
+ *
26
+ * Counts of passing/failing TESTS are deliberately never asserted. The suite is nondeterministic at
27
+ * the margin (subprocess and FIFO timeouts), so any count is a flake generator; see
28
+ * src/__tests__/helpers/source-assertions.ts. Only set membership and directions are gated.
29
+ *
30
+ * Usage:
31
+ * bun scripts/ci-linux-suite.ts --check validate the quarantine file, print counts
32
+ * bun scripts/ci-linux-suite.ts --gated newline-separated gated files
33
+ * bun scripts/ci-linux-suite.ts --quarantined newline-separated quarantined files
34
+ * bun scripts/ci-linux-suite.ts --verify-run <log> assert a gated run honoured the partition
35
+ */
36
+ import { readdirSync, readFileSync, statSync } from "node:fs";
37
+ import { join, relative, sep } from "node:path";
38
+ import { spawnSync } from "node:child_process";
39
+
40
+ /** Repo-relative path of the quarantine list, relative to the repository root. */
41
+ export const QUARANTINE_FILE = ".github/linux-quarantine.txt";
42
+
43
+ const TEST_SUFFIX = ".test.ts";
44
+
45
+ /** Directories never worth walking. `.build` holds SwiftPM output, which can be enormous. */
46
+ const SKIPPED_DIRS = new Set([".git", "node_modules", ".build", "dist"]);
47
+
48
+ /**
49
+ * Parse the quarantine list.
50
+ *
51
+ * Blank lines and `#` comments are dropped. Leading and trailing whitespace is stripped so a stray
52
+ * indent cannot produce an entry that matches no discovered file and reads as a typo in the list
53
+ * rather than as the misformatting it is.
54
+ */
55
+ export function parseQuarantine(text: string): string[] {
56
+ return text
57
+ .split("\n")
58
+ .map((line) => line.trim())
59
+ .filter((line) => line.length > 0 && !line.startsWith("#"));
60
+ }
61
+
62
+ /**
63
+ * Entries not directly preceded by a `# reason:` line.
64
+ *
65
+ * An exemption without a stated reason cannot be told apart from a suite somebody muted to get a
66
+ * green check, which is the whole failure mode this file guards. Implemented in the script rather
67
+ * than in the test so that it is enforced whenever an entry is ADDED — the list is currently empty,
68
+ * and a check living only in a test runs against the list as it stands today.
69
+ *
70
+ * The marker is required rather than accepting any comment, and that is not pedantry: the first
71
+ * version accepted "the line above starts with #", which the explanatory header at the top of the
72
+ * file satisfies. Appending an entry to the end of that header therefore passed with no reason of
73
+ * its own — found by running the negative control rather than by reading the code.
74
+ */
75
+ const REASON_MARKER = /^#\s*reason:\s*\S/;
76
+
77
+ export function entriesMissingReason(text: string): string[] {
78
+ const lines = text.split("\n").map((line) => line.trim());
79
+ const missing: string[] = [];
80
+ lines.forEach((line, index) => {
81
+ if (line.length === 0 || line.startsWith("#")) return;
82
+ if (!REASON_MARKER.test(lines[index - 1] ?? "")) missing.push(line);
83
+ });
84
+ return missing;
85
+ }
86
+
87
+ /**
88
+ * Enumerate tracked test files with git.
89
+ *
90
+ * git is the authoritative enumeration in CI, where the checkout contains tracked files and
91
+ * nothing else. It is also the one enumeration that cannot be fooled by a leftover build artifact
92
+ * in the working tree.
93
+ */
94
+ export function testFilesFromGit(repoRoot: string): string[] {
95
+ const result = spawnSync("git", ["-C", repoRoot, "ls-files", "-z", `*${TEST_SUFFIX}`], {
96
+ encoding: "utf8",
97
+ });
98
+ if (result.status !== 0) {
99
+ throw new Error(`git ls-files failed (${result.status}): ${result.stderr?.trim() ?? ""}`);
100
+ }
101
+ // -z because a path may legally contain a newline, and a newline-split enumeration would report
102
+ // 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();
104
+ }
105
+
106
+ /**
107
+ * Enumerate test files by walking the filesystem.
108
+ *
109
+ * This exists purely as an independent second opinion on {@link testFilesFromGit}. It is not the
110
+ * gate's source of truth; it is the thing that makes a wrong source of truth visible.
111
+ */
112
+ export function testFilesFromWalk(repoRoot: string): string[] {
113
+ const found: string[] = [];
114
+ const walk = (dir: string): void => {
115
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
116
+ if (entry.isSymbolicLink()) continue;
117
+ const absolute = join(dir, entry.name);
118
+ if (entry.isDirectory()) {
119
+ if (SKIPPED_DIRS.has(entry.name)) continue;
120
+ walk(absolute);
121
+ } else if (entry.isFile() && entry.name.endsWith(TEST_SUFFIX)) {
122
+ found.push(relative(repoRoot, absolute).split(sep).join("/"));
123
+ }
124
+ }
125
+ };
126
+ walk(repoRoot);
127
+ return found.sort();
128
+ }
129
+
130
+ export type DiscoveryDisagreement = { trackedNotOnDisk: string[]; onDiskUntracked: string[] };
131
+
132
+ /**
133
+ * Compare the two enumerations.
134
+ *
135
+ * The two directions are NOT symmetric, and treating them as one check is what made the first
136
+ * version of this script unusable:
137
+ *
138
+ * trackedNotOnDisk — always fatal. The index names a suite the checkout does not have, so the
139
+ * gated list contains a file the runner cannot run.
140
+ * onDiskUntracked — fatal in CI ONLY. In CI the checkout is exactly the tracked tree, so an
141
+ * untracked suite means the enumeration itself is wrong. On a developer's machine it usually
142
+ * means a test being written right now, and failing the whole suite for that teaches people to
143
+ * delete the check rather than to commit the file.
144
+ */
145
+ export function compareDiscovery(fromGit: string[], fromWalk: string[]): DiscoveryDisagreement {
146
+ const gitSet = new Set(fromGit);
147
+ const walkSet = new Set(fromWalk);
148
+ return {
149
+ trackedNotOnDisk: fromGit.filter((path) => !walkSet.has(path)),
150
+ onDiskUntracked: fromWalk.filter((path) => !gitSet.has(path)),
151
+ };
152
+ }
153
+
154
+ export type Partition = { gated: string[]; quarantined: string[] };
155
+
156
+ /**
157
+ * Split `discovered` using `quarantined`.
158
+ *
159
+ * Throws when an entry names something that was not discovered, which is the anti-rot property:
160
+ * the exemption list can only ever refer to suites that currently exist.
161
+ */
162
+ export function partition(discovered: string[], quarantined: string[]): Partition {
163
+ const discoveredSet = new Set(discovered);
164
+ const seen = new Set<string>();
165
+ for (const entry of quarantined) {
166
+ if (!discoveredSet.has(entry)) {
167
+ throw new Error(
168
+ `${QUARANTINE_FILE} names "${entry}", which is not a discovered test file. ` +
169
+ `If the suite was renamed or deleted, delete this line.`,
170
+ );
171
+ }
172
+ if (seen.has(entry)) {
173
+ throw new Error(`${QUARANTINE_FILE} lists "${entry}" more than once.`);
174
+ }
175
+ seen.add(entry);
176
+ }
177
+ return {
178
+ gated: discovered.filter((path) => !seen.has(path)),
179
+ quarantined: [...seen].sort(),
180
+ };
181
+ }
182
+
183
+ /**
184
+ * The set of test files a run actually executed, read from bun's JUnit report.
185
+ *
186
+ * WHY THE JUNIT REPORT AND NOT THE CONSOLE OUTPUT. Not because the console is empty — an earlier
187
+ * version of this comment claimed a fully passing run prints only a summary, which is true when you
188
+ * run bun in a terminal and FALSE on an Actions runner, where it emits a `##[group]<file>:` header
189
+ * and a `(pass)` line per test. Asserting against a stream whose shape depends on the environment is
190
+ * the problem; the JUnit report has one documented shape with a `file=` attribute per suite whether
191
+ * or not anything failed.
192
+ *
193
+ * What makes the check load-bearing at all is measured bun behaviour that is genuinely surprising:
194
+ * positional arguments are SUBSTRING FILTERS, not paths, so `bun test gen1` matched four files, and
195
+ * `bun test src/nope.test.ts` on a path that does not exist EXITS 0 — it reports nothing to run and
196
+ * succeeds. So "the command was given 56 paths and returned 0" is not evidence that 56 suites ran,
197
+ * or that any did.
198
+ *
199
+ * Attributes are matched with a regex rather than a parser because the path set is already known to
200
+ * be free of whitespace and XML metacharacters (`--check` enforces the whitespace half, and the
201
+ * paths are repo-relative `*.test.ts`), so there is nothing for a parser to disambiguate.
202
+ */
203
+ export function executedFilesFromJUnit(xml: string): string[] {
204
+ const executed = new Set<string>();
205
+ for (const match of xml.matchAll(/\bfile="([^"]+)"/g)) {
206
+ const path = match[1];
207
+ if (path?.endsWith(TEST_SUFFIX)) executed.add(path);
208
+ }
209
+ return [...executed].sort();
210
+ }
211
+
212
+ export type SuiteSkips = { file: string; total: number; skipped: number };
213
+
214
+ /**
215
+ * Per-file test and skip counts from the JUnit report.
216
+ *
217
+ * Membership in the executed set is NOT the same as being asserted. bun emits a `file=` attribute for
218
+ * a suite whose every test is `describe.skip`ped, so such a file counts as executed and the partition
219
+ * check still reports "56 suites executed, exactly the 56 gated" while that suite asserts nothing.
220
+ * Set equality cannot see that; only the skip counts can.
221
+ *
222
+ * A skipped case is `<testcase ...><skipped /></testcase>` while a real one self-closes, so each
223
+ * `<testcase` chunk is inspected up to its terminator rather than the attributes being trusted —
224
+ * the `skipped=` attribute appears on the enclosing `<testsuite>` at several nesting levels and
225
+ * summing those double-counts.
226
+ */
227
+ export function suiteSkips(xml: string): SuiteSkips[] {
228
+ const counts = new Map<string, { total: number; skipped: number }>();
229
+ for (const chunk of xml.split("<testcase").slice(1)) {
230
+ const file = chunk.match(/\bfile="([^"]+)"/)?.[1];
231
+ if (!file?.endsWith(TEST_SUFFIX)) continue;
232
+ const body = chunk.split("</testcase>")[0] ?? chunk;
233
+ const entry = counts.get(file) ?? { total: 0, skipped: 0 };
234
+ entry.total += 1;
235
+ // The self-closing form ends the element before any child could appear, so a `<skipped` found in
236
+ // a self-closed chunk belongs to the NEXT case and must not be attributed here.
237
+ if (!/^[^>]*\/>/.test(chunk) && /<skipped\b/.test(body)) entry.skipped += 1;
238
+ counts.set(file, entry);
239
+ }
240
+ return [...counts.entries()]
241
+ .map(([file, { total, skipped }]) => ({ file, total, skipped }))
242
+ .sort((a, b) => a.file.localeCompare(b.file));
243
+ }
244
+
245
+ /**
246
+ * Whether the JUnit report is a complete one.
247
+ *
248
+ * A run killed by a job timeout can leave a truncated file behind. Without this, a truncated report
249
+ * naming three suites looks identical to a run that only had three suites to name.
250
+ */
251
+ export function isCompleteJUnit(xml: string): boolean {
252
+ return /<testsuites\b/.test(xml) && /<\/testsuites>/.test(xml);
253
+ }
254
+
255
+ function fail(message: string): never {
256
+ console.error(message);
257
+ process.exit(1);
258
+ }
259
+
260
+ function loadPartition(repoRoot: string): Partition & { discovered: string[] } {
261
+ const fromGit = testFilesFromGit(repoRoot);
262
+ const { trackedNotOnDisk, onDiskUntracked } = compareDiscovery(fromGit, testFilesFromWalk(repoRoot));
263
+ if (trackedNotOnDisk.length > 0) {
264
+ fail(
265
+ "The index names test files the checkout does not have, so the gated list contains files the " +
266
+ `runner cannot run:\n${trackedNotOnDisk.map((path) => ` ${path}`).join("\n")}`,
267
+ );
268
+ }
269
+ if (onDiskUntracked.length > 0) {
270
+ const detail = onDiskUntracked.map((path) => ` ${path}`).join("\n");
271
+ // In CI the checkout IS the tracked tree, so this is a broken enumeration rather than
272
+ // work in progress, and a smaller gate is exactly the failure that must not pass quietly.
273
+ if (process.env.CI) {
274
+ fail(`Untracked test files are invisible to the gate; commit or delete them:\n${detail}`);
275
+ }
276
+ console.error(`warning: untracked test files are not gated (local only):\n${detail}`);
277
+ }
278
+ if (fromGit.length === 0) {
279
+ fail("Discovered zero test files. A gate over zero files is green for the wrong reason.");
280
+ }
281
+ const quarantineText = readFileSync(join(repoRoot, QUARANTINE_FILE), "utf8");
282
+ const unexplained = entriesMissingReason(quarantineText);
283
+ if (unexplained.length > 0) {
284
+ fail(
285
+ `Every entry in ${QUARANTINE_FILE} needs a reason comment on the line directly above it. ` +
286
+ `Without one an exemption is indistinguishable from a muted suite:\n` +
287
+ unexplained.map((entry) => ` ${entry}`).join("\n"),
288
+ );
289
+ }
290
+ let split: Partition;
291
+ try {
292
+ split = partition(fromGit, parseQuarantine(quarantineText));
293
+ } catch (error) {
294
+ fail(error instanceof Error ? error.message : String(error));
295
+ }
296
+ if (split.gated.length === 0) {
297
+ fail(
298
+ `Every discovered test file is quarantined by ${QUARANTINE_FILE}. The gate would run nothing.`,
299
+ );
300
+ }
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));
306
+ if (whitespace.length > 0) {
307
+ fail(
308
+ "Test paths containing whitespace cannot be passed through unquoted word splitting:\n" +
309
+ whitespace.map((path) => ` ${path}`).join("\n"),
310
+ );
311
+ }
312
+ return { ...split, discovered: fromGit };
313
+ }
314
+
315
+ function main(argv: string[]): void {
316
+ const repoRoot = process.cwd();
317
+ if (!statSync(join(repoRoot, QUARANTINE_FILE), { throwIfNoEntry: false })) {
318
+ fail(`Run this from the repository root; ${QUARANTINE_FILE} is not there.`);
319
+ }
320
+ const mode = argv[0];
321
+ const { gated, quarantined, discovered } = loadPartition(repoRoot);
322
+
323
+ if (mode === "--gated") {
324
+ console.log(gated.join("\n"));
325
+ return;
326
+ }
327
+ if (mode === "--quarantined") {
328
+ console.log(quarantined.join("\n"));
329
+ return;
330
+ }
331
+ if (mode === "--check") {
332
+ console.log(
333
+ `discovered ${discovered.length} test files: ${gated.length} gated, ` +
334
+ `${quarantined.length} quarantined by ${QUARANTINE_FILE}`,
335
+ );
336
+ for (const entry of quarantined) console.log(` quarantined: ${entry}`);
337
+ return;
338
+ }
339
+ if (mode === "--verify-run") {
340
+ const reportPath = argv[1];
341
+ if (!reportPath) fail("usage: --verify-run <junit.xml>");
342
+ // Reported as a verdict rather than as an exception. This step runs with `if: always()`, so it
343
+ // is the step that speaks when the suite crashed before writing anything, and a stack trace
344
+ // there buries the actual failure under a Node error.
345
+ if (!statSync(reportPath, { throwIfNoEntry: false })) {
346
+ fail(`${reportPath} does not exist; the gated run produced no report at all.`);
347
+ }
348
+ const xml = readFileSync(reportPath, "utf8");
349
+ if (!isCompleteJUnit(xml)) {
350
+ fail(`${reportPath} is not a complete JUnit report; the gated run did not finish.`);
351
+ }
352
+ const executed = executedFilesFromJUnit(xml);
353
+ const leaked = executed.filter((path) => quarantined.includes(path));
354
+ if (leaked.length > 0) {
355
+ fail(
356
+ "The gated run executed quarantined suites, so its result is not the gated result. " +
357
+ "`bun test` matches its arguments as SUBSTRING filters, not as paths:\n" +
358
+ leaked.map((path) => ` ${path}`).join("\n"),
359
+ );
360
+ }
361
+ const missing = gated.filter((path) => !executed.includes(path));
362
+ if (missing.length > 0) {
363
+ fail(
364
+ `${missing.length} gated suite(s) contributed nothing to the run, so the gate did not ` +
365
+ "cover them. Either the filter did not match the path, or the file declares no tests:\n" +
366
+ missing.map((path) => ` ${path}`).join("\n"),
367
+ );
368
+ }
369
+ const unexpected = executed.filter((path) => !gated.includes(path));
370
+ if (unexpected.length > 0) {
371
+ fail(
372
+ "The gated run executed suites outside the gated set:\n" +
373
+ unexpected.map((path) => ` ${path}`).join("\n"),
374
+ );
375
+ }
376
+ console.log(
377
+ `gated run honoured the partition: ${executed.length} suites executed, ` +
378
+ `exactly the ${gated.length} gated, none of the ${quarantined.length} quarantined`,
379
+ );
380
+
381
+ // Membership is not assertion. Reported rather than gated: a suite MAY legitimately skip
382
+ // everything on a given platform, so failing here would be a false red — but leaving it silent is
383
+ // how `describe.skip` hollows out a gate that still reports full coverage.
384
+ const skips = suiteSkips(xml);
385
+ const totalSkipped = skips.reduce((sum, s) => sum + s.skipped, 0);
386
+ const hollow = skips.filter((s) => s.total > 0 && s.skipped === s.total);
387
+ console.log(
388
+ `${skips.reduce((sum, s) => sum + s.total, 0)} tests reported, ${totalSkipped} skipped`,
389
+ );
390
+ for (const suite of hollow) {
391
+ console.log(
392
+ `::warning title=Suite asserts nothing::${suite.file} ran ${suite.total} test(s) and skipped ` +
393
+ "ALL of them. It counts as covered by the gate while asserting nothing.",
394
+ );
395
+ }
396
+ return;
397
+ }
398
+ fail("usage: --check | --gated | --quarantined | --verify-run <junit.xml>");
399
+ }
400
+
401
+ if (import.meta.main) {
402
+ main(process.argv.slice(2));
403
+ }