@gotcos/glasses-server 6.94.0 → 6.94.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,23 @@
1
+ ## 6.94.2 - Scoped profile matches for saved Mac recordings
2
+
3
+ 2026-10-28. Compatible with COS Control 0.5.352; no Control or glasses package change required.
4
+
5
+ - Saved Mac recordings can match existing profiles using two independent, coherent long audio windows. Names apply only to supported spans at the unchanged .88 identity floor and .05 separation margin. This does not enroll or train profiles.
6
+ - Durable repair evidence distinguishes machine matches from human confirmations and protects words, timestamps, existing corrections, retained audio and interrupted-save recovery. The review no longer treats one scored occurrence as proof for every repaired occurrence.
7
+ - Existing named spans no longer prevent anonymous refinement of the remainder of a strict Mac recording. Historical recordings require explicit validated repair; upgrading does not silently rewrite them.
8
+ - Known limitation: live numbering still loses continuity across long gaps, and some anonymous groups contain mixed voices. This release does not claim full-call deduplication, an attendee count, or recognition of every speaker.
9
+ - Qualification: 9,704 tests passed, 14 skipped, zero failed; typecheck passed. Isolated natural-recording runs preserved all speech and timestamps, accepted exact supported spans, and withheld incoherent or insufficient matches.
10
+
11
+ ## 6.94.1 - Mac filing waits for a pipeline that can title a Mac recording
12
+
13
+ 2026-10-27. Pairs with COS operations `sync_meetings.py` on branch `claude/mac-library-cos` (the `mac_title: 1` capability). Works with COS Control 0.5.351; Control needs no change.
14
+
15
+ - **Why:** after 6.94.0 and COS d51abbff2 went live, every Mac import came back untitled. The pipeline's model prompt said the title was "ONLY for G2 source, null otherwise", and a Mac recording's prompt reads `Source: mac_system_audio`, so the model answered with no title. The pipeline then refused to write the import, so it stayed a staged copy under `personal/` with its review marker. On 2026-10-27 the hourly sync spent two Opus calls an hour on the same two recordings. The model step itself ran: it classified both as quilt.
16
+ - **What:** the server files a Mac recording only when `sync_meetings.py --capabilities` also reports `mac_title: 1` or later. That is the script whose prompt asks a Mac recording for its title. A server paired with the d51abbff2 script no longer spends three model calls per save on an import that cannot be written: the recording stays standalone and is filed later by the hourly sync or `mac_library_backfill.py` once the script is updated.
17
+ - **COS side (same branch):** the prompt asks a Mac recording for its title, and G2 and cloud prompts stay byte for byte the same. The hourly sync's Mac catch-up runs with the explicit save's model allowance (`COS_MEETING_LLM` off does not stop it; `--no-llm` or a missing model does), stages nothing in a run without a model, and counts an attempt only when the model is called. Attempts spent under the old prompt are not held against a recording.
18
+ - **Unchanged:** G2 saves (pinned by `server/routes/g2-filing-golden.test.ts`), the import itself, twins, and the 6.94.0 retry and catch-up budgets.
19
+ - **Publish order:** either order is safe. This server against the d51abbff2 script leaves Mac recordings standalone; the 6.94.0 server against the new script files them as before, now titled.
20
+
1
21
  ## 6.94.0 - Saved Mac recordings are filed into the COS meeting library
2
22
 
3
23
  2026-10-27. Pairs with COS operations `sync_meetings.py` on branch `claude/mac-library-cos` (the `--mac-import-file` import and `--capabilities`). Works with COS Control 0.5.351; Control needs no change.
