@gotcos/glasses-server 6.95.0 → 6.98.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 (123) hide show
  1. package/CHANGELOG.md +157 -0
  2. package/README.md +4 -0
  3. package/managed-runtime-contract.json +13 -0
  4. package/package.json +3 -1
  5. package/server/index.ts +12 -0
  6. package/server/lib/attached-provider-adapter.ts +35 -1
  7. package/server/lib/claude-bridge.ts +47 -0
  8. package/server/lib/codex-bridge.ts +11 -1
  9. package/server/lib/codex-rollout-watermark.ts +42 -0
  10. package/server/lib/cursor-bridge.ts +2 -1
  11. package/server/lib/decision-provider.ts +4 -2
  12. package/server/lib/dictation-clean-models.ts +2 -0
  13. package/server/lib/dictation-clean.ts +2 -2
  14. package/server/lib/dictation-polish-pipeline.ts +22 -2
  15. package/server/lib/dictation-prewarm.ts +16 -0
  16. package/server/lib/direct-bridge.ts +14 -2
  17. package/server/lib/direct-sse.ts +6 -1
  18. package/server/lib/display-bus.ts +1 -1
  19. package/server/lib/fork-thread.ts +32 -1
  20. package/server/lib/held-naming-batches.ts +44 -7
  21. package/server/lib/held-voice-groups.ts +5 -4
  22. package/server/lib/job-audit-caller.ts +20 -0
  23. package/server/lib/keyed-sessions.ts +21 -1
  24. package/server/lib/lens-gist-engines.ts +17 -5
  25. package/server/lib/live-cos-ask.ts +29 -6
  26. package/server/lib/live-cue-verdicts.ts +43 -8
  27. package/server/lib/live-cues-engine.ts +4 -1
  28. package/server/lib/live-cues-filter.ts +50 -0
  29. package/server/lib/live-cues-general-prompts.ts +137 -0
  30. package/server/lib/live-cues-general-topic.ts +245 -0
  31. package/server/lib/live-cues-general.ts +715 -0
  32. package/server/lib/live-cues-v2-data.ts +453 -0
  33. package/server/lib/live-cues-v2-grounded.ts +262 -0
  34. package/server/lib/live-cues-v2-judge-b.ts +59 -0
  35. package/server/lib/live-cues-v2-names.ts +49 -0
  36. package/server/lib/live-cues-v2-notes.ts +8 -4
  37. package/server/lib/live-cues-v2-open-items.ts +132 -0
  38. package/server/lib/live-cues-v2-pacing.ts +45 -0
  39. package/server/lib/live-cues-v2-prompts-kinds.ts +9 -4
  40. package/server/lib/live-cues-v2-settings.ts +89 -10
  41. package/server/lib/live-cues-v2.ts +570 -47
  42. package/server/lib/mac-recording-asr.ts +6 -3
  43. package/server/lib/mac-speaker-call-human.ts +371 -0
  44. package/server/lib/mac-speaker-call-interval-human.ts +499 -0
  45. package/server/lib/mac-speaker-call-interval-projection.ts +233 -0
  46. package/server/lib/mac-speaker-call-original-view.ts +328 -0
  47. package/server/lib/mac-speaker-call-producer-worker.ts +615 -0
  48. package/server/lib/mac-speaker-call-producer.ts +539 -0
  49. package/server/lib/mac-speaker-call-store.ts +264 -0
  50. package/server/lib/mac-speaker-call-view.ts +373 -0
  51. package/server/lib/mac-speaker-live-compute.ts +72 -0
  52. package/server/lib/mac-speaker-live-memory.ts +414 -0
  53. package/server/lib/mac-speaker-live-projection.ts +126 -0
  54. package/server/lib/mac-speaker-live-save.ts +90 -0
  55. package/server/lib/mac-speaker-live-store.ts +71 -0
  56. package/server/lib/mac-speaker-live-worker.ts +58 -0
  57. package/server/lib/mac-speaker-live.ts +175 -0
  58. package/server/lib/mac-speaker-profile-extractor.ts +15 -10
  59. package/server/lib/mac-speaker-shadow-repair.ts +307 -0
  60. package/server/lib/mac-voice-assignment.ts +46 -27
  61. package/server/lib/mac-voice-display.ts +195 -0
  62. package/server/lib/mac-voice-naming-batch.ts +1067 -0
  63. package/server/lib/mac-voice-suggestions.ts +548 -0
  64. package/server/lib/maintenance-lifecycle.ts +2 -0
  65. package/server/lib/media-store.ts +2 -1
  66. package/server/lib/meeting-ask-pasted.ts +135 -0
  67. package/server/lib/meeting-ask-review.ts +8 -1
  68. package/server/lib/meeting-ask-runner.ts +4 -1
  69. package/server/lib/meeting-ask-runtime.ts +189 -17
  70. package/server/lib/meeting-ask-store.ts +2 -0
  71. package/server/lib/meeting-ask-web.ts +284 -13
  72. package/server/lib/meeting-batch-progress.ts +51 -5
  73. package/server/lib/meeting-batch-transcribe.ts +35 -2
  74. package/server/lib/meeting-corrections.ts +2 -1
  75. package/server/lib/meeting-fact-check.ts +3 -0
  76. package/server/lib/meeting-finalization-jobs.ts +69 -2
  77. package/server/lib/meeting-public-topic.ts +6 -1
  78. package/server/lib/meeting-relabel-enrolment.ts +2 -1
  79. package/server/lib/meeting-relabel.ts +17 -10
  80. package/server/lib/meeting-speaker-review.ts +187 -21
  81. package/server/lib/meeting-store.ts +6 -1
  82. package/server/lib/parakeet.ts +51 -5
  83. package/server/lib/pending-batch-retention.ts +58 -0
  84. package/server/lib/private-reference.ts +38 -0
  85. package/server/lib/provider-usage.ts +561 -0
  86. package/server/lib/public-https-url.ts +32 -0
  87. package/server/lib/query-job-runtime.ts +3 -0
  88. package/server/lib/rich-media-safety.ts +23 -2
  89. package/server/lib/session-find-ask.ts +288 -0
  90. package/server/lib/session-find-collect.ts +715 -0
  91. package/server/lib/session-find-redact.ts +254 -0
  92. package/server/lib/session-find.ts +345 -0
  93. package/server/lib/speaker-calibration-log.ts +3 -2
  94. package/server/lib/speaker-embeddings.ts +46 -35
  95. package/server/lib/speaker-name-nfc.ts +37 -0
  96. package/server/lib/speaker-rename-fanout.ts +7 -6
  97. package/server/lib/speaker-shadow-decide.ts +59 -22
  98. package/server/lib/speaker-shadow-pair-source.ts +91 -0
  99. package/server/lib/speaker-shadow-paired.ts +268 -0
  100. package/server/lib/speaker-shadow-report.ts +2 -2
  101. package/server/lib/speaker-shadow-worker.ts +50 -15
  102. package/server/lib/speaker-shadow.ts +80 -19
  103. package/server/lib/speaker-trainer.ts +2 -1
  104. package/server/lib/token-audit.ts +36 -6
  105. package/server/lib/transcript-cleanup.ts +3 -2
  106. package/server/lib/voice-directory.ts +10 -5
  107. package/server/lib/voice-profile-store.ts +24 -14
  108. package/server/lib/web-lookup-ledger.ts +151 -0
  109. package/server/lib/work-review-runtime.ts +2 -0
  110. package/server/lib/work-search.ts +3 -1
  111. package/server/lib/xlsx-text.ts +277 -0
  112. package/server/routes/decision-provider.ts +4 -1
  113. package/server/routes/jev.ts +5 -1
  114. package/server/routes/live-cues.ts +36 -14
  115. package/server/routes/meeting.ts +515 -61
  116. package/server/routes/session-find.ts +73 -0
  117. package/server/routes/transcribe-stream.ts +114 -33
  118. package/server/routes/voice.ts +28 -18
  119. package/server/scripts/live-cues-v3-mutation-gate.mjs +311 -0
  120. package/server/scripts/mac-voice-assignment-6930-mutation-gate.mjs +5 -5
  121. package/shared/live-cues-v2.ts +38 -5
  122. package/shared/media-attachment.ts +2 -0
  123. package/shared/meeting-ask.ts +11 -0
