Vendor tested Lesavka observer assets and numeric receipt schema from 6b97ceb1. Verified 65 JS tests, 10 service tests, kustomize render and Kubernetes client dry-run. No media upload, capture activation or changes to the homepage and private OCR.
90 lines
5.5 KiB
JavaScript
90 lines
5.5 KiB
JavaScript
// Receiver observation only: no media, identities, storage, network or PASS.
|
|
// A watch never resets capture to produce a new reporting interval.
|
|
export const WATCH_MS=10*60*60*1000;
|
|
export const WATCH_ROWS=1202; // One row per 30 seconds plus start/terminal.
|
|
const integer=value=>Number.isSafeInteger(value)&&value>=0;
|
|
const number=value=>Number.isFinite(value)&&value>=0;
|
|
|
|
export class ContinuityWatch {
|
|
constructor(now=()=>performance.now(),wall=()=>Date.now()) {
|
|
this.now=now;this.wall=wall;this.started=now();this.wallStarted=wall();
|
|
this.previousAt=this.started;this.nextRow=0;this.rows=[];this.findings={};
|
|
this.previous=null;this.lastAudioProgress=0;this.lastVideoProgress=0;
|
|
this.latest=null;this.terminal=null;this.maxObserverGap=0;
|
|
}
|
|
note(kind,elapsed) {
|
|
const finding=this.findings[kind]??{firstObservedMs:elapsed,observations:0};
|
|
finding.observations++;this.findings[kind]=finding;
|
|
}
|
|
observe(sample,audioReceivedAt) {
|
|
if(this.terminal)return;
|
|
const now=this.now(),elapsed=now-this.started,wallElapsed=this.wall()-this.wallStarted;
|
|
if(!number(elapsed)||!number(wallElapsed)||now<this.previousAt) {
|
|
this.note('observer-clock-invalid',this.latest?.elapsedMs??0);return;
|
|
}
|
|
const gap=now-this.previousAt;
|
|
this.previousAt=now;this.maxObserverGap=Math.max(this.maxObserverGap,gap);
|
|
if(gap>2500)this.note('observer-coverage-gap',elapsed);
|
|
if(Math.abs(wallElapsed-elapsed)>2000)this.note('wall-monotonic-divergence',elapsed);
|
|
const audio=sample.audio??{},video=sample.video??{};
|
|
const audioAge=audioReceivedAt===null?elapsed:now-audioReceivedAt;
|
|
const row={elapsedMs:elapsed,videoCallbacks:video.callbacks,videoResets:video.clockOrCounterResets,
|
|
audioInputFrames:audio.inputFrames,audioProcessedFrames:audio.processedFrames,
|
|
audioZeroFrames:audio.exactZeroFrames,audioRate:audio.sampleRate,
|
|
audioMessageAgeMs:number(audioAge)?audioAge:null,
|
|
videoCallbackAgeMs:number(video.observationAgeMs)?video.observationAgeMs:null,
|
|
videoGapMaxMs:number(video.callbackGapMaxMs)?video.callbackGapMaxMs:null};
|
|
for(const key of ['videoCallbacks','videoResets','audioInputFrames','audioProcessedFrames','audioZeroFrames','audioRate']) {
|
|
if(!integer(row[key]))row[key]=null;
|
|
}
|
|
if(row.audioMessageAgeMs===null||row.audioMessageAgeMs>2000)this.note('audio-observation-stale',elapsed);
|
|
if(row.videoCallbackAgeMs===null||row.videoCallbackAgeMs>2000) {
|
|
if(elapsed>2000)this.note('video-observation-stale',elapsed);
|
|
}
|
|
if(row.audioInputFrames===null||row.audioProcessedFrames===null||row.audioRate===null) {
|
|
if(elapsed>2000)this.note('audio-counters-unavailable',elapsed);
|
|
}
|
|
const previous=this.previous;
|
|
if(previous) {
|
|
for(const key of ['videoCallbacks','audioInputFrames','audioProcessedFrames','audioZeroFrames']) {
|
|
if(row[key]!==null&&previous[key]!==null&&row[key]<previous[key])this.note('counter-reset',elapsed);
|
|
}
|
|
if(row.audioRate!==null&&previous.audioRate!==null&&row.audioRate!==previous.audioRate)
|
|
this.note('audio-rate-changed',elapsed);
|
|
if(row.videoResets!==null&&previous.videoResets!==null&&row.videoResets>previous.videoResets)
|
|
this.note('video-clock-or-counter-reset',elapsed);
|
|
}
|
|
if(row.audioInputFrames!==null&&(!previous||row.audioInputFrames>previous.audioInputFrames))this.lastAudioProgress=elapsed;
|
|
if(row.videoCallbacks!==null&&(!previous||row.videoCallbacks>previous.videoCallbacks))this.lastVideoProgress=elapsed;
|
|
if(elapsed-this.lastAudioProgress>2000)this.note('audio-input-not-advancing',elapsed);
|
|
if(elapsed-this.lastVideoProgress>2000)this.note('video-not-advancing',elapsed);
|
|
this.previous=row;this.latest=row;
|
|
if(elapsed>=this.nextRow) {
|
|
this.append(row);this.nextRow=elapsed+30000;
|
|
}
|
|
}
|
|
append(row) {
|
|
if(this.rows.length<WATCH_ROWS)this.rows.push({...row});
|
|
else this.note('timeline-capacity-reached',row.elapsedMs);
|
|
}
|
|
finish(reason) {
|
|
if(this.terminal)return;
|
|
if(this.latest&&this.rows.at(-1)?.elapsedMs!==this.latest.elapsedMs)this.append(this.latest);
|
|
const duration=this.latest?.elapsedMs??0;
|
|
const observerIncomplete=['observer-clock-invalid','observer-coverage-gap',
|
|
'wall-monotonic-divergence','timeline-capacity-reached'].some(key=>this.findings[key]);
|
|
this.terminal=reason==='10 hours observed'&&duration>=WATCH_MS&&!observerIncomplete?
|
|
(Object.keys(this.findings).length?'duration-observed-with-findings':'duration-observed'):'incomplete';
|
|
}
|
|
summary(includeTimeline=false) {
|
|
return {kind:'receiver-continuity-diagnostic',state:this.terminal??'observing',
|
|
startedUnixMs:integer(this.wallStarted)?this.wallStarted:null,wallClockCalibrated:false,
|
|
requestedDurationMs:WATCH_MS,observedDurationMs:this.latest?.elapsedMs??0,
|
|
maxObserverGapMs:this.maxObserverGap,findings:structuredClone(this.findings),
|
|
retainedRows:this.rows.length,rowLimit:WATCH_ROWS,latest:this.latest?{...this.latest}:null,
|
|
...(includeTimeline?{timeline:this.rows.map(row=>({...row}))}:{}),
|
|
releaseEligible:false,mediaRecorded:false,uploaded:false,
|
|
note:'Browser observations only. Silence is not loss; clocks and callback gaps do not prove audible quality or A/V sync.'};
|
|
}
|
|
}
|