@gotcos/glasses-server 6.91.1 → 6.93.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/.env.example +64 -2
  2. package/CHANGELOG.md +154 -29
  3. package/README.md +90 -1
  4. package/docs/active-query-jobs.md +1 -1
  5. package/docs/test-plan-6.71.7.md +1 -1
  6. package/managed-runtime-contract.json +10 -1
  7. package/package.json +2 -1
  8. package/server/lib/ask-session-id.ts +2 -1
  9. package/server/lib/claude-circuit.ts +8 -4
  10. package/server/lib/claude-text-runner.ts +113 -0
  11. package/server/lib/dictation-fillers-fixture-cases.ts +214 -0
  12. package/server/lib/dictation-fillers.ts +30 -0
  13. package/server/lib/dictation-meaning-guard.ts +47 -0
  14. package/server/lib/dictation-polish-pipeline.ts +90 -12
  15. package/server/lib/embedding-eviction.ts +60 -2
  16. package/server/lib/fuzzy-correct.ts +1 -1
  17. package/server/lib/hallucination-filter.ts +1 -1
  18. package/server/lib/imported-library-rows.ts +4 -2
  19. package/server/lib/live-cos-ask.ts +3 -2
  20. package/server/lib/mac-live-voices.ts +57 -0
  21. package/server/lib/mac-recording-asr.ts +16 -8
  22. package/server/lib/mac-speaker-projection.ts +211 -0
  23. package/server/lib/mac-stream-diarizer.ts +185 -0
  24. package/server/lib/mac-voice-assignment.ts +361 -0
  25. package/server/lib/mac-voice-labels.ts +70 -8
  26. package/server/lib/mac-window-replay.ts +271 -0
  27. package/server/lib/mac-window-voices.ts +390 -0
  28. package/server/lib/meeting-actions.ts +1 -1
  29. package/server/lib/meeting-ask-owner.ts +4 -4
  30. package/server/lib/meeting-ask-transcript-sources.ts +41 -3
  31. package/server/lib/meeting-audio-archive.ts +4 -0
  32. package/server/lib/meeting-batch-transcribe.ts +7 -1
  33. package/server/lib/meeting-context-keys.ts +6 -3
  34. package/server/lib/meeting-corrections.ts +4 -4
  35. package/server/lib/meeting-fact-check.ts +3 -3
  36. package/server/lib/meeting-parse.ts +1 -1
  37. package/server/lib/meeting-relabel-enrolment.ts +8 -8
  38. package/server/lib/meeting-relabel.ts +3 -3
  39. package/server/lib/meeting-scribe-content.ts +1 -1
  40. package/server/lib/meeting-speaker-review.ts +8 -8
  41. package/server/lib/meeting-store.ts +19 -9
  42. package/server/lib/nemotron-final.ts +114 -9
  43. package/server/lib/nemotron-live.ts +58 -6
  44. package/server/lib/phone-recording.ts +114 -0
  45. package/server/lib/prompt-edit.ts +84 -3
  46. package/server/lib/prompt-tail-guard.ts +1 -1
  47. package/server/lib/speaker-embeddings.ts +154 -28
  48. package/server/lib/speaker-name.ts +1 -1
  49. package/server/lib/stranded-session-actions.ts +7 -2
  50. package/server/lib/token-audit.ts +6 -1
  51. package/server/lib/training-audio-provenance.ts +3 -2
  52. package/server/lib/transcript-cleanup-context.ts +3 -3
  53. package/server/lib/transcript-cleanup-model.ts +1 -1
  54. package/server/lib/transcript-cleanup-spans.ts +10 -10
  55. package/server/lib/transcript-cleanup-text.ts +4 -4
  56. package/server/lib/transcript-cleanup.ts +1 -1
  57. package/server/lib/unsaved-audio-quarantine.ts +10 -1
  58. package/server/lib/voice-directory.ts +1 -1
  59. package/server/lib/voice-evidence-guard.ts +2 -1
  60. package/server/lib/voice-profile-store.ts +11 -5
  61. package/server/lib/whisper-local.ts +1 -1
  62. package/server/routes/dictation-polish-settings.ts +8 -1
  63. package/server/routes/health.ts +8 -0
  64. package/server/routes/meeting.ts +189 -19
  65. package/server/routes/meetings.ts +9 -5
  66. package/server/routes/prompt-drafts.ts +3 -2
  67. package/server/routes/prompt-edit.ts +47 -5
  68. package/server/routes/transcribe-stream.ts +140 -15
  69. package/server/routes/voice.ts +12 -4
  70. package/server/scripts/dictation-parity-6920-mutation-gate.mjs +229 -0
  71. package/server/scripts/generate-dictation-fillers-fixture.ts +18 -0
  72. package/server/scripts/mac-voice-assignment-6930-mutation-gate.mjs +225 -0
  73. package/server/scripts/mac-window-voices-6930-mutation-gate.mjs +193 -0
