@gotcos/glasses-server 6.95.0 → 6.98.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +166 -0
- package/README.md +4 -0
- package/managed-runtime-contract.json +13 -0
- package/package.json +3 -1
- package/server/index.ts +12 -0
- package/server/lib/attached-provider-adapter.ts +35 -1
- package/server/lib/claude-bridge.ts +47 -0
- package/server/lib/codex-bridge.ts +11 -1
- package/server/lib/codex-rollout-watermark.ts +42 -0
- package/server/lib/cursor-bridge.ts +2 -1
- package/server/lib/decision-provider.ts +4 -2
- package/server/lib/dictation-clean-models.ts +2 -0
- package/server/lib/dictation-clean.ts +2 -2
- package/server/lib/dictation-polish-pipeline.ts +22 -2
- package/server/lib/dictation-prewarm.ts +16 -0
- package/server/lib/direct-bridge.ts +14 -2
- package/server/lib/direct-sse.ts +6 -1
- package/server/lib/display-bus.ts +1 -1
- package/server/lib/fork-thread.ts +32 -1
- package/server/lib/held-naming-batches.ts +44 -7
- package/server/lib/held-voice-groups.ts +5 -4
- package/server/lib/job-audit-caller.ts +20 -0
- package/server/lib/keyed-sessions.ts +21 -1
- package/server/lib/lens-gist-engines.ts +17 -5
- package/server/lib/live-cos-ask.ts +29 -6
- package/server/lib/live-cue-verdicts.ts +43 -8
- package/server/lib/live-cues-engine.ts +4 -1
- package/server/lib/live-cues-filter.ts +50 -0
- package/server/lib/live-cues-general-prompts.ts +137 -0
- package/server/lib/live-cues-general-topic.ts +245 -0
- package/server/lib/live-cues-general.ts +715 -0
- package/server/lib/live-cues-v2-data.ts +763 -0
- package/server/lib/live-cues-v2-grounded.ts +262 -0
- package/server/lib/live-cues-v2-judge-b.ts +59 -0
- package/server/lib/live-cues-v2-names.ts +49 -0
- package/server/lib/live-cues-v2-notes.ts +8 -4
- package/server/lib/live-cues-v2-open-items.ts +132 -0
- package/server/lib/live-cues-v2-pacing.ts +45 -0
- package/server/lib/live-cues-v2-prompts-kinds.ts +9 -4
- package/server/lib/live-cues-v2-settings.ts +89 -10
- package/server/lib/live-cues-v2.ts +572 -47
- package/server/lib/mac-recording-asr.ts +6 -3
- package/server/lib/mac-speaker-call-human.ts +371 -0
- package/server/lib/mac-speaker-call-interval-human.ts +499 -0
- package/server/lib/mac-speaker-call-interval-projection.ts +233 -0
- package/server/lib/mac-speaker-call-original-view.ts +328 -0
- package/server/lib/mac-speaker-call-producer-worker.ts +615 -0
- package/server/lib/mac-speaker-call-producer.ts +539 -0
- package/server/lib/mac-speaker-call-store.ts +264 -0
- package/server/lib/mac-speaker-call-view.ts +373 -0
- package/server/lib/mac-speaker-live-compute.ts +72 -0
- package/server/lib/mac-speaker-live-memory.ts +414 -0
- package/server/lib/mac-speaker-live-projection.ts +126 -0
- package/server/lib/mac-speaker-live-save.ts +90 -0
- package/server/lib/mac-speaker-live-store.ts +71 -0
- package/server/lib/mac-speaker-live-worker.ts +58 -0
- package/server/lib/mac-speaker-live.ts +175 -0
- package/server/lib/mac-speaker-profile-extractor.ts +15 -10
- package/server/lib/mac-speaker-shadow-repair.ts +307 -0
- package/server/lib/mac-voice-assignment.ts +46 -27
- package/server/lib/mac-voice-display.ts +195 -0
- package/server/lib/mac-voice-naming-batch.ts +1067 -0
- package/server/lib/mac-voice-suggestions.ts +548 -0
- package/server/lib/maintenance-lifecycle.ts +2 -0
- package/server/lib/media-store.ts +2 -1
- package/server/lib/meeting-ask-pasted.ts +135 -0
- package/server/lib/meeting-ask-review.ts +8 -1
- package/server/lib/meeting-ask-runner.ts +4 -1
- package/server/lib/meeting-ask-runtime.ts +189 -17
- package/server/lib/meeting-ask-store.ts +2 -0
- package/server/lib/meeting-ask-web.ts +284 -13
- package/server/lib/meeting-batch-progress.ts +51 -5
- package/server/lib/meeting-batch-transcribe.ts +35 -2
- package/server/lib/meeting-corrections.ts +2 -1
- package/server/lib/meeting-fact-check.ts +3 -0
- package/server/lib/meeting-finalization-jobs.ts +69 -2
- package/server/lib/meeting-public-topic.ts +6 -1
- package/server/lib/meeting-relabel-enrolment.ts +2 -1
- package/server/lib/meeting-relabel.ts +17 -10
- package/server/lib/meeting-speaker-review.ts +187 -21
- package/server/lib/meeting-store.ts +6 -1
- package/server/lib/parakeet.ts +51 -5
- package/server/lib/pending-batch-retention.ts +58 -0
- package/server/lib/private-reference.ts +38 -0
- package/server/lib/provider-usage.ts +561 -0
- package/server/lib/public-https-url.ts +32 -0
- package/server/lib/query-job-runtime.ts +3 -0
- package/server/lib/rich-media-safety.ts +23 -2
- package/server/lib/session-find-ask.ts +288 -0
- package/server/lib/session-find-collect.ts +715 -0
- package/server/lib/session-find-redact.ts +254 -0
- package/server/lib/session-find.ts +345 -0
- package/server/lib/speaker-calibration-log.ts +3 -2
- package/server/lib/speaker-embeddings.ts +46 -35
- package/server/lib/speaker-name-nfc.ts +37 -0
- package/server/lib/speaker-rename-fanout.ts +7 -6
- package/server/lib/speaker-shadow-decide.ts +59 -22
- package/server/lib/speaker-shadow-pair-source.ts +91 -0
- package/server/lib/speaker-shadow-paired.ts +268 -0
- package/server/lib/speaker-shadow-report.ts +2 -2
- package/server/lib/speaker-shadow-worker.ts +50 -15
- package/server/lib/speaker-shadow.ts +80 -19
- package/server/lib/speaker-trainer.ts +2 -1
- package/server/lib/token-audit.ts +36 -6
- package/server/lib/transcript-cleanup.ts +3 -2
- package/server/lib/voice-directory.ts +10 -5
- package/server/lib/voice-profile-store.ts +24 -14
- package/server/lib/web-lookup-ledger.ts +151 -0
- package/server/lib/work-review-runtime.ts +2 -0
- package/server/lib/work-search.ts +3 -1
- package/server/lib/xlsx-text.ts +277 -0
- package/server/routes/decision-provider.ts +4 -1
- package/server/routes/jev.ts +5 -1
- package/server/routes/live-cues.ts +36 -14
- package/server/routes/meeting.ts +515 -61
- package/server/routes/session-find.ts +73 -0
- package/server/routes/transcribe-stream.ts +114 -33
- package/server/routes/voice.ts +28 -18
- package/server/scripts/live-cues-v3-mutation-gate.mjs +311 -0
- package/server/scripts/mac-voice-assignment-6930-mutation-gate.mjs +5 -5
- package/shared/live-cues-v2.ts +38 -5
- package/shared/media-attachment.ts +2 -0
- package/shared/meeting-ask.ts +11 -0
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
/** Explicit, offline publication of a reviewed derived view. Synchronous bounded
|
|
2
|
+
* IO: call only in the guarded offline/worker lane, never on an HTTP thread.
|
|
3
|
+
* No inference, base
|
|
4
|
+
* edits, correction-ledger writes, enrollment, feature enablement or discovery.
|
|
5
|
+
* A qualification receipt is a CALLER trust decision, not native authentication:
|
|
6
|
+
* this module never turns self-hashed proposals into qualified evidence. */
|
|
7
|
+
import { createHash, randomUUID } from 'node:crypto'
|
|
8
|
+
import { closeSync, constants, fstatSync, fsyncSync, lstatSync, mkdirSync, openSync, readSync, realpathSync, renameSync, unlinkSync, writeFileSync } from 'node:fs'
|
|
9
|
+
import { dirname, isAbsolute, join, resolve } from 'node:path'
|
|
10
|
+
import { readPcm16Wav } from './speaker-shadow-timeline.js'
|
|
11
|
+
import { validateMacSpeakerCallPacket, validateMacSpeakerCallIntervalNativeSource, type MacSpeakerCallPacket } from './mac-speaker-call-view.js'
|
|
12
|
+
import type { MacSpeakerCallIntervalMembership } from './mac-speaker-call-interval-projection.js'
|
|
13
|
+
|
|
14
|
+
const SHA=/^[a-f0-9]{64}$/, MAX_ARTIFACT=16*1024*1024, MAX_FILES=6500
|
|
15
|
+
const sha=(b:string|Buffer)=>createHash('sha256').update(b).digest('hex')
|
|
16
|
+
const digest=(v:unknown)=>sha(JSON.stringify(v))
|
|
17
|
+
const fail=(s:string):never=>{throw Error(`Mac speaker call store: ${s}`)}
|
|
18
|
+
export type MacSpeakerCallAdmissionPolicy='strict'|'anonymous-estimate'|'source-bound-intervals'
|
|
19
|
+
export type MacSpeakerCallFileRole='original'|'current'|'ledger'|'raw-pcm-manifest'|'cli'|'model-manifest'|'native-export'|'embedding-export'|'raw-audio'|'model-file'
|
|
20
|
+
export interface MacSpeakerCallFrozenFile {role:MacSpeakerCallFileRole;path:string;sha256:string|null}
|
|
21
|
+
export interface MacSpeakerCallRawPCMManifest {
|
|
22
|
+
version:1;captureId:string
|
|
23
|
+
rows:Array<{rawChunkIndex:number;path:string;wavSHA256:string;pcmSHA256:string;sampleCount:number}>
|
|
24
|
+
}
|
|
25
|
+
export interface MacSpeakerCallModelManifest {
|
|
26
|
+
version:1;modelSHA256:string;files:Array<{path:string;sha256:string}>
|
|
27
|
+
}
|
|
28
|
+
/** Independently supplied by the trusted QA caller. No receipt-issuing helper
|
|
29
|
+
* exists here. reviewer/reviewReceiptSHA256 identify the external decision. */
|
|
30
|
+
interface MacSpeakerCallQualificationBase {
|
|
31
|
+
kind:'mac-speaker-call-qualified';captureId:string
|
|
32
|
+
qualification:{status:'qualified';reviewer:string;reviewReceiptSHA256:string}
|
|
33
|
+
sourceFilesSHA256:string
|
|
34
|
+
bindings:{originalSHA256:string;currentSHA256:string;ledgerSHA256:string;rawPCMManifestSHA256:string;cliSHA256:string;modelSHA256:string;nativeExportSHA256:string;embeddingExportSHA256:string;sourceBindingSHA256:string;stateSHA256:string;packetSHA256:string;stateFileSHA256:string;packetFileSHA256:string}
|
|
35
|
+
}
|
|
36
|
+
/** Interval qualification is an explicit new decision. A legacy receipt can
|
|
37
|
+
* never authorize interval membership, even when all file hashes still match. */
|
|
38
|
+
export type MacSpeakerCallQualification=MacSpeakerCallQualificationBase&(
|
|
39
|
+
{version:1;policy:'fluid256-call-view-v1';admissionPolicy:'strict'|'anonymous-estimate';intervalMembership?:never;intervalSupportSHA256?:never;timelinePCMSHA256?:never}
|
|
40
|
+
|{version:2;policy:'fluid256-call-interval-view-v1';admissionPolicy:'source-bound-intervals';intervalMembership:MacSpeakerCallIntervalMembership;intervalSupportSHA256:string;timelinePCMSHA256:string}
|
|
41
|
+
)
|
|
42
|
+
export interface MacSpeakerCallPublicationInput {
|
|
43
|
+
captureId:string;transactionRoot:string;packetPath:string;statePath:string
|
|
44
|
+
packetJSON:string;stateJSON:string
|
|
45
|
+
expectedPacketSHA256:string|null;expectedStateFileSHA256:string|null
|
|
46
|
+
sources:MacSpeakerCallFrozenFile[]
|
|
47
|
+
qualificationReceipt:{path:string;expectedSHA256:string}
|
|
48
|
+
}
|
|
49
|
+
export interface MacSpeakerCallStoreOptions {
|
|
50
|
+
/** Explicit dead-PID lock reclamation only; never kills/reclaims a live owner. */
|
|
51
|
+
resume?:boolean
|
|
52
|
+
/** Deterministic fault seams for private tests; called before the final CAS. */
|
|
53
|
+
beforeWrite?:(index:number)=>void;afterWrite?:(index:number)=>void
|
|
54
|
+
}
|
|
55
|
+
type Image={path:string;before:string;after:string;beforeSHA256:string|null;afterSHA256:string}
|
|
56
|
+
type Intent={version:1;captureId:string;directory:string;root:string;lockPath:string;captureLockPath:string;sources:MacSpeakerCallFrozenFile[];qualification:{path:string;sha256:string};images:Image[];owned:Array<{name:string;sha256:string}>}
|
|
57
|
+
type Journal={version:1;intentSHA256:string;phase:'prepared'|'applying'|'applied'|'undoing'|'reverted'|'partial';completed:number[];journalSHA256:string}
|
|
58
|
+
const roleLimit=(role:MacSpeakerCallFileRole)=>({original:32,current:32,ledger:8,'raw-pcm-manifest':16,cli:256,'model-manifest':16,'native-export':64,'embedding-export':96,'raw-audio':1,'model-file':1024}[role]*1024*1024)
|
|
59
|
+
const ROLES=new Set<MacSpeakerCallFileRole>(['original','current','ledger','raw-pcm-manifest','cli','model-manifest','native-export','embedding-export','raw-audio','model-file'])
|
|
60
|
+
const uniqueRoles=['original','current','ledger','raw-pcm-manifest','cli','model-manifest','native-export','embedding-export'] as const
|
|
61
|
+
const budget=()=>{const deadline=Date.now()+60000;return()=>{if(Date.now()>deadline)fail('operation deadline')}}
|
|
62
|
+
function canonicalDir(path:string):string {
|
|
63
|
+
if(typeof path!=='string'||!isAbsolute(path)||path.length>4096||resolve(path)!==path||path==='/'||realpathSync(path)!==path||!lstatSync(path).isDirectory())fail('canonical directory required')
|
|
64
|
+
return path
|
|
65
|
+
}
|
|
66
|
+
function safePath(path:string):string {
|
|
67
|
+
if(typeof path!=='string'||!isAbsolute(path)||path.length>4096||resolve(path)!==path)fail('absolute canonical path required')
|
|
68
|
+
canonicalDir(dirname(path));return path
|
|
69
|
+
}
|
|
70
|
+
type Stamp={dev:number;ino:number;size:number;mtimeMs:number;ctimeMs:number}
|
|
71
|
+
const stamp=(s:Stamp):Stamp=>({dev:s.dev,ino:s.ino,size:s.size,mtimeMs:s.mtimeMs,ctimeMs:s.ctimeMs})
|
|
72
|
+
function read(path:string,limit:number,check:()=>void,capture=true,absent=false):{hash:string;bytes:Buffer;stamp:Stamp}|null {
|
|
73
|
+
safePath(path);let fd:number
|
|
74
|
+
try{fd=openSync(path,constants.O_RDONLY|constants.O_NOFOLLOW|constants.O_NONBLOCK)}catch(e){if(absent&&(e as NodeJS.ErrnoException).code==='ENOENT')return null;throw e}
|
|
75
|
+
try{
|
|
76
|
+
const first=fstatSync(fd);if(!first.isFile()||first.size>limit||realpathSync(path)!==path)fail('unsafe/oversized file')
|
|
77
|
+
const hash=createHash('sha256'),parts:Buffer[]=[],buffer=Buffer.allocUnsafe(65536);let total=0
|
|
78
|
+
for(;;){check();const n=readSync(fd,buffer,0,buffer.length,null);if(!n)break;total+=n;if(total>limit)fail('file grew');hash.update(buffer.subarray(0,n));if(capture)parts.push(Buffer.from(buffer.subarray(0,n)))}
|
|
79
|
+
const after=fstatSync(fd),atPath=lstatSync(path)
|
|
80
|
+
if(total!==first.size||after.size!==first.size||after.mtimeMs!==first.mtimeMs||after.ctimeMs!==first.ctimeMs||atPath.ino!==after.ino||atPath.dev!==after.dev||atPath.isSymbolicLink())fail('file drift during read')
|
|
81
|
+
return{hash:hash.digest('hex'),bytes:capture?Buffer.concat(parts):Buffer.alloc(0),stamp:stamp(after)}
|
|
82
|
+
}finally{closeSync(fd)}
|
|
83
|
+
}
|
|
84
|
+
function syncDir(dir:string){canonicalDir(dir);const fd=openSync(dir,constants.O_RDONLY|constants.O_DIRECTORY|constants.O_NOFOLLOW|constants.O_NONBLOCK);try{fsyncSync(fd)}finally{closeSync(fd)}}
|
|
85
|
+
function writeNew(path:string,bytes:Buffer|string,mode=0o400){safePath(path);const fd=openSync(path,constants.O_WRONLY|constants.O_CREAT|constants.O_EXCL|constants.O_NOFOLLOW|constants.O_NONBLOCK,mode);try{writeFileSync(fd,bytes);fsyncSync(fd)}finally{closeSync(fd)}syncDir(dirname(path))}
|
|
86
|
+
function publish(path:string,bytes:Buffer|null,beforeRename:()=>void=()=>{}){
|
|
87
|
+
safePath(path)
|
|
88
|
+
if(bytes===null){beforeRename();unlinkSync(path);syncDir(dirname(path));return}
|
|
89
|
+
const temp=join(dirname(path),`.speaker-call-${randomUUID()}.tmp`)
|
|
90
|
+
try{writeNew(temp,bytes,0o600);safePath(path);beforeRename();renameSync(temp,path);syncDir(dirname(path))}catch(e){try{unlinkSync(temp)}catch{}throw e}
|
|
91
|
+
}
|
|
92
|
+
function json(bytes:Buffer){return JSON.parse(bytes.toString('utf8')) as Record<string,any>}
|
|
93
|
+
function same(a:unknown,b:unknown){return digest(a)===digest(b)}
|
|
94
|
+
/** Canonical inventory digest only. It grants no qualification. */
|
|
95
|
+
export function hashMacSpeakerCallFileInventory(files:readonly MacSpeakerCallFrozenFile[]):string {
|
|
96
|
+
return digest([...files].sort((a,b)=>a.role<b.role?-1:a.role>b.role?1:a.path<b.path?-1:a.path>b.path?1:0))
|
|
97
|
+
}
|
|
98
|
+
function pinShape(files:MacSpeakerCallFrozenFile[]){
|
|
99
|
+
if(!Array.isArray(files)||files.length>MAX_FILES)fail('source file bound')
|
|
100
|
+
const seen=new Set<string>()
|
|
101
|
+
for(const f of files){if(!f||!ROLES.has(f.role)||!SHA.test(f.sha256??'')&&!(f.role==='ledger'&&f.sha256===null)||seen.has(`${f.role}:${f.path}`))fail('source pin schema');safePath(f.path);seen.add(`${f.role}:${f.path}`)}
|
|
102
|
+
for(const role of uniqueRoles)if(files.filter(f=>f.role===role).length!==1)fail('required source role')
|
|
103
|
+
const byPath=new Map<string,string|null>()
|
|
104
|
+
for(const f of files){if(byPath.has(f.path)&&byPath.get(f.path)!==f.sha256)fail('conflicting source alias');byPath.set(f.path,f.sha256)}
|
|
105
|
+
}
|
|
106
|
+
function verifyFiles(files:MacSpeakerCallFrozenFile[],check:()=>void){
|
|
107
|
+
pinShape(files);const bodies=new Map<MacSpeakerCallFileRole,Buffer>(),stamps=new Map<string,Stamp|null>()
|
|
108
|
+
const rawPin=files.find(f=>f.role==='raw-pcm-manifest')!,modelPin=files.find(f=>f.role==='model-manifest')!
|
|
109
|
+
const rawRead=read(rawPin.path,roleLimit(rawPin.role),check)!,modelRead=read(modelPin.path,roleLimit(modelPin.role),check)!
|
|
110
|
+
if(rawRead.hash!==rawPin.sha256||modelRead.hash!==modelPin.sha256)fail('manifest drift')
|
|
111
|
+
const raw=json(rawRead.bytes),model=json(modelRead.bytes)
|
|
112
|
+
if(raw.version!==1||!Array.isArray(raw.rows)||!raw.rows.length||raw.rows.length>6000||model.version!==1||!Array.isArray(model.files)||!model.files.length||model.files.length>100)fail('manifest schema')
|
|
113
|
+
const rawFiles=files.filter(f=>f.role==='raw-audio'),modelFiles=files.filter(f=>f.role==='model-file')
|
|
114
|
+
if(rawFiles.length!==raw.rows.length||modelFiles.length!==model.files.length)fail('complete manifest inventory required')
|
|
115
|
+
for(const[r,i]of raw.rows.map((r:any,i:number)=>[r,i]))if(r.rawChunkIndex!==i||!SHA.test(r.wavSHA256)||!SHA.test(r.pcmSHA256)||!Number.isSafeInteger(r.sampleCount)||r.sampleCount<=0||r.sampleCount>480000||!rawFiles.some(f=>f.path===r.path&&f.sha256===r.wavSHA256))fail('raw manifest binding')
|
|
116
|
+
if(new Set(raw.rows.map((r:any)=>r.path)).size!==raw.rows.length||new Set(model.files.map((r:any)=>r.path)).size!==model.files.length)fail('duplicate manifest paths')
|
|
117
|
+
for(const m of model.files)if(!SHA.test(m.sha256)||!modelFiles.some(f=>f.path===m.path&&f.sha256===m.sha256))fail('model manifest binding')
|
|
118
|
+
let totalBytes=0
|
|
119
|
+
for(const f of files){
|
|
120
|
+
const capture=!['cli','model-file'].includes(f.role),result=read(f.path,roleLimit(f.role),check,capture,f.sha256===null)
|
|
121
|
+
if((result?.hash??null)!==f.sha256)fail('frozen source drift')
|
|
122
|
+
stamps.set(f.path,result?.stamp??null)
|
|
123
|
+
if(result&&capture){totalBytes+=result.bytes.length;if(totalBytes>512*1024*1024)fail('source byte budget');if(uniqueRoles.includes(f.role as any))bodies.set(f.role,result.bytes)}
|
|
124
|
+
if(f.role==='raw-audio'){
|
|
125
|
+
const r=raw.rows.find((r:any)=>r.path===f.path),pcm=readPcm16Wav(result!.bytes)
|
|
126
|
+
if(pcm.sampleCount!==r.sampleCount||sha(pcm.pcm)!==r.pcmSHA256)fail('raw PCM drift')
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return{bodies,raw,model,stamps}
|
|
130
|
+
}
|
|
131
|
+
function immediateFence(stamps:Map<string,Stamp|null>){
|
|
132
|
+
for(const[path,expected]of stamps){safePath(path);let actual:ReturnType<typeof lstatSync>|null=null;try{actual=lstatSync(path)}catch(e){if((e as NodeJS.ErrnoException).code!=='ENOENT')throw e}
|
|
133
|
+
if(expected===null?actual!==null:!actual||actual.isSymbolicLink()||!actual.isFile()||!same(stamp(actual),expected))fail('immediate source stamp drift')}
|
|
134
|
+
}
|
|
135
|
+
function verifyQualified(packetBytes:Buffer,stateBytes:Buffer,qualification:Buffer,files:MacSpeakerCallFrozenFile[],verified:ReturnType<typeof verifyFiles>,captureId:string){
|
|
136
|
+
const p=json(packetBytes),s=json(stateBytes),q=json(qualification) as MacSpeakerCallQualification
|
|
137
|
+
const legacy=q.version===1&&q.policy==='fluid256-call-view-v1'&&['strict','anonymous-estimate'].includes(q.admissionPolicy)&&q.intervalMembership===undefined&&q.intervalSupportSHA256===undefined&&q.timelinePCMSHA256===undefined
|
|
138
|
+
const interval=q.version===2&&q.policy==='fluid256-call-interval-view-v1'&&q.admissionPolicy==='source-bound-intervals'&&['accepted-only','native-exclusive-members'].includes(q.intervalMembership)&&SHA.test(q.intervalSupportSHA256)&&SHA.test(q.timelinePCMSHA256)
|
|
139
|
+
if(!legacy&&!interval||q.kind!=='mac-speaker-call-qualified'||q.captureId!==captureId||q.qualification?.status!=='qualified'||typeof q.qualification.reviewer!=='string'||!q.qualification.reviewer.trim()||q.qualification.reviewer.length>200||!SHA.test(q.qualification.reviewReceiptSHA256)||q.sourceFilesSHA256!==hashMacSpeakerCallFileInventory(files))fail('independent qualification receipt required')
|
|
140
|
+
if(p.version!==(interval?2:1)||p.policy!==q.policy||p.admission!=='qualified'||p.projectionPolicy!==q.admissionPolicy||!interval&&p.intervalSupport!==undefined||s.version!==1||s.policy!=='fluid256-recurrence-v1'||p.captureId!==captureId||s.captureId!==captureId||p.generation!==s.generation||typeof p.generation!=='string'||!p.generation||p.generation.length>128||p.revision!==s.revision||!Number.isSafeInteger(s.revision)||s.revision<1||s.revision>=9999999)fail('qualified packet/state policy')
|
|
141
|
+
const{packetSHA256,...pb}=p,{stateSHA256,...sb}=s
|
|
142
|
+
if(packetSHA256!==digest(pb)||stateSHA256!==digest(sb)||p.stateSHA256!==stateSHA256||!same(p.groups,s.groups)||!same(p.rawBindings,s.rawBindings)||p.modelSHA256!==s.modelSHA256||p.cliSHA256!==s.cliSHA256||p.sourceBindingSHA256!==s.sourceBindingSHA256||p.provenance?.speakerPurityVerified!==false||p.provenance?.nativeAuthenticated!==false||s.provenance?.nativeAuthenticated!==false||s.provenance?.productionQualified!==false)fail('packet/state integrity')
|
|
143
|
+
const body=(role:MacSpeakerCallFileRole)=>verified.bodies.get(role)!,pin=(role:MacSpeakerCallFileRole)=>files.find(f=>f.role===role)!
|
|
144
|
+
const expected={originalSHA256:sha(body('original')),currentSHA256:sha(body('current')),ledgerSHA256:sha(body('ledger')??''),rawPCMManifestSHA256:pin('raw-pcm-manifest').sha256!,cliSHA256:pin('cli').sha256!,modelSHA256:s.modelSHA256,nativeExportSHA256:sha(body('native-export')),embeddingExportSHA256:sha(body('embedding-export')),sourceBindingSHA256:s.sourceBindingSHA256,stateSHA256,packetSHA256,stateFileSHA256:sha(stateBytes),packetFileSHA256:sha(packetBytes)}
|
|
145
|
+
if(!same(q.bindings,expected)||Object.values(expected).some(h=>!SHA.test(h))||p.baseSHA256!==expected.currentSHA256||p.ledgerSHA256!==expected.ledgerSHA256||s.cliSHA256!==expected.cliSHA256||verified.raw.captureId!==captureId||verified.model.modelSHA256!==s.modelSHA256||json(body('current')).sessionId!==captureId||json(body('original')).sessionId!==captureId)fail('qualification/source bindings')
|
|
146
|
+
if(!Array.isArray(s.rawBindings)||s.rawBindings.length!==verified.raw.rows.length||s.rawBindings.some((r:any,i:number)=>{const v=verified.raw.rows[i];return r.rawChunkIndex!==i||r.audioSHA256!==v.wavSHA256||r.pcmSHA256!==v.pcmSHA256||r.endSample-r.startSample!==v.sampleCount}))fail('qualified raw manifest')
|
|
147
|
+
for(const role of ['original','current'] as const){const doc=json(body(role));if(doc.finalizationState!=='complete'||doc.sourceType!=='mac_system_audio'||doc.active||doc.isActive||doc.claimPending||!Array.isArray(doc.chunkEntries)||doc.chunkEntries.length!==s.rawBindings.length)fail('closed source required')
|
|
148
|
+
for(const[e,i]of doc.chunkEntries.map((e:any,i:number)=>[e,i])){const r=s.rawBindings[i];if(e.chunkIndex!==i||!e.chunk||e.chunk.audioSha256&&e.chunk.audioSha256!==r.audioSHA256||!Number.isFinite(e.chunk.elapsed)||Math.floor(e.chunk.elapsed*16+.5)!==r.startSample||role==='original'&&digest(e.chunk)!==r.originalRowSHA256)fail('raw source geometry binding')}}
|
|
149
|
+
if(s.provenance.originalSHA256!==expected.originalSHA256||s.provenance.currentSHA256!==expected.currentSHA256||s.provenance.ledgerSHA256!==expected.ledgerSHA256||s.provenance.nativeSHA256!==expected.nativeExportSHA256||s.provenance.embeddingsSHA256!==expected.embeddingExportSHA256)fail('state source provenance')
|
|
150
|
+
if(interval&&q.version===2){
|
|
151
|
+
// The trusted caller supplies qualification; the store independently checks
|
|
152
|
+
// packet geometry against the pinned native export, not a self-hashed list.
|
|
153
|
+
validateMacSpeakerCallPacket(body('current').toString('utf8'),(body('ledger')??Buffer.alloc(0)).toString('utf8'),p as MacSpeakerCallPacket)
|
|
154
|
+
const support=p.intervalSupport
|
|
155
|
+
if(!support||support.membership!==q.intervalMembership||support.supportSHA256!==q.intervalSupportSHA256||support.timelinePCMSHA256!==q.timelinePCMSHA256||support.nativeSHA256!==expected.nativeExportSHA256)fail('qualified interval evidence binding')
|
|
156
|
+
validateMacSpeakerCallIntervalNativeSource(p as MacSpeakerCallPacket,body('native-export').toString('utf8'))
|
|
157
|
+
if(!Array.isArray(s.identities)||!Array.isArray(s.groups)||s.groups.some((g:any)=>{const id=s.identities.find((i:any)=>i.identityId===g.identityId&&!i.retired);return !id||!Number.isSafeInteger(id.ordinal)||p.displayLabels[g.identityId]!==`Anonymous group ${id.ordinal}`}))fail('qualified anonymous identity display')
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
function lock(path:string,directory:string,resume=false){
|
|
161
|
+
const old=read(path,4096,()=>{},true,true)
|
|
162
|
+
if(old){
|
|
163
|
+
if(!resume)fail('existing capture lock requires explicit resume')
|
|
164
|
+
const value=json(old.bytes);if(!Number.isSafeInteger(value.pid)||value.pid<=0||typeof value.token!=='string')fail('invalid capture lock')
|
|
165
|
+
try{process.kill(value.pid,0);fail('capture lock owner alive')}catch(e){if((e as NodeJS.ErrnoException).code!=='ESRCH')throw e}
|
|
166
|
+
if(read(path,4096,()=>{})!.hash!==old.hash)fail('lock drift');unlinkSync(path);syncDir(dirname(path))
|
|
167
|
+
}
|
|
168
|
+
const bytes=JSON.stringify({pid:process.pid,directory,token:randomUUID()});writeNew(path,bytes,0o600)
|
|
169
|
+
return()=>{if(read(path,4096,()=>{})?.hash!==sha(bytes))fail('lock owner drift');unlinkSync(path);syncDir(dirname(path))}
|
|
170
|
+
}
|
|
171
|
+
function locks(targetPath:string,capturePath:string,directory:string,resume=false){
|
|
172
|
+
const target=lock(targetPath,directory,resume);let capture:()=>void
|
|
173
|
+
try{capture=lock(capturePath,directory,resume)}catch(e){target();throw e}
|
|
174
|
+
return()=>{try{capture()}finally{target()}}
|
|
175
|
+
}
|
|
176
|
+
function sealedJournal(j:Omit<Journal,'journalSHA256'>):Journal{return{...j,journalSHA256:digest(j)}}
|
|
177
|
+
function checkJournal(bytes:Buffer,intentSHA256:string):Journal{
|
|
178
|
+
const j=json(bytes) as Journal,{journalSHA256,...body}=j
|
|
179
|
+
if(j.version!==1||j.intentSHA256!==intentSHA256||journalSHA256!==digest(body)||!['prepared','applying','applied','undoing','reverted','partial'].includes(j.phase)||!Array.isArray(j.completed)||j.completed.some(i=>i!==0&&i!==1)||new Set(j.completed).size!==j.completed.length)fail('journal integrity')
|
|
180
|
+
return j
|
|
181
|
+
}
|
|
182
|
+
function targets(intent:Intent,check:()=>void){return intent.images.map(i=>{const r=read(i.path,MAX_ARTIFACT,check,true,true),h=r?.hash??null;if(h!==i.beforeSHA256&&h!==i.afterSHA256)fail('target CAS conflict');return h})}
|
|
183
|
+
function owned(intent:Intent,check:()=>void){for(const image of intent.owned)if(read(join(intent.directory,image.name),MAX_ARTIFACT,check)!.hash!==image.sha256)fail('owned proof/image drift')}
|
|
184
|
+
function shapeIntent(intent:Intent,directory:string){
|
|
185
|
+
if(intent.version!==1||intent.directory!==directory||canonicalDir(intent.root)!==dirname(directory)||!/^mac_[A-Za-z0-9_-]{1,100}$/.test(intent.captureId)||!Array.isArray(intent.images)||intent.images.length!==2||!Array.isArray(intent.owned)||intent.owned.length!==5)fail('intent schema')
|
|
186
|
+
pinShape(intent.sources);const current=intent.sources.find(f=>f.role==='current')!.path,stem=current.replace(/\.g2-chunks\.json$/,'')
|
|
187
|
+
if(stem===current||intent.images[0].path!==`${stem}.speaker-call.json`||intent.images[1].path!==`${stem}.speaker-call-state.json`||intent.lockPath!==join(dirname(current),`.${intent.captureId}.speaker-call.lock`)||intent.captureLockPath!==join(intent.root,`.speaker-call.capture.${intent.captureId}.lock`))fail('target scope')
|
|
188
|
+
const paths=new Set(intent.sources.map(f=>f.path));paths.add(intent.qualification.path)
|
|
189
|
+
for(const[i,image]of intent.images.entries())if(paths.has(image.path)||image.before!==`${i}.before`||image.after!==`${i}.after`||!SHA.test(image.afterSHA256)||image.beforeSHA256!==null&&!SHA.test(image.beforeSHA256))fail('target/image schema')
|
|
190
|
+
const names=['0.before','0.after','1.before','1.after','qualification.json'];if(!same(intent.owned.map(x=>x.name).sort(),names.sort())||intent.owned.some(x=>!SHA.test(x.sha256)))fail('owned schema')
|
|
191
|
+
for(const i of intent.images)if(intent.owned.find(x=>x.name===i.before)?.sha256!==(i.beforeSHA256??sha(''))||intent.owned.find(x=>x.name===i.after)?.sha256!==i.afterSHA256)fail('image/proof binding')
|
|
192
|
+
if(intent.owned.find(x=>x.name==='qualification.json')?.sha256!==intent.qualification.sha256)fail('qualification/proof binding')
|
|
193
|
+
if(!SHA.test(intent.qualification.sha256))fail('qualification schema')
|
|
194
|
+
}
|
|
195
|
+
/** Prepare immutable before/after images and durable intent. Sources and base
|
|
196
|
+
* bytes are never written. The explicit receipt is verified again on execution. */
|
|
197
|
+
export function prepareMacSpeakerCallPublication(input:MacSpeakerCallPublicationInput,options:MacSpeakerCallStoreOptions={}):string {
|
|
198
|
+
const check=budget(),root=canonicalDir(input.transactionRoot)
|
|
199
|
+
if(!/^mac_[A-Za-z0-9_-]{1,100}$/.test(input.captureId)||Buffer.byteLength(input.packetJSON)>MAX_ARTIFACT||Buffer.byteLength(input.stateJSON)>MAX_ARTIFACT)fail('publication bound')
|
|
200
|
+
const directory=join(root,`${input.captureId}-${randomUUID()}`),current=input.sources.find(f=>f.role==='current')
|
|
201
|
+
if(!current)return fail('current source required')
|
|
202
|
+
const lockPath=join(dirname(safePath(current.path)),`.${input.captureId}.speaker-call.lock`),captureLockPath=join(root,`.speaker-call.capture.${input.captureId}.lock`),release=locks(lockPath,captureLockPath,directory,options.resume)
|
|
203
|
+
try{
|
|
204
|
+
pinShape(input.sources)
|
|
205
|
+
const packet=Buffer.from(input.packetJSON),state=Buffer.from(input.stateJSON),qualified=read(input.qualificationReceipt.path,1024*1024,check)!
|
|
206
|
+
if(qualified.hash!==input.qualificationReceipt.expectedSHA256)fail('qualification receipt drift')
|
|
207
|
+
const verified=verifyFiles(input.sources,check);verifyQualified(packet,state,qualified.bytes,input.sources,verified,input.captureId)
|
|
208
|
+
const before=[input.packetPath,input.statePath].map(p=>read(p,MAX_ARTIFACT,check,true,true))
|
|
209
|
+
if((before[0]?.hash??null)!==input.expectedPacketSHA256||(before[1]?.hash??null)!==input.expectedStateFileSHA256)fail('initial target CAS')
|
|
210
|
+
const intent:Intent={version:1,captureId:input.captureId,directory,root,lockPath,captureLockPath,sources:structuredClone(input.sources),qualification:{path:input.qualificationReceipt.path,sha256:qualified.hash},images:[input.packetPath,input.statePath].map((path,i)=>({path,before:`${i}.before`,after:`${i}.after`,beforeSHA256:before[i]?.hash??null,afterSHA256:sha(i?state:packet)})),owned:[]}
|
|
211
|
+
intent.owned=[...intent.images.flatMap((i,n)=>[{name:i.before,sha256:sha(before[n]?.bytes??Buffer.alloc(0))},{name:i.after,sha256:i.afterSHA256}]),{name:'qualification.json',sha256:qualified.hash}]
|
|
212
|
+
shapeIntent(intent,directory);options.beforeWrite?.(-1)
|
|
213
|
+
// Recheck ALL source/proof/target bytes immediately before durable intent.
|
|
214
|
+
verifyFiles(intent.sources,check);if(read(intent.qualification.path,1024*1024,check)!.hash!==qualified.hash)fail('qualification receipt drift');if(targets(intent,check).some((h,i)=>h!==intent.images[i].beforeSHA256))fail('preparation target CAS')
|
|
215
|
+
mkdirSync(directory,{mode:0o700});syncDir(root)
|
|
216
|
+
for(const[i,image]of intent.images.entries()){writeNew(join(directory,image.before),before[i]?.bytes??Buffer.alloc(0));writeNew(join(directory,image.after),i?state:packet)}
|
|
217
|
+
writeNew(join(directory,'qualification.json'),qualified.bytes)
|
|
218
|
+
const finalSources=verifyFiles(intent.sources,check),finalReceipt=read(intent.qualification.path,1024*1024,check)!
|
|
219
|
+
if(finalReceipt.hash!==qualified.hash)fail('qualification receipt drift');if(targets(intent,check).some((h,i)=>h!==intent.images[i].beforeSHA256))fail('preparation target CAS');owned(intent,check);immediateFence(finalSources.stamps);immediateFence(new Map([[intent.qualification.path,finalReceipt.stamp]]))
|
|
220
|
+
const intentBytes=JSON.stringify(intent);writeNew(join(directory,'intent.json'),intentBytes)
|
|
221
|
+
writeNew(join(directory,'transaction.json'),JSON.stringify(sealedJournal({version:1,intentSHA256:sha(intentBytes),phase:'prepared',completed:[]})),0o600)
|
|
222
|
+
return directory
|
|
223
|
+
}finally{release()}
|
|
224
|
+
}
|
|
225
|
+
/** Recover a torn two-artifact save by exact bytes. Reader admission depends on
|
|
226
|
+
* the self-contained packet; HTTP must never combine this file with mixed state. */
|
|
227
|
+
export function executeMacSpeakerCallPublication(directory:string,mode:'apply'|'undo',options:MacSpeakerCallStoreOptions={}):{phase:'applied'|'reverted';directory:string} {
|
|
228
|
+
canonicalDir(directory);const check=budget(),intentFile=join(directory,'intent.json'),journalFile=join(directory,'transaction.json'),intentRead=read(intentFile,4*1024*1024,check)!,intent=json(intentRead.bytes) as Intent
|
|
229
|
+
shapeIntent(intent,directory);let journalRead=read(journalFile,65536,check)!,journal=checkJournal(journalRead.bytes,intentRead.hash)
|
|
230
|
+
const release=locks(intent.lockPath,intent.captureLockPath,directory,options.resume)
|
|
231
|
+
try{
|
|
232
|
+
const assertOwned=()=>{check();if(read(intentFile,4*1024*1024,check)!.hash!==intentRead.hash||read(journalFile,65536,check)!.hash!==journalRead.hash)fail('intent/journal drift');owned(intent,check)}
|
|
233
|
+
const verify=()=>{assertOwned();const verified=verifyFiles(intent.sources,check),q=read(intent.qualification.path,1024*1024,check)!;if(q.hash!==intent.qualification.sha256)fail('qualification receipt drift');verifyQualified(read(join(directory,'0.after'),MAX_ARTIFACT,check)!.bytes,read(join(directory,'1.after'),MAX_ARTIFACT,check)!.bytes,q.bytes,intent.sources,verified,intent.captureId);targets(intent,check);assertOwned();verified.stamps.set(intent.qualification.path,q.stamp);immediateFence(verified.stamps);return verified.stamps}
|
|
234
|
+
const save=(phase:Journal['phase'],completed:number[],sourceStamps?:Map<string,Stamp|null>)=>{
|
|
235
|
+
assertOwned()
|
|
236
|
+
const intended=sealedJournal({version:1,intentSHA256:intentRead.hash,phase,completed}),bytes=Buffer.from(JSON.stringify(intended))
|
|
237
|
+
publish(journalFile,bytes,()=>{assertOwned();if(sourceStamps)immediateFence(sourceStamps)})
|
|
238
|
+
const written=read(journalFile,65536,check)!
|
|
239
|
+
// A post-rename replacement belongs to another writer. Never adopt its
|
|
240
|
+
// bytes as our next CAS baseline or overwrite them in failure recovery.
|
|
241
|
+
if(written.hash!==sha(bytes))fail('journal publication readback drift')
|
|
242
|
+
journal=intended;journalRead=written
|
|
243
|
+
}
|
|
244
|
+
verify()
|
|
245
|
+
const initial=targets(intent,check)
|
|
246
|
+
if(journal.phase==='prepared'&&initial.some((h,i)=>h!==intent.images[i].beforeSHA256)||journal.phase==='applied'&&initial.some((h,i)=>h!==intent.images[i].afterSHA256))fail('journal/target ownership CAS')
|
|
247
|
+
if(['partial','applying','undoing'].includes(journal.phase)&&!options.resume)fail('partial intent requires explicit resume')
|
|
248
|
+
if(mode==='apply'&&journal.phase==='reverted')fail('reverted transaction cannot be reapplied')
|
|
249
|
+
const terminal=mode==='apply'?'applied':'reverted'
|
|
250
|
+
if(journal.phase===terminal){if(targets(intent,check).some((h,i)=>h!==(mode==='apply'?intent.images[i].afterSHA256:intent.images[i].beforeSHA256)))fail('completed target drift');return{phase:terminal,directory}}
|
|
251
|
+
save(mode==='apply'?'applying':'undoing',[])
|
|
252
|
+
try{
|
|
253
|
+
for(const[i,image]of intent.images.entries()){
|
|
254
|
+
options.beforeWrite?.(i);const stamps=verify()
|
|
255
|
+
const desired=mode==='apply'?image.afterSHA256:image.beforeSHA256,currentRead=read(image.path,MAX_ARTIFACT,check,true,true),current=currentRead?.hash??null
|
|
256
|
+
if(current!==desired){const bytes=mode==='apply'?read(join(directory,image.after),MAX_ARTIFACT,check)!.bytes:desired===null?null:read(join(directory,image.before),MAX_ARTIFACT,check)!.bytes;publish(image.path,bytes,()=>{assertOwned();immediateFence(stamps);immediateFence(new Map([[image.path,currentRead?.stamp??null]]))})}
|
|
257
|
+
options.afterWrite?.(i);verify();save(mode==='apply'?'applying':'undoing',[...journal.completed,i])
|
|
258
|
+
}
|
|
259
|
+
const finalStamps=verify();if(targets(intent,check).some((h,i)=>h!==(mode==='apply'?intent.images[i].afterSHA256:intent.images[i].beforeSHA256)))fail('final target drift')
|
|
260
|
+
for(const image of intent.images)finalStamps.set(image.path,read(image.path,MAX_ARTIFACT,check,true,true)?.stamp??null)
|
|
261
|
+
save(terminal,[0,1],finalStamps);return{phase:terminal,directory}
|
|
262
|
+
}catch(e){try{assertOwned();save('partial',journal.completed)}catch{/* preserve externally changed journal/proofs exactly */}throw e}
|
|
263
|
+
}finally{release()}
|
|
264
|
+
}
|