@hasna/recordings 0.2.13 → 0.3.1
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/Dockerfile.package +4 -5
- package/README.md +113 -8
- package/bun.lock +2 -2
- package/dist/__tests__/helpers/installer-guard-execution.d.ts +22 -0
- package/dist/__tests__/helpers/installer-guard-execution.d.ts.map +1 -0
- package/dist/__tests__/helpers/installer-preflight.d.ts +22 -0
- package/dist/__tests__/helpers/installer-preflight.d.ts.map +1 -0
- package/dist/__tests__/helpers/native-fs-guard.d.ts.map +1 -1
- package/dist/__tests__/helpers/source-assertions.d.ts +169 -0
- package/dist/__tests__/helpers/source-assertions.d.ts.map +1 -0
- package/dist/__tests__/preload.d.ts +1 -4
- package/dist/__tests__/preload.d.ts.map +1 -1
- package/dist/cli/desktop-snapshot.d.ts +20 -0
- package/dist/cli/desktop-snapshot.d.ts.map +1 -0
- package/dist/cli/index.js +2078 -277
- package/dist/cli/macos-permissions.d.ts +72 -0
- package/dist/cli/macos-permissions.d.ts.map +1 -1
- package/dist/cli/macos-shortcut.d.ts +139 -0
- package/dist/cli/macos-shortcut.d.ts.map +1 -0
- package/dist/cli/trigger-probe.d.ts +172 -0
- package/dist/cli/trigger-probe.d.ts.map +1 -0
- package/dist/db/database.d.ts +9 -0
- package/dist/db/database.d.ts.map +1 -1
- package/dist/http/client.d.ts +10 -7
- package/dist/http/client.d.ts.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +634 -49
- package/dist/lib/capture-probe.d.ts +216 -0
- package/dist/lib/capture-probe.d.ts.map +1 -0
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/macos-bundle.d.ts +50 -0
- package/dist/lib/macos-bundle.d.ts.map +1 -0
- package/dist/lib/persistence-probe.d.ts +208 -0
- package/dist/lib/persistence-probe.d.ts.map +1 -0
- package/dist/lib/recorder.d.ts.map +1 -1
- package/dist/lib/retired-deployment-modes.d.ts +24 -0
- package/dist/lib/retired-deployment-modes.d.ts.map +1 -0
- package/dist/lib/transcriber.d.ts +60 -0
- package/dist/lib/transcriber.d.ts.map +1 -1
- package/dist/mcp/index.js +140 -56
- package/dist/server/cloud-config.d.ts +25 -3
- package/dist/server/cloud-config.d.ts.map +1 -1
- package/dist/server/cloud-readiness.d.ts.map +1 -1
- package/dist/server/cloud.d.ts +8 -7
- package/dist/server/cloud.d.ts.map +1 -1
- package/dist/server/index.js +187 -79
- package/dist/server/migrate-command.d.ts.map +1 -1
- package/dist/server/serve.d.ts.map +1 -1
- package/dist/storage.d.ts +2 -2
- package/dist/storage.d.ts.map +1 -1
- package/dist/storage.js +108 -49
- package/dist/store.d.ts +1 -1
- package/dist/store.d.ts.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.d.ts.map +1 -1
- package/package.json +25 -6
- package/scripts/build_native_fs_guard.sh +15 -2
- package/scripts/ci-linux-suite.ts +403 -0
- package/scripts/ci-native-build.ts +402 -0
- package/scripts/enforce_identity_migration.sh +110 -0
- package/scripts/generate-sdk.ts +1 -1
- package/scripts/install_macos_app.sh +138 -24
- package/scripts/macos_artifact.ts +205 -33
- package/scripts/native/realtime-settle-probe/README.md +39 -0
- package/scripts/native/realtime-settle-probe/main.swift +135 -0
- package/scripts/policy/local-only-approved-targets.txt +21 -0
- package/scripts/read_local_only_targets.sh +203 -0
- package/scripts/scan-artifact.ts +40 -0
- package/scripts/set-version.ts +145 -0
- package/scripts/vacuity-manifest-gen.ts +96 -0
- package/scripts/vacuity-manifests/enhancement-screen-mirror.tsv +44 -0
- package/scripts/vacuity-manifests/install-chain.tsv +13 -0
- package/scripts/vacuity-manifests/must-survive-controls.tsv +10 -0
- package/scripts/vacuity-manifests/reorder.tsv +4 -0
- package/scripts/vacuity-manifests/source-side.tsv +15 -0
- package/scripts/vacuity-manifests/variable-operands.tsv +16 -0
- package/scripts/vacuity-manifests/version-sites.tsv +61 -0
- package/scripts/vacuity-mutation-battery.ts +266 -0
- package/src/native/Recordings/App/MenuBarStatusView.swift +14 -7
- package/src/native/Recordings/App/RuntimeSmoke.swift +5 -1
- package/src/native/Recordings/App/SidebarView.swift +1 -1
- package/src/native/Recordings/Package.swift +24 -5
- package/src/native/Recordings/RecordingsLib/EnhancementScreen.swift +91 -0
- package/src/native/Recordings/RecordingsLib/FnKeyMonitor.swift +27 -4
- package/src/native/Recordings/RecordingsLib/Info.plist +2 -2
- package/src/native/Recordings/RecordingsLib/MenuBarPresentation.swift +56 -3
- package/src/native/Recordings/RecordingsLib/NativePCMRecorder.swift +13 -0
- package/src/native/Recordings/RecordingsLib/PasteDeliveryVerification.swift +460 -0
- package/src/native/Recordings/RecordingsLib/RealtimeTranscriptionClient.swift +41 -7
- package/src/native/Recordings/RecordingsLib/RecordingAttemptAlert.swift +36 -0
- package/src/native/Recordings/RecordingsLib/RecordingEngine.swift +1071 -75
- package/src/native/Recordings/RecordingsLib/SettingsView.swift +47 -3
- package/src/native/Recordings/RecordingsLib/VoiceShortcuts.swift +11 -6
- package/src/native/Recordings/RecordingsTests/CLIRunnerTests.swift +3 -3
- package/src/native/Recordings/RecordingsTests/EnhancementScreenTests.swift +191 -0
- package/src/native/Recordings/RecordingsTests/MenuBarPresentationTests.swift +144 -4
- package/src/native/Recordings/RecordingsTests/PasteDeliveryVerificationTests.swift +463 -0
- package/src/native/Recordings/RecordingsTests/PasteTargetTests.swift +33 -20
- package/src/native/Recordings/RecordingsTests/ProjectStoreTests.swift +1 -1
- package/src/native/Recordings/RecordingsTests/RealtimeSettleBenchmark.swift +200 -0
- package/src/native/Recordings/RecordingsTests/RealtimeSettleBudgetTests.swift +80 -0
- package/src/native/Recordings/RecordingsTests/RealtimeTranscriptionTests.swift +22 -4
- package/src/native/Recordings/RecordingsTests/RecordingEngineDeliveryTests.swift +12 -6
- package/src/native/Recordings/RecordingsTests/RecordingEngineHomeIsolationTests.swift +67 -0
- package/src/native/Recordings/RecordingsTests/RecordingStartGateTests.swift +12 -3
- package/src/native/Recordings/RecordingsTests/RecordingStartTimingTests.swift +207 -11
- package/src/native/Recordings/RecordingsTests/SpeechIntentTests.swift +17 -6
- package/src/native/Recordings/RecordingsTests/TestHomeDirectory.swift +23 -0
- package/src/native/Recordings/Updater/Broker/ApplicationProcessQuiescence.swift +7 -1
- package/src/native/Recordings/Updater/Broker/AtomicActivation.swift +10 -10
- package/src/native/Recordings/Updater/Broker/BrokerMain.swift +5 -1
- package/src/native/Recordings/Updater/Broker/HostOSProductVersion.swift +1 -0
- package/src/native/Recordings/Updater/Broker/PeerIdentity.swift +59 -3
- package/src/native/Recordings/Updater/Protocol/UpdateProtocol.swift +21 -2
- package/src/native/Recordings/Updater/ProtocolTests/ExpectationIntegrityTests.swift +60 -0
- package/src/native/Recordings/Updater/ProtocolTests/UpdateXPCInterfaceTests.swift +113 -0
- package/src/native/Recordings/Updater/VerifierLauncher/RecordingsVerifierLauncher.c +106 -5
- package/src/native/Recordings/build.sh +75 -9
- package/scripts/native/prebuilds/darwin-universal/recordings_fs_guard.node +0 -0
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generates the test-literal mutation manifest for the vacuity battery (OPE41-00029).
|
|
3
|
+
*
|
|
4
|
+
* This exists instead of a committed manifest of byte offsets. Offsets are correct for exactly one
|
|
5
|
+
* revision of the test files: the first commit that adds a comment above a converted site shifts
|
|
6
|
+
* every offset below it, and the battery then reports MUTATION-NOT-APPLIED for rows that were fine.
|
|
7
|
+
* That happened — 66 of 176 rows went stale in one commit — and while the driver refuses to score a
|
|
8
|
+
* mutation it could not apply, a stale manifest still costs a full run and reads like a coverage
|
|
9
|
+
* hole. Generating from the current tree removes the failure mode rather than documenting it.
|
|
10
|
+
*
|
|
11
|
+
* For every `expectOrder` / `sliceBetween` / `sliceBetweenUnique` call site it emits one `corrupt`
|
|
12
|
+
* row per string-literal operand: append a sentinel to that literal in the test file, which is
|
|
13
|
+
* equivalent — from the assertion's point of view — to deleting the needle from the source, but
|
|
14
|
+
* needs no map from haystack variable to source path and leaves the script under test byte-identical
|
|
15
|
+
* so the run still succeeds and only the converted assertion can fail.
|
|
16
|
+
*
|
|
17
|
+
* Operands that are `const` variables rather than literals are NOT emitted here; those are covered
|
|
18
|
+
* by the source-side rows in scripts/vacuity-manifests/variable-operands.tsv.
|
|
19
|
+
*
|
|
20
|
+
* Usage: bun scripts/vacuity-manifest-gen.ts > /tmp/corrupt-sites.tsv
|
|
21
|
+
*/
|
|
22
|
+
import { readFileSync } from "node:fs";
|
|
23
|
+
import { Glob } from "bun";
|
|
24
|
+
|
|
25
|
+
const HELPERS = /\b(expectOrder|sliceBetweenUnique|sliceBetween)\s*\(/g;
|
|
26
|
+
|
|
27
|
+
/** The nearest enclosing `it(...)` / `test(...)` name, used as the `-t` filter. */
|
|
28
|
+
function enclosingTest(source: string, position: number): string | undefined {
|
|
29
|
+
let best: string | undefined;
|
|
30
|
+
const pattern = /\b(?:it|test)\s*\(\s*(["'`])((?:\\.|(?!\1).)*)\1/g;
|
|
31
|
+
let match: RegExpExecArray | null;
|
|
32
|
+
while ((match = pattern.exec(source))) {
|
|
33
|
+
if (match.index >= position) break;
|
|
34
|
+
best = match[2];
|
|
35
|
+
}
|
|
36
|
+
return best;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Top-level string-literal arguments of a call, as [offset-of-inner-text, raw-inner-text]. */
|
|
40
|
+
function literalArguments(source: string, openParen: number): Array<[number, string]> {
|
|
41
|
+
const found: Array<[number, string]> = [];
|
|
42
|
+
let index = openParen + 1;
|
|
43
|
+
let depth = 1;
|
|
44
|
+
while (index < source.length && depth > 0) {
|
|
45
|
+
const character = source[index];
|
|
46
|
+
if (character === "(") depth++;
|
|
47
|
+
else if (character === ")") depth--;
|
|
48
|
+
else if (character === '"' || character === "'" || character === "`") {
|
|
49
|
+
const quote = character;
|
|
50
|
+
const start = ++index;
|
|
51
|
+
while (index < source.length && source[index] !== quote) {
|
|
52
|
+
if (source[index] === "\\") index++;
|
|
53
|
+
index++;
|
|
54
|
+
}
|
|
55
|
+
if (depth === 1) found.push([start, source.slice(start, index)]);
|
|
56
|
+
}
|
|
57
|
+
index++;
|
|
58
|
+
}
|
|
59
|
+
return found;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** TSV cannot carry a real tab or newline. Backslash sequences pass through untouched, because the
|
|
63
|
+
* mutation compares the file's RAW bytes and `\n` inside a TS literal is two of them. */
|
|
64
|
+
function escapeForTsv(text: string): string {
|
|
65
|
+
return text.replace(/\t/g, "\\t").replace(/\n/g, "\\n");
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const emitted = new Set<string>();
|
|
69
|
+
const lines: string[] = [
|
|
70
|
+
"# GENERATED by scripts/vacuity-manifest-gen.ts - do not hand-edit, regenerate.",
|
|
71
|
+
"# kind\ttestFile\ttestName\ttargetFile\toffset\tliteral",
|
|
72
|
+
];
|
|
73
|
+
|
|
74
|
+
for (const path of new Glob("src/**/*.test.ts").scanSync(".")) {
|
|
75
|
+
// The guards suite calls the helpers on hand-built strings to prove they THROW. Its literals are
|
|
76
|
+
// fixtures, not needles into a real source file, so corrupting them measures nothing about the
|
|
77
|
+
// repo and only adds noise to the tally.
|
|
78
|
+
if (path.endsWith("source-assertions-guards.test.ts")) continue;
|
|
79
|
+
const source = readFileSync(path, "utf8");
|
|
80
|
+
HELPERS.lastIndex = 0;
|
|
81
|
+
let call: RegExpExecArray | null;
|
|
82
|
+
while ((call = HELPERS.exec(source))) {
|
|
83
|
+
const testName = enclosingTest(source, call.index);
|
|
84
|
+
if (!testName) continue;
|
|
85
|
+
for (const [offset, raw] of literalArguments(source, call.index + call[0].length - 1).slice(0, 2)) {
|
|
86
|
+
if (raw.length < 3) continue;
|
|
87
|
+
const key = `${path}:${offset}`;
|
|
88
|
+
if (emitted.has(key)) continue;
|
|
89
|
+
emitted.add(key);
|
|
90
|
+
lines.push(`corrupt\t${path}\t${testName}\t${path}\t${offset}\t${escapeForTsv(raw)}`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
console.log(lines.join("\n"));
|
|
96
|
+
console.error(`generated ${emitted.size} corrupt rows`);
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Re-derivation battery for the guards PR #35 introduces (todos 96546cc7).
|
|
2
|
+
#
|
|
3
|
+
# Scope: enhancer.test.ts (the PR's cross-language fixture twins) and
|
|
4
|
+
# enhancement-screen-mirror-contract.test.ts (added because the three claims below were
|
|
5
|
+
# enforced by nothing runnable on Linux). The three station-contended suites are NOT in scope;
|
|
6
|
+
# an already-red target makes every mutation look caught. The Swift suites cannot run here at
|
|
7
|
+
# all -- no Swift toolchain exists on this machine -- so every row below is a TEXT-level check.
|
|
8
|
+
#
|
|
9
|
+
# Rows marked MUST-SURVIVE are controls in the sense of must-survive-controls.tsv, so this
|
|
10
|
+
# manifest exits 1 by design and is not wired into a package.json aggregate script.
|
|
11
|
+
#
|
|
12
|
+
# --- TS side of the cross-language twin. Bound before this task; kept as regression coverage.
|
|
13
|
+
delete src/__tests__/enhancer.test.ts enhances: make this sound more professional src/lib/enhancer.ts professional| 1
|
|
14
|
+
delete src/__tests__/enhancer.test.ts passes through: draft beer is better than bottled in my opinion src/lib/enhancer.ts (?:email|message|response|reply|letter|note|text|slack|dm) 1
|
|
15
|
+
# --- SWIFT regex mirror drift. SURVIVED before enhancement-screen-mirror-contract existed:
|
|
16
|
+
# nothing on Linux read EnhancementScreen.swift, so the 'byte-identical' claim was unenforced.
|
|
17
|
+
delete src/__tests__/enhancement-screen-mirror-contract.test.ts the instruction pattern sources are byte-identical in the CLI and the Swift mirror src/native/Recordings/RecordingsLib/EnhancementScreen.swift professional| 1
|
|
18
|
+
# --- and the same drift introduced on the CLI side must break identity too, not just behaviour.
|
|
19
|
+
delete src/__tests__/enhancement-screen-mirror-contract.test.ts the instruction pattern sources are byte-identical in the CLI and the Swift mirror src/lib/enhancer.ts professional| 1
|
|
20
|
+
# --- 'shared verbatim fixture table'. SURVIVED before: the tables were the same text only by
|
|
21
|
+
# convention, and the TS fixtures exercised the TS implementation alone.
|
|
22
|
+
delete src/__tests__/enhancement-screen-mirror-contract.test.ts the cross-language fixture table is the same text in both suites src/native/Recordings/RecordingsTests/EnhancementScreenTests.swift "draft beer is better than bottled in my opinion" 1
|
|
23
|
+
# --- review F2's fail-closed contract at the PRODUCTION call site. SURVIVED before.
|
|
24
|
+
delete src/__tests__/enhancement-screen-mirror-contract.test.ts every Swift call site of shouldPasteBeforePersistence states enhanceTriggersJSON src/native/Recordings/RecordingsLib/RecordingEngine.swift ,\n enhanceTriggersJSON: processingConfiguration.enhanceTriggersJSON\n 1
|
|
25
|
+
# --- and at a test call site: these five were shipped without the argument at all, which does
|
|
26
|
+
# not compile. Swift catches that on macOS; this row makes Linux catch it too.
|
|
27
|
+
delete src/__tests__/enhancement-screen-mirror-contract.test.ts every Swift call site of shouldPasteBeforePersistence states enhanceTriggersJSON src/native/Recordings/RecordingsTests/SpeechIntentTests.swift ,\n enhanceTriggersJSON: "[]" 1
|
|
28
|
+
# --- MUST-SURVIVE: a neutral comment deletion in the CLI must not trip the fixture twins.
|
|
29
|
+
delete src/__tests__/enhancer.test.ts enhances: make this sound more professional src/lib/enhancer.ts // Check instruction patterns\n 1
|
|
30
|
+
# --- MUST-SURVIVE: nor may a comment deletion in the Swift mirror trip the identity guard --
|
|
31
|
+
# otherwise the guard is a whole-file checksum rather than an assertion about the patterns.
|
|
32
|
+
delete src/__tests__/enhancement-screen-mirror-contract.test.ts the instruction pattern sources are byte-identical in the CLI and the Swift mirror src/native/Recordings/RecordingsLib/EnhancementScreen.swift /// Deterministic local pre-screen for the CLI's enhancement decision.\n 1
|
|
33
|
+
|
|
34
|
+
# --- ADVERSARIAL REVIEW ADDITION (todos 96546cc7). The one place the mirror was LESS
|
|
35
|
+
# conservative than the CLI: `needsEnhancement` tests `lower.includes(trigger.toLowerCase())` and
|
|
36
|
+
# JS `includes("")` is true for every string, so one empty row in enhance_triggers makes the
|
|
37
|
+
# helper rewrite every transcript. Foundation's `contains("")` is FALSE, so the mirror has to
|
|
38
|
+
# special-case the row. It skipped it instead, and deleting the skip SURVIVED all three guards --
|
|
39
|
+
# the fixture twins pin transcript text, never trigger lists. Deleting the fail-closed branch now
|
|
40
|
+
# breaks the new guard's source assertion.
|
|
41
|
+
delete src/__tests__/enhancement-screen-mirror-contract.test.ts an empty configured trigger makes both implementations fail closed src/native/Recordings/RecordingsLib/EnhancementScreen.swift guard !loweredTrigger.isEmpty else { return true }\n 1
|
|
42
|
+
# --- and the CLI half of the same claim: deleting the trigger scan makes needsEnhancement stop
|
|
43
|
+
# treating an empty row as a match.
|
|
44
|
+
delete src/__tests__/enhancement-screen-mirror-contract.test.ts an empty configured trigger makes both implementations fail closed src/lib/enhancer.ts if (lower.includes(trigger.toLowerCase())) { 1
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# The "not TSV-expressible" chain markers, expressed via occurrence index instead of a line delete.
|
|
2
|
+
# A bare identifier's FIRST occurrence is the shell function DEFINITION (`name() {`), which is why a
|
|
3
|
+
# newline-free needle appeared unusable; the CALL site is simply a later occurrence.
|
|
4
|
+
delete src/__tests__/install-maintenance.test.ts orders exclusivity before recovery and the authoritative stopped-state copy scripts/install_macos_app.sh acquire_maintenance_marker 2
|
|
5
|
+
delete src/__tests__/install-maintenance.test.ts orders exclusivity before recovery and the authoritative stopped-state copy scripts/install_macos_app.sh drain_store_reader_leases 2
|
|
6
|
+
delete src/__tests__/install-maintenance.test.ts orders exclusivity before recovery and the authoritative stopped-state copy scripts/install_macos_app.sh acquire_sqlite_barrier 3
|
|
7
|
+
delete src/__tests__/install-maintenance.test.ts orders exclusivity before recovery and the authoritative stopped-state copy scripts/install_macos_app.sh acquire_sqlite_barrier 5
|
|
8
|
+
delete src/__tests__/install-maintenance.test.ts orders exclusivity before recovery and the authoritative stopped-state copy scripts/install_macos_app.sh acquire_sqlite_barrier 6
|
|
9
|
+
delete src/__tests__/install-maintenance.test.ts orders exclusivity before recovery and the authoritative stopped-state copy scripts/install_macos_app.sh stop_old_processes 3
|
|
10
|
+
delete src/__tests__/install-maintenance.test.ts orders exclusivity before recovery and the authoritative stopped-state copy scripts/install_macos_app.sh stop_old_processes 4
|
|
11
|
+
delete src/__tests__/install-maintenance.test.ts orders exclusivity before recovery and the authoritative stopped-state copy scripts/install_macos_app.sh stop_old_processes 5
|
|
12
|
+
delete src/__tests__/install-maintenance.test.ts orders exclusivity before recovery and the authoritative stopped-state copy scripts/install_macos_app.sh trap cleanup_install_coordination_and_work EXIT 1
|
|
13
|
+
delete src/__tests__/install-maintenance.test.ts orders exclusivity before recovery and the authoritative stopped-state copy scripts/install_macos_app.sh write_journal processes-stopped 1
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# DELIBERATE MUST-SURVIVE CONTROL. `options: .atomic` occurs twice in RecordingsApp.swift and only
|
|
2
|
+
# the SECOND is inside the region under test. Deleting the FIRST (the runtimeSmokeOutput evidence
|
|
3
|
+
# write, outside the region) MUST leave the test green. A battery that reports KILLED here is
|
|
4
|
+
# over-matching, and would send someone to "fix" a guard that is working correctly.
|
|
5
|
+
delete src/__tests__/macos-smoke-process-identity.test.ts uses challenge-bound completion with identity-verified failure cleanup src/native/Recordings/App/RecordingsApp.swift options: .atomic 1
|
|
6
|
+
# Deleting one of FOUR legitimate `run_swift build` invocations must NOT fail: "tests precede any
|
|
7
|
+
# build" is still true with three left. The reorder in reorder.tsv is the mutation that proves that
|
|
8
|
+
# ordering claim is live. A harness that flagged this as a survivor would be over-matching.
|
|
9
|
+
delete src/__tests__/native-release-build-hardening-contract.test.ts pins a clean full source revision before native compilation src/native/Recordings/build.sh run_swift build -c "$BUILD_CONFIGURATION" 1
|
|
10
|
+
delete src/__tests__/macos-artifact-binding.test.ts verification and durability gates precede final manifest publication scripts/macos_artifact.ts fsyncSync(descriptor) 1
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
# The semantically meaningful mutation for a 1-of-4 needle: REORDER, not delete. Deleting one of the
|
|
2
|
+
# four legitimate `run_swift build` invocations correctly survives, because "tests precede any build"
|
|
3
|
+
# is still true. Swapping the test past the first build is the mutation the assertion exists for.
|
|
4
|
+
swap src/__tests__/native-release-build-hardening-contract.test.ts pins a clean full source revision before native compilation src/native/Recordings/build.sh run_swift test -c "$BUILD_CONFIGURATION" run_swift build -c "$BUILD_CONFIGURATION"
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Source-side mutations for sites whose expectOrder operands are VARIABLES, so the offset-precise
|
|
2
|
+
# test-literal corruption cannot reach them. Occurrence indices are load-bearing.
|
|
3
|
+
delete src/__tests__/release-output-publication-contract.test.ts package notarization also verifies the log digest before stapling packaging/macos/build_release_pkg.sh SUBMITTED_PKG_SHA256="$(sha256_file "$PKG")" 1
|
|
4
|
+
delete src/__tests__/release-output-publication-contract.test.ts package notarization also verifies the log digest before stapling packaging/macos/build_release_pkg.sh "$STAGED_VERIFIER" assert-notary-log 1
|
|
5
|
+
delete src/__tests__/release-output-publication-contract.test.ts package notarization also verifies the log digest before stapling packaging/macos/build_release_pkg.sh --submitted-archive-sha256 "$SUBMITTED_PKG_SHA256" 1
|
|
6
|
+
delete src/__tests__/release-output-publication-contract.test.ts package notarization also verifies the log digest before stapling packaging/macos/build_release_pkg.sh /usr/bin/xcrun stapler staple 1
|
|
7
|
+
swap src/__tests__/release-output-publication-contract.test.ts package notarization also verifies the log digest before stapling packaging/macos/build_release_pkg.sh --submission-id --submitted-archive-sha256
|
|
8
|
+
delete src/__tests__/macos-smoke-process-identity.test.ts uses challenge-bound completion with identity-verified failure cleanup src/native/Recordings/App/RecordingsApp.swift options: .atomic 2
|
|
9
|
+
duplicate src/__tests__/macos-smoke-process-identity.test.ts uses challenge-bound completion with identity-verified failure cleanup src/native/Recordings/App/RecordingsApp.swift Darwin._exit(0) 1
|
|
10
|
+
delete src/__tests__/macos-smoke-process-identity.test.ts uses challenge-bound completion with identity-verified failure cleanup scripts/smoke_macos_app.sh cleanup() 1
|
|
11
|
+
delete src/__tests__/macos-artifact-command-pinning.test.ts production requirement digests use the Darwin-pinned codesign executable scripts/macos_artifact.ts function assertFilesystemTree( 1
|
|
12
|
+
# The two residual vacuities the semantics reviewer found. Both PASSED before the fix; both must
|
|
13
|
+
# now be KILLED. Occurrence 1 is the authoritative site in each case.
|
|
14
|
+
delete src/__tests__/install-maintenance.test.ts orders exclusivity before recovery and the authoritative stopped-state copy scripts/install_macos_app.sh write_journal processes-stopped 1
|
|
15
|
+
delete src/__tests__/tailscale-cli-resolution.test.ts keeps all test app and tool overrides after the real Darwin branch scripts/resolve_tailscale_cli.sh codesign_executable="${RECORDINGS_TEST_TAILSCALE_CODESIGN_EXECUTABLE:-/usr/bin/codesign}" 1
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
delete src/__tests__/native-release-build-hardening-contract.test.ts normalizes umask-077 staging before nested signing and finalizes data before app signing src/native/Recordings/build.sh normalize_unsigned_app_bundle_modes "$APP_DIR" 1
|
|
2
|
+
delete src/__tests__/native-release-build-hardening-contract.test.ts normalizes umask-077 staging before nested signing and finalizes data before app signing src/native/Recordings/build.sh run_bun "$SOURCE_PACKAGE_ROOT/scripts/macos_artifact.ts" provenance 1
|
|
3
|
+
delete src/__tests__/native-release-build-hardening-contract.test.ts normalizes umask-077 staging before nested signing and finalizes data before app signing src/native/Recordings/build.sh run_codesign "${APP_SIGN_ARGUMENTS[@]}" 1
|
|
4
|
+
delete src/__tests__/native-release-build-hardening-contract.test.ts normalizes umask-077 staging before nested signing and finalizes data before app signing src/native/Recordings/build.sh run_codesign "${HELPER_SIGN_ARGUMENTS[@]}" 1
|
|
5
|
+
delete src/__tests__/native-release-build-hardening-contract.test.ts normalizes umask-077 staging before nested signing and finalizes data before app signing src/native/Recordings/build.sh run_sensitive_tool "$DITTO_EXECUTABLE" "$APP_DIR" "$OUTPUT_APP_DIR" 1
|
|
6
|
+
delete src/__tests__/native-release-build-hardening-contract.test.ts pins a clean full source revision before native compilation src/native/Recordings/build.sh run_swift build -c "$BUILD_CONFIGURATION" 1
|
|
7
|
+
delete src/__tests__/native-release-build-hardening-contract.test.ts pins a clean full source revision before native compilation src/native/Recordings/build.sh run_swift test -c "$BUILD_CONFIGURATION" 1
|
|
8
|
+
delete src/__tests__/native-release-build-hardening-contract.test.ts pins a validated macOS SDK sysroot before compiling the descriptor guard scripts/build_native_fs_guard.sh "$CLANG" "${COMMON[@]}" -arch arm64 1
|
|
9
|
+
delete src/__tests__/native-release-build-hardening-contract.test.ts pins bootstrap path tools before deriving the source tree src/native/Recordings/build.sh HOST_UNAME_EXECUTABLE="/usr/bin/uname" 1
|
|
10
|
+
delete src/__tests__/native-release-build-hardening-contract.test.ts pins bootstrap path tools before deriving the source tree src/native/Recordings/build.sh SYSTEM_DIRNAME_EXECUTABLE="/usr/bin/dirname" 1
|
|
11
|
+
delete src/__tests__/native-release-build-hardening-contract.test.ts pins bootstrap path tools before deriving the source tree src/native/Recordings/build.sh SYSTEM_PWD_EXECUTABLE="/bin/pwd" 1
|
|
12
|
+
delete src/__tests__/native-release-build-hardening-contract.test.ts ships a pinned universal descriptor guard and fails closed before installer mutation scripts/install_macos_app.sh "$BUN_EXECUTABLE" "$ARTIFACT_TOOL" native-fs-guard-check 1
|
|
13
|
+
delete src/__tests__/release-output-publication-contract.test.ts package notarization also verifies the log digest before stapling packaging/macos/build_release_pkg.sh "$STAGED_VERIFIER" assert-notary-log 1
|
|
14
|
+
delete src/__tests__/release-output-publication-contract.test.ts package notarization also verifies the log digest before stapling packaging/macos/build_release_pkg.sh --submitted-archive-sha256 "$SUBMITTED_PKG_SHA256" 1
|
|
15
|
+
delete src/__tests__/release-output-publication-contract.test.ts package notarization also verifies the log digest before stapling packaging/macos/build_release_pkg.sh SUBMITTED_PKG_SHA256="$(sha256_file "$PKG")" 1
|
|
16
|
+
delete src/__tests__/release-output-publication-contract.test.ts package notarization also verifies the log digest before stapling packaging/macos/build_release_pkg.sh /usr/bin/xcrun stapler staple 1
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# Re-derivation battery for the version-site guards (todos 96546cc7), measured on 15303a6.
|
|
2
|
+
#
|
|
3
|
+
# Scope is named explicitly and EXCLUDES the three permanently-red files on this tree
|
|
4
|
+
# (macos-app-lifecycle.test.ts, native-app-companion-contract.test.ts, config.test.ts). An
|
|
5
|
+
# already-red target makes every mutation look caught -- the inverse trap. The driver's
|
|
6
|
+
# RED-ON-CLEAN control enforces that mechanically; the scope is written down so a reader can
|
|
7
|
+
# check it without running anything.
|
|
8
|
+
#
|
|
9
|
+
# Expected verdict for every row below is a kill, EXCEPT the rows explicitly marked
|
|
10
|
+
# MUST-SURVIVE or EXPECTED-SURVIVOR. The driver reports these kills as
|
|
11
|
+
# KILLED-UNATTRIBUTED because these guards assert with `expect` rather than emitting one of the
|
|
12
|
+
# GUARD_SIGNAL messages; attribution was completed by confirming each failure lands on the
|
|
13
|
+
# guard's own assertion line, not on an early throw. GUARD_SIGNAL was deliberately NOT widened
|
|
14
|
+
# to make this file exit 0 -- and, for the same reason must-survive-controls.tsv is not, this
|
|
15
|
+
# manifest is not wired into a package.json aggregate script.
|
|
16
|
+
#
|
|
17
|
+
# --- every version site agrees with package.json. Models 8de7068: one site bumped, the rest not.
|
|
18
|
+
duplicate src/__tests__/version-site-guard.test.ts agree in the working tree, on the version package.json declares src/version.ts .15 1
|
|
19
|
+
duplicate src/__tests__/version-site-guard.test.ts agree in the working tree, on the version package.json declares src/native/Recordings/RecordingsLib/Info.plist 0.2.15 1
|
|
20
|
+
duplicate src/__tests__/version-site-guard.test.ts agree in the working tree, on the version package.json declares src/native/Recordings/RecordingsLib/Info.plist 0.2.15 2
|
|
21
|
+
# --- a stale site is reported, not silently accepted: the mismatch filter removed.
|
|
22
|
+
delete src/__tests__/version-site-guard.test.ts a single stale site is reported rather than silently accepted scripts/set-version.ts .filter((reading) => reading.version !== expected) 1
|
|
23
|
+
# --- applyVersion writes every site in one pass: the per-file grouping removed.
|
|
24
|
+
delete src/__tests__/version-site-guard.test.ts applyVersion rewrites every site in one pass scripts/set-version.ts .filter((candidate) => candidate.file === file) 1
|
|
25
|
+
# --- A DROPPED SITE. The writer silently stops writing a plist key the release path reads, and
|
|
26
|
+
# every site that remains still agrees. Both rows SURVIVED the original assertions
|
|
27
|
+
# (`readings).toHaveLength(versionSites.length)` compares the array to its own source, and
|
|
28
|
+
# `not.toContain("0.0.1")` is satisfied by a key that was never written at all). They are the
|
|
29
|
+
# reason both assertions were re-pointed to independent literal expectations.
|
|
30
|
+
delete src/__tests__/version-site-guard.test.ts applyVersion rewrites every site in one pass scripts/set-version.ts {\n file: "src/native/Recordings/RecordingsLib/Info.plist",\n label: "Info.plist CFBundleVersion",\n pattern: /(<key>CFBundleVersion<\/key>\s*<string>)([^<]+)(<\/string>)/,\n },\n 1
|
|
31
|
+
delete src/__tests__/version-site-guard.test.ts agree in the working tree, on the version package.json declares scripts/set-version.ts {\n file: "src/native/Recordings/RecordingsLib/Info.plist",\n label: "Info.plist CFBundleVersion",\n pattern: /(<key>CFBundleVersion<\/key>\s*<string>)([^<]+)(<\/string>)/,\n },\n 1
|
|
32
|
+
# --- version-format rejection, one anchor at a time. Occurrence 5 of "^" and occurrence 1 of "$"
|
|
33
|
+
# are the two anchors in assertReleaseVersion; the indices are load-bearing. The "$" row SURVIVED
|
|
34
|
+
# until a junk-suffixed case was added, because `v1.2.3` binds only the leading anchor.
|
|
35
|
+
delete src/__tests__/version-site-guard.test.ts rejects versions the release path already refuses scripts/set-version.ts ^ 5
|
|
36
|
+
delete src/__tests__/version-site-guard.test.ts rejects versions the release path already refuses scripts/set-version.ts $ 1
|
|
37
|
+
# --- prepack wiring. Nothing asserted version:check was in prepack at all; the one prepack needle
|
|
38
|
+
# in the repo (native-release-build-hardening-contract's toStartWith) was broken outright by
|
|
39
|
+
# putting version:check ahead of the fail-closed macOS gate.
|
|
40
|
+
delete src/__tests__/version-site-guard.test.ts prepack checks the version sites after the platform gate and before the build package.json bun run version:check && 1
|
|
41
|
+
# --- MUST-SURVIVE CONTROL. A semantically neutral edit to the same file must NOT trip the guard.
|
|
42
|
+
# A battery with no surviving control cannot distinguish a guard from a checksum.
|
|
43
|
+
delete src/__tests__/version-site-guard.test.ts agree in the working tree, on the version package.json declares scripts/set-version.ts /** Label used in check output. */\n 1
|
|
44
|
+
|
|
45
|
+
# --- ADVERSARIAL REVIEW ADDITIONS (todos 96546cc7).
|
|
46
|
+
#
|
|
47
|
+
# The writer's OWN validation call. Deleting it left this whole file at 5 pass / 0 fail while
|
|
48
|
+
# `bun run version:set v9.9.9` exited 0 and wrote `v9.9.9` into all four sites, after which
|
|
49
|
+
# `version:check` reported them in agreement -- nothing else in the repo pins the version's
|
|
50
|
+
# FORMAT. The first row is the guard added to catch it; the two EXPECTED-SURVIVOR rows are the
|
|
51
|
+
# same mutation against the two pre-existing tests, kept because they are the measurement that
|
|
52
|
+
# neither one ever bound the writer.
|
|
53
|
+
delete src/__tests__/version-site-guard.test.ts the writer refuses a version the release path refuses, and writes nothing scripts/set-version.ts assertReleaseVersion(version);\n 1
|
|
54
|
+
# EXPECTED-SURVIVOR
|
|
55
|
+
delete src/__tests__/version-site-guard.test.ts rejects versions the release path already refuses scripts/set-version.ts assertReleaseVersion(version);\n 1
|
|
56
|
+
# EXPECTED-SURVIVOR
|
|
57
|
+
delete src/__tests__/version-site-guard.test.ts applyVersion rewrites every site in one pass scripts/set-version.ts assertReleaseVersion(version);\n 1
|
|
58
|
+
#
|
|
59
|
+
# The generated SDK's version stamp -- a fifth copy of the release version that `versionSites`
|
|
60
|
+
# deliberately does not write -- goes stale again.
|
|
61
|
+
delete src/__tests__/version-site-guard.test.ts the generated SDK header carries the version package.json declares src/sdk/v1.generated.ts .15 1
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mutation battery for the indexOf/-1 vacuity sweep (OPE41-00029).
|
|
3
|
+
*
|
|
4
|
+
* A converted ordering assertion is only proven non-vacuous by an input that makes it FAIL. This
|
|
5
|
+
* driver takes a manifest of mutations and, for each one:
|
|
6
|
+
*
|
|
7
|
+
* 1. asserts the mutation actually CHANGED the file — a positive control, because a needle that
|
|
8
|
+
* never matched, or an occurrence index past the last match, would otherwise read as a caught
|
|
9
|
+
* mutation when nothing was mutated at all;
|
|
10
|
+
* 2. runs the target test CLEAN and requires it to PASS — without this control an already-red
|
|
11
|
+
* test makes every mutation look caught, which is the inverse trap;
|
|
12
|
+
* 3. applies the mutation and requires the named test to FAIL;
|
|
13
|
+
* 4. restores the file byte-for-byte from the copy held in memory, never from git, so sibling
|
|
14
|
+
* working-tree edits are never reverted.
|
|
15
|
+
*
|
|
16
|
+
* Mutation kinds, and why one is not enough:
|
|
17
|
+
*
|
|
18
|
+
* delete — remove the Nth occurrence of a needle. The occurrence index is not optional.
|
|
19
|
+
* `options: .atomic` occurs twice in RecordingsApp.swift and only the SECOND is
|
|
20
|
+
* inside the region under test, so deleting the first must stay GREEN; a harness
|
|
21
|
+
* that always deletes the first records a false survivor and sends someone to
|
|
22
|
+
* "fix" a guard that was working. Three more needles are masked the same way by an
|
|
23
|
+
* earlier occurrence that is the helper's own DEFINITION rather than its call site.
|
|
24
|
+
* swap — exchange two needles' text so the ordering flips. An ordering assertion that
|
|
25
|
+
* survives a REORDER is the same vacuity class this sweep exists to close wearing
|
|
26
|
+
* different clothes, and it is entirely invisible to a deletion-only harness.
|
|
27
|
+
* duplicate — insert a second copy of a needle. This is what separates a uniqueness bound from
|
|
28
|
+
* a bare `toContain`: the `toContain` stays green on a duplicate, the bound does not.
|
|
29
|
+
* corrupt — replace a string literal in the TEST at an exact byte offset with a sentinel that
|
|
30
|
+
* cannot occur in any source. Equivalent, from the assertion's point of view, to
|
|
31
|
+
* deleting that needle from the source, but it needs no map from haystack variable
|
|
32
|
+
* to source path, so it covers every converted site mechanically.
|
|
33
|
+
*
|
|
34
|
+
* Needles may contain \n and \t as escapes, because several properties can only be pinned by a
|
|
35
|
+
* whole-line needle (a bare helper name matches its own definition first).
|
|
36
|
+
*
|
|
37
|
+
* Exit codes come from spawnSync status, never through a pipe. Runs are strictly serial: a sibling
|
|
38
|
+
* assertion in capture-probe.test.ts scans a shared /tmp, so concurrent suites corrupt each other.
|
|
39
|
+
*
|
|
40
|
+
* Usage: bun scripts/vacuity-mutation-battery.ts <manifest.tsv> [--only <substring>]
|
|
41
|
+
* Manifest, tab-separated: kind <TAB> testFile <TAB> testName <TAB> targetFile <TAB> a <TAB> b
|
|
42
|
+
*/
|
|
43
|
+
import { readFileSync, writeFileSync } from "node:fs";
|
|
44
|
+
import { spawnSync } from "node:child_process";
|
|
45
|
+
|
|
46
|
+
type Kind = "delete" | "swap" | "duplicate" | "corrupt";
|
|
47
|
+
type Row = { kind: Kind; testFile: string; testName: string; target: string; a: string; b: string };
|
|
48
|
+
|
|
49
|
+
const [manifestPath, ...rest] = process.argv.slice(2);
|
|
50
|
+
if (!manifestPath) {
|
|
51
|
+
console.error("usage: bun scripts/vacuity-mutation-battery.ts <manifest.tsv> [--only <substr>]");
|
|
52
|
+
process.exit(2);
|
|
53
|
+
}
|
|
54
|
+
const onlyIndex = rest.indexOf("--only");
|
|
55
|
+
const only = onlyIndex >= 0 ? rest[onlyIndex + 1] : undefined;
|
|
56
|
+
|
|
57
|
+
const rows: Row[] = readFileSync(manifestPath, "utf8")
|
|
58
|
+
.split("\n")
|
|
59
|
+
.filter((line) => line.trim() && !line.startsWith("#"))
|
|
60
|
+
.map((line, index) => {
|
|
61
|
+
const [kind, testFile, testName, target, a, b] = line.split("\t");
|
|
62
|
+
// Fail loudly on a malformed row. Redirecting the generator with `2>&1` merges its progress line
|
|
63
|
+
// into the manifest, which parsed as a row with an undefined target and crashed the driver 164
|
|
64
|
+
// rows in — after all the expensive work, and with a stack trace that named the wrong cause.
|
|
65
|
+
if (!kind || !testFile || !testName || !target) {
|
|
66
|
+
console.error(`manifest line ${index + 1} is malformed (${line.split("\t").length} fields): ${line.slice(0, 120)}`);
|
|
67
|
+
process.exit(2);
|
|
68
|
+
}
|
|
69
|
+
return { kind: kind as Kind, testFile, testName, target, a: a ?? "", b: b ?? "" };
|
|
70
|
+
})
|
|
71
|
+
.filter((r) => !only || `${r.testFile} ${r.testName} ${r.a}`.includes(only));
|
|
72
|
+
|
|
73
|
+
/** Byte offset of the nth (1-based) occurrence, or -1. */
|
|
74
|
+
function nthIndex(haystack: string, needle: string, n: number): number {
|
|
75
|
+
let at = -1;
|
|
76
|
+
for (let i = 0; i < n; i++) {
|
|
77
|
+
at = haystack.indexOf(needle, i === 0 ? 0 : at + 1);
|
|
78
|
+
if (at < 0) return -1;
|
|
79
|
+
}
|
|
80
|
+
return at;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** TSV cannot carry literal tabs or newlines, so needles arrive escaped. */
|
|
84
|
+
function unescape(text: string): string {
|
|
85
|
+
return text.replace(/\\n/g, "\n").replace(/\\t/g, "\t");
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** Apply a mutation, or return undefined — which the caller treats as a control FAILURE. */
|
|
89
|
+
function mutate(row: Row, original: string): string | undefined {
|
|
90
|
+
const a = unescape(row.a);
|
|
91
|
+
const b = unescape(row.b);
|
|
92
|
+
|
|
93
|
+
if (row.kind === "corrupt") {
|
|
94
|
+
// Deliberately NOT unescaped: this mutates the test file's RAW bytes, and a `\n` inside a
|
|
95
|
+
// TypeScript string literal is two bytes on disk. Unescaping it would move the offset and the
|
|
96
|
+
// guard below would reject every row — silently, as a control failure rather than a survivor.
|
|
97
|
+
const offset = Number(row.a);
|
|
98
|
+
if (!Number.isFinite(offset)) return undefined;
|
|
99
|
+
if (original.slice(offset, offset + row.b.length) !== row.b) return undefined;
|
|
100
|
+
return (
|
|
101
|
+
original.slice(0, offset) +
|
|
102
|
+
row.b +
|
|
103
|
+
"__OPE41_00029_ABSENT__" +
|
|
104
|
+
original.slice(offset + row.b.length)
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (row.kind === "delete") {
|
|
109
|
+
const at = nthIndex(original, a, Number(row.b || "1"));
|
|
110
|
+
if (at < 0) return undefined;
|
|
111
|
+
return original.slice(0, at) + original.slice(at + a.length);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (row.kind === "duplicate") {
|
|
115
|
+
const at = nthIndex(original, a, Number(row.b || "1"));
|
|
116
|
+
if (at < 0) return undefined;
|
|
117
|
+
return original.slice(0, at + a.length) + a + original.slice(at + a.length);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (row.kind === "swap") {
|
|
121
|
+
const atA = original.indexOf(a);
|
|
122
|
+
const atB = original.indexOf(b);
|
|
123
|
+
if (atA < 0 || atB < 0 || a === b) return undefined;
|
|
124
|
+
const firstAt = Math.min(atA, atB);
|
|
125
|
+
const secondAt = Math.max(atA, atB);
|
|
126
|
+
const [first, second] = atA < atB ? [a, b] : [b, a];
|
|
127
|
+
if (firstAt + first.length > secondAt) return undefined; // overlapping; cannot swap cleanly
|
|
128
|
+
return (
|
|
129
|
+
original.slice(0, firstAt) +
|
|
130
|
+
second +
|
|
131
|
+
original.slice(firstAt + first.length, secondAt) +
|
|
132
|
+
first +
|
|
133
|
+
original.slice(secondAt + second.length)
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
return undefined;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* `bun test -t` takes a REGEX, not a substring.
|
|
141
|
+
*
|
|
142
|
+
* A test named `rendering RecordingEngine.logResolvedTrigger()'s format string parses as expected`
|
|
143
|
+
* contains `()`, which as a pattern is an empty capture group, so the literal parentheses in the
|
|
144
|
+
* name are never matched and bun reports `matched 0 tests` and exits 1. Unescaped, that presented as
|
|
145
|
+
* RED-ON-CLEAN — a refusal to conclude rather than a false pass, but still two sites left unproven
|
|
146
|
+
* for a reason that had nothing to do with them.
|
|
147
|
+
*/
|
|
148
|
+
function escapeForTestFilter(name: string): string {
|
|
149
|
+
return name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function runTest(testFile: string, testName: string): { code: number; output: string } {
|
|
153
|
+
const filter = escapeForTestFilter(testName);
|
|
154
|
+
const result = spawnSync("bun", ["test", testFile, "-t", filter, "--timeout", "120000"], {
|
|
155
|
+
encoding: "utf8",
|
|
156
|
+
env: { ...process.env, NO_COLOR: "1" },
|
|
157
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
158
|
+
});
|
|
159
|
+
return { code: result.status ?? 1, output: `${result.stdout ?? ""}${result.stderr ?? ""}` };
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/** Did the filter select anything? 0 pass 0 fail exits 0 and would read as a clean pass. */
|
|
163
|
+
function ranSomething(output: string): boolean {
|
|
164
|
+
const p = output.match(/(\d+)\s+pass/);
|
|
165
|
+
const f = output.match(/(\d+)\s+fail/);
|
|
166
|
+
return (p ? Number(p[1]) : 0) + (f ? Number(f[1]) : 0) > 0;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
type Verdict =
|
|
170
|
+
| "KILLED"
|
|
171
|
+
| "KILLED-UNATTRIBUTED"
|
|
172
|
+
| "SURVIVED"
|
|
173
|
+
| "MUTATION-NOT-APPLIED"
|
|
174
|
+
| "RED-ON-CLEAN"
|
|
175
|
+
| "FILTER-MATCHED-NOTHING";
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* A red test is not automatically proof that the converted assertion fired.
|
|
179
|
+
*
|
|
180
|
+
* Most of these suites EXECUTE the script they also read, so deleting a needle from
|
|
181
|
+
* `install_macos_app.sh` can break the run and fail `expect(result.exitCode).toBe(0)` long before
|
|
182
|
+
* the ordering assertion is reached. The driver would see a non-zero exit and record KILLED while
|
|
183
|
+
* the assertion under test did nothing at all — a kill credited to the wrong cause is exactly the
|
|
184
|
+
* "evidence that does not prove the claim" this sweep exists to remove.
|
|
185
|
+
*
|
|
186
|
+
* So a kill only counts when the failure output carries one of the guard's own messages. Anything
|
|
187
|
+
* else is reported as KILLED-UNATTRIBUTED and proves nothing about the site.
|
|
188
|
+
*
|
|
189
|
+
* The patterns are deliberately fragments rather than whole sentences. Suites add their own named
|
|
190
|
+
* guards — "test override is missing entirely: ...", "resolver function is missing entirely: ..." —
|
|
191
|
+
* and a signal list enumerating only the helper's own wording reported those as UNATTRIBUTED, which
|
|
192
|
+
* cost two hand-attributions that the driver should have made itself.
|
|
193
|
+
*/
|
|
194
|
+
const GUARD_SIGNAL =
|
|
195
|
+
/(is missing entirely|marker is missing|marker is not unique|too small to assert anything about|is missing or out of order|no longer sanitizes)/;
|
|
196
|
+
|
|
197
|
+
function attributed(output: string): boolean {
|
|
198
|
+
return GUARD_SIGNAL.test(output);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const results: { row: Row; verdict: Verdict }[] = [];
|
|
202
|
+
const cleanCache = new Map<string, "ok" | "red" | "no-match">();
|
|
203
|
+
|
|
204
|
+
for (const [n, row] of rows.entries()) {
|
|
205
|
+
const label = `[${n + 1}/${rows.length}] ${row.kind} ${row.testFile} :: ${row.testName}`;
|
|
206
|
+
const original = readFileSync(row.target, "utf8");
|
|
207
|
+
|
|
208
|
+
const mutated = mutate(row, original);
|
|
209
|
+
if (mutated === undefined || mutated === original) {
|
|
210
|
+
results.push({ row, verdict: "MUTATION-NOT-APPLIED" });
|
|
211
|
+
console.log(`${label}\n MUTATION-NOT-APPLIED ${row.a.slice(0, 70)}`);
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// The cache records WHY a clean run was unusable, not merely that it was. Collapsing both causes
|
|
216
|
+
// into one boolean made every row after the first report RED-ON-CLEAN, so a broken `-t` filter
|
|
217
|
+
// masqueraded as a failing test and pointed the reader at the wrong thing to fix.
|
|
218
|
+
const cleanKey = `${row.testFile} ${row.testName}`;
|
|
219
|
+
if (!cleanCache.has(cleanKey)) {
|
|
220
|
+
const clean = runTest(row.testFile, row.testName);
|
|
221
|
+
cleanCache.set(
|
|
222
|
+
cleanKey,
|
|
223
|
+
!ranSomething(clean.output) ? "no-match" : clean.code === 0 ? "ok" : "red",
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
const cleanState = cleanCache.get(cleanKey);
|
|
227
|
+
if (cleanState !== "ok") {
|
|
228
|
+
const verdict: Verdict = cleanState === "no-match" ? "FILTER-MATCHED-NOTHING" : "RED-ON-CLEAN";
|
|
229
|
+
results.push({ row, verdict });
|
|
230
|
+
console.log(`${label}\n ${verdict} - no mutation verdict can be drawn here`);
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
try {
|
|
235
|
+
writeFileSync(row.target, mutated, "utf8");
|
|
236
|
+
const after = runTest(row.testFile, row.testName);
|
|
237
|
+
const verdict: Verdict =
|
|
238
|
+
after.code === 0 ? "SURVIVED" : attributed(after.output) ? "KILLED" : "KILLED-UNATTRIBUTED";
|
|
239
|
+
results.push({ row, verdict });
|
|
240
|
+
console.log(`${label}\n ${verdict} (mutated exit ${after.code}) ${row.a.slice(0, 60)}`);
|
|
241
|
+
} finally {
|
|
242
|
+
writeFileSync(row.target, original, "utf8");
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const tally = results.reduce<Record<string, number>>((acc, r) => {
|
|
247
|
+
acc[r.verdict] = (acc[r.verdict] ?? 0) + 1;
|
|
248
|
+
return acc;
|
|
249
|
+
}, {});
|
|
250
|
+
console.log(`\n==== BATTERY SUMMARY (${results.length} mutations) ====`);
|
|
251
|
+
for (const [verdict, count] of Object.entries(tally).sort()) console.log(`${verdict}: ${count}`);
|
|
252
|
+
|
|
253
|
+
const notKilled = results.filter((r) => r.verdict !== "KILLED");
|
|
254
|
+
if (notKilled.length) {
|
|
255
|
+
console.log(`\n---- NOT KILLED (${notKilled.length}) ----`);
|
|
256
|
+
for (const s of notKilled) {
|
|
257
|
+
console.log(
|
|
258
|
+
`${s.verdict}\t${s.row.kind}\t${s.row.testFile}\t${s.row.testName}\t${s.row.a.slice(0, 60)}`,
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// An instrument that always exits 0 is not a gate. Anything other than a clean kill — a survivor, a
|
|
264
|
+
// kill credited to the wrong cause, or a control that could not be applied — has to be able to fail
|
|
265
|
+
// a caller, or the summary is decoration that a script above it will happily ignore.
|
|
266
|
+
process.exit(notKilled.length === 0 ? 0 : 1);
|
|
@@ -13,8 +13,10 @@ struct MenuBarStatusLabel: View {
|
|
|
13
13
|
private var presentation: MenuBarPresentation {
|
|
14
14
|
MenuBarPresentation(
|
|
15
15
|
isRecording: store.engine.isRecording,
|
|
16
|
+
isWarmingUpCapture: store.engine.isWarmingUpCapture,
|
|
16
17
|
canStartRecording: store.engine.canStartRecording,
|
|
17
|
-
statusMessage: store.engine.statusMessage
|
|
18
|
+
statusMessage: store.engine.statusMessage,
|
|
19
|
+
blockedReason: store.engine.blockedReason
|
|
18
20
|
)
|
|
19
21
|
}
|
|
20
22
|
}
|
|
@@ -43,7 +45,7 @@ struct MenuBarStatusView: View {
|
|
|
43
45
|
.frame(maxWidth: .infinity)
|
|
44
46
|
}
|
|
45
47
|
.buttonStyle(.borderedProminent)
|
|
46
|
-
.tint(store.engine.
|
|
48
|
+
.tint(store.engine.captureIsActive ? .red : .accentColor)
|
|
47
49
|
.disabled(!presentation.primaryActionEnabled)
|
|
48
50
|
|
|
49
51
|
Divider()
|
|
@@ -73,27 +75,32 @@ struct MenuBarStatusView: View {
|
|
|
73
75
|
}
|
|
74
76
|
|
|
75
77
|
private var statusColor: Color {
|
|
76
|
-
store.engine.
|
|
78
|
+
if store.engine.captureIsActive { return .red }
|
|
79
|
+
// The popover repeats the menu-bar signal rather than relying on the caption text, which
|
|
80
|
+
// is rendered `.secondary` and reads as an aside.
|
|
81
|
+
return presentation.isBlocked ? .orange : .accentColor
|
|
77
82
|
}
|
|
78
83
|
|
|
79
84
|
private var presentation: MenuBarPresentation {
|
|
80
85
|
MenuBarPresentation(
|
|
81
86
|
isRecording: store.engine.isRecording,
|
|
87
|
+
isWarmingUpCapture: store.engine.isWarmingUpCapture,
|
|
82
88
|
canStartRecording: store.engine.canStartRecording,
|
|
83
|
-
statusMessage: store.engine.statusMessage
|
|
89
|
+
statusMessage: store.engine.statusMessage,
|
|
90
|
+
blockedReason: store.engine.blockedReason
|
|
84
91
|
)
|
|
85
92
|
}
|
|
86
93
|
|
|
87
94
|
private var recordButtonTitle: String {
|
|
88
|
-
store.engine.
|
|
95
|
+
store.engine.captureIsActive ? "Stop and Transcribe" : "Start Recording"
|
|
89
96
|
}
|
|
90
97
|
|
|
91
98
|
private var recordButtonIcon: String {
|
|
92
|
-
store.engine.
|
|
99
|
+
store.engine.captureIsActive ? "stop.fill" : "mic.fill"
|
|
93
100
|
}
|
|
94
101
|
|
|
95
102
|
private func toggleRecording() {
|
|
96
|
-
if store.engine.
|
|
103
|
+
if store.engine.captureIsActive {
|
|
97
104
|
store.engine.stopAndTranscribe()
|
|
98
105
|
} else {
|
|
99
106
|
store.engine.startRecording()
|
|
@@ -21,8 +21,12 @@ final class RuntimeSmokeProbe: ObservableObject {
|
|
|
21
21
|
var presentation: MenuBarPresentation {
|
|
22
22
|
MenuBarPresentation(
|
|
23
23
|
isRecording: phase == .recording,
|
|
24
|
+
// The smoke probe drives phases directly and never runs a real recorder, so it has
|
|
25
|
+
// no warm-up window.
|
|
26
|
+
isWarmingUpCapture: false,
|
|
24
27
|
canStartRecording: phase == .idle,
|
|
25
|
-
statusMessage: phase == .transcribing ? "Transcribing" : "Ready"
|
|
28
|
+
statusMessage: phase == .transcribing ? "Transcribing" : "Ready",
|
|
29
|
+
blockedReason: nil
|
|
26
30
|
)
|
|
27
31
|
}
|
|
28
32
|
|
|
@@ -37,7 +37,7 @@ struct SidebarView: View {
|
|
|
37
37
|
withAnimation(.spring(response: 0.3, dampingFraction: 0.85)) { store.pane = .record }
|
|
38
38
|
} label: {
|
|
39
39
|
rowLabel(icon: "mic.fill", label: "Record", count: nil,
|
|
40
|
-
selected: store.pane == .record, accentDot: store.engine.
|
|
40
|
+
selected: store.pane == .record, accentDot: store.engine.captureIsActive)
|
|
41
41
|
}
|
|
42
42
|
.buttonStyle(.plain)
|
|
43
43
|
}
|