@@ -0,0 +1,229 @@
1
+ #!/usr/bin/env node
2
+ // 6.92.0 dictation parity Wave A mutation gate (plan r3): the keyed and slow polish breakers, the
3
+ // prewarm route on the keyed breaker, prompt edit's switch, cap, own counter, breaker, refusals and
4
+ // text runner, the filler pass's language gate and token exemptions, the options answer's
5
+ // meaningGuard, and the eval harness limits. The runner is the 6.91.0 voice-style gate's:
6
+ //
7
+ // 0. The target suites are GREEN unmutated (a gate over a red suite proves nothing).
8
+ // 1. Each target string appears EXACTLY ONCE in its file, and the mutated file differs.
9
+ // 2. The mutated file still PARSES (esbuild). One that does not is INVALID, never a kill.
10
+ // 3. A KILL counts only when the test NAMED for that mutant fails (its 5th field, a substring of
11
+ // exactly one test's "file > describe > test"). SURVIVED, MISATTRIBUTED, INVALID and CRASHED
12
+ // each fail the gate.
13
+ // 4. The file is restored byte for byte before the next mutant.
14
+ // Each mutant runs the named test's file only; the baseline runs all.
15
+ // Usage: node server/scripts/dictation-parity-6920-mutation-gate.mjs [--list | --check | --discover] [name...]
16
+ import { mkdtempSync, cpSync, readFileSync, writeFileSync, symlinkSync, rmSync, existsSync, realpathSync } from 'node:fs'
17
+ import { tmpdir } from 'node:os'
18
+ import { resolve, join, dirname, basename } from 'node:path'
19
+ import { fileURLToPath } from 'node:url'
20
+ import { spawnSync } from 'node:child_process'
21
+ import { transformSync } from 'esbuild'
22
+
23
+
24
+ const root = resolve(dirname(fileURLToPath(import.meta.url)), '../..')
25
+ const tests = ['server/lib/dictation-polish-breakers.test.ts', 'server/lib/claude-circuit.test.ts', 'server/routes/prompt-drafts.test.ts',
26
+ 'server/routes/prompt-edit.test.ts', 'server/routes/prompt-edit-g2-contract.test.ts', 'server/lib/claude-text-runner.test.ts',
27
+ 'server/lib/key-lanes-spawn-census.test.ts', 'server/lib/dictation-fillers.test.ts', 'server/lib/dictation-fillers-fixture.test.ts',
28
+ 'server/lib/dictation-meaning-guard.test.ts', 'server/routes/dictation-polish-settings.test.ts', 'server/scripts/dictation-eval-harness.test.ts',
29
+ 'server/lib/dictation-polish-budget.test.ts']
30
+ const PL = 'server/lib/dictation-polish-pipeline.ts', PD = 'server/routes/prompt-drafts.ts'
31
+ const PE = 'server/lib/prompt-edit.ts', PR = 'server/routes/prompt-edit.ts', CT = 'server/lib/claude-text-runner.ts'
32
+ const F = 'server/lib/dictation-fillers.ts', G = 'server/lib/dictation-meaning-guard.ts', R = 'server/routes/dictation-polish-settings.ts'
33
+ const FE = 'server/scripts/dictation-format-eval.ts', CE = 'server/scripts/dictation-custom-polish-eval.ts'
34
+ const BEFORE = 'const TOUCH_BEFORE = /[/.@_:#$]$/'
35
+ const AFTER = 'const TOUCH_AFTER = /^(?:[/@_#$]|[.:][\\p{L}\\p{N}])/u'
36
+ const BK = 'a filler right after a path or handle character is kept', AK = 'a filler right before a path or code character is kept'
37
+ const mutations = [
38
+ // A-W0a: an abort is neutral to the failure breaker; the slow breaker; keyed by (model, bucket group).
39
+ ['abort-neutral', PL, " if (opts.signal?.aborted) {\n // 6.92.0: the client went away", " if (false) {\n // 6.92.0: the client went away", '2 client aborts leave the failure breaker closed'],
40
+ ['abort-then-error-counts-one', PL, ' } else {\n breakers.failure.recordFailure()', ' } else {\n breakers.failure.recordFailure(); breakers.failure.recordFailure()', 'an abort followed by an error counts 1'],
41
+ ['slow-threshold-4', PL, 'envPositiveInt(env.COS_DICTATION_SLOW_BREAKER_ABORTS, 4)', 'envPositiveInt(env.COS_DICTATION_SLOW_BREAKER_ABORTS, 5)', '4 aborts with at least 4 s of model time open the slow breaker'],
42
+ ['slow-open-5-minutes', PL, 'envPositiveInt(env.COS_DICTATION_SLOW_BREAKER_MS, 300_000)', 'envPositiveInt(env.COS_DICTATION_SLOW_BREAKER_MS, 600_000)', '4 aborts with at least 4 s of model time open the slow breaker'],
43
+ ['slow-env-read', PL, 'cooldownMs: () => slowBreakerMs()', 'cooldownMs: 300_000', 'COS_DICTATION_SLOW_BREAKER_ABORTS and _MS set the threshold'],
44
+ ['slow-counted', PL, ' if (durationMs >= SLOW_ABORT_MIN_MODEL_MS) breakers.slow.recordFailure()\n', '', '4 aborts with at least 4 s of model time open the slow breaker'],
45
+ ['slow-consulted', PL, ' return breakers.failure.isOpen() || breakers.slow.isOpen()', ' return breakers.failure.isOpen()', '4 aborts with at least 4 s of model time open the slow breaker'],
46
+ ['slow-reset-on-success', PL, ' breakers.slow.recordSuccess()\n', '', 'a success between aborts resets the slow count'],
47
+ ['model-time-4s', PL, 'export const SLOW_ABORT_MIN_MODEL_MS = 4_000', 'export const SLOW_ABORT_MIN_MODEL_MS = 0', 'an abort under 4 s of model time does not count'],
48
+ ['model-time-inclusive', PL, ' if (durationMs >= SLOW_ABORT_MIN_MODEL_MS)', ' if (durationMs > SLOW_ABORT_MIN_MODEL_MS)', 'an abort under 4 s of model time does not count'],
49
+ ['aborted-audit-row', PL, " polishOutcome: 'aborted',\n", '', 'an abort writes an audit row with outcome aborted'],
50
+ ['bucket-key-meeting', PL, " return bucket === 'meeting' ? 'meeting' : 'dictation'", " return 'dictation'", 'a meeting failure leaves the message breaker closed'],
51
+ ['bucket-key-drafts-with-messages', PL, " return bucket === 'meeting' ? 'meeting' : 'dictation'", " return bucket === 'message' ? 'dictation' : 'meeting'", 'message and prompt_draft are one group'],
52
+ ['prewarm-keyed', PD, ' if (polishBreakerOpen(model, surface)) return res.status(409)', " if (polishBreakerOpen(model, 'message')) return res.status(409)", 'prewarm refuses when it could not be used'],
53
+ // A-W0b: prompt edit's switch, cap, own counter, breaker, refusals and runner.
54
+ ['edit-switch', PE, " if (!promptEditEnabled(env)) return 'off'", " if (false) return 'off'", 'COS_PROMPT_EDIT=0 answers 503'],
55
+ ['edit-switch-default-on', PE, ' return readSwitch(env.COS_PROMPT_EDIT, true)', ' return readSwitch(env.COS_PROMPT_EDIT, false)', 'COS_PROMPT_EDIT=0 answers 503'],
56
+ ['edit-cap', PE, " if (promptEditCountToday() >= promptEditDailyCap(env)) return 'cap'", " if (false) return 'cap'", 'the daily cap (COS_PROMPT_EDIT_DAILY_CAP'],
57
+ ['edit-cap-at-limit', PE, " if (promptEditCountToday() >= promptEditDailyCap(env)) return 'cap'", " if (promptEditCountToday() > promptEditDailyCap(env)) return 'cap'", 'the daily cap (COS_PROMPT_EDIT_DAILY_CAP'],
58
+ ['edit-cap-default-200', PE, 'export const PROMPT_EDIT_DEFAULT_DAILY_CAP = 200', 'export const PROMPT_EDIT_DEFAULT_DAILY_CAP = 300', 'the daily cap (COS_PROMPT_EDIT_DAILY_CAP'],
59
+ ['edit-call-counted', PR, ' recordPromptEditCall()\n', '', 'the daily cap (COS_PROMPT_EDIT_DAILY_CAP'],
60
+ ['edit-own-counter', PE, "export const PROMPT_EDIT_COUNT_FILE = '.prompt_edit_count.json'", "export const PROMPT_EDIT_COUNT_FILE = '.dictation_autoclean_count.json'", 'the daily cap (COS_PROMPT_EDIT_DAILY_CAP'],
61
+ ['edit-breaker', PE, " if (promptEditBreaker.isOpen()) return 'paused'", " if (false) return 'paused'", '3 CLI failures open the breaker'],
62
+ ['edit-breaker-3', PE, 'export const PROMPT_EDIT_BREAKER_FAILURES = 3', 'export const PROMPT_EDIT_BREAKER_FAILURES = 4', '3 CLI failures open the breaker'],
63
+ ['edit-breaker-counts', PR, ' promptEditBreaker.recordFailure()\n', '', '3 CLI failures open the breaker'],
64
+ ['edit-breaker-success', PR, ' promptEditBreaker.recordSuccess()\n', '', '3 CLI failures open the breaker'],
65
+ ['edit-abort-neutral', PR, ' if (abort.signal.aborted) {', ' if (false) {', 'a client abort is neutral to the breaker'],
66
+ ['edit-refusal-text-plain', PR, " res.status(status).type('text/plain').send(body)", ' res.status(status).json({ error: body })', 'COS_PROMPT_EDIT=0 answers 503'],
67
+ ['edit-500-no-tails', PR, " refuse(res, 'failed')\n }\n})", ' res.status(500).json({ error: String(err) })\n }\n})', 'a failure answers 500'],
68
+ ['edit-refusal-short', PE, " cap: { status: 429, body: 'Edit limit reached' },", " cap: { status: 429, body: 'Prompt edit paused until tomorrow (daily limit).' },", 'cap: the lens footer is 40 characters or fewer'],
69
+ ['edit-runs-isolated', PE, ' return runClaudeText(buildPromptEditPrompt(input), {', ' return spawnClaudeText(buildPromptEditPrompt(input), {', 'prompt edit runs through the tool-less text runner'],
70
+ ['runner-no-tools', CT, " '--tools', '', '--settings'", " '--settings'", 'isolation pin'],
71
+ ['runner-scratch-cwd', CT, 'env: claudeTextEnv(binary.path), cwd: workspace,', 'env: claudeTextEnv(binary.path), cwd: process.cwd(),', 'runs the binary with that argv in a scratch directory'],
72
+ ['runner-keeps-env', CT, ' const out = { ...env }\n delete out.CLAUDECODE', " const out = { ...env }\n delete out.ANTHROPIC_API_KEY\n delete out.CLAUDECODE", 'keeps the environment as spawnClaudeText did'],
73
+ // A-W10: the language gate.
74
+ ['gate-wired', F, ' return fillerTextLooksEnglish(text) ? stripDisfluencies(text) : text', ' return stripDisfluencies(text)', 'the server filler step is gated'],
75
+ // QA fix: the filler gate has its own function; the meaning guard keeps 6.91.1's looksEnglish.
76
+ ['gate-short-replies', G, ' const english = tokens.filter(t => ENGLISH.has(t.low) || SHORT_REPLY_ENGLISH.has(t.low)).length', ' const english = tokens.filter(t => ENGLISH.has(t.low)).length', 'a short English reply or command counts as English evidence'],
77
+ ['gate-long-text-ratio', G, ' if (!looksEnglish(text)) return false\n', '', 'a short text with foreign function words is not English'],
78
+ ['guard-names-keep-6911-language', G, ' const withNames = looksEnglish(cleaned)', ' const withNames = fillerTextLooksEnglish(cleaned)', '"um thanks Dana" polished to "Thanks." is refused for the name'],
79
+ // QA S1: the abort's model time is stamped in the listener.
80
+ ['abort-time-stamped', PL, ' const durationMs = (abortedAt ?? Date.now()) - startedAt', ' const durationMs = Date.now() - startedAt', 'the model time is stamped at the abort'],
81
+ // Forces the gate open (an English word ahead of the text, sliced off after): the pipeline must call the gated step.
82
+ ['gate-in-pipeline', PL, 'cleaned = stripFillersIfEnglish(cleaned)', "cleaned = stripFillersIfEnglish('the ' + cleaned).slice(4)", 'a German or Portuguese message reaches the model as said'],
83
+ ['gate-foreign-words', G, ' if (foreign > 0) return english > foreign', ' if (false) return english > foreign', 'a short text with foreign function words is not English'],
84
+ ['gate-all-caps-not-foreign', G, ' && !(t.raw.length > 1 && t.raw === t.raw.toUpperCase())).length', ').length', 'a short text with foreign function words is not English'],
85
+ ['gate-inline-um-er', G, ' if (inlineAmbiguous) return english > 0', ' if (false) return english > 0', 'an inline um or er needs an English function word'],
86
+ ['gate-paused-is-hesitation', G, '&& i + 1 < tokens.length && !tokens[i + 1].pause)', '&& i + 1 < tokens.length)', 'an inline um or er needs an English function word'],
87
+ // A-W10: the token exemptions, one character at a time.
88
+ ['touch-before-slash', F, BEFORE, 'const TOUCH_BEFORE = /[.@_:#$]$/', `${BK} (before /)`],
89
+ ['touch-before-dot', F, BEFORE, 'const TOUCH_BEFORE = /[/@_:#$]$/', `${BK} (before .)`],
90
+ ['touch-before-at', F, BEFORE, 'const TOUCH_BEFORE = /[/._:#$]$/', `${BK} (before @)`],
91
+ ['touch-before-underscore', F, BEFORE, 'const TOUCH_BEFORE = /[/.@:#$]$/', `${BK} (before _)`],
92
+ ['touch-before-colon', F, BEFORE, 'const TOUCH_BEFORE = /[/.@_#$]$/', `${BK} (before :)`],
93
+ ['touch-before-hash', F, BEFORE, 'const TOUCH_BEFORE = /[/.@_:$]$/', `${BK} (before #)`],
94
+ ['touch-before-dollar', F, BEFORE, 'const TOUCH_BEFORE = /[/.@_:#]$/', `${BK} (before $)`],
95
+ ['touch-before-wired', F, ' || TOUCH_BEFORE.test(out + before)\n', '', `${BK} (before /)`],
96
+ ['touch-after-slash', F, AFTER, 'const TOUCH_AFTER = /^(?:[@_#$]|[.:][\\p{L}\\p{N}])/u', `${AK} (after /)`],
97
+ ['touch-after-at', F, AFTER, 'const TOUCH_AFTER = /^(?:[/_#$]|[.:][\\p{L}\\p{N}])/u', `${AK} (after @)`],
98
+ ['touch-after-underscore', F, AFTER, 'const TOUCH_AFTER = /^(?:[/@#$]|[.:][\\p{L}\\p{N}])/u', `${AK} (after _)`],
99
+ ['touch-after-hash', F, AFTER, 'const TOUCH_AFTER = /^(?:[/@_$]|[.:][\\p{L}\\p{N}])/u', `${AK} (after #)`],
100
+ ['touch-after-dollar', F, AFTER, 'const TOUCH_AFTER = /^(?:[/@_#]|[.:][\\p{L}\\p{N}])/u', `${AK} (after $)`],
101
+ ['touch-after-dot', F, AFTER, 'const TOUCH_AFTER = /^(?:[/@_#$]|[:][\\p{L}\\p{N}])/u', `${AK} (after . and a letter)`],
102
+ ['touch-after-colon', F, AFTER, 'const TOUCH_AFTER = /^(?:[/@_#$]|[.][\\p{L}\\p{N}])/u', `${AK} (after : and a digit)`],
103
+ ['touch-after-needs-alnum', F, AFTER, 'const TOUCH_AFTER = /^(?:[/@_#$]|[.:])/u', 'a sentence-final period, ellipsis or colon is not a token character'],
104
+ ['touch-after-wired', F, ' || TOUCH_AFTER.test(after)\n', '', `${AK} (after /)`],
105
+ // A-W2: the options answer.
106
+ ['options-meaning-guard-flag', R, ' meaningGuard: meaningGuardEnabled(),', ' meaningGuard: true,', 'the options answer says meaningGuard'],
107
+ ['options-style-pairs', R, 'filter(model => styleAndRegisterApply(model, effectiveByModel[model]))', "filter(model => model === 'sonnet')", 'the options answer says meaningGuard'],
108
+ // A-W0c: the harness defaults.
109
+ ['format-default-fits', FE, 'export const FORMAT_DEFAULT_MAX_CALLS = 50', 'export const FORMAT_DEFAULT_MAX_CALLS = 40', 'the format default --max-calls is at least'],
110
+ ['custom-default-fits', CE, 'export const CUSTOM_POLISH_DEFAULT_MAX_CALLS = 205', 'export const CUSTOM_POLISH_DEFAULT_MAX_CALLS = 200', 'the custom-polish default fits its cap'],
111
+ ]
112
+
113
+ const DISCOVER_FILES = {}
114
+
115
+ const args = process.argv.slice(2)
116
+ if (args.includes('--list')) { for (const [name, , , , check] of mutations) console.log(`${name} <- ${check ?? '(no check)'}`); process.exit(0) }
117
+ const names = args.filter(a => !a.startsWith('--'))
118
+ const selected = names.length ? mutations.filter(([name]) => names.includes(name)) : mutations
119
+ if (names.length && selected.length !== names.length) throw new Error('Unknown mutation name in: ' + args.join(' '))
120
+ const discover = args.includes('--discover')
121
+
122
+ let badTargets = 0
123
+ for (const [name, file, find, replacement] of mutations) {
124
+ const text = readFileSync(join(root, file), 'utf8'), count = text.split(find).length - 1
125
+ if (count !== 1 || find === replacement) { badTargets++; console.log(`${name}: ${count} matches in ${file}`) }
126
+ }
127
+ if (badTargets) { console.log(`${mutations.length - badTargets} of ${mutations.length} targets unique`); process.exit(1) }
128
+
129
+ /** Parse only (esbuild, no type check): a mutant that does not parse is INVALID. */
130
+ function parses(file, text) {
131
+ try { transformSync(text, { loader: file.endsWith('.ts') ? 'ts' : 'js', format: 'esm', logLevel: 'silent' }); return null }
132
+ catch (err) { return String(err?.errors?.[0]?.text ?? err?.message ?? err).split('\n')[0] }
133
+ }
134
+
135
+ // The parse check itself must be able to fail: the 31c59dd gate counted exactly this shape (an object
136
+ // spread with a bare object in it) as a kill.
137
+ if (parses('x.ts', 'const used = {\n ...a,\n { polishRegister: register },\n}') === null || parses('x.ts', 'const n: number = 1') !== null) {
138
+ throw new Error('parse self-test failed')
139
+ }
140
+ console.log('parse self-test: rejects a bare object in a spread list, accepts plain TypeScript')
141
+
142
+ const scratch = mkdtempSync(join(tmpdir(), 'dictation-parity-6920-mutations-'))
143
+ // --check and --discover end with process.exit, which skips `finally`: remove the copy on exit too.
144
+ process.on('exit', () => rmSync(scratch, { recursive: true, force: true }))
145
+ try {
146
+ const skip = new Set(['data', 'models', 'certs', 'node_modules'])
147
+ cpSync(join(root, 'server'), join(scratch, 'server'), { recursive: true, filter: src => !(skip.has(basename(src)) && dirname(src) === join(root, 'server')) })
148
+ cpSync(join(root, 'shared'), join(scratch, 'shared'), { recursive: true })
149
+ cpSync(join(root, 'server/models/silero_vad.onnx'), join(scratch, 'server/models/silero_vad.onnx'))
150
+ cpSync(join(root, 'bin'), join(scratch, 'bin'), { recursive: true })
151
+ cpSync(join(root, 'vitest.config.ts'), join(scratch, 'vitest.config.ts'))
152
+ cpSync(join(root, 'package.json'), join(scratch, 'package.json'))
153
+ cpSync(join(root, 'managed-runtime-contract.json'), join(scratch, 'managed-runtime-contract.json'))
154
+ // 6.92.0: dictation-polish-budget.test.ts checks every key is documented there.
155
+ cpSync(join(root, '.env.example'), join(scratch, '.env.example'))
156
+ symlinkSync(join(root, 'node_modules'), join(scratch, 'node_modules'), 'dir')
157
+ const report = join(scratch, 'vitest-report.json')
158
+ const scratchReal = realpathSync(scratch)
159
+ /** Run vitest on these files; the per-test results from its JSON report, or null when there is none. */
160
+ const run = files => {
161
+ rmSync(report, { force: true })
162
+ const result = spawnSync(process.execPath, [join(root, 'node_modules/vitest/vitest.mjs'), 'run', '--maxWorkers=1', '--reporter=json', `--outputFile=${report}`, ...files], {
163
+ cwd: scratch,
164
+ env: {
165
+ PATH: process.env.PATH, HOME: scratch, COS_DATA_DIR: join(scratch, 'data'), COS_PROFILE_PATH: join(scratch, 'data', 'profile.json'),
166
+ COS_MAINTENANCE_GATE_PATH: join(scratch, 'gate', 'maintenance-gate.json'),
167
+ },
168
+ encoding: 'utf8', timeout: 300000,
169
+ })
170
+ if (result.error || !existsSync(report)) return { status: result.status, error: result.error?.message ?? 'no report', tests: null }
171
+ const json = JSON.parse(readFileSync(report, 'utf8'))
172
+ const tests = json.testResults.flatMap(file => {
173
+ // The report names files by real path (/private/var/... for a /var/... temp dir).
174
+ const rel = file.name.startsWith(scratchReal + '/') ? file.name.slice(scratchReal.length + 1)
175
+ : file.name.startsWith(scratch + '/') ? file.name.slice(scratch.length + 1) : file.name
176
+ // A file that failed to load has no tests: report it as one failed entry, never as a kill.
177
+ if (!file.assertionResults.length && file.status === 'failed') return [{ name: `${rel} (file failed to load)`, status: 'failed' }]
178
+ return file.assertionResults.map(a => ({ name: [rel, ...a.ancestorTitles, a.title].join(' > '), status: a.status }))
179
+ })
180
+ return { status: result.status, error: null, tests }
181
+ }
182
+
183
+ const baseline = run(tests)
184
+ if (baseline.status !== 0 || !baseline.tests || baseline.tests.some(t => t.status === 'failed')) {
185
+ const failing = (baseline.tests ?? []).filter(t => t.status === 'failed').map(t => t.name)
186
+ throw new Error('Baseline failed; a gate over a red suite proves nothing: ' + (baseline.error ?? failing.join(' | ')))
187
+ }
188
+ const passed = baseline.tests.filter(t => t.status === 'passed').length
189
+ console.log(`baseline green (${passed} tests passed in ${tests.length} files)`)
190
+
191
+ // Every named check names exactly one test that ran green in the baseline.
192
+ if (!discover) {
193
+ let unnamed = 0
194
+ for (const [name, , , , check] of selected) {
195
+ const hits = typeof check === 'string' && check ? baseline.tests.filter(t => t.name.includes(check)) : []
196
+ if (hits.length !== 1 || hits[0].status !== 'passed') { unnamed++; console.log(`${name}: check ${JSON.stringify(check)} names ${hits.length} tests`) }
197
+ }
198
+ if (unnamed) throw new Error(`${unnamed} mutants have no single named check`)
199
+ if (args.includes('--check')) { console.log(`${mutations.length} of ${mutations.length} targets unique; ${selected.length} named checks each name one green test`); process.exit(0) }
200
+ }
201
+
202
+ const tally = { KILLED: [], SURVIVED: [], MISATTRIBUTED: [], INVALID: [], CRASHED: [] }
203
+ for (const [name, file, find, replacement, check] of selected) {
204
+ const target = join(scratch, file), original = readFileSync(target, 'utf8')
205
+ if (original.split(find).length !== 2) throw new Error(`Mutation ${name} does not match exactly once in ${file}`)
206
+ const mutated = original.replace(find, () => replacement)
207
+ if (mutated === original) throw new Error(`Mutation ${name} did not change ${file}`)
208
+ const parseError = parses(file, mutated)
209
+ if (parseError) { tally.INVALID.push(name); console.log(`INVALID ${name} (does not parse: ${parseError})`); continue }
210
+ writeFileSync(target, mutated)
211
+ const checkFile = discover ? null : baseline.tests.find(t => t.name.includes(check)).name.split(' > ')[0]
212
+ let result = run(discover ? DISCOVER_FILES[file] ?? tests : [checkFile])
213
+ if (discover && result.tests && !result.tests.some(t => t.status === 'failed') && DISCOVER_FILES[file]) result = run(tests)
214
+ writeFileSync(target, original)
215
+ if (readFileSync(target, 'utf8') !== original) throw new Error(`Restore failed for ${file} after ${name}`)
216
+ if (!result.tests) { tally.CRASHED.push(name); console.log(`CRASHED ${name} (${result.error})`); continue }
217
+ const failed = result.tests.filter(t => t.status === 'failed').map(t => t.name)
218
+ if (discover) { console.log(`${failed.length ? 'FAILS' : 'SURVIVED'} ${name}${failed.map(f => '\n ' + f).join('')}`); (failed.length ? tally.KILLED : tally.SURVIVED).push(name); continue }
219
+ if (failed.some(f => f.includes(check))) { tally.KILLED.push(name); console.log(`KILLED ${name} <- ${check}`); continue }
220
+ if (!failed.length) { tally.SURVIVED.push(name); console.log(`SURVIVED ${name} (named: ${check})`); continue }
221
+ tally.MISATTRIBUTED.push(name); console.log(`MISATTRIBUTED ${name} (named: ${check}; failed: ${failed.slice(0, 2).join(' | ')})`)
222
+ }
223
+ const bad = selected.length - tally.KILLED.length
224
+ console.log(`${tally.KILLED.length} killed by their named check, ${tally.SURVIVED.length} survived, ${tally.MISATTRIBUTED.length} misattributed, `
225
+ + `${tally.INVALID.length} invalid, ${tally.CRASHED.length} crashed`)
226
+ if (discover) process.exit(0)
227
+ if (bad) { for (const k of ['SURVIVED', 'MISATTRIBUTED', 'INVALID', 'CRASHED']) if (tally[k].length) console.log(`${k}: ${tally[k].join(', ')}`); process.exitCode = 1 }
228
+ else console.log(`PASS dictation parity 6.92.0 mutation gate: ${selected.length} of ${selected.length}, each killed by its named check, green baseline`)
229
+ } finally { rmSync(scratch, { recursive: true, force: true }) }
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env -S npx tsx
2
+ // 6.92.0 (plan r3, A-W10): writes server/lib/__fixtures__/dictation-fillers.json from the server's own
3
+ // filler functions (lib/dictation-fillers-fixture-cases.ts). No model calls, no network.
4
+ // Usage: npx tsx server/scripts/generate-dictation-fillers-fixture.ts
5
+ import { createHash } from 'node:crypto'
6
+ import { writeFileSync } from 'node:fs'
7
+ import { fileURLToPath } from 'node:url'
8
+ import { FILLER_FIXTURE_CASES, fillerFixtureOutput, renderFillerFixture } from '../lib/dictation-fillers-fixture-cases.js'
9
+
10
+ const wrong = FILLER_FIXTURE_CASES.filter(c => fillerFixtureOutput(c.scope, c.input) !== c.want)
11
+ if (wrong.length) {
12
+ for (const c of wrong) console.error(`${c.id}: want ${JSON.stringify(c.want)}, got ${JSON.stringify(fillerFixtureOutput(c.scope, c.input))}`)
13
+ process.exit(1)
14
+ }
15
+ const text = renderFillerFixture()
16
+ const target = fileURLToPath(new URL('../lib/__fixtures__/dictation-fillers.json', import.meta.url))
17
+ writeFileSync(target, text)
18
+ console.log(`${FILLER_FIXTURE_CASES.length} rows, sha256 ${createHash('sha256').update(text).digest('hex')}`)
@@ -0,0 +1,225 @@
1
+ #!/usr/bin/env node
2
+ // 6.93.0 mutation gate: assigning a Mac recording `Voice N` to an existing voice profile (lib/mac-voice-assignment.ts and
3
+ // its hook in the relabel route of routes/meeting.ts). Engine copied unchanged from mac-window-voices-6930-mutation-gate.mjs;
4
+ // mutations run only in a disposable copy of the checkout.
5
+ //
6
+ // What it proves, in order:
7
+ // 0. The target suites are GREEN unmutated. A gate over a red suite reads every mutant as killed and proves nothing.
8
+ // 1. Each target string appears EXACTLY ONCE in its file, so the mutation is unambiguous.
9
+ // 2. The mutated file differs from the original, so the edit landed.
10
+ // 3. A NAMED test fails ("FAIL server/x.test.ts > name"); a build error or a harness failure is not a kill.
11
+ // 4. The file is restored byte for byte (compared) before the next mutant.
12
+ // Usage: node server/scripts/mac-voice-assignment-6930-mutation-gate.mjs [--list] [name…]
13
+ import { mkdtempSync, cpSync, readFileSync, writeFileSync, symlinkSync, rmSync } from 'node:fs'
14
+ import { tmpdir } from 'node:os'
15
+ import { resolve, join, dirname, basename } from 'node:path'
16
+ import { fileURLToPath } from 'node:url'
17
+ import { spawnSync } from 'node:child_process'
18
+
19
+ const root = resolve(dirname(fileURLToPath(import.meta.url)), '../..')
20
+ const LIB_T = ['server/lib/mac-voice-assignment.test.ts']
21
+ const ROUTE_T = ['server/routes/meeting-mac-assign-route.test.ts']
22
+ const G2_T = ['server/routes/meeting-relabel-route.test.ts']
23
+ const EV_T = ['server/lib/embedding-eviction.test.ts', 'server/lib/embedding-eviction-integration.test.ts']
24
+ const SE_T = ['server/lib/speaker-embeddings-enroll.test.ts']
25
+ const TP_T = ['server/lib/training-audio-provenance.test.ts']
26
+ const MERGE_T = ['server/lib/voice-profile-merge.test.ts']
27
+ const VS_T = ['server/routes/voice-safety.test.ts']
28
+ const ALL = [...LIB_T, ...ROUTE_T, ...G2_T, ...EV_T, ...SE_T, ...TP_T, ...MERGE_T, ...VS_T]
29
+ const A = 'server/lib/mac-voice-assignment.ts', M = 'server/routes/meeting.ts'
30
+ const VP = 'server/lib/voice-profile-store.ts', VR = 'server/routes/voice.ts'
31
+ const EV = 'server/lib/embedding-eviction.ts', SE = 'server/lib/speaker-embeddings.ts', TP = 'server/lib/training-audio-provenance.ts'
32
+ const m = (name, file, edits, tests) => ({ name, file, edits: typeof edits[0] === 'string' ? [edits] : edits, tests })
33
+
34
+ const mutations = [
35
+ // ── Library guards: each refusal stands on its own, not only behind the route's ──
36
+ m('guard-not-mac', A, [" if (!isMacCaptureSidecar(sidecar, sessionId)) return none('not_mac_capture')\n", ''], [...LIB_T, ...ROUTE_T]),
37
+ m('guard-voice-label', A, [" if (!isVoiceLabel(from)) return none('not_a_voice_label')\n", ''], [...LIB_T, ...ROUTE_T]),
38
+ m('guard-placeholder-target', A, [" if (isPlaceholderLabel(to)) return none('unknown_profile')\n", ''], [...LIB_T, ...ROUTE_T]),
39
+ m('guard-unknown-profile', A, [" if (!profile) return none('unknown_profile')\n", ''], [...LIB_T, ...ROUTE_T]),
40
+ m('profile-name-case-insensitive', A, ['.find(p => p.name === to) } catch', '.find(p => p.name.toLowerCase() === to.toLowerCase()) } catch'], [...LIB_T, ...ROUTE_T]),
41
+ m('guard-embeddings-switch', A, [" if (!chunkEmbeddingsEnabled()) return none('disabled')\n", ''], LIB_T),
42
+ m('guard-model-present', A, [" if (!isEmbeddingAvailable()) return none('store_unavailable')\n", ''], [...LIB_T, ...ROUTE_T]),
43
+ m('guard-index-mapping', A, [" if (!rawIndices) return none('no_index_mapping')\n", ''], LIB_T),
44
+ // ── B3: the wearer profile is never a target ──
45
+ m('guard-owner', A, [" if (input.ownerLabel && sameName(to, input.ownerLabel)) return none('owner_profile')\n", ''], [...LIB_T, ...ROUTE_T]),
46
+ m('owner-match-case-sensitive', A, ["a.normalize('NFKC').trim().toLowerCase() === b.normalize('NFKC').trim().toLowerCase()", "a === b"], [...LIB_T, ...ROUTE_T]),
47
+ m('route-owner-label-not-passed', M, ['sidecar: parsedForAssign, ownerLabel })', 'sidecar: parsedForAssign })'], ROUTE_T),
48
+ m('route-owner-not-409', M, [" if (macAssign.report.skipped === 'owner_profile') {", ' if (false) {'], ROUTE_T),
49
+ m('guard-no-stored-voice', A, [" if (!stored) return none('no_stored_voice')\n", ''], LIB_T),
50
+ // ── B2: capacity (a Mac-assigned sample can only use free slots or replace its own kind) ──
51
+ m('guard-profile-full', A, [" if (capacity <= 0) return none('profile_full')\n", ''], ROUTE_T),
52
+ m('capacity-ignores-replaceable', A, ['Math.max(0, MAX_EMBEDDINGS_PER_SPEAKER - profile.embeddings.length) + macCount)', 'Math.max(0, MAX_EMBEDDINGS_PER_SPEAKER - profile.embeddings.length))'], ROUTE_T),
53
+ m('selection-not-trimmed-to-room', A, ['Math.min(MAX_ENROL_PER_CORRECTION, capacity)).slice(0, capacity)', 'Math.min(MAX_ENROL_PER_CORRECTION, capacity))'], ROUTE_T),
54
+ m('selection-ignores-mac-cap', A, ['Math.min(MAX_ENROL_PER_CORRECTION, capacity)).slice(0, capacity)', 'MAX_ENROL_PER_CORRECTION)'], ROUTE_T),
55
+ // ── Which audio ──
56
+ m('mixed-chunk-used-whole', A, ['if (turns.length === 0 || turns.every(t => t.speaker === from)) {', 'if (true) {'], ROUTE_T),
57
+ m('other-voices-turns-sliced', A, ['const usable = turns.filter(t => t.speaker === from\n', 'const usable = turns.filter(t => true\n'], ROUTE_T),
58
+ m('slice-min-ignored', A, ['>= MAC_ASSIGN_MIN_SLICE_SEC)', '>= 0)'], ROUTE_T),
59
+ m('slice-min-off-by-one', A, ['export const MAC_ASSIGN_MIN_SLICE_SEC = 3.0', 'export const MAC_ASSIGN_MIN_SLICE_SEC = 2.0'], ROUTE_T),
60
+ m('slice-inset-removed', A, ['export const MAC_ASSIGN_SLICE_INSET_SEC = 0.15', 'export const MAC_ASSIGN_SLICE_INSET_SEC = 0'], ROUTE_T),
61
+ m('slice-start-inset-removed', A, ['slice: { startSec: (t.startSec as number) + MAC_ASSIGN_SLICE_INSET_SEC,', 'slice: { startSec: (t.startSec as number),'], ROUTE_T),
62
+ m('slice-end-inset-removed', A, ['endSec: (t.endSec as number) - MAC_ASSIGN_SLICE_INSET_SEC }', 'endSec: (t.endSec as number) }'], ROUTE_T),
63
+ m('final-segments-ignored', A, ['const list = Array.isArray(final) && final.length > 0 ? final : Array.isArray(live) ? live : []', 'const list = Array.isArray(live) ? live : []'], ROUTE_T),
64
+ m('short-chunk-not-skipped', A, [' if (sec < VOICEPRINT_MIN_AUDIO_SEC) { report.shortSkipped += 1; continue }\n', ''], ROUTE_T),
65
+ m('unit-cap-ignored', A, ['const picked = spread(units, MAC_ASSIGN_MAX_UNITS)', 'const picked = units'], ROUTE_T),
66
+ m('spread-takes-the-first', A, ['out.push(items[Math.floor((k * items.length) / max)])', 'out.push(items[k])'], ROUTE_T),
67
+ m('missing-audio-not-counted', A, [' if (!path) { report.missingAudio += 1; continue }\n', ' if (!path) continue\n'], [...LIB_T, ...ROUTE_T]),
68
+ m('audio-gone-reads-as-unusable', A, ["report.skipped = units.length === 0 ? 'no_usable_audio' : 'audio_not_retained'", "report.skipped = 'no_usable_audio'"], [...LIB_T, ...ROUTE_T]),
69
+ m('wrong-sample-rate-accepted', A, [' if (!pcm || pcm.sampleRate !== SAMPLE_RATE) continue\n', ' if (!pcm) continue\n'], ROUTE_T),
70
+ // ── The same gates a glasses rename uses ──
71
+ m('coherence-gate-skipped', A, ['greedyDiversitySelect(cluster.members.map(m => embeddings[m]), Math.min(', 'greedyDiversitySelect(embeddings, Math.min('], ROUTE_T),
72
+ m('coherence-skips-not-reported', A, ['report.clusterSkipped = embeddings.length - cluster.members.length', 'report.clusterSkipped = 0'], ROUTE_T),
73
+ m('no-cluster-still-enrols', A, [" if (cluster.members.length === 0) { report.skipped = 'no_usable_audio'; return { report, selected: [], stats } }\n", ''], ROUTE_T),
74
+ m('diversity-cap-ignored', A, ['Math.min(MAX_ENROL_PER_CORRECTION, capacity)).slice(0, capacity)', 'Infinity).slice(0, capacity)'], ROUTE_T),
75
+ // ── B1: similarity, counts and the three gates ──
76
+ m('below-floor-never-flagged', A, ['report.belowFloor = report.similarity < MERGE_SIMILARITY_FLOOR', 'report.belowFloor = false'], ROUTE_T),
77
+ m('below-floor-wrong-way', A, ['report.belowFloor = report.similarity < MERGE_SIMILARITY_FLOOR', 'report.belowFloor = report.similarity > MERGE_SIMILARITY_FLOOR'], ROUTE_T),
78
+ m('similarity-not-reported', A, ['report.similarity = Math.round(rawCosineSimilarity(voice, stored) * 1000) / 1000', 'report.similarity = null'], ROUTE_T),
79
+ m('floor-not-the-merge-floor', A, ['belowFloor: false, floor: MERGE_SIMILARITY_FLOOR,', 'belowFloor: false, floor: 0.5,'], ROUTE_T),
80
+ m('gate-below-floor-dropped', A, [" if (report.belowFloor) report.gates.push('below_floor')\n", ''], [...LIB_T, ...ROUTE_T]),
81
+ m('gate-mixed-dropped', A, [" if (report.groupSize < embeddings.length * MAC_ASSIGN_MIXED_FRACTION) report.gates.push('mixed_voice')\n", ''], [...LIB_T, ...ROUTE_T]),
82
+ m('gate-mixed-edge-inclusive', A, ['report.groupSize < embeddings.length * MAC_ASSIGN_MIXED_FRACTION', 'report.groupSize <= embeddings.length * MAC_ASSIGN_MIXED_FRACTION'], LIB_T),
83
+ m('gate-mixed-fraction-half', A, ['export const MAC_ASSIGN_MIXED_FRACTION = 2 / 3', 'export const MAC_ASSIGN_MIXED_FRACTION = 1 / 2'], [...LIB_T, ...ROUTE_T]),
84
+ m('gate-too-little-dropped', A, [" if (report.groupSize < MAC_ASSIGN_MIN_UNITS && report.groupSec < MAC_ASSIGN_MIN_SINGLE_SEC) report.gates.push('too_little_audio')\n", ''], [...LIB_T, ...ROUTE_T]),
85
+ m('gate-too-little-or', A, ['report.groupSize < MAC_ASSIGN_MIN_UNITS && report.groupSec < MAC_ASSIGN_MIN_SINGLE_SEC', 'report.groupSize < MAC_ASSIGN_MIN_UNITS || report.groupSec < MAC_ASSIGN_MIN_SINGLE_SEC'], [...LIB_T, ...ROUTE_T]),
86
+ m('gate-single-seconds-lowered', A, ['export const MAC_ASSIGN_MIN_SINGLE_SEC = 6', 'export const MAC_ASSIGN_MIN_SINGLE_SEC = 5'], LIB_T),
87
+ m('gate-min-units-one', A, ['export const MAC_ASSIGN_MIN_UNITS = 2', 'export const MAC_ASSIGN_MIN_UNITS = 1'], [...LIB_T, ...ROUTE_T]),
88
+ m('group-size-not-reported', A, [' report.groupSize = cluster.members.length\n', ''], [...LIB_T, ...ROUTE_T]),
89
+ m('group-seconds-wrong', A, ['report.groupSec = Math.round(cluster.members.reduce((sum, m) => sum + secs[m], 0) * 10) / 10', 'report.groupSec = Math.round(secs.reduce((sum, v) => sum + v, 0) * 10) / 10'], LIB_T),
90
+ // ── The route: gates refuse unless forced; force is not wider than that ──
91
+ m('route-gates-never-refuse', M, [' if (macAssign && macAssign.report.gates.length > 0 && req.body?.force !== true) {', ' if (false) {'], ROUTE_T),
92
+ m('route-force-ignored', M, [' if (macAssign && macAssign.report.gates.length > 0 && req.body?.force !== true) {', ' if (macAssign && macAssign.report.gates.length > 0) {'], ROUTE_T),
93
+ m('route-gate-reason-not-first', M, [' const gate = macAssign.report.gates[0]\n', ' const gate = macAssign.report.gates[macAssign.report.gates.length - 1]\n'], ROUTE_T),
94
+ m('route-force-skips-pending-correction', M, [' if (stalled.length > 0 && (req.body?.force !== true || macAssign)) {', ' if (stalled.length > 0 && req.body?.force !== true) {'], ROUTE_T),
95
+ // ── B4: capture, interleave, deadline, yield, vector reuse ──
96
+ m('route-capture-pre-check-dropped', M, [' if (captureActive()) {\n res.status(409).json({ error: \'A meeting is being recorded right now.', ' if (false) {\n res.status(409).json({ error: \'A meeting is being recorded right now.'], ROUTE_T),
97
+ m('route-capture-post-check-dropped', M, [' if (macAssign && captureActive()) {', ' if (false) {'], ROUTE_T),
98
+ m('route-capture-ignored-by-default-probe', M, ['(() => getTranscriptionSessionLiveness().live > 0)', '(() => false)'], ROUTE_T),
99
+ m('route-in-flight-not-guarded', M, [' if (macAssignInFlight.has(sessionId)) {', ' if (false) {'], ROUTE_T),
100
+ m('route-in-flight-never-released', M, [' } finally {\n macAssignInFlight.delete(sessionId)\n }', ' } finally {\n }'], ROUTE_T),
101
+ m('route-meeting-bytes-not-rechecked', M, [' if (sidecarAfter !== sidecarRaw || revisionAfter) {', ' if (revisionAfter) {'], ROUTE_T),
102
+ m('route-revision-not-rechecked', M, [' if (sidecarAfter !== sidecarRaw || revisionAfter) {', ' if (sidecarAfter !== sidecarRaw) {'], ROUTE_T),
103
+ m('plan-does-not-yield', A, [' await yieldToLoop()\n', ''], [...LIB_T, ...ROUTE_T]),
104
+ m('plan-deadline-ignored', A, [' if (now() >= deadline) {', ' if (false) {'], LIB_T),
105
+ m('plan-deadline-not-a-refusal', A, [" report.skipped = 'deadline'\n", ''], LIB_T),
106
+ m('env-deadline-unbounded', A, ['raw >= 1000 && raw <= 25_000', 'raw >= 0'], LIB_T),
107
+ m('vectors-not-reused', A, [' const hit = cacheGet(key)\n', ' const hit = undefined as ReturnType<typeof cacheGet>\n'], [...LIB_T, ...ROUTE_T]),
108
+ m('vector-key-ignores-file', A, ['|${info.size}|${Math.round(info.mtimeMs)}|${info.ino}|${Math.round(info.ctimeMs)}`', '`'], LIB_T),
109
+ m('vector-key-ignores-mtime', A, ['|${Math.round(info.mtimeMs)}|${info.ino}', '|${info.ino}'], LIB_T),
110
+ m('vector-key-ignores-inode', A, ['|${info.ino}|${Math.round(info.ctimeMs)}`', '|${Math.round(info.ctimeMs)}`'], LIB_T),
111
+ m('vector-key-ignores-ctime', A, ['|${info.ino}|${Math.round(info.ctimeMs)}`', '|${info.ino}`'], LIB_T),
112
+ m('vector-key-ignores-slice', A, ["${unit.slice ? `${unit.slice.startSec.toFixed(3)}-${unit.slice.endSec.toFixed(3)}` : 'whole'}", "whole"], LIB_T),
113
+ m('apply-writes-per-sample', A, ['const result = enrollEmbeddingsBatch(plan.report.person, plan.selected.map(embedding => ({ embedding, source })))', 'const result = plan.selected.map(embedding => enrollEmbeddingsBatch(plan.report.person, [{ embedding, source }])).reduce((a, r) => ({ enrolled: a.enrolled + r.enrolled, declined: a.declined + r.declined, persisted: a.persisted && r.persisted }), { enrolled: 0, declined: 0, persisted: true })'], LIB_T),
114
+ // ── Commit ──
115
+ m('commit-ignores-refusal', A, [" if (plan.report.skipped !== null || plan.selected.length === 0) return { ...plan.report, enrolled: 0 }\n", ''], LIB_T),
116
+ m('commit-source-tag', A, ['const source = `${MAC_ASSIGN_SOURCE}:${sessionId}`', 'const source = `correction:${sessionId}`'], [...LIB_T, ...ROUTE_T]),
117
+ m('commit-source-tag-unstamped', A, ['const source = `${MAC_ASSIGN_SOURCE}:${sessionId}`', 'const source = MAC_ASSIGN_SOURCE'], [...LIB_T, ...ROUTE_T]),
118
+ m('commit-store-failure-swallowed', A, [" if (!result.persisted) return { ...plan.report, enrolled: 0, skipped: 'store_unavailable' }\n", ''], [...LIB_T, ...ROUTE_T]),
119
+ m('commit-counts-everything', A, [' return { ...plan.report, enrolled: result.enrolled }', ' return { ...plan.report, enrolled: plan.selected.length }'], ROUTE_T),
120
+ m('plan-creates-profile-flag', A, ['person, enrolled: 0, attempted: 0, created: false,', 'person, enrolled: 0, attempted: 0, created: true as unknown as false,'], ROUTE_T),
121
+ // ── The relabel route ──
122
+ m('route-flag-always-on', M, [' if (req.body?.assignVoiceProfile === true) {', ' if (true) {'], [...ROUTE_T, ...G2_T]),
123
+ m('route-flag-ignored', M, [' if (req.body?.assignVoiceProfile === true) {', ' if (false) {'], ROUTE_T),
124
+ m('route-mac-only', M, [' if (!isMacCaptureSidecar(parsedForAssign, sessionId)) {\n', ' if (false) {\n'], ROUTE_T),
125
+ m('route-voice-label-only', M, [' if (!isVoiceLabel(from)) {\n res.status(400).json({ error: \'Only an unnamed Voice N', ' if (false) {\n res.status(400).json({ error: \'Only an unnamed Voice N'], ROUTE_T),
126
+ m('route-unknown-profile-404', M, [" if (macAssign.report.skipped === 'unknown_profile') {", ' if (false) {'], ROUTE_T),
127
+ m('route-nothing-to-enrol-refusal', M, [' if (macAssign && macAssign.report.skipped !== null) {', ' if (false) {'], ROUTE_T),
128
+ m('route-preview-hides-enrolment', M, [' ...(macAssign ? { enrolment: macAssign.report } : {}),\n', ''], ROUTE_T),
129
+ m('route-preview-message', M, [' ? `, and adding up to ${macAssign.report.enrolled} voice sample(s) to ${to}\'s profile`', ' ? ``'], ROUTE_T),
130
+ m('route-preview-hides-refusal-reason', M, [' : `. Assigning is not possible: ${macAssignSkipText(macAssign.report.skipped)}. You can still rename the speaker in the transcript without adding any voice samples`', ' : \'\''], ROUTE_T),
131
+ m('route-apply-uses-glasses-path', M, [' const enrolment = macAssign\n ? commitMacVoiceAssignment(sessionId, macAssign)\n : enrolNamedVoice(', ' const enrolment = false\n ? commitMacVoiceAssignment(sessionId, macAssign!)\n : enrolNamedVoice('], ROUTE_T),
132
+ m('route-plan-uses-renamed-sidecar', M, ['macAssign = await planMacVoiceAssignment({ sessionId, from, to, changed: plan.value.changed, sidecar: parsedForAssign, ownerLabel })', 'macAssign = await planMacVoiceAssignment({ sessionId, from, to, changed: [], sidecar: parsedForAssign, ownerLabel })'], ROUTE_T),
133
+ // ── B2 policy (embedding-eviction.ts) ──
134
+ m('mac-assign-not-assisted', EV, [" case MAC_ASSIGN_SOURCE:\n", ''], [...EV_T, ...SE_T, ...ROUTE_T]),
135
+ m('mac-assign-vouches', EV, ["return head === 'manual' || head === 'fireflies' || head === 'g2-enrollment'", "return head === 'manual' || head === 'fireflies' || head === 'g2-enrollment' || head === 'mac-assign'"], EV_T),
136
+ m('mac-assign-is-a-correction', EV, ["return String(source ?? '').trim().toLowerCase().startsWith('correction')", "return String(source ?? '').trim().toLowerCase().startsWith('correction') || isMacAssign(source)"], EV_T),
137
+ m('mac-assign-evicts-others', EV, [" if (macIdx.length > 0) {\n return {\n index: macIdx[0],", " if (false) {\n return {\n index: macIdx[0],"], [...EV_T, ...SE_T, ...ROUTE_T]),
138
+ m('mac-assign-refusal-evicts-index-zero', EV, ["return { index: -1, tier: 'assisted', reason: 'profile is full and holds no Mac-assigned sample to replace, refusing the Mac-assigned sample', refuse: true }", "return { index: 0, tier: 'assisted', reason: 'profile is full and holds no Mac-assigned sample to replace, refusing the Mac-assigned sample' }"], [...EV_T, ...SE_T, ...ROUTE_T]),
139
+ m('mac-assign-outlives-auto', EV, [" if (macIdx.length > 0) {\n return {\n index: macIdx[0],\n tier: 'assisted',\n reason: `dropping the oldest Mac-assigned sample", " if (false) {\n return {\n index: macIdx[0],\n tier: 'assisted',\n reason: `dropping the oldest Mac-assigned sample"], [...EV_T, ...SE_T]),
140
+ m('mac-assign-evicted-only-within-assisted', EV, [[
141
+ " if (macIdx.length > 0) {\n return {\n index: macIdx[0],\n tier: 'assisted',\n reason: `dropping the oldest Mac-assigned sample (${String(sources[macIdx[0]])}) before any other kind, it is the weakest evidence`,\n }\n }\n", ''],
142
+ [" const idx = sources.findIndex(s => provenanceTier(s) === tier)", " const idx = tier === 'assisted' && macIdx.length > 0 ? macIdx[0] : sources.findIndex(s => provenanceTier(s) === tier)"]], [...EV_T, ...SE_T]),
143
+ // ── 6.93.0 polish: the cap is min(10, the profile's other samples), at plan time, at write time and in a merge ──
144
+ m('cap-is-flat-ten', EV, ['return Math.min(MAC_ASSIGN_PROFILE_CAP, Math.max(0, sampleCount - macCount))', 'return MAC_ASSIGN_PROFILE_CAP'], [...EV_T, ...SE_T, ...LIB_T, ...ROUTE_T, ...MERGE_T]),
145
+ m('cap-counts-mac-as-other', EV, ['return Math.min(MAC_ASSIGN_PROFILE_CAP, Math.max(0, sampleCount - macCount))', 'return Math.min(MAC_ASSIGN_PROFILE_CAP, sampleCount)'], [...EV_T, ...SE_T, ...LIB_T, ...ROUTE_T, ...MERGE_T]),
146
+ m('plan-capacity-flat-ten', A, ['Math.min(macAssignCapFor(profile.embeddings.length, macCount), Math.max(0,', 'Math.min(10, Math.max(0,'], ROUTE_T),
147
+ m('store-guard-flat-ten', SE, ['macCount >= macAssignCapFor(profile.embeddings.length, macCount)', 'macCount >= 10'], [...SE_T, ...LIB_T]),
148
+ m('store-guard-not-for-mac-incoming', SE, ['const macAtCap = isMacAssign(source) && macCount >= macAssignCapFor(profile.embeddings.length, macCount)', 'const macAtCap = false'], [...SE_T, ...LIB_T]),
149
+ m('merge-mac-not-trimmed', VP, ['const trimmed = new Set(macKept.slice(0, Math.max(0, macKept.length - macAllowed)))', 'const trimmed = new Set<number>()'], MERGE_T),
150
+ m('merge-trims-newest-mac', VP, ['const trimmed = new Set(macKept.slice(0, Math.max(0, macKept.length - macAllowed)))', 'const trimmed = new Set(macKept.slice(macAllowed))'], MERGE_T),
151
+ m('merge-mac-cap-flat-ten', VP, ['macAssignCapFor(kept.length, macKept.length)', 'Math.min(10, kept.length)'], MERGE_T),
152
+ m('merge-mac-ranked-as-assisted', VP, ['isMacAssign(sources[i]) ? -1 : EVICTION_ORDER', 'false ? -1 : EVICTION_ORDER'], MERGE_T),
153
+ m('profiles-listing-mac-assign-uncollapsed', VR, [": source.startsWith('mac-assign:') ? 'mac-assign' : source", ': source'], VS_T),
154
+ m('route-finalizing-post-check-dropped', M, [' const finalizingAfter = finalizingRefusal(sessionId)\n', ' const finalizingAfter = null as ReturnType<typeof finalizingRefusal>\n'], ROUTE_T),
155
+ m('deattribute-retracts-nothing', M, ['retracted = retractEmbeddingsBySource(from, s => isSampleFromSession(s, sessionId)).removed', 'retracted = 0'], ROUTE_T),
156
+ m('deattribute-retracts-every-sample', M, ['retracted = retractEmbeddingsBySource(from, s => isSampleFromSession(s, sessionId)).removed', 'retracted = retractEmbeddingsBySource(from, () => true).removed'], ROUTE_T),
157
+ m('mac-assign-cap-changed', EV, ['export const MAC_ASSIGN_PROFILE_CAP = 10', 'export const MAC_ASSIGN_PROFILE_CAP = 20'], [...EV_T, ...SE_T, ...ROUTE_T]),
158
+ m('mac-assign-not-retractable', TP, ["['auto:', 'correction:', 'g2-training:', 'ext-group:', 'mac-assign:']", "['auto:', 'correction:', 'g2-training:', 'ext-group:']"], [...TP_T, ...ROUTE_T]),
159
+ // ── 6.93.0 final: a merge never leaves an empty profile; a correction at quota runs before the Mac step ──
160
+ m('merge-allows-empty-profile', SE, ["if (wouldBe.mergedFrom.length > 0 && wouldBe.samplesAfter === 0) {", 'if (false) {'], MERGE_T),
161
+ m('merge-empty-refusal-not-409', VR, [' if (report.refusal) {\n', ' if (false) {\n'], VS_T),
162
+ m('correction-quota-after-mac-step', EV, ['if (isCorrection(incomingSource) && correctionIdx.length >= correctionQuota(cap)) {', 'if (isCorrection(incomingSource) && correctionIdx.length >= correctionQuota(cap) && macIdx.length === 0) {'], [...EV_T, ...SE_T]),
163
+ // ── B2/B4/W8: the real store code ──
164
+ m('batch-writes-per-sample', SE, [' appendEmbedding(profile, Array.from(item.embedding), item.source)\n added++\n', ' appendEmbedding(profile, Array.from(item.embedding), item.source)\n added++\n writeProfileStore(store)\n'], SE_T),
165
+ m('batch-dedup-dropped', SE, [' if (profile.embeddings.some(existing => rawCosineSimilarity(item.embedding, new Float32Array(existing)) > 0.95)) { declined++; continue }\n', ''], SE_T),
166
+ m('batch-room-not-made', SE, [' const room = makeRoomForSample(name, profile, item.source)\n if (!room.ok) { declined++; continue }\n', ''], SE_T),
167
+ m('batch-write-failure-swallowed', SE, [" if (!writeProfileStore(store)) throw new Error('the store refused the write')\n } catch (err: unknown) {\n restoreProfileFromDisk(name)\n return { enrolled: 0, declined: items.length", " writeProfileStore(store)\n } catch (err: unknown) {\n restoreProfileFromDisk(name)\n return { enrolled: 0, declined: items.length"], SE_T),
168
+ m('batch-failure-memory-kept', SE, [" } catch (err: unknown) {\n restoreProfileFromDisk(name)\n return { enrolled: 0, declined: items.length", " } catch (err: unknown) {\n return { enrolled: 0, declined: items.length"], SE_T),
169
+ m('batch-failure-counts-enrolled', SE, ["return { enrolled: 0, declined: items.length, persisted: false, error: `Profile store write failed: ${errMsg(err)}` }\n }\n rebuildSpeakerInManager", "return { enrolled: added, declined: items.length, persisted: false, error: `Profile store write failed: ${errMsg(err)}` }\n }\n rebuildSpeakerInManager"], SE_T),
170
+ m('mac-cap-not-enforced', SE, ['if (profile.embeddings.length < MAX_EMBEDDINGS_PER_SPEAKER && !macAtCap) return { ok: true }', 'if (profile.embeddings.length < MAX_EMBEDDINGS_PER_SPEAKER) return { ok: true }'], SE_T),
171
+ m('refusal-falls-back-to-oldest', SE, [" if (choice?.refuse) {\n", " if (false) {\n"], SE_T),
172
+ m('persist-swallows-write-error', SE, [" restoreProfileFromDisk(name)\n throw err instanceof Error ? err : new Error(errMsg(err))\n", " restoreProfileFromDisk(name)\n"], SE_T),
173
+ m('persist-ignores-refused-write', SE, [" if (!writeProfileStore(store)) throw new Error('the store refused the write')\n } catch (err: unknown) {\n console.error('[speaker] Profile persist error:', errMsg(err))", " writeProfileStore(store)\n } catch (err: unknown) {\n console.error('[speaker] Profile persist error:', errMsg(err))"], SE_T),
174
+ m('persist-failure-memory-kept', SE, [" console.error('[speaker] Profile persist error:', errMsg(err))\n restoreProfileFromDisk(name)\n", " console.error('[speaker] Profile persist error:', errMsg(err))\n"], SE_T),
175
+ m('enroll-reports-success-after-failed-write', SE, [" try {\n persistProfile(name, embedding, source)\n } catch (err: unknown) {\n return { success: false, dim: extractor.dim, error: `Profile store write failed: ${errMsg(err)}` }\n }\n const updatedStore", " try {\n persistProfile(name, embedding, source)\n } catch (err: unknown) {\n }\n const updatedStore"], SE_T),
176
+ m('enroll-reports-success-after-failed-write-new', SE, [" // Persist to disk with provenance\n try {\n persistProfile(name, embedding, source)\n } catch (err: unknown) {\n return { success: false, dim: extractor.dim, error: `Profile store write failed: ${errMsg(err)}` }\n }", " // Persist to disk with provenance\n try {\n persistProfile(name, embedding, source)\n } catch (err: unknown) {\n }"], SE_T),
177
+ m('restore-leaves-phantom-speaker', SE, [" else if (manager?.contains(name)) manager.remove(name)\n", ''], SE_T),
178
+ ]
179
+
180
+ const args = process.argv.slice(2)
181
+ if (args.includes('--list')) { for (const { name } of mutations) console.log(name); process.exit(0) }
182
+ const selected = args.length ? mutations.filter(({ name }) => args.includes(name)) : mutations
183
+ if (args.length && selected.length !== args.length) throw new Error('Unknown mutation name in: ' + args.join(' '))
184
+ if (new Set(mutations.map(x => x.name)).size !== mutations.length) throw new Error('Duplicate mutation name')
185
+
186
+ // Record exactly which code this run proved (QA R-N1): the commit, and whether the tree matched it.
187
+ const rev = spawnSync('git', ['-C', root, 'rev-parse', 'HEAD'], { encoding: 'utf8' }).stdout.trim()
188
+ const modified = spawnSync('git', ['-C', root, 'status', '--porcelain', '--untracked-files=no'], { encoding: 'utf8' }).stdout.split('\n').filter(Boolean).length
189
+ console.log(`commit ${rev || 'unknown'} (${modified ? `${modified} modified files: NOT the committed code` : 'clean tree'})`)
190
+ const scratch = mkdtempSync(join(tmpdir(), "mac-voice-assignment-6930-mutations-"))
191
+ try {
192
+ const skip = new Set(['data', 'models', 'certs', 'node_modules'])
193
+ cpSync(join(root, 'server'), join(scratch, 'server'), { recursive: true, filter: src => !(skip.has(basename(src)) && dirname(src) === join(root, 'server')) })
194
+ for (const dir of ['shared', 'work-task-runtime', 'bin', 'docs', 'memory-runtime']) cpSync(join(root, dir), join(scratch, dir), { recursive: true })
195
+ for (const file of ['vitest.config.ts', 'package.json', 'managed-runtime-contract.json']) cpSync(join(root, file), join(scratch, file))
196
+ symlinkSync(join(root, 'node_modules'), join(scratch, 'node_modules'), 'dir')
197
+ const run = tests => spawnSync(process.execPath, [join(root, 'node_modules/vitest/vitest.mjs'), 'run', '--maxWorkers=1', ...tests], {
198
+ cwd: scratch, env: { PATH: process.env.PATH, HOME: scratch, TMPDIR: process.env.TMPDIR, COS_DATA_DIR: join(scratch, 'data'), COS_PROFILE_PATH: join(scratch, 'data', 'profile.json') },
199
+ encoding: 'utf8', timeout: 300000,
200
+ })
201
+ const baseline = run(ALL)
202
+ if (baseline.status !== 0) throw new Error('Baseline failed; a gate over a red suite proves nothing:\n' + baseline.stdout + '\n' + baseline.stderr)
203
+ const totals = /Tests\s+(\d+) passed/.exec(baseline.stdout)
204
+ console.log(`baseline PASS (${totals ? totals[1] : '?'} tests in ${ALL.length} files)`)
205
+ let killed = 0
206
+ for (const { name, file, edits, tests } of selected) {
207
+ const target = join(scratch, file), original = readFileSync(target, 'utf8')
208
+ let mutated = original
209
+ for (const [find, replacement] of edits) {
210
+ if (mutated.split(find).length !== 2) throw new Error(`Mutation ${name} does not match exactly once in ${file}: ${find.slice(0, 80)}`)
211
+ mutated = mutated.replace(find, () => replacement)
212
+ }
213
+ if (mutated === original) throw new Error(`Mutation ${name} did not change ${file}`)
214
+ writeFileSync(target, mutated)
215
+ let result
216
+ try { result = run(tests) } finally { writeFileSync(target, original) }
217
+ if (readFileSync(target, 'utf8') !== original) throw new Error(`Restore failed for ${file} after ${name}`)
218
+ const output = result.stdout + '\n' + result.stderr
219
+ const killers = [...new Set([...output.matchAll(/FAIL\s+(server\/\S+\.test\.ts > [^\n]+)/g)].map(x => x[1].trim()))]
220
+ if (result.status === 0 || result.error || !killers.length) { console.log(`${name} SURVIVED or harness failed\n${output.slice(-3000)}`); process.exitCode = 1; continue }
221
+ killed += 1
222
+ console.log(`${name} KILLED by ${killers.slice(0, 2).join(' | ')}${killers.length > 2 ? ` (+${killers.length - 2} more)` : ''}`)
223
+ }
224
+ console.log(`${killed} of ${selected.length} Mac voice assignment 6.93.0 mutations killed`)
225
+ } finally { rmSync(scratch, { recursive: true, force: true }) }