package/README.md CHANGED
@@ -699,6 +699,29 @@ flag a Mac rename is text only. `GET /api/health` reports `capabilities.macLiveV
699
699
  The saved meeting is relabeled by the whole-meeting pass. Live numbers are kept;
700
700
  voices the live path did not prove are numbered above them at save.
701
701
 
702
+ ### Saved Mac profile reconciliation (6.94.2)
703
+
704
+ After transcription and diarization, eligible Mac recordings receive a bounded
705
+ local voice-profile pass. It checks up to twelve 28–30 second windows across the
706
+ call against the existing profile cohort. Two independent windows must agree;
707
+ each needs profile similarity of at least .88, a .05 margin over alternatives,
708
+ and coherent full/half-window voice evidence. Only the supported audio spans
709
+ receive a name. Other occurrences of the same live Voice number remain unchanged.
710
+
711
+ This pass does not train profiles, infer identities from attendees, or change the
712
+ transcript words and timestamps. Existing human corrections are protected. A
713
+ durable journal preserves recovery across interrupted writes; retained audio is
714
+ not released over an unfinished repair. Unsupported or ambiguous evidence leaves
715
+ the existing labels in place. The pass uses one bounded CPU worker and can skip
716
+ when its budget or prerequisites are unavailable.
717
+
718
+ The speaker review distinguishes qualified machine matches from human
719
+ confirmations. A pre-existing named span no longer prevents anonymous refinement
720
+ of the rest of a strict Mac recording. Live voice numbering still has the window
721
+ limits above: this release does not promise meeting-wide speaker deduplication
722
+ or an attendee count. Older recordings require an explicit, validated repair;
723
+ they are not silently rewritten on upgrade.
724
+
702
725
  ### iPhone microphone meetings (6.93.0, off by default)
703
726
 
704
727
  With `COS_IPHONE_MICROPHONE_MEETINGS=1`, HQ set to Parakeet Ultra and Parakeet
@@ -719,7 +742,7 @@ into `operations/{domain}/meetings/YYYY-MM/` after its standalone summary, throu
719
742
  `sync_meetings.py --mac-import-file`: the same summary, title and domain step as a
720
743
  glasses meeting, titled `... (Mac)`, never matched or blended into a Fireflies or
721
744
  Granola meeting. The server checks `sync_meetings.py --capabilities` first (it needs
722
- `mac_import` and `mac_twins` 2); an older script leaves Mac recordings standalone. A Mac
745
+ `mac_import`, `mac_twins` 2 and, from 6.94.1, `mac_title` 1); an older script leaves Mac recordings standalone. A Mac
723
746
  recording of a meeting the glasses or Fireflies also recorded is filed as a twin:
724
747
  searchable, but kept out of LightRAG, task scans and Work (`operations/scripts/mac_twins.py`). Recordings saved before 6.94.0 are filed with
725
748
  `operations/scripts/mac_library_backfill.py` (dry run by default, `--apply` to file).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gotcos/glasses-server",
3
- "version": "6.94.0",
3
+ "version": "6.94.2",
4
4
  "description": "COS Glasses: self-hosted AI heads-up-display server for Even G2 smart glasses, powered by Claude Code, Codex, Cursor Agent CLI, or local Ollama",
5
5
  "type": "module",
6
6
  "bin": {
@@ -398,6 +398,13 @@ export type CapabilityProbe = (pythonBin: string, syncScript: string, cwd: strin
398
398
  const CAPABILITY_PROBE_TIMEOUT_MS = 30_000
399
399
  /** sync_meetings.py `mac_twins` version the server needs: 2 = twins of G2 recordings AND cloud scribes. */
400
400
  export const MAC_TWINS_REQUIRED = 2
401
+ /**
402
+ * sync_meetings.py `mac_title` version the server needs: 1 = its model prompt asks a Mac recording
403
+ * for a title. The script that first shipped `mac_import` (COS d51abbff2) told the model the title
404
+ * was "ONLY for G2 source, null otherwise", so every Mac import came back untitled and was never
405
+ * written, after three model calls per save (2026-10-27).
406
+ */
407
+ export const MAC_TITLE_REQUIRED = 1
401
408
  const capabilityCache = new Map<string, SyncCapabilities>()
402
409
 
403
410
  function spawnCapabilityProbe(pythonBin: string, syncScript: string, cwd: string): Promise<CapabilityProbeResult> {
@@ -421,7 +428,7 @@ export function resetSyncCapabilitiesCache(): void {
421
428
 
422
429
  /**
423
430
  * What the installed sync_meetings.py can do, read from `--capabilities` rather than guessed.
424
- * Mac filing needs `mac_import: 1` and `mac_twins: 2` or later.
431
+ * Mac filing needs `mac_import: 1`, `mac_twins: 2` or later and `mac_title: 1` or later.
425
432
  *
426
433
  * Cached per python + script + mtime + size, so an upgraded script is probed again. A script
427
434
  * whose text never mentions `--capabilities` is an older one: it would take the sync lock
@@ -458,7 +465,9 @@ export async function readSyncCapabilities(
458
465
  // that keeps a Mac copy of an already-recorded meeting out of LightRAG and Work. A script with
459
466
  // the import but an older twin rule would file a Mac + Fireflies meeting twice.
460
467
  const caps = !!parsed && typeof parsed === 'object' ? parsed as Record<string, unknown> : {}
461
- const macImport = caps.mac_import === 1 && typeof caps.mac_twins === 'number' && caps.mac_twins >= MAC_TWINS_REQUIRED
468
+ const macImport = caps.mac_import === 1
469
+ && typeof caps.mac_twins === 'number' && caps.mac_twins >= MAC_TWINS_REQUIRED
470
+ && typeof caps.mac_title === 'number' && caps.mac_title >= MAC_TITLE_REQUIRED
462
471
  const result = { macImport, reason: macImport ? null : 'sync_script_upgrade_required' }
463
472
  capabilityCache.set(key, result)
464
473
  return result
@@ -0,0 +1,111 @@
1
+ /** Read-only bridge from a labels-only repair receipt to scoped machine claims.
2
+ * Capture similarities, human confirmations and profile training are untouched.
3
+ * Persisted bytes are trusted only after current speech/raw-map/receipt checks.
4
+ */
5
+ import {
6
+ hashMacSpeakerDocument, hashMacSpeakerMachineBinding, validateMacSpeakerTargetEvidence,
7
+ type MacSpeakerMachineEvidence, type MacSpeakerRepairPlan,
8
+ } from './mac-speaker-repair-projection.js'
9
+ import { checkSpeakerName } from './speaker-name.js'
10
+ type Doc = Record<string, any>
11
+ const SHA = /^[a-f0-9]{64}$/
12
+ const uint = (v: unknown): v is number => Number.isSafeInteger(v) && Number(v) >= 0
13
+ const equal = (a: unknown, b: unknown) => JSON.stringify(a) === JSON.stringify(b)
14
+ function refuse(reason: string): never { throw new Error(reason) }
15
+ export interface QualifiedMachinePosition {
16
+ label: string
17
+ rawChunkIndex: number
18
+ evidenceId: string
19
+ receiptHash: string
20
+ modelSHA256: string
21
+ profileSHA256: string
22
+ algorithmVersion: string
23
+ independentWindows: number
24
+ minimumSimilarity: number
25
+ minimumMargin: number
26
+ }
27
+ export interface MacMachineEvidenceRead {
28
+ status: 'absent' | 'qualified' | 'invalid'
29
+ reason?: string
30
+ /** Compacted positions, obtained by the strict raw join below. Never guess
31
+ * positions from raw indexes, text similarity or repeated phrases. */
32
+ positions: Map<number, QualifiedMachinePosition>
33
+ }
34
+ export function readMacSpeakerMachineEvidence(value: unknown): MacMachineEvidenceRead {
35
+ const positions = new Map<number, QualifiedMachinePosition>()
36
+ const doc = value as Doc | null
37
+ const evidence = doc?.speakerRepair?.machineEvidence as MacSpeakerMachineEvidence | undefined
38
+ if (!evidence) return { status: 'absent', positions }
39
+ try {
40
+ if (!doc || doc.sourceType !== 'mac_system_audio' || typeof doc.sessionId !== 'string' || !doc.sessionId.startsWith('mac_')
41
+ || doc.finalizationState !== 'complete' || doc.claimPending === true || doc.active === true || doc.isActive === true || doc.isMerged || doc.mergedFrom || doc.sourceSessionIds) refuse('source_not_closed_original')
42
+ // Deliberately global: any later human revision invalidates all machine
43
+ // claims until requalification. Real ledger confirmations still prevail.
44
+ if (evidence.version !== 1 || !uint(evidence.correctionRevision) || evidence.correctionRevision < 1 || doc.correctionRevision !== evidence.correctionRevision) refuse('revision_drift')
45
+ if (!SHA.test(evidence.documentBinding) || hashMacSpeakerMachineBinding(doc) !== evidence.documentBinding) refuse('speech_binding_mismatch')
46
+ const receipt = evidence.receipt
47
+ if (!receipt || receipt.version !== 1 || !SHA.test(receipt.receiptHash) || receipt.id !== doc.speakerRepair.id || receipt.evidenceHash !== doc.speakerRepair.evidenceHash
48
+ || !SHA.test(receipt.evidenceHash) || !Array.isArray(receipt.assignments) || !receipt.assignments.length || receipt.assignments.length > 65536
49
+ || !Array.isArray(receipt.patches) || receipt.patches.length > 1_000_000) refuse('receipt_invalid')
50
+ const { receiptHash, ...unsigned } = receipt
51
+ if (hashMacSpeakerDocument(unsigned) !== receiptHash) refuse('receipt_invalid')
52
+ if (!Array.isArray(doc.chunks) || !Array.isArray(doc.chunkEntries) || doc.chunks.length > 65536 || doc.chunkEntries.length > 65536) refuse('raw_map_ambiguous')
53
+ const byRaw = new Map<number, { position: number; chunk: Doc }>()
54
+ let position = 0, previous = -1
55
+ for (const entry of doc.chunkEntries) {
56
+ if (!entry || !uint(entry.chunkIndex) || entry.chunkIndex <= previous) refuse('raw_map_ambiguous')
57
+ previous = entry.chunkIndex
58
+ if (typeof entry.chunk?.text !== 'string' || !entry.chunk.text.trim()) continue
59
+ const chunk = doc.chunks[position]
60
+ if (!chunk || ['text', 'speaker', 'elapsed', 'audioDurationMs', 'audioSha256', 'segments', 'finalSegments', 'words'].some(key => !equal(chunk[key], entry.chunk[key]))) refuse('raw_map_ambiguous')
61
+ byRaw.set(entry.chunkIndex, { position, chunk }); position++
62
+ }
63
+ if (position !== doc.chunks.length) refuse('raw_map_ambiguous')
64
+ const source = structuredClone(doc)
65
+ // Reconstruct only the original label surfaces from the validated receipt.
66
+ // Lifecycle/prose/profile metadata are never inverse-applied by this reader.
67
+ const paths = new Set<string>()
68
+ for (const patch of [...receipt.patches].reverse()) {
69
+ if (!Array.isArray(patch.path) || !patch.path.length || patch.path.length > 8 || patch.path.some(k => !['string', 'number'].includes(typeof k) || ['__proto__', 'prototype', 'constructor'].includes(String(k)))) refuse('receipt_invalid')
70
+ const rootSpeakers = patch.path.length === 1 && patch.path[0] === 'speakers'
71
+ if (!rootSpeakers && (!['chunks', 'chunkEntries', 'batchSegments'].includes(String(patch.path[0])) || patch.path.at(-1) !== 'speaker')) refuse('receipt_invalid')
72
+ const key = JSON.stringify(patch.path)
73
+ if (paths.has(key)) refuse('receipt_invalid')
74
+ paths.add(key)
75
+ let row = source
76
+ for (const part of patch.path.slice(0, -1)) {
77
+ if (!row || typeof row !== 'object' || !Object.hasOwn(row, part)) refuse('receipt_invalid')
78
+ row = row[part]
79
+ }
80
+ const last = patch.path.at(-1)!
81
+ if (!row || !equal(row[last], patch.after)) refuse('receipt_label_drift')
82
+ if (patch.before === undefined) delete row[last]
83
+ else row[last] = structuredClone(patch.before)
84
+ }
85
+ if (!evidence.rawAudioSampleCounts || typeof evidence.rawAudioSampleCounts !== 'object') refuse('receipt_invalid')
86
+ const plan: MacSpeakerRepairPlan = { id: receipt.id, evidenceHash: receipt.evidenceHash, expectedDocumentHash: receipt.beforeHash,
87
+ protectedRawChunkIndices: [], rawAudioSampleCounts: evidence.rawAudioSampleCounts, assignments: receipt.assignments, withheldBatchWords: receipt.withheldBatchWords }
88
+ const seen = new Set<number>()
89
+ for (const assignment of receipt.assignments) {
90
+ if (!uint(assignment.rawChunkIndex) || seen.has(assignment.rawChunkIndex)) refuse('receipt_invalid')
91
+ seen.add(assignment.rawChunkIndex)
92
+ const proof = assignment.targetEvidence
93
+ if (!proof) continue // Anonymous recurrence cannot authorize a name.
94
+ if (!checkSpeakerName(assignment.to).ok || !proof.windows?.length || proof.windows.length > 128) refuse('proof_rejected')
95
+ try { validateMacSpeakerTargetEvidence(source, plan, assignment) } catch { refuse('proof_rejected') }
96
+ const current = byRaw.get(assignment.rawChunkIndex)
97
+ if (!current || current.chunk.speaker !== assignment.to || !SHA.test(current.chunk.audioSha256)) refuse('receipt_label_drift')
98
+ const restored = source.chunkEntries.find((entry: Doc) => entry.chunkIndex === assignment.rawChunkIndex)?.chunk
99
+ if (restored?.speaker !== assignment.from) refuse('receipt_label_drift')
100
+ positions.set(current.position, {
101
+ label: assignment.to, rawChunkIndex: assignment.rawChunkIndex, evidenceId: assignment.evidenceId, receiptHash,
102
+ modelSHA256: proof.modelSha256, profileSHA256: proof.profileSha256, algorithmVersion: proof.algorithmVersion,
103
+ independentWindows: proof.windows.length, minimumSimilarity: Math.min(...proof.windows.map(w => w.similarity)),
104
+ minimumMargin: Math.min(...proof.windows.map(w => w.similarity - w.runnerUpSimilarity)),
105
+ })
106
+ }
107
+ return { status: 'qualified', positions }
108
+ } catch (error) {
109
+ return { status: 'invalid', positions: new Map(), reason: error instanceof Error ? error.message : 'proof_rejected' }
110
+ }
111
+ }
@@ -0,0 +1,41 @@
1
+ /** Isolated, killable read-only extraction. Native model work never runs on the
2
+ * server event loop and never reaches recognition/enrollment/calibration APIs. */
3
+ import { spawn } from 'node:child_process'
4
+ import { createRequire } from 'node:module'
5
+ const require = createRequire(import.meta.url)
6
+ const worker = `
7
+ const fs = require('node:fs');
8
+ const sherpa = require(process.argv[1]);
9
+ const model = process.argv[2];
10
+ const extractor = new sherpa.SpeakerEmbeddingExtractor({model, numThreads:1, provider:'cpu'});
11
+ let size=0, parts=[];
12
+ process.stdin.on('data', b=>{size+=b.length;if(size>960000)process.exit(2);parts.push(b)});
13
+ process.stdin.on('end',()=>{
14
+ const pcm=Buffer.concat(parts); if(pcm.length<896000||pcm.length%2)process.exit(3);
15
+ const embed=(b)=>{const samples=new Float32Array(b.length/2);for(let i=0;i<samples.length;i++)samples[i]=b.readInt16LE(i*2)/32768;
16
+ const stream=extractor.createStream();stream.acceptWaveform({samples,sampleRate:16000});stream.inputFinished();
17
+ if(!extractor.isReady(stream))throw Error('not ready');return Array.from(extractor.compute(stream));};
18
+ const half=Math.floor(pcm.length/4)*2; process.stdout.write(JSON.stringify([embed(pcm),embed(pcm.subarray(0,half)),embed(pcm.subarray(half))]));
19
+ });`
20
+ export async function extractQualifiedMacWindow(pcm: Buffer, modelPath: string, timeoutMs: number, deps: { spawn?: typeof spawn; modulePath?: string } = {}): Promise<readonly number[][] | null> {
21
+ if (pcm.length < 896000 || pcm.length > 960000 || pcm.length % 2 || timeoutMs <= 0) return null
22
+ let modulePath: string
23
+ try { modulePath = deps.modulePath ?? require.resolve('sherpa-onnx-node') } catch { return null }
24
+ return new Promise(resolve => {
25
+ const child = (deps.spawn ?? spawn)(process.execPath, ['-e', worker, modulePath, modelPath], { stdio: ['pipe', 'pipe', 'pipe'] })
26
+ const output: Buffer[] = []; let size = 0, settled = false, aborted = false
27
+ const finish = (result: readonly number[][] | null) => { if (settled) return; settled = true; clearTimeout(timer); resolve(result) }
28
+ const abort = () => { aborted = true; child.kill('SIGKILL') }
29
+ // Keep ownership until close confirms native termination. Releasing on
30
+ // kill() alone permits a second native job while the first still exits.
31
+ const timer = setTimeout(abort, timeoutMs)
32
+ child.stdout.on('data', b => { size += b.length; if (size > 131072) abort(); else if (!aborted) output.push(Buffer.from(b)) })
33
+ // Drain diagnostics without retaining native stderr or logging private input.
34
+ child.stderr.on('data', () => {})
35
+ child.on('error', () => { aborted = true; if (!child.pid) finish(null); else child.kill('SIGKILL') }); child.stdin.on('error', () => {})
36
+ child.on('close', code => {
37
+ try { const rows = JSON.parse(Buffer.concat(output).toString()); finish(!aborted && code === 0 && Array.isArray(rows) && rows.length === 3 ? rows : null) } catch { finish(null) }
38
+ })
39
+ child.stdin.end(pcm)
40
+ })
41
+ }
@@ -0,0 +1,257 @@
1
+ /** Qualified, named, exact-window finalization only. No anonymous merging,
2
+ * enrollment, guessed participant count, model-channel naming or lower floor.
3
+ * A durable attempt and two-file before/after journal make retries fail closed. */
4
+ import { createHash } from 'node:crypto'
5
+ import { existsSync, lstatSync, readFileSync } from 'node:fs'
6
+ import { readFile, stat } from 'node:fs/promises'
7
+ import { join, resolve } from 'node:path'
8
+ import { durableAtomicWriteFileSync } from './atomic-fs.js'
9
+ import { MAC_CAPTURE_ID } from './mac-recording.js'
10
+ import { dataPath } from './data-dir.js'
11
+ import { readPcm16Mono, wavHeader } from './nemotron-segments.js'
12
+ import { planMacSpeakerWindows, qualifyMacSpeakerWindows, type VerifiedMacRawChunk, type MacWindowEmbeddingReceipt } from './mac-speaker-windows.js'
13
+ import { normalizedSpeakerCosine, type MacSpeakerProfile } from './mac-speaker-reconcile.js'
14
+ import { hashMacSpeakerDocument, hashMacSpeakerMachineBinding, projectMacSpeakerRepair, type MacProfileMatchEvidence, type MacSpeakerRepairAssignment, type MacSpeakerWordBinding } from './mac-speaker-repair-projection.js'
15
+ import { boundWords, renderBoundSpeakerLabels, renderMarkdown } from './mac-speaker-repair-render.js'
16
+ import { readMacSpeakerMachineEvidence } from './mac-speaker-machine-evidence.js'
17
+ import { extractQualifiedMacWindow } from './mac-speaker-profile-extractor.js'
18
+ type Doc = Record<string, any>
19
+ const sha = (v: string | Buffer) => createHash('sha256').update(v).digest('hex')
20
+ const yieldLoop = () => new Promise<void>(r => setImmediate(r))
21
+ const MAX_WINDOWS = 12, MAX_RAW = 2048, MAX_MS = 90_000
22
+ export const MAX_PROFILE_BYTES = 32 * 1024 * 1024
23
+ function fail(reason: string): never { throw new Error(`qualified Mac profile pass: ${reason}`) }
24
+ export interface QualifiedMacProfileInput { sidecarPath: string; meetingPath: string; audioDir: string; resumeOnly?: boolean }
25
+ export interface QualifiedMacProfileDeps {
26
+ /** Test seams never invoke native inference. Production freezes the complete cohort. */
27
+ runtime?: () => Promise<{ modelPath: string; profilesPath: string } | null>
28
+ extract?: (pcm: Buffer, modelPath: string, timeoutMs: number) => Promise<readonly number[][] | null>
29
+ ledgerPath?: (sessionId: string) => string
30
+ now?: () => number
31
+ beforeCommit?: () => void
32
+ afterWrite?: (index: number) => void
33
+ }
34
+ export interface QualifiedMacProfileOutcome { status: 'applied' | 'skipped'; reason: string; assignments?: number }
35
+ interface Image { path: string; before: string; after: string }
36
+ interface Journal { version: 1; status: 'attempted' | 'prepared' | 'complete'; sessionId: string; sourceSidecarHash: string; imageBinding?: string; rawBindings?: Array<{ name: string; sha256: string }>; reason?: string; images?: Image[]; ledgerPath: string; ledgerHash: string | null; profilePath: string; profileHash: string; modelPath: string; modelHash: string }
37
+ const active = new Set<string>()
38
+ let extractionBusy = false
39
+ function regularRead(path: string, max = 12 * 1024 * 1024): string {
40
+ const s = lstatSync(path)
41
+ if (!s.isFile() || s.isSymbolicLink() || s.size > max) fail('unsafe or oversized file')
42
+ return readFileSync(path, 'utf8')
43
+ }
44
+ function ledgerHash(path: string): string | null {
45
+ try { return sha(regularRead(path, 4 * 1024 * 1024)) } catch (e) { if ((e as NodeJS.ErrnoException).code === 'ENOENT') return null; throw e }
46
+ }
47
+ function ledgerClear(path: string): boolean {
48
+ const digest = ledgerHash(path)
49
+ if (digest === null) return true
50
+ // Every historical human action (including intent/revert) protects the capture.
51
+ return regularRead(path).trim() === ''
52
+ }
53
+
54
+ function strictPcm(wav: Buffer) {
55
+ if (wav.length < 44 || wav.readUInt32LE(4) !== wav.length - 8 || wav.toString('ascii', 12, 16) !== 'fmt ' || wav.readUInt32LE(16) !== 16 || wav.readUInt16LE(20) !== 1 || wav.readUInt16LE(22) !== 1 || wav.readUInt32LE(24) !== 16000 || wav.readUInt32LE(28) !== 32000 || wav.readUInt16LE(32) !== 2 || wav.readUInt16LE(34) !== 16 || wav.toString('ascii', 36, 40) !== 'data' || wav.readUInt32LE(40) !== wav.length - 44 || (wav.length - 44) % 2) return null
56
+ return readPcm16Mono(wav)
57
+ }
58
+ async function runtimeDefault() {
59
+ const { speakerModelState } = await import('./speaker-embeddings.js')
60
+ const model = speakerModelState()
61
+ return model.state === 'active' && model.path ? { modelPath: model.path, profilesPath: dataPath('voice-profiles.json') } : null
62
+ }
63
+ function recover(journalPath: string, journal: Journal, input: QualifiedMacProfileInput, deps: QualifiedMacProfileDeps): QualifiedMacProfileOutcome {
64
+ if (journal.version !== 1 || !MAC_CAPTURE_ID.test(journal.sessionId) || journal.images?.length !== 2
65
+ || journal.images[0].path !== resolve(input.meetingPath) || journal.images[1].path !== resolve(input.sidecarPath)
66
+ || ledgerHash(journal.ledgerPath) !== journal.ledgerHash) fail('journal or human history drift')
67
+ if (journal.status !== 'complete' && (sha(regularRead(journal.profilePath, MAX_PROFILE_BYTES)) !== journal.profileHash || sha(readFileSync(journal.modelPath)) !== journal.modelHash)) fail('journal profile/model drift')
68
+ const images = journal.images
69
+ if (journal.imageBinding !== hashMacSpeakerDocument(images) || sha(images[1].before) !== journal.sourceSidecarHash) fail('journal image integrity drift')
70
+ const beforeDoc = JSON.parse(images[1].before), afterDoc = JSON.parse(images[1].after)
71
+ const proofRead = readMacSpeakerMachineEvidence({ ...afterDoc, finalizationState: 'complete' })
72
+ if (beforeDoc.sessionId !== journal.sessionId || afterDoc.sessionId !== journal.sessionId || proofRead.status !== 'qualified' || !proofRead.positions.size || !afterDoc.speakerRepair?.machineEvidence?.receipt) fail('journal proof rejected')
73
+ const expectedSequence = boundWords(beforeDoc, afterDoc)
74
+ if (renderBoundSpeakerLabels(beforeDoc.batchTranscript, expectedSequence) !== afterDoc.batchTranscript || renderMarkdown(images[0].before, expectedSequence) !== images[0].after) fail('journal word invariant drift')
75
+ if (journal.status !== 'complete') {
76
+ if (!Array.isArray(journal.rawBindings) || !journal.rawBindings.length || journal.rawBindings.length > MAX_RAW) fail('journal raw bindings invalid')
77
+ for (const raw of journal.rawBindings) { if (!/^chunk_\d{4,}\.wav$/.test(raw.name) || sha(readFileSync(join(input.audioDir, raw.name))) !== raw.sha256) fail('journal raw drift') }
78
+ }
79
+ const current = images.map(image => regularRead(image.path))
80
+ if (journal.status !== 'complete' && current.some((bytes, i) => bytes !== images[i].before && bytes !== images[i].after)) fail('journal precondition drift')
81
+ // Completed transactions never reapply over a later change; the exact speech
82
+ // binding/revision guard in readers separately invalidates later stale claims.
83
+ if (journal.status === 'complete') {
84
+ if (current.every((bytes, i) => bytes === images[i].after)) return { status: 'applied', reason: 'already_applied' }
85
+ const saved = JSON.parse(images[1].after), currentDoc = JSON.parse(current[1])
86
+ if (hashMacSpeakerMachineBinding(currentDoc) === hashMacSpeakerMachineBinding(saved) && currentDoc.correctionRevision === saved.correctionRevision && readMacSpeakerMachineEvidence(currentDoc).status === 'qualified') return { status: 'applied', reason: 'already_applied' }
87
+ fail('completed journal changed')
88
+ }
89
+ for (let i = 0; i < images.length; i++) {
90
+ if (ledgerHash(journal.ledgerPath) !== journal.ledgerHash) fail('human history changed')
91
+ for (let j = 0; j < images.length; j++) {
92
+ const bytes = regularRead(images[j].path)
93
+ if (bytes !== (j < i ? images[j].after : current[j])) fail('transaction changed during commit')
94
+ }
95
+ if (current[i] !== images[i].after) durableAtomicWriteFileSync(images[i].path, images[i].after)
96
+ deps.afterWrite?.(i)
97
+ }
98
+ if (images.some(image => regularRead(image.path) !== image.after)) fail('postimage changed')
99
+ journal.status = 'complete'; durableAtomicWriteFileSync(journalPath, JSON.stringify(journal))
100
+ return { status: 'applied', reason: 'qualified_profile' }
101
+ }
102
+ /** Six deterministic adjacent pairs spread across the entire call. Scheduling
103
+ * hints never add identity votes; each pair still needs acoustic qualification. */
104
+ export function selectQualifiedProfileWindows<T>(windows: readonly T[]): T[] {
105
+ if (windows.length <= MAX_WINDOWS) return [...windows]
106
+ const indexes = new Set<number>()
107
+ for (let i = 0; i < MAX_WINDOWS / 2; i++) { const index = Math.floor(i * (windows.length - 2) / (MAX_WINDOWS / 2 - 1)); indexes.add(index); indexes.add(index + 1) }
108
+ return [...indexes].sort((a, b) => a - b).map(i => windows[i])
109
+ }
110
+ /** Called twice by the finalizer: resumeOnly BEFORE any ASR retry, and ordinary
111
+ * mode AFTER ASR+diarization, before raw deletion or downstream copies/sync. */
112
+ export async function runQualifiedMacProfilePass(input: QualifiedMacProfileInput, deps: QualifiedMacProfileDeps = {}): Promise<QualifiedMacProfileOutcome> {
113
+ const key = resolve(input.sidecarPath), journalPath = `${key}.speaker-profile-pass.json`
114
+ if (active.has(key)) return { status: 'skipped', reason: 'active' }
115
+ active.add(key)
116
+ let ownsExtraction = false
117
+ try {
118
+ if (existsSync(journalPath)) {
119
+ const prior = JSON.parse(regularRead(journalPath, 64 * 1024 * 1024)) as Journal
120
+ if (prior.status === 'prepared' || prior.status === 'complete') return recover(journalPath, prior, input, deps)
121
+ if (prior.version !== 1 || prior.status !== 'attempted') fail('invalid attempt journal')
122
+ return { status: 'skipped', reason: prior.reason ?? 'previous_attempt' }
123
+ }
124
+ if (input.resumeOnly) return { status: 'skipped', reason: 'no_transaction' }
125
+ const now = deps.now ?? Date.now, deadline = now() + MAX_MS
126
+ const budget = () => { if (now() >= deadline) fail('budget_exhausted') }
127
+ const beforeSidecar = regularRead(key), source = JSON.parse(beforeSidecar) as Doc
128
+ if (source.sourceType !== 'mac_system_audio' || !MAC_CAPTURE_ID.test(source.sessionId) || !['batch_pending', 'ops_pending', 'complete'].includes(source.finalizationState)
129
+ || source.claimPending || source.active || source.isActive || source.status === 'recording' || source.isMerged || source.mergedFrom || source.sourceSessionIds || source.supersededBy || source.superseded || source.speakerRepair
130
+ || (source.correctionRevision ?? 0) !== 0 || source.batchApplied !== true || !Array.isArray(source.batchSegments) || !Array.isArray(source.chunkEntries) || source.chunkEntries.length > MAX_RAW) return { status: 'skipped', reason: 'source_ineligible' }
131
+ const ledgerPath = deps.ledgerPath?.(source.sessionId) ?? dataPath('meeting-corrections', `${source.sessionId}.jsonl`)
132
+ if (!ledgerClear(ledgerPath)) return { status: 'skipped', reason: 'human_corrected' }
133
+ const ledger = ledgerHash(ledgerPath), beforeMarkdown = regularRead(input.meetingPath, 4 * 1024 * 1024)
134
+ const runtime = await (deps.runtime ?? runtimeDefault)()
135
+ if (!runtime) return { status: 'skipped', reason: 'model_unavailable' }
136
+ const profileBytes = regularRead(runtime.profilesPath, MAX_PROFILE_BYTES), profileSHA256 = sha(profileBytes)
137
+ const cohort = JSON.parse(profileBytes).profiles
138
+ if (!Array.isArray(cohort) || !cohort.length || cohort.length > 1024) return { status: 'skipped', reason: 'profile_cohort_invalid' }
139
+ if ((await stat(runtime.modelPath)).size > 128 * 1024 * 1024) return { status: 'skipped', reason: 'model_oversized' }
140
+ const modelBytes = await readFile(runtime.modelPath); budget()
141
+ if (modelBytes.length > 128 * 1024 * 1024) return { status: 'skipped', reason: 'model_oversized' }
142
+ if (cohort.some(p => !p || typeof p.name !== 'string' || !Array.isArray(p.embeddings) || !p.embeddings.length || p.embeddings.length > 128 || p.embeddings.some((r: unknown) => !Array.isArray(r) || r.length !== 192 || r.some(v => !Number.isFinite(v)))) || cohort.reduce((n, p) => n + p.embeddings.length, 0) > 8192) return { status: 'skipped', reason: 'profile_cohort_invalid' }
143
+ const modelSHA256 = sha(modelBytes)
144
+ const profiles: MacSpeakerProfile[] = cohort.map(p => ({ name: p.name, embeddings: p.embeddings, profileSHA256 }))
145
+ // One bounded native profile job server-wide. A concurrent capture keeps
146
+ // its saved labels rather than multiplying native processes/CPU load.
147
+ if (extractionBusy) return { status: 'skipped', reason: 'profile_lane_busy' }
148
+ extractionBusy = true; ownsExtraction = true
149
+ const journal: Journal = { version: 1, status: 'attempted', sessionId: source.sessionId, sourceSidecarHash: sha(beforeSidecar), ledgerPath, ledgerHash: ledger, profilePath: resolve(runtime.profilesPath), profileHash: profileSHA256, modelPath: resolve(runtime.modelPath), modelHash: modelSHA256 }
150
+ durableAtomicWriteFileSync(journalPath, JSON.stringify(journal))
151
+ try {
152
+ const raw: VerifiedMacRawChunk[] = [], counts: Record<string, number> = {}
153
+ for (const entry of source.chunkEntries) {
154
+ budget()
155
+ if (!Number.isSafeInteger(entry.chunkIndex) || entry.chunkIndex < 0) fail('invalid raw index')
156
+ const path = join(input.audioDir, `chunk_${String(entry.chunkIndex).padStart(4, '0')}.wav`)
157
+ const info = await stat(path)
158
+ if (!info.isFile() || info.size > 960044) fail('invalid raw file')
159
+ const wav = await readFile(path), audioSHA256 = sha(wav), pcm = strictPcm(wav)
160
+ if (!pcm || pcm.sampleRate !== 16000 || wav.length > 960044 || (entry.chunk.audioSha256 !== undefined && audioSHA256 !== entry.chunk.audioSha256)) fail('raw hash/format drift')
161
+ const sampleCount = pcm.pcm.length / 2
162
+ counts[entry.chunkIndex] = sampleCount
163
+ raw.push({ chunkIndex: entry.chunkIndex, audioSHA256, pcmSHA256: sha(pcm.pcm), sampleRate: 16000, channels: 1, format: 'pcm_s16le', sampleCount, verified: true })
164
+ if (raw.length % 4 === 0) await yieldLoop()
165
+ }
166
+ const plan = planMacSpeakerWindows({ sidecar: source as any, rawChunks: raw, correctionHistory: 'verified-clear' })
167
+ plan.windows = selectQualifiedProfileWindows(plan.windows)
168
+ if (plan.windows.length < 2) fail('insufficient_long_windows')
169
+ const receipts: MacWindowEmbeddingReceipt[] = []
170
+ for (const window of plan.windows) {
171
+ budget(); await yieldLoop()
172
+ const buffers: Buffer[] = []
173
+ for (const s of window.sources) {
174
+ const wav = await readFile(join(input.audioDir, `chunk_${String(s.chunkIndex).padStart(4, '0')}.wav`)), pcm = strictPcm(wav)
175
+ if (sha(wav) !== s.audioSHA256 || !pcm || sha(pcm.pcm) !== s.pcmSHA256) fail('raw changed before extraction')
176
+ buffers.push(pcm.pcm)
177
+ }
178
+ const pcm = Buffer.concat(buffers), wav = Buffer.concat([wavHeader(pcm.length), pcm])
179
+ const vectors = await (deps.extract ?? extractQualifiedMacWindow)(pcm, runtime.modelPath, Math.min(15_000, deadline - now()))
180
+ budget()
181
+ if (!vectors || vectors.length !== 3) continue
182
+ receipts.push({ windowId: window.id, windowBindingSHA256: window.bindingSHA256, modelSHA256, audioSHA256: sha(wav), pcmSHA256: sha(pcm), sampleCount: pcm.length / 2, embedding: vectors[0], halfEmbeddings: [vectors[1], vectors[2]] })
183
+ }
184
+ const qualified = qualifyMacSpeakerWindows(plan, receipts, profiles, modelSHA256)
185
+ const byWindow = new Map(plan.windows.map(w => [w.id, w])), byReceipt = new Map(receipts.map(r => [r.windowId, r]))
186
+ const assignments: MacSpeakerRepairAssignment[] = []
187
+ const groups = new Map<string, typeof qualified>()
188
+ for (const c of qualified) if (c.status === 'qualified' && c.profileName) { const k = `${c.acousticIdentity}:${c.profileName}`; groups.set(k, [...(groups.get(k) ?? []), c]) }
189
+ for (const group of groups.values()) {
190
+ if (group.length < 2) continue
191
+ const profile = profiles.find(p => p.name === group[0].profileName)!
192
+ // Same raw-average centroid used by the production recognizer. The
193
+ // qualifier already rejected corrupt/mismatched rows and profile ties.
194
+ const rows = profile.embeddings, dimensions = rows[0].length
195
+ const centroid = Array.from({ length: dimensions }, (_, i) => rows.reduce((n, r) => n + r[i] / rows.length, 0))
196
+ const centroids = profiles.map(p => ({ name: p.name, vector: Array.from({ length: dimensions }, (_, i) => p.embeddings.reduce((n, r) => n + r[i] / p.embeddings.length, 0)) }))
197
+ const proof: MacProfileMatchEvidence = { kind: 'profile-match', profileName: profile.name, profileSha256: profileSHA256, modelSha256: modelSHA256, algorithmVersion: 'qualified-long-window-profile-v1',
198
+ crossWindowCosines: group.map(a => group.map(b => normalizedSpeakerCosine(byReceipt.get(a.windowId)!.embedding, byReceipt.get(b.windowId)!.embedding)!)),
199
+ windows: group.map(c => { const w = byWindow.get(c.windowId)!, r = byReceipt.get(c.windowId)!; return { id: w.id, captureStartSample: w.startSample, captureEndSample: w.endSample, pcmSha256: r.pcmSHA256,
200
+ similarity: normalizedSpeakerCosine(r.embedding, centroid)!, runnerUpSimilarity: Math.max(-1, ...centroids.filter(p => p.name !== profile.name).map(p => normalizedSpeakerCosine(r.embedding, p.vector)!)),
201
+ rawChunks: w.sources.map(s => ({ rawChunkIndex: s.chunkIndex, audioSha256: s.audioSHA256, sampleCount: s.sampleCount })) } }) }
202
+ for (const c of group) for (const s of byWindow.get(c.windowId)!.sources) assignments.push({ rawChunkIndex: s.chunkIndex, from: c.originalVoiceHint, to: profile.name, evidenceId: c.acousticIdentity!, targetEvidence: proof, batchWords: [] })
203
+ }
204
+ if (!assignments.length) fail('no_qualified_profile')
205
+ const selected = new Map(assignments.map(a => [a.rawChunkIndex, a])), withheldBatchWords: Array<MacSpeakerWordBinding & { reason: 'outside-qualified-span' }> = []
206
+ source.batchSegments.forEach((segment: Doc, segmentIndex: number) => {
207
+ const spans: { raw: number; start: number; end: number }[] = []; let end = 0
208
+ if (!Number.isSafeInteger(segment.startChunkIdx) || !Number.isSafeInteger(segment.endChunkIdx) || segment.endChunkIdx - segment.startChunkIdx > MAX_RAW) fail('invalid batch range')
209
+ for (let raw = segment.startChunkIdx; raw <= segment.endChunkIdx; raw++) { if (!counts[raw]) fail('missing batch raw clock'); spans.push({ raw, start: end, end: end + counts[raw] / 16000 }); end += counts[raw] / 16000 }
210
+ segment.speakerWords.forEach((word: Doc, wordIndex: number) => {
211
+ const overlaps = spans.filter(s => word.end === word.start ? word.start >= s.start && word.start < s.end : word.start < s.end && word.end > s.start)
212
+ if (!overlaps.some(s => selected.has(s.raw))) return
213
+ const binding = { segmentIndex, wordIndex, word: word.word, from: word.speaker, start: word.start, end: word.end }
214
+ if (overlaps.some(s => !selected.has(s.raw))) withheldBatchWords.push({ ...binding, reason: 'outside-qualified-span' })
215
+ else if (new Set(overlaps.map(s => selected.get(s.raw)!.to)).size === 1) selected.get(overlaps[0].raw)!.batchWords.push(binding)
216
+ else fail('crossed identities')
217
+ })
218
+ })
219
+ budget()
220
+ const staging = { ...source, finalizationState: 'complete' }, repairPlan = { id: `profile_${sha(beforeSidecar).slice(0, 24)}`, evidenceHash: hashMacSpeakerDocument(receipts), expectedDocumentHash: hashMacSpeakerDocument(staging), protectedRawChunkIndices: [], rawAudioSampleCounts: counts, assignments, withheldBatchWords }
221
+ const projected = projectMacSpeakerRepair(staging, repairPlan), after = projected.doc, sequence = boundWords(staging, after)
222
+ after.batchTranscript = renderBoundSpeakerLabels(source.batchTranscript, sequence)
223
+ const afterMarkdown = renderMarkdown(beforeMarkdown, sequence)
224
+ after.finalizationState = source.finalizationState
225
+ after.correctionRevision = 1; after.lifecycleRevision = (source.lifecycleRevision ?? 0) + 1
226
+ after.speakerRepair = { version: 1, id: repairPlan.id, evidenceHash: repairPlan.evidenceHash, machineEvidence: { version: 1, correctionRevision: 1, documentBinding: hashMacSpeakerMachineBinding(after), receipt: projected.receipt, rawAudioSampleCounts: counts } }
227
+ after.speakerProfilePass = { version: 1, status: 'applied', windows: receipts.length, assignments: assignments.length }
228
+ deps.beforeCommit?.(); budget()
229
+ for (const c of assignments) {
230
+ const rawBytes = await readFile(join(input.audioDir, `chunk_${String(c.rawChunkIndex).padStart(4, '0')}.wav`))
231
+ if (sha(rawBytes) !== raw.find(r => r.chunkIndex === c.rawChunkIndex)!.audioSHA256) fail('raw changed before commit')
232
+ }
233
+ const currentModelHash = sha(await readFile(runtime.modelPath))
234
+ if (regularRead(key) !== beforeSidecar || regularRead(input.meetingPath) !== beforeMarkdown || ledgerHash(ledgerPath) !== ledger || sha(regularRead(runtime.profilesPath, MAX_PROFILE_BYTES)) !== profileSHA256 || currentModelHash !== modelSHA256) fail('source/profile/model/history drift')
235
+ journal.images = [{ path: resolve(input.meetingPath), before: beforeMarkdown, after: afterMarkdown }, { path: key, before: beforeSidecar, after: JSON.stringify(after, null, 2) + '\n' }]
236
+ journal.imageBinding = hashMacSpeakerDocument(journal.images)
237
+ journal.rawBindings = assignments.map(a => ({ name: `chunk_${String(a.rawChunkIndex).padStart(4, '0')}.wav`, sha256: raw.find(r => r.chunkIndex === a.rawChunkIndex)!.audioSHA256 }))
238
+ journal.status = 'prepared'; durableAtomicWriteFileSync(journalPath, JSON.stringify(journal))
239
+ const outcome = recover(journalPath, journal, input, deps)
240
+ return { ...outcome, assignments: assignments.length }
241
+ } catch (error) {
242
+ // Prepared transactions must be retried before ASR, never reported as an
243
+ // ordinary no-match that permits downstream filing of mixed generations.
244
+ if (journal.status === 'prepared') throw error
245
+ journal.reason = error instanceof Error ? error.message : 'unqualified'; durableAtomicWriteFileSync(journalPath, JSON.stringify(journal))
246
+ return { status: 'skipped', reason: journal.reason }
247
+ }
248
+ } catch (error) {
249
+ // Recovery failures must block ASR/copying; ordinary unavailable resources
250
+ // leave the already saved recording alone.
251
+ if (existsSync(journalPath)) {
252
+ const saved = JSON.parse(regularRead(journalPath, 64 * 1024 * 1024)) as Journal
253
+ if (saved.status === 'prepared' || saved.status === 'complete') throw error
254
+ }
255
+ return { status: 'skipped', reason: error instanceof Error ? error.message : 'unavailable' }
256
+ } finally { if (ownsExtraction) extractionBusy = false; active.delete(key) }
257
+ }
@@ -0,0 +1,22 @@
1
+ /** One conservative predicate for automatic acoustic support and label writes.
2
+ * Human markers protect even still-anonymous labels and unlabeled ASR words. */
3
+ export function isMacSpeakerHumanProtected(value: unknown): boolean {
4
+ if (!value || typeof value !== 'object') return false
5
+ const row = value as Record<string, unknown>
6
+ if (['speakerCorrectionBatchId', 'speakerCorrectionId', 'manualSpeaker', 'speakerManuallyAssigned', 'confirmedByHuman', 'manuallyAssigned', 'manuallyCorrected'].some(key => !!row[key])) return true
7
+ return ['speakerOrigin', 'speakerSource', 'speakerPolicy', 'speakerProvenance', 'source'].some(key =>
8
+ typeof row[key] === 'string' && /(?:manual|human|correction|confirmed)/i.test(row[key] as string))
9
+ }
10
+ /** Inspect every stored chunk representation before using any of its audio as
11
+ * positive evidence. Malformed declared surfaces also fail closed. */
12
+ export function hasMacSpeakerProtectedRows(value: unknown): boolean {
13
+ if (!value || typeof value !== 'object') return true
14
+ const chunk = value as Record<string, unknown>
15
+ if (isMacSpeakerHumanProtected(chunk)) return true
16
+ for (const key of ['segments', 'finalSegments', 'words']) {
17
+ if (chunk[key] === undefined) continue
18
+ if (!Array.isArray(chunk[key])) return true
19
+ if ((chunk[key] as unknown[]).some(row => !row || typeof row !== 'object' || isMacSpeakerHumanProtected(row))) return true
20
+ }
21
+ return false
22
+ }