@@ -0,0 +1,373 @@
1
+ /** Read-only call-wide anonymous view. Packet hashes prove integrity, NOT native
2
+ * authentication. Only the trusted offline producer may grant qualified admission
3
+ * after independent QA. Full export replay belongs in that producer, never HTTP. */
4
+ import { createHash } from 'node:crypto'
5
+ import { closeSync, constants, fstatSync, openSync, readSync } from 'node:fs'
6
+ import { dataPath } from './data-dir.js'
7
+ import { validateMacSpeaker256State, type MacSpeaker256SnapshotInput, type MacSpeaker256State, type MacSpeaker256Group } from './mac-speaker-live-memory.js'
8
+ import { inspectMacSpeakerShadowCallSource } from './mac-speaker-shadow-repair.js'
9
+ import { readMacSpeakerMachineEvidence } from './mac-speaker-machine-evidence.js'
10
+ import { hasMacSpeakerProtectedRows, isMacSpeakerHumanProtected } from './mac-speaker-protection.js'
11
+ import { boundWords, renderMarkdown } from './mac-speaker-repair-render.js'
12
+ import { applyMacCallHumanAnnotation, macCallHumanPaths, readMacCallHumanFile } from './mac-speaker-call-human.js'
13
+ import { intervalSamples, nativeTurns } from './speaker-shadow-decide.js'
14
+ import { createMacSpeakerCallIntervalProjector, prepareMacSpeakerCallIntervalSupport, validateMacSpeakerCallIntervalSupport, type MacSpeakerCallIntervalMembership, type MacSpeakerCallIntervalSupport } from './mac-speaker-call-interval-projection.js'
15
+ import { applyMacIntervalHumanAnnotation, inspectMacIntervalHumanJournal, macIntervalHumanPaths, type MacIntervalHumanMetadata } from './mac-speaker-call-interval-human.js'
16
+ import { inspectOriginalViewTransaction } from './mac-speaker-call-original-view.js'
17
+
18
+ type Doc = Record<string, any>
19
+ const HASH = /^[a-f0-9]{64}$/
20
+ const VOICE = /^Voice (?:[1-9]|[1-9]\d)$/
21
+ const sha = (s: string) => createHash('sha256').update(s).digest('hex')
22
+ const digest = (v: unknown) => sha(JSON.stringify(v))
23
+ function refuse(s: string): never { throw new Error(`Mac call view: ${s}`) }
24
+ const MAX_PACKET = 16 * 1024 * 1024
25
+ // Exact-byte identities only; a changed packet/base/ledger never inherits trust.
26
+ const checkedPackets = new Set<string>()
27
+ const checkedBaseProofs = new Map<string, ReturnType<typeof readMacSpeakerMachineEvidence>['status']>()
28
+ function remember<K, V>(cache: Map<K, V>, key: K, value: V): void { if (cache.size >= 8) cache.delete(cache.keys().next().value!); cache.set(key, value) }
29
+ export interface MacSpeakerCallPacket {
30
+ version: 1 | 2; policy: 'fluid256-call-view-v1' | 'fluid256-call-interval-view-v1'; admission: 'off' | 'qualified'
31
+ projectionPolicy: 'strict' | 'anonymous-estimate' | 'source-bound-intervals'
32
+ captureId: string; generation: string; revision: number
33
+ baseSHA256: string; ledgerSHA256: string; sourceBindingSHA256: string
34
+ stateSHA256: string; modelSHA256: string; cliSHA256: string
35
+ rawBindings: MacSpeaker256State['rawBindings']; groups: MacSpeaker256Group[]
36
+ protectedRawChunkIndices: number[]
37
+ aliasProtectedRawChunkIndices: number[]
38
+ displayLabels: Record<string, string>
39
+ provenance: { nativeAuthenticated: false; timingBasis: 'native-exclusive-intersect-machine-production'; speakerPurityVerified: false; excludedProductionSegments?: ReturnType<typeof inspectMacSpeakerShadowCallSource>['excludedProductionSegments']; nativeSHA256?: string; timelinePCMSHA256?: string }
40
+ intervalSupport?: MacSpeakerCallIntervalSupport
41
+ packetSHA256: string
42
+ }
43
+ export interface MacSpeakerCallView {
44
+ doc: Doc
45
+ markdown?: string
46
+ metadata: { status: 'base' | 'applied'; reason: string; mode: 'strict' | 'anonymous-estimate' | 'alias-evaluation' | 'source-bound-intervals'; revision?: number; generation?: string; packetSHA256?: string; changedChunks: number; changedWords: number; groups: Array<{ identityId: string; label: string; memberVoices: string[]; intervals: MacSpeaker256Group['intervals'] }>; localization: 'estimated-ASR-with-native-support' | 'inherited-machine-estimate'; speakerPurityVerified: false; humanAnnotation?: { revision: number; annotationSHA256: string; members: number }; humanAnnotationInvalid?: true; intervalHumanAnnotation?: MacIntervalHumanMetadata; intervalHumanAnnotationInvalid?: true; intervalHumanRecoveryRequired?: true; intervalMembership?: MacSpeakerCallIntervalMembership; supportSHA256?: string; effectiveWordView?: { version: 1; anonymousGroups: Array<{ identityId: string; label: string }>; unresolvedLabel: 'Ext' } }
47
+ }
48
+ /** Bind every independently checked admission/source stamp to interval support.
49
+ * Admission is intentionally excluded: offline QA may grant an off packet without
50
+ * changing the underlying evidence. This integrity hash is not authentication. */
51
+ function intervalSourceBinding(p: Pick<MacSpeakerCallPacket, 'captureId' | 'generation' | 'revision' | 'baseSHA256' | 'ledgerSHA256' | 'sourceBindingSHA256' | 'stateSHA256' | 'modelSHA256' | 'cliSHA256' | 'rawBindings' | 'groups' | 'aliasProtectedRawChunkIndices' | 'displayLabels'>): string {
52
+ return digest([p.captureId, p.generation, p.revision, p.baseSHA256, p.ledgerSHA256, p.sourceBindingSHA256, p.stateSHA256, p.modelSHA256, p.cliSHA256, p.rawBindings, p.groups, p.aliasProtectedRawChunkIndices, p.displayLabels])
53
+ }
54
+
55
+ /** Expensive pure preparation: call only off-thread/offline with full verified inputs. */
56
+ export function prepareMacSpeakerCallPacket(input: MacSpeaker256SnapshotInput, state: MacSpeaker256State, options: { admission?: 'off' | 'qualified'; projectionPolicy?: 'strict' | 'anonymous-estimate' | 'source-bound-intervals'; intervalMembership?: MacSpeakerCallIntervalMembership } = {}): MacSpeakerCallPacket {
57
+ validateMacSpeaker256State(state, input)
58
+ const { generation: _g, expectedStateSHA256: _e, ...source } = input
59
+ const inspected = inspectMacSpeakerShadowCallSource(source)
60
+ const intervalPolicy = options.projectionPolicy === 'source-bound-intervals'
61
+ if (options.intervalMembership !== undefined && !intervalPolicy) refuse('interval membership requires interval policy')
62
+ const displayLabels: Record<string, string> = {}
63
+ // A display hint is a retained, already estimated Voice label, not a new
64
+ // anonymous namespace. Identity claims live ONLY in the separate interval
65
+ // metadata, never on unsupported occurrences of this same display hint.
66
+ for (const g of state.groups) {
67
+ const identity = state.identities.find(i => i.identityId === g.identityId && !i.retired)
68
+ if (intervalPolicy) {
69
+ if (!identity || identity.ordinal < 1 || identity.ordinal > 999999) refuse('admitted anonymous identity display bound')
70
+ displayLabels[g.identityId] = `Anonymous group ${identity.ordinal}`
71
+ continue
72
+ }
73
+ const hint = identity?.birthIntervals[0]?.originalVoice
74
+ if (!hint || !VOICE.test(hint) || !g.memberVoices.includes(hint)) refuse('retained anonymous display hint')
75
+ displayLabels[g.identityId] = hint
76
+ }
77
+ const body = {
78
+ version: 1 as const, policy: 'fluid256-call-view-v1' as const, admission: options.admission ?? 'off', projectionPolicy: options.projectionPolicy ?? 'strict',
79
+ captureId: state.captureId, generation: state.generation, revision: state.revision,
80
+ baseSHA256: sha(input.currentJSON), ledgerSHA256: sha(input.ledgerJSONL), sourceBindingSHA256: state.sourceBindingSHA256,
81
+ stateSHA256: state.stateSHA256, modelSHA256: state.modelSHA256, cliSHA256: state.cliSHA256,
82
+ rawBindings: structuredClone(state.rawBindings), groups: structuredClone(state.groups),
83
+ protectedRawChunkIndices: [...inspected.protection].sort((a, b) => a - b), aliasProtectedRawChunkIndices: [...inspected.humanProtection].sort((a, b) => a - b), displayLabels,
84
+ provenance: { nativeAuthenticated: false as const, timingBasis: 'native-exclusive-intersect-machine-production' as const, speakerPurityVerified: false as const, excludedProductionSegments: structuredClone(inspected.excludedProductionSegments) },
85
+ }
86
+ let packet: MacSpeakerCallPacket = { ...body, packetSHA256: digest(body) }
87
+ if (intervalPolicy) {
88
+ const rawUsedSamples = state.rawBindings.map(r => inspected.placed.find(p => p.rawChunkIndex === r.rawChunkIndex)!.usedSampleCount)
89
+ const intervalSupport = prepareMacSpeakerCallIntervalSupport({ rawBindings: state.rawBindings, groups: state.groups, displayLabels, protectedRawChunkIndices: [...inspected.humanProtection] }, {
90
+ version: 1, membership: options.intervalMembership ?? 'accepted-only', sourceSHA256: intervalSourceBinding(body), nativeSHA256: state.provenance.nativeSHA256, timelinePCMSHA256: input.timeline.pcmSHA256, totalSamples: input.timeline.total, rawUsedSamples,
91
+ nativeIntervals: intervalSamples(inspected.turns, input.timeline.total).filter(([lo, hi]) => hi > lo).map(([startSample, endSample, nativeCluster]) => ({ startSample, endSample, nativeCluster })),
92
+ })
93
+ const intervalBody = { ...body, version: 2 as const, policy: 'fluid256-call-interval-view-v1' as const, projectionPolicy: 'source-bound-intervals' as const, intervalSupport, provenance: { ...body.provenance, nativeSHA256: intervalSupport.nativeSHA256, timelinePCMSHA256: intervalSupport.timelinePCMSHA256 } }
94
+ packet = { ...intervalBody, packetSHA256: digest(intervalBody) }
95
+ }
96
+ if (Buffer.byteLength(JSON.stringify(packet)) > MAX_PACKET) refuse('packet byte bound')
97
+ return packet
98
+ }
99
+
100
+ function checkPacket(p: MacSpeakerCallPacket, baseJSON: string, ledger: string, doc: Doc): void {
101
+ const intervalPolicy = p?.version === 2 && p.policy === 'fluid256-call-interval-view-v1' && p.projectionPolicy === 'source-bound-intervals'
102
+ const legacyPolicy = p?.version === 1 && p.policy === 'fluid256-call-view-v1' && ['strict', 'anonymous-estimate'].includes(p.projectionPolicy) && p.intervalSupport === undefined
103
+ if (!p || !intervalPolicy && !legacyPolicy || !['off', 'qualified'].includes(p.admission) || p.captureId !== doc.sessionId || typeof p.generation !== 'string' || !p.generation || p.generation.length > 128 || !Number.isSafeInteger(p.revision) || p.revision < 1) refuse('packet identity')
104
+ const { packetSHA256, ...body } = p
105
+ if ([packetSHA256, p.baseSHA256, p.ledgerSHA256, p.sourceBindingSHA256, p.stateSHA256, p.modelSHA256, p.cliSHA256].some(h => !HASH.test(h)) || packetSHA256 !== digest(body) || p.baseSHA256 !== sha(baseJSON) || p.ledgerSHA256 !== sha(ledger)) refuse('packet/source/ledger drift')
106
+ const cacheKey = `${packetSHA256}:${p.baseSHA256}:${p.ledgerSHA256}`
107
+ if (checkedPackets.has(cacheKey)) return
108
+ if (p.provenance?.nativeAuthenticated !== false || p.provenance?.speakerPurityVerified !== false || p.provenance?.timingBasis !== 'native-exclusive-intersect-machine-production') refuse('provenance')
109
+ if (!Array.isArray(doc.chunkEntries) || !Array.isArray(doc.chunks) || !Array.isArray(p.rawBindings) || p.rawBindings.length !== doc.chunkEntries.length || p.rawBindings.length > 6000 || !Array.isArray(p.groups) || p.groups.length > 99 || !Array.isArray(p.protectedRawChunkIndices) || p.protectedRawChunkIndices.length > 6000) refuse('packet bounds')
110
+ for (const indices of [p.protectedRawChunkIndices, p.aliasProtectedRawChunkIndices]) {
111
+ if (!Array.isArray(indices) || indices.length > 6000) refuse('protected indices')
112
+ const protectedSet = new Set(indices)
113
+ if (protectedSet.size !== indices.length || [...protectedSet].some(i => !Number.isInteger(i) || i < 0 || i >= p.rawBindings.length)) refuse('protected indices')
114
+ }
115
+ for (const [i, r] of p.rawBindings.entries()) {
116
+ const e = doc.chunkEntries[i]
117
+ if (r.rawChunkIndex !== i || e.chunkIndex !== i || !HASH.test(r.audioSHA256) || !HASH.test(r.pcmSHA256) || !HASH.test(r.originalRowSHA256) || !Number.isSafeInteger(r.startSample) || !Number.isSafeInteger(r.endSample) || r.startSample < 0 || r.endSample <= r.startSample || r.endSample - r.startSample > 480000 || r.startSample !== Math.round(e.chunk.elapsed * 16) || e.chunk.audioSha256 && e.chunk.audioSha256 !== r.audioSHA256) refuse('raw binding')
118
+ }
119
+ const quarantined = p.provenance.excludedProductionSegments ?? []
120
+ if (!Array.isArray(quarantined) || quarantined.length > 40000) refuse('quarantine bound')
121
+ for (const q of quarantined) {
122
+ const raw = p.rawBindings[q.rawChunkIndex], row = doc.chunkEntries[q.rawChunkIndex]?.chunk?.segments?.[q.segmentIndex]
123
+ if (!raw || !row || !Number.isInteger(q.segmentIndex) || q.segmentIndex < 0 || q.reason !== 'generic-segment-crosses-source-end' || !HASH.test(q.rowSHA256) || q.rowSHA256 !== digest(row) || row.speaker !== 'Ext' || doc.chunkEntries[q.rawChunkIndex].chunk.speaker !== 'Ext' || q.startSec !== row.startSec || q.endSec !== row.endSec || q.sourceSamples !== raw.endSample - raw.startSample || q.overrunSamples <= 0 || q.overrunSamples > 16000 || Math.abs(q.overrunSamples - (q.endSec * 16000 - q.sourceSamples)) > 1e-6 || !p.protectedRawChunkIndices.includes(q.rawChunkIndex) || !p.aliasProtectedRawChunkIndices.includes(q.rawChunkIndex)) refuse('quarantine binding')
124
+ }
125
+ const ids = new Set<string>(), labels = new Set<string>(), voices = new Set<string>()
126
+ let intervals = 0
127
+ for (const g of p.groups) {
128
+ if (typeof g.identityId !== 'string' || g.identityId.length > 128 || ids.has(g.identityId) || !HASH.test(g.groupSHA256) || !Array.isArray(g.memberVoices) || g.memberVoices.length < 2 || g.memberVoices.length > 99 || !g.memberVoices.every(v => VOICE.test(v) && !voices.has(v)) || new Set(g.memberVoices).size !== g.memberVoices.length || !g.memberVoices.includes(g.keepVoiceHint)) refuse('group identity')
129
+ ids.add(g.identityId); g.memberVoices.forEach(v => voices.add(v))
130
+ const label = p.displayLabels?.[g.identityId]
131
+ if (labels.has(label) || (intervalPolicy ? !/^Anonymous group [1-9]\d{0,5}$/.test(label) : !VOICE.test(label) || !g.memberVoices.includes(label))) refuse('anonymous display label')
132
+ labels.add(label)
133
+ if (!Array.isArray(g.witnesses) || g.witnesses.length !== 2 || !Array.isArray(g.intervals) || (intervals += g.intervals.length) > 40000) refuse('group bounds')
134
+ const [a, b] = g.witnesses
135
+ for (const w of [a, b]) if (!HASH.test(w.evidenceId) || !Number.isSafeInteger(w.startSample) || !Number.isSafeInteger(w.endSample) || w.endSample <= w.startSample || !Array.isArray(w.rawChunkIndices) || !w.rawChunkIndices.length || w.rawChunkIndices.length > 6000 || !Array.isArray(w.pcmSHA256s) || w.pcmSHA256s.length !== w.rawChunkIndices.length || w.pcmSHA256s.some(h => !HASH.test(h))) refuse('witness')
136
+ if (a.startSample < b.endSample && b.startSample < a.endSample || a.rawChunkIndices.some(i => b.rawChunkIndices.includes(i)) || a.pcmSHA256s.some(h => b.pcmSHA256s.includes(h))) refuse('dependent witnesses')
137
+ for (const s of g.intervals) {
138
+ const r = p.rawBindings[s.rawChunkIndex]
139
+ if (!r || s.identityId !== g.identityId || s.timingBasis !== p.provenance.timingBasis || !g.memberVoices.includes(s.originalVoice) || s.audioSHA256 !== r.audioSHA256 || s.pcmSHA256 !== r.pcmSHA256 || !Number.isSafeInteger(s.startSample) || !Number.isSafeInteger(s.endSample) || s.startSample < r.startSample || s.endSample > r.endSample || s.endSample <= s.startSample || !Array.isArray(s.evidenceIds) || s.evidenceIds.length !== 2 || s.evidenceIds.some(h => !g.witnesses.some(w => w.evidenceId === h))) refuse('interval binding')
140
+ }
141
+ }
142
+ if (intervalPolicy) {
143
+ const support = p.intervalSupport
144
+ if (!support || support.sourceSHA256 !== intervalSourceBinding(p) || support.nativeSHA256 !== p.provenance.nativeSHA256 || support.timelinePCMSHA256 !== p.provenance.timelinePCMSHA256) refuse('interval source provenance')
145
+ for (const g of p.groups) if (g.groupSHA256 !== digest({ nativeCluster: g.nativeCluster, memberVoices: g.memberVoices, keepVoiceHint: g.keepVoiceHint, witnesses: g.witnesses, intervals: g.intervals.map(({ identityId: _id, ...i }) => i) })) refuse('admission group proof')
146
+ // New display labels cannot inherit an unrelated occurrence already in BASE.
147
+ const hasDisplayCollision = (row: unknown): boolean => {
148
+ if (Array.isArray(row)) return row.some(hasDisplayCollision)
149
+ if (!row || typeof row !== 'object') return false
150
+ const value = row as Doc
151
+ return typeof value.speaker === 'string' && labels.has(value.speaker) || Object.values(value).some(v => v && typeof v === 'object' && hasDisplayCollision(v))
152
+ }
153
+ if (hasDisplayCollision(doc)) refuse('anonymous display collision with BASE')
154
+ validateMacSpeakerCallIntervalSupport({ rawBindings: p.rawBindings, groups: p.groups, displayLabels: p.displayLabels, protectedRawChunkIndices: p.aliasProtectedRawChunkIndices }, support)
155
+ }
156
+ if (checkedPackets.size >= 8) checkedPackets.delete(checkedPackets.values().next().value!)
157
+ checkedPackets.add(cacheKey)
158
+ }
159
+
160
+ /** Shared structural/source validator for trusted offline publication callers.
161
+ * This proves integrity and exact support geometry, not native authenticity or
162
+ * independent speaker purity. Publication still needs its qualification receipt. */
163
+ export function validateMacSpeakerCallPacket(baseJSON: string, ledgerJSONL: string, packet: MacSpeakerCallPacket): void {
164
+ if (Buffer.byteLength(baseJSON) > 32 * 1024 * 1024 || Buffer.byteLength(ledgerJSONL) > 8 * 1024 * 1024 || Buffer.byteLength(JSON.stringify(packet)) > MAX_PACKET) refuse('byte bound')
165
+ const base = JSON.parse(baseJSON) as Doc
166
+ checkPacket(packet, baseJSON, ledgerJSONL, base)
167
+ if (readMacSpeakerMachineEvidence(base).status === 'invalid') refuse('base-machine-proof-invalid')
168
+ }
169
+
170
+ /** Offline qualification must compare packet native geometry to the ACTUAL
171
+ * hash-pinned export, not a caller's self-resealed projection. HTTP readers use
172
+ * only the packet published by that trusted, source-verified caller. */
173
+ export function validateMacSpeakerCallIntervalNativeSource(packet: MacSpeakerCallPacket, nativeJSON: string): void {
174
+ const support = packet.intervalSupport
175
+ if (packet.version !== 2 || packet.projectionPolicy !== 'source-bound-intervals' || !support || Buffer.byteLength(nativeJSON) > 64 * 1024 * 1024 || sha(nativeJSON) !== support.nativeSHA256) refuse('native source binding')
176
+ const native = JSON.parse(nativeJSON)
177
+ if (!Array.isArray(native?.segments) || native.segments.length > 20000) refuse('native source schema')
178
+ for (const n of native.segments) if (!n || typeof n.speakerId !== 'string' || !n.speakerId || n.speakerId.length > 100 || !Number.isFinite(n.startTimeSeconds) || !Number.isFinite(n.endTimeSeconds) || n.startTimeSeconds < 0 || n.endTimeSeconds <= n.startTimeSeconds || n.endTimeSeconds > support.totalSamples / 16000 + 1) refuse('native source turn bound')
179
+ const { turns, dropped } = nativeTurns(native.segments, support.totalSamples / 16000)
180
+ if (dropped !== 0) refuse('native source dropped turn')
181
+ const intervals = intervalSamples(turns, support.totalSamples).filter(([lo, hi]) => hi > lo).map(([startSample, endSample, nativeCluster]) => ({ startSample, endSample, nativeCluster }))
182
+ if (digest(intervals) !== digest(support.nativeIntervals)) refuse('native source geometry drift')
183
+ }
184
+
185
+ /** Evaluation aliases intentionally test an aggregate hypothesis over residuals.
186
+ * They can NEVER be selected by the HTTP loader below. They assert no real name. */
187
+ export function resolveMacSpeakerCallView(baseJSON: string, ledgerJSONL: string, packet: MacSpeakerCallPacket | null, options: { mode?: 'strict' | 'anonymous-estimate' | 'alias-evaluation' | 'source-bound-intervals'; allowEvaluation?: boolean } = {}): MacSpeakerCallView {
188
+ const base = JSON.parse(baseJSON) as Doc
189
+ const mode = options.mode ?? packet?.projectionPolicy ?? 'strict'
190
+ const localization = mode === 'strict' || mode === 'source-bound-intervals' ? 'estimated-ASR-with-native-support' as const : 'inherited-machine-estimate' as const
191
+ const fallback = (reason: string): MacSpeakerCallView => ({ doc: base, metadata: { status: 'base', reason, mode, changedChunks: 0, changedWords: 0, groups: [], localization, speakerPurityVerified: false } })
192
+ try {
193
+ if (!packet) return fallback('packet-absent')
194
+ if (!['strict', 'anonymous-estimate', 'alias-evaluation', 'source-bound-intervals'].includes(mode)) refuse('view mode')
195
+ if (Buffer.byteLength(baseJSON) > 32 * 1024 * 1024 || Buffer.byteLength(ledgerJSONL) > 8 * 1024 * 1024 || Buffer.byteLength(JSON.stringify(packet)) > MAX_PACKET) refuse('byte bound')
196
+ checkPacket(packet, baseJSON, ledgerJSONL, base)
197
+ if (mode === 'alias-evaluation' ? options.allowEvaluation !== true : packet.admission !== 'qualified') return fallback(mode === 'alias-evaluation' ? 'evaluation-not-authorized' : 'packet-off')
198
+ if (mode === 'anonymous-estimate' && packet.projectionPolicy !== 'anonymous-estimate') refuse('packet policy mismatch')
199
+ if ((mode === 'source-bound-intervals') !== (packet.projectionPolicy === 'source-bound-intervals')) refuse('interval packet policy mismatch')
200
+ // Old qualified names are always validated against immutable BASE bytes.
201
+ let proofStatus = checkedBaseProofs.get(packet.baseSHA256)
202
+ if (!proofStatus) { proofStatus = readMacSpeakerMachineEvidence(base).status; remember(checkedBaseProofs, packet.baseSHA256, proofStatus) }
203
+ if (proofStatus === 'invalid') return fallback('base-machine-proof-invalid')
204
+ if (mode === 'source-bound-intervals') {
205
+ const projected = createMacSpeakerCallIntervalProjector({ rawBindings: packet.rawBindings, groups: packet.groups, displayLabels: packet.displayLabels, protectedRawChunkIndices: packet.aliasProtectedRawChunkIndices }, packet.intervalSupport!).project(base)
206
+ // This is an HQ word-backed closed-call policy. Without that surface the
207
+ // legacy reviewer cannot interpret its anonymous namespace safely.
208
+ if (!projected.usableHQWords) return fallback('no-usable-hq-words')
209
+ const anonymousGroups = packet.groups.map(g => ({ identityId: g.identityId, label: packet.displayLabels[g.identityId] }))
210
+ return { doc: projected.doc, metadata: { status: 'applied', reason: 'source-bound-anonymous-interval-view', mode, revision: packet.revision, generation: packet.generation, packetSHA256: packet.packetSHA256, changedChunks: projected.changedChunks, changedWords: projected.changedWords, groups: packet.groups.map(g => ({ identityId: g.identityId, label: packet.displayLabels[g.identityId], memberVoices: g.memberVoices, intervals: g.intervals })), localization, speakerPurityVerified: false, intervalMembership: packet.intervalSupport!.membership, supportSHA256: packet.intervalSupport!.supportSHA256, ...(projected.usableHQWords ? { effectiveWordView: { version: 1 as const, anonymousGroups, unresolvedLabel: 'Ext' as const } } : {}) } }
211
+ }
212
+ const doc = structuredClone(base), protectedRaw = new Set(mode === 'anonymous-estimate' ? packet.aliasProtectedRawChunkIndices : packet.protectedRawChunkIndices)
213
+ const existingVoiceHints = new Set(base.chunks.map((c: Doc) => c.speaker).filter((label: unknown) => typeof label === 'string' && VOICE.test(label)))
214
+ const byRaw = new Map<number, Array<{ g: MacSpeaker256Group; lo: number; hi: number; voice: string }>>()
215
+ const aliases = new Map(packet.groups.flatMap(g => g.memberVoices.map(v => [v, g] as const)))
216
+ // Nested/HQ estimates must agree with their immutable source turn. A
217
+ // different accepted Voice in a mixed turn is not authorized by the root's
218
+ // aggregate recurrence decision, even if that other Voice has its own group.
219
+ const sourceGroups = new Map<number, string>()
220
+ let basePosition = 0
221
+ for (const e of base.chunkEntries) {
222
+ if (typeof e.chunk.text !== 'string' || !e.chunk.text.trim()) continue
223
+ const root = base.chunks[basePosition++]
224
+ const rawGroup = aliases.get(e.chunk.speaker)?.identityId
225
+ if (rawGroup && aliases.get(root?.speaker)?.identityId === rawGroup) sourceGroups.set(e.chunkIndex, rawGroup)
226
+ }
227
+ const agreesWithSource = (raw: number, voice: unknown): boolean => typeof voice === 'string' && sourceGroups.has(raw) && aliases.get(voice)?.identityId === sourceGroups.get(raw)
228
+ for (const g of packet.groups) for (const s of g.intervals) { const list = byRaw.get(s.rawChunkIndex) ?? []; list.push({ g, lo: s.startSample, hi: s.endSample, voice: s.originalVoice }); byRaw.set(s.rawChunkIndex, list) }
229
+ for (const list of byRaw.values()) list.sort((a, b) => a.lo - b.lo || a.hi - b.hi)
230
+ const labelFor = (raw: number, lo: number, hi: number, voice: unknown): string | null => {
231
+ if (protectedRaw.has(raw) || typeof voice !== 'string' || !VOICE.test(voice) || hi <= lo) return null
232
+ if (mode !== 'strict') { const hint = mode === 'anonymous-estimate' ? packet.displayLabels[aliases.get(voice)?.identityId ?? ''] : aliases.get(voice)?.keepVoiceHint; return hint && existingVoiceHints.has(hint) ? hint : null }
233
+ let end = lo, group: MacSpeaker256Group | null = null
234
+ for (const s of byRaw.get(raw) ?? []) {
235
+ if (s.voice !== voice || s.hi <= lo || s.lo >= hi) continue
236
+ if (group && group.identityId !== s.g.identityId || s.lo > end) return null
237
+ group = s.g; end = Math.max(end, s.hi)
238
+ }
239
+ return end >= hi && group && existingVoiceHints.has(packet.displayLabels[group.identityId]) ? packet.displayLabels[group.identityId] : null
240
+ }
241
+ let changedChunks = 0, changedWords = 0, position = 0
242
+ for (const e of doc.chunkEntries) {
243
+ const raw = e.chunkIndex, c = e.chunk, r = packet.rawBindings[raw]
244
+ if (typeof c.text !== 'string' || !c.text.trim()) continue
245
+ const root = doc.chunks[position++]
246
+ if (mode === 'anonymous-estimate') {
247
+ if (!root || protectedRaw.has(raw) || hasMacSpeakerProtectedRows(c) || hasMacSpeakerProtectedRows(root)) continue
248
+ let changed = false
249
+ // This policy consolidates an ALREADY automatic Voice estimate. A
250
+ // structural Ext/Unknown field stays unchanged. Nested fields may use
251
+ // only the source turn's accepted group, never an independent group.
252
+ for (const row of [root, c]) {
253
+ for (const nested of [...(row.segments ?? []), ...(row.finalSegments ?? []), ...(row.words ?? [])]) {
254
+ if (!agreesWithSource(raw, nested.speaker)) continue
255
+ const to = labelFor(raw, r.startSample, r.endSample, nested.speaker)
256
+ if (to && to !== nested.speaker) { nested.speaker = to; changed = true }
257
+ }
258
+ const to = labelFor(raw, r.startSample, r.endSample, row.speaker)
259
+ if (to && to !== row.speaker) { row.speaker = to; changed = true }
260
+ }
261
+ if (changed) changedChunks++
262
+ continue
263
+ }
264
+ if (!root || protectedRaw.has(raw) || hasMacSpeakerProtectedRows(c) || hasMacSpeakerProtectedRows(root) || !VOICE.test(root.speaker) || root.speaker !== c.speaker) continue
265
+ const segs = Array.isArray(c.segments) && c.segments.length ? c.segments : [{ speaker: c.speaker, startSec: 0, endSec: (r.endSample - r.startSample) / 16000 }]
266
+ const labels = segs.map((s: Doc) => labelFor(raw, r.startSample + Math.round(s.startSec * 16000), r.startSample + Math.round(s.endSec * 16000), s.speaker))
267
+ const to = labels[0]
268
+ if (!to || labels.some((v: unknown) => v !== to)) continue
269
+ // A root claim cannot silently extend beyond another stored localization
270
+ // surface. Final segments and ASR words have their own clocks; every
271
+ // declared row must be covered, even when its speaker field is absent.
272
+ if (mode === 'strict' && [root, c].some(row => ['segments', 'finalSegments', 'words'].some(key => row[key] !== undefined && (!Array.isArray(row[key]) || row[key].some((nested: Doc) => {
273
+ const start = key === 'words' ? nested.start : nested.startSec, end = key === 'words' ? nested.end : nested.endSec
274
+ return !Number.isFinite(start) || !Number.isFinite(end) || start < 0 || end <= start || (nested.speaker !== undefined && nested.speaker !== root.speaker) || labelFor(raw, r.startSample + Math.round(start * 16000), r.startSample + Math.round(end * 16000), root.speaker) !== to
275
+ }))))) continue
276
+ const from = root.speaker
277
+ for (const row of [root, c]) { row.speaker = to; for (const key of ['segments', 'finalSegments', 'words']) for (const nested of row[key] ?? []) if (nested.speaker === from) nested.speaker = to }
278
+ if (to !== base.chunks[position - 1].speaker) changedChunks++
279
+ }
280
+ // HQ word seconds index concatenated RAW files, including silent files.
281
+ for (const segment of doc.batchSegments ?? []) {
282
+ if (!Number.isInteger(segment.startChunkIdx) || !Number.isInteger(segment.endChunkIdx) || segment.startChunkIdx < 0 || segment.endChunkIdx < segment.startChunkIdx || segment.endChunkIdx >= packet.rawBindings.length || !Array.isArray(segment.speakerWords)) refuse('batch geometry')
283
+ const clock: Array<{ raw: number; lo: number; hi: number }> = []; let offset = 0
284
+ for (let raw = segment.startChunkIdx; raw <= segment.endChunkIdx; raw++) { const r = packet.rawBindings[raw], n = r.endSample - r.startSample; clock.push({ raw, lo: offset, hi: offset + n }); offset += n }
285
+ for (const w of segment.speakerWords) {
286
+ if (isMacSpeakerHumanProtected(w) || !VOICE.test(w.speaker)) continue
287
+ const lo = Math.round(w.start * 16000), hi = Math.round(w.end * 16000)
288
+ if (!Number.isSafeInteger(lo) || !Number.isSafeInteger(hi) || lo < 0 || hi <= lo || hi > offset) continue
289
+ // Raw PCM intervals are contiguous and ordered even when ASR words are
290
+ // not. Avoid scanning every raw file for every word on the HTTP path.
291
+ let left = 0, right = clock.length
292
+ while (left < right) { const mid = (left + right) >>> 1; if (clock[mid].hi <= lo) left = mid + 1; else right = mid }
293
+ const parts: typeof clock = []
294
+ for (let i = left; i < clock.length && clock[i].lo < hi; i++) parts.push(clock[i])
295
+ const targets = parts.map(c => { const r = packet.rawBindings[c.raw]; return mode === 'anonymous-estimate' && !agreesWithSource(c.raw, w.speaker) ? null : labelFor(c.raw, r.startSample + Math.max(0, lo - c.lo), r.startSample + Math.min(c.hi - c.lo, hi - c.lo), w.speaker) })
296
+ if (targets.length && targets[0] && targets.every(t => t === targets[0])) { if (w.speaker !== targets[0]) changedWords++; w.speaker = targets[0] }
297
+ }
298
+ }
299
+ return { doc, metadata: { status: 'applied', reason: 'source-bound-anonymous-view', mode, revision: packet.revision, generation: packet.generation, packetSHA256: packet.packetSHA256, changedChunks, changedWords, groups: packet.groups.map(g => ({ identityId: g.identityId, label: mode === 'alias-evaluation' ? g.keepVoiceHint : packet.displayLabels[g.identityId], memberVoices: g.memberVoices, intervals: g.intervals })), localization, speakerPurityVerified: false } }
300
+ } catch (e) { return fallback(e instanceof Error ? e.message : 'invalid-packet') }
301
+ }
302
+ /** Legacy naming actions cannot represent projected multi-source/word scopes. */
303
+ export function isEffectiveMacSpeakerCallView(view: MacSpeakerCallView): boolean {
304
+ return !!view.metadata.intervalHumanAnnotation || view.metadata.status === 'applied' && (view.metadata.changedChunks > 0 || view.metadata.changedWords > 0 || view.metadata.mode === 'source-bound-intervals' && !!view.metadata.effectiveWordView)
305
+ }
306
+
307
+ /** Exact prefix-only label edits; refuses transcript drift instead of guessing. */
308
+ export function renderMacSpeakerCallMarkdown(markdown: string, base: Doc, view: MacSpeakerCallView): string {
309
+ if (!(view.metadata.status === 'applied' && view.metadata.changedWords) && !view.metadata.intervalHumanAnnotation?.confirmedWords.length) return markdown
310
+ return renderMarkdown(markdown, boundWords(base, view.doc))
311
+ }
312
+ function boundedFile(path: string, maximum: number): string {
313
+ const fd = openSync(path, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK)
314
+ try {
315
+ const st = fstatSync(fd); if (!st.isFile() || st.size > maximum) refuse('file bound')
316
+ const buffer = Buffer.allocUnsafe(st.size + 1); let length = 0
317
+ while (length < buffer.length) { const n = readSync(fd, buffer, length, buffer.length - length, null); if (!n) break; length += n }
318
+ const after = fstatSync(fd)
319
+ if (length !== st.size || st.size !== after.size || st.mtimeMs !== after.mtimeMs || st.ctimeMs !== after.ctimeMs) refuse('file changed during read')
320
+ return buffer.subarray(0, length).toString('utf8')
321
+ } finally { closeSync(fd) }
322
+ }
323
+ export function readMacSpeakerCallView(sidecarPath: string, baseJSON: string, markdownPath?: string): MacSpeakerCallView {
324
+ let humanPresent = false, intervalPresent = false, recoveryRequired = false
325
+ let verifiedLegacyView: MacSpeakerCallView | undefined
326
+ try {
327
+ const doc = JSON.parse(baseJSON)
328
+ if (!/^[A-Za-z0-9:_-]{3,96}$/.test(doc.sessionId) || !sidecarPath.endsWith('.g2-chunks.json')) return resolveMacSpeakerCallView(baseJSON, '', null)
329
+ const mdPath = markdownPath ?? sidecarPath.replace(/\.g2-chunks\.json$/, '.md')
330
+ const files = { sidecarPath, meetingPath: mdPath }, paths = macCallHumanPaths(files, doc.sessionId), intervalPaths = macIntervalHumanPaths(files, doc.sessionId)
331
+ // Inspect the archival intent before looking at active interval artifacts:
332
+ // removal may have landed while the durable completion receipt has not.
333
+ const originalTransaction = inspectOriginalViewTransaction(files, doc.sessionId)
334
+ recoveryRequired = originalTransaction.status === 'prepared' || originalTransaction.status === 'recovery-invalid'
335
+ let packet: MacSpeakerCallPacket | null = null
336
+ if (!recoveryRequired) {
337
+ try { packet = JSON.parse(boundedFile(sidecarPath.replace(/\.g2-chunks\.json$/, '.speaker-call.json'), MAX_PACKET)) as MacSpeakerCallPacket } catch { /* Human decisions survive automatic withdrawal. */ }
338
+ }
339
+ humanPresent = true // An unreadable annotation must refuse competing writes.
340
+ const annotation = readMacCallHumanFile(paths.annotation, MAX_PACKET, true)
341
+ humanPresent = annotation !== null
342
+ let ledger = ''
343
+ try { ledger = boundedFile(dataPath('meeting-corrections', `${doc.sessionId.replace(/:/g, '_')}.jsonl`), 8 * 1024 * 1024) } catch (e) { if ((e as NodeJS.ErrnoException).code !== 'ENOENT') throw e }
344
+ // Independently validated older human decisions remain visible if only the
345
+ // newer interval transaction is invalid or needs recovery.
346
+ verifiedLegacyView = applyMacCallHumanAnnotation(baseJSON, ledger, annotation?.text ?? null, resolveMacSpeakerCallView(baseJSON, ledger, null))
347
+ try {
348
+ const legacyMarkdown = boundedFile(mdPath, MAX_PACKET)
349
+ verifiedLegacyView.markdown = renderMacSpeakerCallMarkdown(legacyMarkdown, doc, verifiedLegacyView)
350
+ } catch (e) { if ((e as NodeJS.ErrnoException).code !== 'ENOENT') throw e }
351
+ if (recoveryRequired) throw new Error('original view recovery required')
352
+ intervalPresent = true
353
+ const intervalAnnotation = readMacCallHumanFile(intervalPaths.annotation, MAX_PACKET, true)
354
+ intervalPresent = intervalAnnotation !== null
355
+ // A prepared or malformed journal is an explicit recovery state, even when
356
+ // its sidecar replacement landed. Never present that half-commit as saved.
357
+ const journal = inspectMacIntervalHumanJournal(files, doc.sessionId)
358
+ recoveryRequired = recoveryRequired || journal.status === 'prepared' || journal.status === 'recovery-invalid'
359
+ let view = applyMacCallHumanAnnotation(baseJSON, ledger, annotation?.text ?? null, resolveMacSpeakerCallView(baseJSON, ledger, packet))
360
+ let markdown: string
361
+ try { markdown = boundedFile(mdPath, MAX_PACKET) } catch (e) { if ((e as NodeJS.ErrnoException).code === 'ENOENT' && !intervalPresent && !recoveryRequired) return view; throw e }
362
+ if (recoveryRequired) throw new Error('interval annotation recovery required')
363
+ view = applyMacIntervalHumanAnnotation({ baseJSON, markdown, ledgerJSONL: ledger, legacyAnnotationJSON: annotation?.text ?? null, annotationJSON: intervalAnnotation?.text ?? null, packet, view })
364
+ view.markdown = renderMacSpeakerCallMarkdown(markdown, doc, view)
365
+ return view
366
+ } catch {
367
+ const view = verifiedLegacyView ?? resolveMacSpeakerCallView(baseJSON, '', null)
368
+ if (humanPresent || intervalPresent || recoveryRequired) { view.metadata.humanAnnotationInvalid = true; view.metadata.reason = 'human-annotation-invalid' }
369
+ if (intervalPresent) view.metadata.intervalHumanAnnotationInvalid = true
370
+ if (recoveryRequired) { view.metadata.intervalHumanRecoveryRequired = true; view.metadata.reason = 'interval-human-recovery-required' }
371
+ return view
372
+ }
373
+ }
@@ -0,0 +1,72 @@
1
+ /** Bounded pure worker ownership and opaque, single-use publication authority. */
2
+ import { Worker } from 'node:worker_threads'
3
+ import { createRequire } from 'node:module'
4
+ import { pathToFileURL } from 'node:url'
5
+ import { randomUUID } from 'node:crypto'
6
+ import type { MacSpeakerLiveComputeInput, MacSpeakerLiveComputePrepared, MacSpeakerLiveComputeResult, MacSpeakerLivePreparedPublication } from './mac-speaker-live-worker.js'
7
+ import type { MacWindowEmbeddingReceipt } from './mac-speaker-windows.js'
8
+ export type { MacSpeakerLiveComputeInput, MacSpeakerLiveComputePrepared, MacSpeakerLiveComputeResult } from './mac-speaker-live-worker.js'
9
+ export interface MacSpeakerLivePublicationCapability {readonly kind:'mac-speaker-live-compute'}
10
+ interface Binding {captureId:string;generation:string;sourceBindingSHA256:string;fileBindingsSHA256:string;revision:number;voiceHighWater:number;mode:'mac-native-v1'}
11
+ const capabilities=new WeakMap<object,{binding:Binding;publication:MacSpeakerLivePreparedPublication}>()
12
+ /** A plain object, resealed state or copied capability never confers authority. */
13
+ export function takeMacSpeakerLivePublication(capability:unknown,expected:Binding):MacSpeakerLivePreparedPublication|null{
14
+ if(!capability||typeof capability!=='object')return null
15
+ const record=capabilities.get(capability);if(!record)return null
16
+ capabilities.delete(capability)
17
+ if(Object.keys(record.binding).some(key=>record.binding[key as keyof Binding]!==expected[key as keyof Binding]))return null
18
+ return record.publication
19
+ }
20
+ function createWorker():Worker{
21
+ const url=new URL('./mac-speaker-live-worker'+(import.meta.url.endsWith('.ts')?'.ts':'.js'),import.meta.url)
22
+ const registration=url.pathname.endsWith('.ts')?pathToFileURL(createRequire(import.meta.url).resolve('tsx/esm/api')).href:null
23
+ return new Worker(`(async()=>{if(${JSON.stringify(registration)}){const tsx=await import(${JSON.stringify(registration)});tsx.register()}await import(${JSON.stringify(url.href)})})().catch(e=>{throw e})`,{eval:true,resourceLimits:{maxOldGenerationSizeMb:192,stackSizeMb:4}})
24
+ }
25
+ let busy=false
26
+ export async function startMacSpeakerLiveCompute(input:MacSpeakerLiveComputeInput,options:{timeoutMs?:number;workerFactory?:()=>Worker}={}){
27
+ if(busy)throw Error('compute_lane_busy')
28
+ if(options.timeoutMs!==undefined&&(!Number.isFinite(options.timeoutMs)||options.timeoutMs<=0||options.timeoutMs>5000))throw Error('compute_deadline_bound')
29
+ if(input.profileJSONBytes.byteLength>32*1024*1024)throw Error('compute_input_bound')
30
+ const binding:Binding=Object.freeze({captureId:input.captureId,generation:input.generation,sourceBindingSHA256:input.sourceBindingSHA256,fileBindingsSHA256:input.fileBindingsSHA256,revision:input.revision,voiceHighWater:input.voiceHighWater,mode:'mac-native-v1'})
31
+ busy=true
32
+ let worker:Worker
33
+ try{worker=(options.workerFactory??createWorker)()}catch(error){busy=false;throw error}
34
+ const job=randomUUID(),budget=options.timeoutMs??5000;let used=0,next=0,closing:Promise<void>|undefined,waiting:{id:number;resolve:(v:any)=>void;reject:(e:Error)=>void;timer:ReturnType<typeof setTimeout>;deadline:number;started:number}|undefined
35
+ const close=():Promise<void>=>{
36
+ if(!closing){const prior=waiting;waiting=undefined;if(prior){clearTimeout(prior.timer);prior.reject(Error('compute_cancelled'))}
37
+ closing=worker.terminate().then(()=>{}).finally(()=>{busy=false})}
38
+ return closing
39
+ }
40
+ let terminalError:Error|undefined
41
+ const fail=(error:Error)=>{terminalError??=error;const prior=waiting;waiting=undefined;if(prior){clearTimeout(prior.timer);used+=Date.now()-prior.started}void close().then(()=>prior?.reject(error),()=>prior?.reject(error))}
42
+ worker.on('error',error=>fail(error));worker.on('exit',()=>{if(!closing)fail(Error('compute_worker_exited'))})
43
+ worker.on('message',(reply:{id:number;job:string;ok:boolean;value?:unknown;error?:string})=>{
44
+ const pending=waiting;if(!pending)return
45
+ if(reply.id!==pending.id||reply.job!==job)return fail(Error('compute_stale_reply'))
46
+ if(Date.now()>=pending.deadline)return fail(Error('compute_timeout'))
47
+ if(!reply.ok)return fail(Error(reply.error??'compute_failed'))
48
+ clearTimeout(pending.timer);waiting=undefined;used+=Date.now()-pending.started;pending.resolve(reply.value)
49
+ })
50
+ const request=(op:string,payload:Record<string,unknown>,transfer:readonly ArrayBuffer[]=[]):Promise<any>=>new Promise((resolve,reject)=>{
51
+ if(terminalError||closing||waiting){reject(terminalError??Error('compute_not_available'));return}
52
+ const remaining=budget-used;if(remaining<=0){void close().then(()=>reject(Error('compute_timeout')));return}
53
+ const id=++next,started=Date.now(),deadline=started+remaining
54
+ const timer=setTimeout(()=>fail(Error('compute_timeout')),remaining)
55
+ waiting={id,resolve,reject,timer,deadline,started}
56
+ try{worker.postMessage({id,job,op,...payload},transfer)}catch(error){fail(error instanceof Error?error:Error('compute_post_failed'))}
57
+ })
58
+ try{
59
+ const prepared=await request('prepare',{input},[input.profileJSONBytes.buffer as ArrayBuffer])as MacSpeakerLiveComputePrepared
60
+ return{prepared,close,async finish(receipt?:MacWindowEmbeddingReceipt){
61
+ try{
62
+ const result=await request('finish',{receipt})as MacSpeakerLiveComputeResult
63
+ // Keep the authority's payload private; public state/result copies may be
64
+ // inspected or mutated by callers without changing certified bytes.
65
+ const capability=Object.freeze({kind:'mac-speaker-live-compute' as const})
66
+ capabilities.set(capability,{binding,publication:result.publication})
67
+ const {publication,...publicResult}=result
68
+ return{...structuredClone(publicResult),capability}
69
+ }finally{await close()}
70
+ }}
71
+ }catch(error){await close();throw error}
72
+ }