livedesk 0.1.461 → 0.1.463

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 (40) hide show
  1. package/README.md +28 -8
  2. package/bin/livedesk.js +36 -9
  3. package/client/bin/livedesk-client-node.js +506 -555
  4. package/client/bin/livedesk-client-update-bootstrap.cjs +59 -0
  5. package/client/bin/livedesk-client.js +195 -58
  6. package/client/package.json +11 -11
  7. package/client/src/runtime/agent-process-lifecycle.js +85 -23
  8. package/client/src/runtime/client-runtime-server.js +775 -127
  9. package/client/src/runtime/hub-wake-listener.js +42 -11
  10. package/diagnostics/livedesk-mac-physical-gate-core.mjs +992 -54
  11. package/diagnostics/livedesk-mac-physical-isolation.mjs +48 -5
  12. package/diagnostics/livedesk-mode3-capture-smoke.mjs +549 -88
  13. package/electron/bounded-desktop-log.mjs +609 -0
  14. package/electron/main.mjs +316 -132
  15. package/electron/runtime-process-owner.mjs +287 -0
  16. package/electron/update-manager.mjs +59 -18
  17. package/hub/package.json +2 -2
  18. package/hub/src/agents/agent-manager.js +27 -11
  19. package/hub/src/agents/codex-agent-runtime.js +325 -41
  20. package/hub/src/frame-packet-contract.mjs +427 -0
  21. package/hub/src/live-capture-transition-retry.mjs +297 -0
  22. package/hub/src/mode4-atlas-pool.js +17 -3
  23. package/hub/src/mode4-atlas-worker.js +134 -21
  24. package/hub/src/mode4-atlas.js +180 -16
  25. package/hub/src/remote-audio-subscription-contract.mjs +109 -0
  26. package/hub/src/remote-audio-subscription-contract.test.mjs +72 -0
  27. package/hub/src/remote-hub.js +6405 -4236
  28. package/hub/src/server.js +3234 -2206
  29. package/hub/src/transport/agent-binary-ingress.js +810 -0
  30. package/hub/src/transport/relay-hub-control.js +372 -56
  31. package/hub/src/transport/udp-hub-transport.js +186 -45
  32. package/hub/src/transport/udp-protocol.js +302 -70
  33. package/package.json +11 -11
  34. package/runtime-core/package.json +1 -1
  35. package/runtime-core/src/windows-owned-process-tree.js +172 -24
  36. package/scripts/sync-web-dist.js +1 -0
  37. package/web/dist/assets/index-Bjyg25Sq.js +186 -0
  38. package/web/dist/index.html +1 -1
  39. package/web/dist/livedesk-build-evidence.json +64 -0
  40. package/web/dist/assets/index-l4YzRK9d.js +0 -25
@@ -1,8 +1,97 @@
1
1
  import path from 'node:path';
2
+ import { createHash } from 'node:crypto';
2
3
 
3
4
  const MAC_VIDEO_HELPER_NAME = 'livedesk-sck-h264';
4
5
  const MAC_AUDIO_HELPER_NAME = 'livedesk-sck-audio';
5
6
 
7
+ // This physical gate intentionally uses the product's 960x540/30 Wall-sized
8
+ // ScreenCaptureKit + required hardware VideoToolbox path. These values are hard
9
+ // release ceilings, not a claim that untested Mac hardware is healthy. A real
10
+ // physical run must remain below both the aggregate and exact-role limits.
11
+ // CPU values are macOS `ps` percentages where 100 is one fully occupied core.
12
+ export const MAC_ACTIVE_MOTION_RESOURCE_BUDGET = Object.freeze({
13
+ profile: 'screencapturekit-hardware-vt-960x540-30',
14
+ width: 960,
15
+ height: 540,
16
+ fps: 30,
17
+ minimumFps: 24,
18
+ minimumSteadySamples: 4,
19
+ minimumMotionObservationMs: 1_500,
20
+ minimumMotionFrameCount: 36,
21
+ minimumDistinctAccessUnits: 12,
22
+ minimumDistinctDecodedFrames: 2,
23
+ minimumNativeCounterDelta: 20,
24
+ maximumMotionHashSamples: 48,
25
+ maximumIdleRatio: 0.1,
26
+ ownedCpuPercentP95Max: 140,
27
+ ownedCpuPercentPeakMax: 200,
28
+ ownedRssBytesPeakMax: 512 * 1024 * 1024,
29
+ // The gate runs the same two unified launcher roots as production. Their
30
+ // Hub, RemoteFast, and SCK descendants must still fit the unchanged CPU/RSS
31
+ // envelope; process ceilings describe topology, not permission to be heavy.
32
+ ownedProcessCountPeakMax: 5,
33
+ ownedChildProcessCountPeakMax: 3,
34
+ captureHelperCountPeakMax: 1,
35
+ roleBudgets: Object.freeze({
36
+ 'hub-launcher': Object.freeze({
37
+ processCountPeakMax: 1,
38
+ cpuPercentP95Max: 5,
39
+ cpuPercentPeakMax: 15,
40
+ rssBytesPeakMax: 96 * 1024 * 1024
41
+ }),
42
+ 'hub-runtime': Object.freeze({
43
+ processCountPeakMax: 1,
44
+ cpuPercentP95Max: 35,
45
+ cpuPercentPeakMax: 60,
46
+ rssBytesPeakMax: 160 * 1024 * 1024
47
+ }),
48
+ 'client-launcher': Object.freeze({
49
+ processCountPeakMax: 1,
50
+ cpuPercentP95Max: 10,
51
+ cpuPercentPeakMax: 25,
52
+ rssBytesPeakMax: 128 * 1024 * 1024
53
+ }),
54
+ 'remote-fast': Object.freeze({
55
+ processCountPeakMax: 1,
56
+ cpuPercentP95Max: 50,
57
+ cpuPercentPeakMax: 85,
58
+ rssBytesPeakMax: 256 * 1024 * 1024
59
+ }),
60
+ 'capture-helper': Object.freeze({
61
+ processCountPeakMax: 1,
62
+ cpuPercentP95Max: 80,
63
+ cpuPercentPeakMax: 125,
64
+ rssBytesPeakMax: 192 * 1024 * 1024
65
+ })
66
+ })
67
+ });
68
+
69
+ // Cold helper prime is not allowed a relaxed "temporary" resource envelope.
70
+ // If source compilation exceeds the normal LiveDesk topology or load ceiling,
71
+ // the release must ship a lighter/prebuilt helper rather than hiding the cost.
72
+ export const MAC_STARTUP_PRIME_RESOURCE_BUDGET = Object.freeze({
73
+ profile: 'cold-sck-helper-prime-to-first-wall-frame',
74
+ minimumSamples: 2,
75
+ maximumDurationMs: 8_000,
76
+ ownedCpuPercentP95Max: MAC_ACTIVE_MOTION_RESOURCE_BUDGET.ownedCpuPercentP95Max,
77
+ ownedCpuPercentPeakMax: MAC_ACTIVE_MOTION_RESOURCE_BUDGET.ownedCpuPercentPeakMax,
78
+ ownedRssBytesPeakMax: MAC_ACTIVE_MOTION_RESOURCE_BUDGET.ownedRssBytesPeakMax,
79
+ ownedProcessCountPeakMax: MAC_ACTIVE_MOTION_RESOURCE_BUDGET.ownedProcessCountPeakMax,
80
+ ownedChildProcessCountPeakMax:
81
+ MAC_ACTIVE_MOTION_RESOURCE_BUDGET.ownedChildProcessCountPeakMax,
82
+ captureHelperCountPeakMax: MAC_ACTIVE_MOTION_RESOURCE_BUDGET.captureHelperCountPeakMax
83
+ });
84
+
85
+ export const MAC_PHYSICAL_RESOURCE_OWNERSHIP_MODEL = 'unified-production-v1';
86
+
87
+ const MAC_PRODUCT_RESOURCE_ROLES = new Set([
88
+ 'hub-launcher',
89
+ 'hub-runtime',
90
+ 'client-launcher',
91
+ 'remote-fast',
92
+ 'capture-helper'
93
+ ]);
94
+
6
95
  function normalizeProcessText(value) {
7
96
  return String(value || '').trim().replaceAll('\\', '/');
8
97
  }
@@ -76,6 +165,137 @@ function finite(value, fallback = 0) {
76
165
  return Number.isFinite(parsed) ? parsed : fallback;
77
166
  }
78
167
 
168
+ function boundedEvenlySpacedItems(items, limit) {
169
+ if (items.length <= limit) return [...items];
170
+ const selected = [];
171
+ for (let index = 0; index < limit; index += 1) {
172
+ const sourceIndex = Math.round(index * (items.length - 1) / Math.max(1, limit - 1));
173
+ selected.push(items[sourceIndex]);
174
+ }
175
+ return selected;
176
+ }
177
+
178
+ /**
179
+ * Produces fail-closed motion evidence from the exact received generation.
180
+ * It does not trust an operator label: two decoded pixel hashes, sustained
181
+ * frame delivery, changing encoded access units, and advancing native counters
182
+ * must all independently agree.
183
+ */
184
+ export function buildMacActiveMotionProof(
185
+ frames = [],
186
+ {
187
+ decodedPixelHashes = [],
188
+ budget = MAC_ACTIVE_MOTION_RESOURCE_BUDGET
189
+ } = {}
190
+ ) {
191
+ const normalizedFrames = (Array.isArray(frames) ? frames : [])
192
+ .filter(frame =>
193
+ frame
194
+ && typeof frame === 'object'
195
+ && Number.isFinite(Number(frame.receivedAt))
196
+ && finite(frame.bytes ?? frame.payload?.byteLength ?? frame.payload?.length) > 0
197
+ );
198
+ const firstReceivedAt = finite(normalizedFrames[0]?.receivedAt);
199
+ const lastReceivedAt = finite(normalizedFrames.at(-1)?.receivedAt);
200
+ const observationMs = Math.max(0, lastReceivedAt - firstReceivedAt);
201
+ const hashCandidates = normalizedFrames.filter(frame =>
202
+ String(frame.payloadHash || '').trim() || frame.payload
203
+ );
204
+ const sampledFrames = boundedEvenlySpacedItems(
205
+ hashCandidates,
206
+ Math.max(1, Math.floor(finite(budget.maximumMotionHashSamples, 48)))
207
+ );
208
+ const accessUnitHashes = new Set(sampledFrames.map(frame =>
209
+ String(frame.payloadHash || '').trim()
210
+ || createHash('sha256').update(frame.payload).digest('hex')
211
+ ));
212
+ const telemetrySnapshots = normalizedFrames
213
+ .map(frame => frame.metadata || {})
214
+ .filter(metadata =>
215
+ finite(metadata.nativeCaptureTelemetryVersion) >= 4
216
+ && metadata.nativeCaptureTelemetryAvailable === true
217
+ && Number.isFinite(Number(metadata.nativeCaptureSampleCount))
218
+ && Number.isFinite(Number(metadata.nativeCompressionOutputCount))
219
+ && Number.isFinite(Number(metadata.nativeEncodedFrameCount))
220
+ && Number.isFinite(Number(metadata.nativeIdleSkippedFrameCount))
221
+ && Number.isFinite(Number(metadata.nativeBackpressureSkippedFrameCount))
222
+ );
223
+ const distinctTelemetry = [];
224
+ let previousTelemetryToken = '';
225
+ for (const metadata of telemetrySnapshots) {
226
+ const token = [
227
+ finite(metadata.nativeCaptureSampleCount),
228
+ finite(metadata.nativeCompressionOutputCount),
229
+ finite(metadata.nativeEncodedFrameCount),
230
+ finite(metadata.nativeIdleSkippedFrameCount),
231
+ finite(metadata.nativeBackpressureSkippedFrameCount)
232
+ ].join(':');
233
+ if (token === previousTelemetryToken) continue;
234
+ distinctTelemetry.push(metadata);
235
+ previousTelemetryToken = token;
236
+ }
237
+ const firstTelemetry = distinctTelemetry[0] || {};
238
+ const lastTelemetry = distinctTelemetry.at(-1) || {};
239
+ const counterDelta = key => Math.max(0, finite(lastTelemetry[key]) - finite(firstTelemetry[key]));
240
+ const captureSampleDelta = counterDelta('nativeCaptureSampleCount');
241
+ const compressionOutputDelta = counterDelta('nativeCompressionOutputCount');
242
+ const stdoutWriteDelta = counterDelta('nativeEncodedFrameCount');
243
+ // These two native fields are one-second window counts rather than lifetime
244
+ // counters. Sum only the de-duplicated telemetry windows; subtracting them
245
+ // could falsely turn a later smaller unhealthy window into zero.
246
+ const idleSkippedObserved = distinctTelemetry.reduce(
247
+ (sum, metadata) => sum + Math.max(0, finite(metadata.nativeIdleSkippedFrameCount)),
248
+ 0
249
+ );
250
+ const backpressureSkippedObserved = distinctTelemetry.reduce(
251
+ (sum, metadata) => sum + Math.max(0, finite(metadata.nativeBackpressureSkippedFrameCount)),
252
+ 0
253
+ );
254
+ const normalizedPixelHashes = (Array.isArray(decodedPixelHashes) ? decodedPixelHashes : [])
255
+ .map(value => String(value || '').trim())
256
+ .filter(Boolean)
257
+ .slice(0, 4);
258
+ const distinctDecodedPixelHashCount = new Set(normalizedPixelHashes).size;
259
+ const nativeCounterMinimum = Math.max(1, Math.floor(finite(budget.minimumNativeCounterDelta, 20)));
260
+ const maximumIdleSkipped = Math.max(
261
+ 1,
262
+ Math.floor(captureSampleDelta * Math.max(0, finite(budget.maximumIdleRatio, 0.1)))
263
+ );
264
+ const proof = {
265
+ method: 'decoded-pixels-native-counters-and-bounded-access-unit-hashes',
266
+ operatorRequirement: 'continuous-visible-motion-on-each-tested-display',
267
+ observationMs: Math.round(observationMs),
268
+ frameCount: normalizedFrames.length,
269
+ sampledAccessUnitCount: sampledFrames.length,
270
+ distinctAccessUnitHashCount: accessUnitHashes.size,
271
+ telemetrySnapshotCount: telemetrySnapshots.length,
272
+ distinctTelemetrySnapshotCount: distinctTelemetry.length,
273
+ captureSampleDelta,
274
+ compressionOutputDelta,
275
+ stdoutWriteDelta,
276
+ idleSkippedObserved,
277
+ backpressureSkippedObserved,
278
+ maximumAllowedIdleSkipped: maximumIdleSkipped,
279
+ decodedPixelHashCount: normalizedPixelHashes.length,
280
+ distinctDecodedPixelHashCount
281
+ };
282
+ return {
283
+ ...proof,
284
+ detected: proof.observationMs >= finite(budget.minimumMotionObservationMs, 1_500)
285
+ && proof.frameCount >= finite(budget.minimumMotionFrameCount, 36)
286
+ && proof.sampledAccessUnitCount >= finite(budget.minimumDistinctAccessUnits, 12)
287
+ && proof.distinctAccessUnitHashCount >= finite(budget.minimumDistinctAccessUnits, 12)
288
+ && proof.distinctTelemetrySnapshotCount >= 2
289
+ && proof.captureSampleDelta >= nativeCounterMinimum
290
+ && proof.compressionOutputDelta >= nativeCounterMinimum
291
+ && proof.stdoutWriteDelta >= nativeCounterMinimum
292
+ && proof.idleSkippedObserved <= maximumIdleSkipped
293
+ && proof.backpressureSkippedObserved === 0
294
+ && proof.decodedPixelHashCount >= finite(budget.minimumDistinctDecodedFrames, 2)
295
+ && proof.distinctDecodedPixelHashCount >= finite(budget.minimumDistinctDecodedFrames, 2)
296
+ };
297
+ }
298
+
79
299
  export function createAsyncSingleFlightSampler(sampleImpl) {
80
300
  if (typeof sampleImpl !== 'function') {
81
301
  throw new TypeError('An async sample function is required.');
@@ -182,15 +402,30 @@ function classifyMacLiveDeskRuntimeRecord(record) {
182
402
  return '';
183
403
  }
184
404
 
185
- function identifyMacLiveDeskRuntimeRecords(records, { ignorePids = [] } = {}) {
405
+ function identifyMacLiveDeskRuntimeRecords(
406
+ records,
407
+ {
408
+ ignorePids = [],
409
+ ignoreDescendantsOfPids = []
410
+ } = {}
411
+ ) {
186
412
  const ignored = new Set(
187
413
  (Array.isArray(ignorePids) ? ignorePids : [])
188
414
  .map(Number)
189
415
  .filter(pid => Number.isInteger(pid) && pid > 1)
190
416
  );
417
+ const ignoredAncestorPids = normalizedOwnedPids(ignoreDescendantsOfPids);
418
+ const recordsByPid = new Map(records.map(record => [record.pid, record]));
191
419
  const results = [];
192
420
  for (const record of records) {
193
- if (ignored.has(record.pid)) continue;
421
+ if (
422
+ ignored.has(record.pid)
423
+ || [...ignoredAncestorPids].some(ancestorPid =>
424
+ record.pid === ancestorPid || hasAncestorPid(record, ancestorPid, recordsByPid)
425
+ )
426
+ ) {
427
+ continue;
428
+ }
194
429
  const kind = classifyMacLiveDeskRuntimeRecord(record);
195
430
  if (!kind) continue;
196
431
  results.push({
@@ -218,6 +453,34 @@ function hasAncestorPid(record, ancestorPid, recordsByPid) {
218
453
  return false;
219
454
  }
220
455
 
456
+ function isUnifiedLauncherRoleRecord(record, role) {
457
+ if (classifyMacLiveDeskRuntimeRecord(record) !== 'unified-runtime') return false;
458
+ const commandLine = normalizeProcessText(record?.commandLine);
459
+ const escapedRole = String(role || '').replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
460
+ return new RegExp(
461
+ `(?:--force-role|--role)(?:=|\\s+)${escapedRole}(?:\\s|$)`
462
+ + `|(?:^|\\s)(?:hub|manager|client)(?:\\s|$)`,
463
+ 'i'
464
+ ).test(commandLine)
465
+ && (
466
+ role === 'hub'
467
+ ? /(?:--force-role|--role)(?:=|\s+)(?:hub|manager)(?:\s|$)|(?:^|\s)(?:hub|manager)(?:\s|$)/i
468
+ .test(commandLine)
469
+ : /(?:--force-role|--role)(?:=|\s+)client(?:\s|$)|(?:^|\s)client(?:\s|$)/i
470
+ .test(commandLine)
471
+ );
472
+ }
473
+
474
+ function classifyOwnedMacProductRole(record, ownerRole, isRoot) {
475
+ if (isRoot) return `${ownerRole}-launcher`;
476
+ const kind = classifyMacLiveDeskRuntimeRecord(record);
477
+ if (kind === 'hub-runtime') return 'hub-runtime';
478
+ if (kind === 'client-agent') return 'remote-fast';
479
+ if (kind === 'capture-helper') return 'capture-helper';
480
+ if (kind) return kind;
481
+ return `${ownerRole}-owned-descendant`;
482
+ }
483
+
221
484
  function normalizedOwnedPids(values) {
222
485
  return new Set(
223
486
  (Array.isArray(values) ? values : [])
@@ -234,17 +497,15 @@ export function buildMacDiagnosticResourceSample(
234
497
  psOutput,
235
498
  {
236
499
  diagnosticPid = 0,
237
- hubPid = 0,
238
- agentPid = 0,
239
- decoderPids = []
500
+ hubLauncherPid = 0,
501
+ clientLauncherPid = 0,
502
+ observerPids = []
240
503
  } = {}
241
504
  ) {
242
505
  const records = parseMacProcessResourceRecords(psOutput);
243
506
  const recordsByPid = new Map(records.map(record => [record.pid, record]));
244
507
  const normalizedDiagnosticPid = Number(diagnosticPid);
245
- const normalizedHubPid = Number(hubPid);
246
- const normalizedAgentPid = Number(agentPid);
247
- const ownedDecoderPids = normalizedOwnedPids(decoderPids);
508
+ const ownedObserverPids = normalizedOwnedPids(observerPids);
248
509
  const helpers = records.filter(isMacCaptureHelperRecord);
249
510
  // The public `npx livedesk diagnose mac-physical` coordinator is itself
250
511
  // classified as a unified LiveDesk runtime. Exclude only that exact,
@@ -253,44 +514,106 @@ export function buildMacDiagnosticResourceSample(
253
514
  const liveDeskRecords = identifyMacLiveDeskRuntimeRecords(records, {
254
515
  ignorePids: [normalizedDiagnosticPid]
255
516
  });
256
- const hubRecord = recordsByPid.get(normalizedHubPid);
257
- const agentRecord = recordsByPid.get(normalizedAgentPid);
258
- const hubOwned = classifyMacLiveDeskRuntimeRecord(hubRecord) === 'hub-runtime';
259
- const agentOwned = classifyMacLiveDeskRuntimeRecord(agentRecord) === 'client-agent';
517
+ const roots = [
518
+ {
519
+ ownerRole: 'hub',
520
+ pid: Number(hubLauncherPid),
521
+ role: 'hub-launcher'
522
+ },
523
+ {
524
+ ownerRole: 'client',
525
+ pid: Number(clientLauncherPid),
526
+ role: 'client-launcher'
527
+ }
528
+ ].map(root => {
529
+ const record = recordsByPid.get(root.pid);
530
+ return {
531
+ ...root,
532
+ record,
533
+ owned: Number.isInteger(root.pid)
534
+ && root.pid > 1
535
+ && isUnifiedLauncherRoleRecord(record, root.ownerRole)
536
+ };
537
+ });
260
538
  const ownedLiveDeskPids = new Set();
261
- if (hubOwned) ownedLiveDeskPids.add(normalizedHubPid);
262
- if (agentOwned) ownedLiveDeskPids.add(normalizedAgentPid);
263
- const helperOwnership = new Map();
264
- for (const helper of helpers) {
265
- const owned = agentOwned && hasAncestorPid(helper, normalizedAgentPid, recordsByPid);
266
- helperOwnership.set(helper.pid, owned);
267
- if (owned) ownedLiveDeskPids.add(helper.pid);
268
- }
269
-
270
- const resourceRecords = [];
271
- const addResource = (record, role, ownedByDiagnostic) => {
539
+ const resourceRecordsByPid = new Map();
540
+ const addResource = (
541
+ record,
542
+ role,
543
+ ownedByDiagnostic,
544
+ {
545
+ ownerRole = '',
546
+ ownership = 'unowned',
547
+ productProcess = false
548
+ } = {}
549
+ ) => {
272
550
  if (!record) return;
273
- resourceRecords.push({
551
+ resourceRecordsByPid.set(record.pid, {
274
552
  role,
275
553
  pid: record.pid,
276
554
  ownedByDiagnostic: ownedByDiagnostic === true,
555
+ ownerRole,
556
+ ownership,
557
+ productProcess: productProcess === true,
277
558
  cpuPercent: Math.round(record.cpuPercent * 100) / 100,
278
559
  rssBytes: record.rssBytes
279
560
  });
280
561
  };
281
- addResource(hubRecord, 'hub', hubOwned);
282
- addResource(agentRecord, 'remote-fast', agentOwned);
283
- for (const decoderPid of [...ownedDecoderPids].sort((left, right) => left - right)) {
284
- addResource(recordsByPid.get(decoderPid), 'decoder', true);
562
+
563
+ for (const root of roots) {
564
+ addResource(root.record, root.role, root.owned, {
565
+ ownerRole: root.ownerRole,
566
+ ownership: 'exact-root',
567
+ productProcess: true
568
+ });
569
+ if (!root.owned) continue;
570
+ ownedLiveDeskPids.add(root.pid);
571
+ for (const record of records) {
572
+ if (record.pid === root.pid || !hasAncestorPid(record, root.pid, recordsByPid)) continue;
573
+ addResource(
574
+ record,
575
+ classifyOwnedMacProductRole(record, root.ownerRole, false),
576
+ true,
577
+ {
578
+ ownerRole: root.ownerRole,
579
+ ownership: 'exact-descendant',
580
+ productProcess: true
581
+ }
582
+ );
583
+ if (classifyMacLiveDeskRuntimeRecord(record)) {
584
+ ownedLiveDeskPids.add(record.pid);
585
+ }
586
+ }
587
+ }
588
+
589
+ for (const observerPid of [...ownedObserverPids].sort((left, right) => left - right)) {
590
+ // ffmpeg exists only to observe diagnostic key frames. It is retained in
591
+ // report-safe telemetry when explicitly requested, but it is never product
592
+ // CPU/RSS/process evidence and therefore never enters owned aggregates.
593
+ addResource(recordsByPid.get(observerPid), 'diagnostic-observer', false, {
594
+ ownerRole: 'diagnostic',
595
+ ownership: 'exact-observer',
596
+ productProcess: false
597
+ });
285
598
  }
286
599
  for (const helper of helpers.sort((left, right) => left.pid - right.pid)) {
287
- addResource(helper, 'capture-helper', helperOwnership.get(helper.pid) === true);
600
+ if (resourceRecordsByPid.has(helper.pid)) continue;
601
+ addResource(helper, 'capture-helper', false, {
602
+ ownerRole: 'unowned',
603
+ ownership: 'unowned',
604
+ productProcess: true
605
+ });
288
606
  }
289
607
 
608
+ const resourceRecords = [...resourceRecordsByPid.values()];
290
609
  return {
610
+ ownershipModel: MAC_PHYSICAL_RESOURCE_OWNERSHIP_MODEL,
291
611
  sampledAt: new Date().toISOString(),
292
612
  totalLiveDeskProcessCount: liveDeskRecords.length,
293
613
  captureHelperCount: helpers.length,
614
+ ownedProductProcessCount: resourceRecords.filter(record =>
615
+ record.ownedByDiagnostic === true && record.productProcess === true
616
+ ).length,
294
617
  unownedLiveDeskPids: liveDeskRecords
295
618
  .filter(record => !ownedLiveDeskPids.has(record.pid))
296
619
  .map(record => record.pid)
@@ -322,6 +645,9 @@ export function summarizeMacDiagnosticResourceSamples(
322
645
  role,
323
646
  pid,
324
647
  ownedByDiagnostic: record?.ownedByDiagnostic === true,
648
+ ownerRole: String(record?.ownerRole || ''),
649
+ ownership: String(record?.ownership || ''),
650
+ productProcess: record?.productProcess !== false,
325
651
  cpuPercentSamples: [],
326
652
  rssBytesPeak: 0
327
653
  });
@@ -338,12 +664,18 @@ export function summarizeMacDiagnosticResourceSamples(
338
664
  role,
339
665
  pid,
340
666
  ownedByDiagnostic: true,
667
+ ownerRole: String(record?.ownerRole || ''),
668
+ ownership: String(record?.ownership || ''),
669
+ productProcess: record?.productProcess === true,
341
670
  cpuPercentSamples: [],
342
671
  rssBytesPeak: 0
343
672
  };
344
673
  grouped.set(key, summary);
345
674
  }
346
675
  summary.ownedByDiagnostic = summary.ownedByDiagnostic && record.ownedByDiagnostic === true;
676
+ summary.productProcess = summary.productProcess && record.productProcess === true;
677
+ if (summary.ownerRole !== String(record?.ownerRole || '')) summary.ownerRole = 'mixed';
678
+ if (summary.ownership !== String(record?.ownership || '')) summary.ownership = 'mixed';
347
679
  summary.cpuPercentSamples.push(Math.max(0, finite(record.cpuPercent)));
348
680
  summary.rssBytesPeak = Math.max(summary.rssBytesPeak, Math.max(0, finite(record.rssBytes)));
349
681
  }
@@ -357,6 +689,9 @@ export function summarizeMacDiagnosticResourceSamples(
357
689
  role: summary.role,
358
690
  pid: summary.pid,
359
691
  ownedByDiagnostic: summary.ownedByDiagnostic,
692
+ ownerRole: summary.ownerRole,
693
+ ownership: summary.ownership,
694
+ productProcess: summary.productProcess,
360
695
  sampleCount: summary.cpuPercentSamples.length,
361
696
  cpuPercentSamples,
362
697
  cpuPercentP95: Math.round(percentile95(summary.cpuPercentSamples) * 100) / 100,
@@ -369,6 +704,72 @@ export function summarizeMacDiagnosticResourceSamples(
369
704
  .map(sample => Math.max(0, Math.round(finite(sample.totalLiveDeskProcessCount))));
370
705
  const captureHelperCountSamples = normalizedSamples
371
706
  .map(sample => Math.max(0, Math.round(finite(sample.captureHelperCount))));
707
+ const productResourceSamples = normalizedSamples.map(sample => {
708
+ const ownedProcesses = (Array.isArray(sample.processes) ? sample.processes : [])
709
+ .filter(record =>
710
+ record?.ownedByDiagnostic === true
711
+ && record?.productProcess === true
712
+ );
713
+ return {
714
+ cpuPercent: ownedProcesses.reduce(
715
+ (sum, record) => sum + Math.max(0, finite(record?.cpuPercent)),
716
+ 0
717
+ ),
718
+ rssBytes: ownedProcesses.reduce(
719
+ (sum, record) => sum + Math.max(0, finite(record?.rssBytes)),
720
+ 0
721
+ ),
722
+ processCount: ownedProcesses.length,
723
+ childProcessCount: ownedProcesses.filter(record =>
724
+ String(record?.ownership || '') === 'exact-descendant'
725
+ ).length
726
+ };
727
+ });
728
+ const ownedCpuPercentSamples = productResourceSamples
729
+ .map(sample => Math.round(sample.cpuPercent * 100) / 100)
730
+ .slice(-120);
731
+ const ownedRssBytesSamples = productResourceSamples
732
+ .map(sample => Math.round(sample.rssBytes))
733
+ .slice(-120);
734
+ const ownedProcessCountSamples = productResourceSamples
735
+ .map(sample => sample.processCount)
736
+ .slice(-120);
737
+ const ownedChildProcessCountSamples = productResourceSamples
738
+ .map(sample => sample.childProcessCount)
739
+ .slice(-120);
740
+ const roles = [...MAC_PRODUCT_RESOURCE_ROLES].map(role => {
741
+ const cpuPercentSamples = [];
742
+ let rssBytesPeak = 0;
743
+ let processCountPeak = 0;
744
+ let sampleCount = 0;
745
+ for (const sample of normalizedSamples) {
746
+ const roleProcesses = (Array.isArray(sample.processes) ? sample.processes : [])
747
+ .filter(record =>
748
+ record?.ownedByDiagnostic === true
749
+ && String(record?.role || '') === role
750
+ );
751
+ if (roleProcesses.length === 0) continue;
752
+ sampleCount += 1;
753
+ processCountPeak = Math.max(processCountPeak, roleProcesses.length);
754
+ cpuPercentSamples.push(Math.round(roleProcesses.reduce(
755
+ (sum, record) => sum + Math.max(0, finite(record?.cpuPercent)),
756
+ 0
757
+ ) * 100) / 100);
758
+ rssBytesPeak = Math.max(rssBytesPeak, roleProcesses.reduce(
759
+ (sum, record) => sum + Math.max(0, finite(record?.rssBytes)),
760
+ 0
761
+ ));
762
+ }
763
+ return {
764
+ role,
765
+ sampleCount,
766
+ processCountPeak,
767
+ cpuPercentSamples: cpuPercentSamples.slice(-120),
768
+ cpuPercentP95: Math.round(percentile95(cpuPercentSamples) * 100) / 100,
769
+ cpuPercentPeak: Math.round(Math.max(0, ...cpuPercentSamples) * 100) / 100,
770
+ rssBytesPeak: Math.round(rssBytesPeak)
771
+ };
772
+ });
372
773
  const unownedLiveDeskPids = [...new Set(
373
774
  normalizedSamples.flatMap(sample =>
374
775
  (Array.isArray(sample.unownedLiveDeskPids) ? sample.unownedLiveDeskPids : [])
@@ -377,12 +778,28 @@ export function summarizeMacDiagnosticResourceSamples(
377
778
  )
378
779
  )].sort((left, right) => left - right);
379
780
  return {
781
+ ownershipModel: normalizedSamples.length > 0
782
+ && normalizedSamples.every(sample =>
783
+ sample.ownershipModel === MAC_PHYSICAL_RESOURCE_OWNERSHIP_MODEL
784
+ )
785
+ ? MAC_PHYSICAL_RESOURCE_OWNERSHIP_MODEL
786
+ : '',
380
787
  sampleCount: normalizedSamples.length,
381
788
  totalLiveDeskProcessCount: totalLiveDeskProcessCountSamples.at(-1) || 0,
382
789
  totalLiveDeskProcessCountPeak: Math.max(0, ...totalLiveDeskProcessCountSamples),
383
790
  captureHelperCountPeak: Math.max(0, ...captureHelperCountSamples),
791
+ ownedCpuPercentSamples,
792
+ ownedCpuPercentP95: Math.round(percentile95(ownedCpuPercentSamples) * 100) / 100,
793
+ ownedCpuPercentPeak: Math.round(Math.max(0, ...ownedCpuPercentSamples) * 100) / 100,
794
+ ownedRssBytesSamples,
795
+ ownedRssBytesPeak: Math.max(0, ...ownedRssBytesSamples),
796
+ ownedProcessCountSamples,
797
+ ownedProcessCountPeak: Math.max(0, ...ownedProcessCountSamples),
798
+ ownedChildProcessCountSamples,
799
+ ownedChildProcessCountPeak: Math.max(0, ...ownedChildProcessCountSamples),
384
800
  unownedLiveDeskPids,
385
- processes
801
+ processes,
802
+ roles
386
803
  };
387
804
  }
388
805
 
@@ -494,7 +911,19 @@ function check(id, ok, detail) {
494
911
  export function classifyMacPhysicalGate(snapshot = {}) {
495
912
  const schemaVersion = Math.floor(finite(snapshot.schemaVersion));
496
913
  const requestedFps = Math.max(1, finite(snapshot.requestedFps, 30));
497
- const minimumFps = requestedFps * 0.8;
914
+ const motion = snapshot.motion || {};
915
+ const activeMotionProfileMatches = motion.required === true
916
+ && motion.kind === 'active'
917
+ && finite(motion.width) === MAC_ACTIVE_MOTION_RESOURCE_BUDGET.width
918
+ && finite(motion.height) === MAC_ACTIVE_MOTION_RESOURCE_BUDGET.height
919
+ && requestedFps === MAC_ACTIVE_MOTION_RESOURCE_BUDGET.fps
920
+ && finite(motion.minimumCaptureFps) === MAC_ACTIVE_MOTION_RESOURCE_BUDGET.minimumFps
921
+ && motion.idleCadenceWaiverAllowed === false;
922
+ const activeMotionOperatorConfirmed = motion.operatorConfirmed === true
923
+ && motion.operatorRequirement === 'continuous-visible-motion-on-each-tested-display';
924
+ const minimumFps = requestedFps === MAC_ACTIVE_MOTION_RESOURCE_BUDGET.fps
925
+ ? MAC_ACTIVE_MOTION_RESOURCE_BUDGET.minimumFps
926
+ : requestedFps * 0.8;
498
927
  const helper = snapshot.helper || {};
499
928
  const monitor = snapshot.monitor || {};
500
929
  const modifier = snapshot.modifier || {};
@@ -518,6 +947,8 @@ export function classifyMacPhysicalGate(snapshot = {}) {
518
947
  && typeof captureStages[name] === 'object'
519
948
  && captureStages[name].telemetry
520
949
  && typeof captureStages[name].telemetry === 'object'
950
+ && captureStages[name].motionProof
951
+ && typeof captureStages[name].motionProof === 'object'
521
952
  && Number.isFinite(Number(captureStages[name].deliveredFps))
522
953
  );
523
954
  const stageEvaluations = requiredStageNames.map(name => {
@@ -529,9 +960,74 @@ export function classifyMacPhysicalGate(snapshot = {}) {
529
960
  const deliveredFps = Math.max(0, finite(stage.deliveredFps));
530
961
  const idleSkipped = Math.max(0, finite(stageTelemetry.idleSkipped));
531
962
  const backpressureSkipped = Math.max(0, finite(stageTelemetry.backpressureSkipped));
532
- // One incidental idle skip must never mask a 3-4 fps regression. A static
533
- // screen is treated as intentionally idle only when most of a current
534
- // one-second capture window was suppressed and keepalive output remained.
963
+ const encodeFramesInFlight = finite(stageTelemetry.encodeFramesInFlight, -1);
964
+ const pendingEncodeFrameCount = finite(stageTelemetry.pendingEncodeFrameCount, -1);
965
+ const encodeTimestampOwnerCount = finite(
966
+ stageTelemetry.encodeTimestampOwnerCount,
967
+ -1
968
+ );
969
+ const encodeTimestampOwnerHighWatermark = finite(
970
+ stageTelemetry.encodeTimestampOwnerHighWatermark,
971
+ -1
972
+ );
973
+ const videoToolboxActiveSessionCount = finite(
974
+ stageTelemetry.videoToolboxActiveSessionCount,
975
+ -1
976
+ );
977
+ const videoToolboxSessionHighWatermark = finite(
978
+ stageTelemetry.videoToolboxSessionHighWatermark,
979
+ -1
980
+ );
981
+ const outputOldestAgeMs = finite(stageTelemetry.outputOldestAgeMs, -1);
982
+ const outputDropped = finite(stageTelemetry.outputDropped, -1);
983
+ const nativeAndPipeResidenceAgeMs = finite(
984
+ stageTelemetry.nativeAndPipeResidenceAgeMs,
985
+ -1
986
+ );
987
+ const handoffDropped = finite(stageTelemetry.handoffDropped, -1);
988
+ const handoffRecoveryRequests = finite(
989
+ stageTelemetry.handoffRecoveryRequests,
990
+ -1
991
+ );
992
+ const handoffOldestAgeMs = finite(stageTelemetry.handoffOldestAgeMs, -1);
993
+ const annexBAllocations = finite(stageTelemetry.annexBAllocations, -1);
994
+ const annexBPayloadCopies = finite(stageTelemetry.annexBPayloadCopies, -1);
995
+ const annexBPayloadCopiedBytes = finite(
996
+ stageTelemetry.annexBPayloadCopiedBytes,
997
+ -1
998
+ );
999
+ const pipePayloadAllocations = finite(stageTelemetry.pipePayloadAllocations, -1);
1000
+ const pipePayloadCopies = finite(stageTelemetry.pipePayloadCopies, -1);
1001
+ const pipePayloadCopiedBytes = finite(stageTelemetry.pipePayloadCopiedBytes, -1);
1002
+ const motionProof = stage.motionProof || {};
1003
+ const motionProven = motionProof.method
1004
+ === 'decoded-pixels-native-counters-and-bounded-access-unit-hashes'
1005
+ && motionProof.operatorRequirement === 'continuous-visible-motion-on-each-tested-display'
1006
+ && motionProof.detected === true
1007
+ && finite(motionProof.observationMs)
1008
+ >= MAC_ACTIVE_MOTION_RESOURCE_BUDGET.minimumMotionObservationMs
1009
+ && finite(motionProof.frameCount)
1010
+ >= MAC_ACTIVE_MOTION_RESOURCE_BUDGET.minimumMotionFrameCount
1011
+ && finite(motionProof.sampledAccessUnitCount)
1012
+ <= MAC_ACTIVE_MOTION_RESOURCE_BUDGET.maximumMotionHashSamples
1013
+ && finite(motionProof.distinctAccessUnitHashCount)
1014
+ >= MAC_ACTIVE_MOTION_RESOURCE_BUDGET.minimumDistinctAccessUnits
1015
+ && finite(motionProof.distinctTelemetrySnapshotCount) >= 2
1016
+ && finite(motionProof.captureSampleDelta)
1017
+ >= MAC_ACTIVE_MOTION_RESOURCE_BUDGET.minimumNativeCounterDelta
1018
+ && finite(motionProof.compressionOutputDelta)
1019
+ >= MAC_ACTIVE_MOTION_RESOURCE_BUDGET.minimumNativeCounterDelta
1020
+ && finite(motionProof.stdoutWriteDelta)
1021
+ >= MAC_ACTIVE_MOTION_RESOURCE_BUDGET.minimumNativeCounterDelta
1022
+ && finite(motionProof.idleSkippedObserved)
1023
+ <= finite(motionProof.maximumAllowedIdleSkipped, -1)
1024
+ && finite(motionProof.backpressureSkippedObserved) === 0
1025
+ && finite(motionProof.decodedPixelHashCount)
1026
+ >= MAC_ACTIVE_MOTION_RESOURCE_BUDGET.minimumDistinctDecodedFrames
1027
+ && finite(motionProof.distinctDecodedPixelHashCount)
1028
+ >= MAC_ACTIVE_MOTION_RESOURCE_BUDGET.minimumDistinctDecodedFrames;
1029
+ // This is the active-motion physical gate. Idle throttling remains a valid
1030
+ // runtime optimization, but it cannot waive any active cadence floor.
535
1031
  const idleDominant = captureFps >= minimumFps
536
1032
  && backpressureSkipped === 0
537
1033
  && idleSkipped >= Math.max(10, captureFps * 0.6);
@@ -544,18 +1040,65 @@ export function classifyMacPhysicalGate(snapshot = {}) {
544
1040
  deliveredFps,
545
1041
  idleSkipped,
546
1042
  backpressureSkipped,
1043
+ encodeFramesInFlight,
1044
+ pendingEncodeFrameCount,
1045
+ encodeTimestampOwnerCount,
1046
+ encodeTimestampOwnerHighWatermark,
1047
+ videoToolboxActiveSessionCount,
1048
+ videoToolboxSessionHighWatermark,
1049
+ outputOldestAgeMs,
1050
+ outputDropped,
1051
+ nativeAndPipeResidenceAgeMs,
1052
+ handoffDropped,
1053
+ handoffRecoveryRequests,
1054
+ handoffOldestAgeMs,
1055
+ annexBAllocations,
1056
+ annexBPayloadCopies,
1057
+ annexBPayloadCopiedBytes,
1058
+ pipePayloadAllocations,
1059
+ pipePayloadCopies,
1060
+ pipePayloadCopiedBytes,
547
1061
  idleDominant,
548
- telemetryCurrent: finite(stageTelemetry.version) >= 2
1062
+ motionProof,
1063
+ motionProven,
1064
+ telemetryCurrent: finite(stageTelemetry.version) >= 5
549
1065
  && Number.isFinite(Number(stageTelemetry.ageMs))
550
1066
  && finite(stageTelemetry.ageMs) <= 3_000,
551
1067
  hardwareAccelerated: stageTelemetry.hardwareAccelerated === true,
1068
+ nativeEncodeOwnershipBounded: encodeFramesInFlight >= 0
1069
+ && encodeFramesInFlight <= 2
1070
+ && pendingEncodeFrameCount >= 0
1071
+ && pendingEncodeFrameCount <= 1
1072
+ && encodeTimestampOwnerCount >= 0
1073
+ && encodeTimestampOwnerCount <= 2
1074
+ && encodeTimestampOwnerHighWatermark >= encodeTimestampOwnerCount
1075
+ && encodeTimestampOwnerHighWatermark <= 2
1076
+ && videoToolboxActiveSessionCount === 1
1077
+ && videoToolboxSessionHighWatermark === 1,
552
1078
  captureAtTarget: captureFps >= minimumFps,
553
- compressionKeepsUp: (idleDominant && compressionFps >= 1)
554
- || (captureFps > 0 && compressionFps >= captureFps * 0.8),
555
- stdoutKeepsUp: (idleDominant && stdoutFps >= 1)
556
- || (compressionFps > 0 && stdoutFps >= compressionFps * 0.8),
557
- deliveredAtTarget: (idleDominant && deliveredFps >= 1)
558
- || deliveredFps >= minimumFps
1079
+ compressionKeepsUp: compressionFps >= minimumFps,
1080
+ stdoutKeepsUp: stdoutFps >= minimumFps,
1081
+ deliveredAtTarget: deliveredFps >= minimumFps,
1082
+ backpressureFree: backpressureSkipped === 0,
1083
+ currentFrameQueuesBounded:
1084
+ outputOldestAgeMs >= 0
1085
+ && outputOldestAgeMs <= 120
1086
+ && outputDropped === 0
1087
+ && stageTelemetry.outputAwaitingKeyFrame === false
1088
+ && nativeAndPipeResidenceAgeMs >= 0
1089
+ && nativeAndPipeResidenceAgeMs <= 250
1090
+ && handoffDropped === 0
1091
+ && handoffRecoveryRequests === 0
1092
+ && handoffOldestAgeMs >= 0
1093
+ && handoffOldestAgeMs <= 120,
1094
+ payloadCopyAccountingCurrent:
1095
+ stageTelemetry.copyTelemetryAvailable === true
1096
+ && annexBAllocations > 0
1097
+ && annexBPayloadCopies >= annexBAllocations
1098
+ && annexBPayloadCopiedBytes > 0
1099
+ && pipePayloadAllocations > 0
1100
+ && pipePayloadCopies >= pipePayloadAllocations
1101
+ && pipePayloadCopiedBytes > 0
559
1102
  };
560
1103
  });
561
1104
  const stageMetricDetail = metric => stageEvaluations
@@ -570,44 +1113,198 @@ export function classifyMacPhysicalGate(snapshot = {}) {
570
1113
  && stageEvaluations.every(stage => stage.stdoutKeepsUp);
571
1114
  const deliveredAtTarget = stageEvidenceAvailable
572
1115
  && stageEvaluations.every(stage => stage.deliveredAtTarget);
1116
+ const backpressureFree = stageEvidenceAvailable
1117
+ && stageEvaluations.every(stage => stage.backpressureFree);
1118
+ const currentFrameQueuesBounded = stageEvidenceAvailable
1119
+ && stageEvaluations.every(stage => stage.currentFrameQueuesBounded);
1120
+ const payloadCopyAccountingCurrent = stageEvidenceAvailable
1121
+ && stageEvaluations.every(stage => stage.payloadCopyAccountingCurrent);
1122
+ const activeMotionProven = stageEvidenceAvailable
1123
+ && stageEvaluations.every(stage => stage.motionProven);
1124
+ const nativeEncodeOwnershipBounded = stageEvidenceAvailable
1125
+ && stageEvaluations.every(stage => stage.nativeEncodeOwnershipBounded);
573
1126
  const helperPids = Array.isArray(helper.observedPids)
574
1127
  ? helper.observedPids.map(Number).filter(pid => Number.isInteger(pid) && pid > 1)
575
1128
  : [];
576
1129
  const resources = snapshot.resources || {};
577
1130
  const baselineResources = resources.baseline || {};
1131
+ const startupPrimeResources = resources.startupPrime || {};
578
1132
  const steadyResources = resources.steadyCapture || {};
579
1133
  const cleanupResources = resources.afterCleanup || {};
580
1134
  const steadyProcesses = Array.isArray(steadyResources.processes)
581
1135
  ? steadyResources.processes
582
1136
  : [];
583
1137
  const sampledSteadyProcesses = steadyProcesses
584
- .filter(processInfo => finite(processInfo?.sampleCount) > 0);
1138
+ .filter(processInfo =>
1139
+ finite(processInfo?.sampleCount) > 0
1140
+ && processInfo?.productProcess === true
1141
+ );
585
1142
  const sampledSteadyRoles = new Set(
586
1143
  sampledSteadyProcesses.map(processInfo => String(processInfo?.role || ''))
587
1144
  );
1145
+ const expectedRoleOwnership = new Map([
1146
+ ['hub-launcher', ['hub', 'exact-root']],
1147
+ ['hub-runtime', ['hub', 'exact-descendant']],
1148
+ ['client-launcher', ['client', 'exact-root']],
1149
+ ['remote-fast', ['client', 'exact-descendant']],
1150
+ ['capture-helper', ['client', 'exact-descendant']]
1151
+ ]);
1152
+ const resourceOwnershipLabelsCurrent = sampledSteadyProcesses.every(processInfo => {
1153
+ const expected = expectedRoleOwnership.get(String(processInfo?.role || ''));
1154
+ if (!expected) {
1155
+ return ['hub', 'client'].includes(String(processInfo?.ownerRole || ''))
1156
+ && String(processInfo?.ownership || '') === 'exact-descendant';
1157
+ }
1158
+ return String(processInfo?.ownerRole || '') === expected[0]
1159
+ && String(processInfo?.ownership || '') === expected[1];
1160
+ });
588
1161
  const resourceTelemetryAvailable = finite(baselineResources.sampleCount) > 0
1162
+ && finite(startupPrimeResources.sampleCount) > 0
589
1163
  && finite(steadyResources.sampleCount) > 0
590
1164
  && finite(cleanupResources.sampleCount) > 0;
591
- const resourceOwnershipCurrent = finite(baselineResources.totalLiveDeskProcessCountPeak) === 0
592
- && sampledSteadyRoles.has('hub')
593
- && sampledSteadyRoles.has('remote-fast')
594
- && sampledSteadyRoles.has('capture-helper')
1165
+ const steadyOwnedProcessCounts = Array.isArray(steadyResources.ownedProcessCountSamples)
1166
+ ? steadyResources.ownedProcessCountSamples.map(value => Math.max(0, Math.round(finite(value))))
1167
+ : [];
1168
+ const steadyOwnedChildCounts = Array.isArray(steadyResources.ownedChildProcessCountSamples)
1169
+ ? steadyResources.ownedChildProcessCountSamples
1170
+ .map(value => Math.max(0, Math.round(finite(value))))
1171
+ : [];
1172
+ const exactSteadyTopologyCurrent =
1173
+ steadyOwnedProcessCounts.length >= MAC_ACTIVE_MOTION_RESOURCE_BUDGET.minimumSteadySamples
1174
+ && steadyOwnedChildCounts.length === steadyOwnedProcessCounts.length
1175
+ && steadyOwnedProcessCounts.every(
1176
+ count => count === MAC_ACTIVE_MOTION_RESOURCE_BUDGET.ownedProcessCountPeakMax
1177
+ )
1178
+ && steadyOwnedChildCounts.every(
1179
+ count => count === MAC_ACTIVE_MOTION_RESOURCE_BUDGET.ownedChildProcessCountPeakMax
1180
+ );
1181
+ const resourceOwnershipCurrent =
1182
+ resources.ownershipModel === MAC_PHYSICAL_RESOURCE_OWNERSHIP_MODEL
1183
+ && baselineResources.ownershipModel === MAC_PHYSICAL_RESOURCE_OWNERSHIP_MODEL
1184
+ && startupPrimeResources.ownershipModel === MAC_PHYSICAL_RESOURCE_OWNERSHIP_MODEL
1185
+ && steadyResources.ownershipModel === MAC_PHYSICAL_RESOURCE_OWNERSHIP_MODEL
1186
+ && cleanupResources.ownershipModel === MAC_PHYSICAL_RESOURCE_OWNERSHIP_MODEL
1187
+ && finite(baselineResources.totalLiveDeskProcessCountPeak) === 0
1188
+ && exactSteadyTopologyCurrent
1189
+ && [...MAC_PRODUCT_RESOURCE_ROLES].every(role => sampledSteadyRoles.has(role))
595
1190
  && sampledSteadyProcesses.every(processInfo => processInfo?.ownedByDiagnostic === true)
1191
+ && resourceOwnershipLabelsCurrent
596
1192
  && (!Array.isArray(steadyResources.unownedLiveDeskPids)
597
1193
  || steadyResources.unownedLiveDeskPids.length === 0);
598
1194
  const resourceDrained = finite(cleanupResources.totalLiveDeskProcessCount) === 0
599
1195
  && finite(cleanupResources.totalLiveDeskProcessCountPeak) === 0
1196
+ && finite(cleanupResources.ownedProcessCountPeak) === 0
1197
+ && finite(cleanupResources.ownedChildProcessCountPeak) === 0
1198
+ && finite(cleanupResources.ownedRssBytesPeak) === 0
600
1199
  && (!Array.isArray(cleanupResources.processes) || cleanupResources.processes.length === 0)
601
1200
  && (!Array.isArray(cleanupResources.unownedLiveDeskPids)
602
1201
  || cleanupResources.unownedLiveDeskPids.length === 0);
1202
+ const resourceReturnedToBaseline = finite(cleanupResources.ownedProcessCountPeak)
1203
+ <= finite(baselineResources.ownedProcessCountPeak)
1204
+ && finite(cleanupResources.ownedChildProcessCountPeak)
1205
+ <= finite(baselineResources.ownedChildProcessCountPeak)
1206
+ && finite(cleanupResources.ownedRssBytesPeak)
1207
+ <= finite(baselineResources.ownedRssBytesPeak);
603
1208
  const resourceHelperPeak = Math.max(
604
1209
  finite(helper.maxConcurrent),
605
1210
  finite(steadyResources.captureHelperCountPeak)
606
1211
  );
1212
+ const resourceBudget = MAC_ACTIVE_MOTION_RESOURCE_BUDGET;
1213
+ const startupPrimeBudget = MAC_STARTUP_PRIME_RESOURCE_BUDGET;
1214
+ const startupPrimeObserved = {
1215
+ samples: Math.max(0, Math.round(finite(startupPrimeResources.sampleCount))),
1216
+ durationMs: Math.max(0, finite(startupPrimeResources.durationMs, -1)),
1217
+ ownedCpuPercentP95: Math.max(0, finite(startupPrimeResources.ownedCpuPercentP95)),
1218
+ ownedCpuPercentPeak: Math.max(0, finite(startupPrimeResources.ownedCpuPercentPeak)),
1219
+ ownedRssBytesPeak: Math.max(
1220
+ 0,
1221
+ Math.round(finite(startupPrimeResources.ownedRssBytesPeak))
1222
+ ),
1223
+ ownedProcessCountPeak: Math.max(
1224
+ 0,
1225
+ Math.round(finite(startupPrimeResources.ownedProcessCountPeak))
1226
+ ),
1227
+ ownedChildProcessCountPeak: Math.max(
1228
+ 0,
1229
+ Math.round(finite(startupPrimeResources.ownedChildProcessCountPeak))
1230
+ ),
1231
+ captureHelperCountPeak: Math.max(
1232
+ 0,
1233
+ Math.round(finite(startupPrimeResources.captureHelperCountPeak))
1234
+ )
1235
+ };
1236
+ const startupPrimeWithinBudget =
1237
+ startupPrimeObserved.samples >= startupPrimeBudget.minimumSamples
1238
+ && startupPrimeObserved.durationMs > 0
1239
+ && startupPrimeObserved.durationMs <= startupPrimeBudget.maximumDurationMs
1240
+ && startupPrimeObserved.ownedCpuPercentP95
1241
+ <= startupPrimeBudget.ownedCpuPercentP95Max
1242
+ && startupPrimeObserved.ownedCpuPercentPeak
1243
+ <= startupPrimeBudget.ownedCpuPercentPeakMax
1244
+ && startupPrimeObserved.ownedRssBytesPeak
1245
+ <= startupPrimeBudget.ownedRssBytesPeakMax
1246
+ && startupPrimeObserved.ownedProcessCountPeak
1247
+ <= startupPrimeBudget.ownedProcessCountPeakMax
1248
+ && startupPrimeObserved.ownedChildProcessCountPeak
1249
+ <= startupPrimeBudget.ownedChildProcessCountPeakMax
1250
+ && startupPrimeObserved.captureHelperCountPeak
1251
+ <= startupPrimeBudget.captureHelperCountPeakMax
1252
+ && (!Array.isArray(startupPrimeResources.unownedLiveDeskPids)
1253
+ || startupPrimeResources.unownedLiveDeskPids.length === 0);
1254
+ const resourceRoleObservations = Object.fromEntries(
1255
+ (Array.isArray(steadyResources.roles) ? steadyResources.roles : [])
1256
+ .map(roleInfo => [String(roleInfo?.role || ''), roleInfo])
1257
+ .filter(([role]) => MAC_PRODUCT_RESOURCE_ROLES.has(role))
1258
+ );
1259
+ const resourceRoleChecks = Object.entries(resourceBudget.roleBudgets).map(([role, roleBudget]) => {
1260
+ const observed = resourceRoleObservations[role] || {};
1261
+ return {
1262
+ role,
1263
+ available: finite(observed.sampleCount) >= resourceBudget.minimumSteadySamples,
1264
+ processCountPeak: Math.max(0, Math.round(finite(observed.processCountPeak))),
1265
+ cpuPercentP95: Math.max(0, finite(observed.cpuPercentP95)),
1266
+ cpuPercentPeak: Math.max(0, finite(observed.cpuPercentPeak)),
1267
+ rssBytesPeak: Math.max(0, Math.round(finite(observed.rssBytesPeak))),
1268
+ budget: roleBudget
1269
+ };
1270
+ });
1271
+ const resourceRolesWithinBudget = resourceRoleChecks.every(roleInfo =>
1272
+ roleInfo.available
1273
+ && roleInfo.processCountPeak <= roleInfo.budget.processCountPeakMax
1274
+ && roleInfo.cpuPercentP95 <= roleInfo.budget.cpuPercentP95Max
1275
+ && roleInfo.cpuPercentPeak <= roleInfo.budget.cpuPercentPeakMax
1276
+ && roleInfo.rssBytesPeak <= roleInfo.budget.rssBytesPeakMax
1277
+ );
1278
+ const resourceBudgetObserved = {
1279
+ steadySamples: Math.max(0, Math.round(finite(steadyResources.sampleCount))),
1280
+ ownedCpuPercentP95: Math.max(0, finite(steadyResources.ownedCpuPercentP95)),
1281
+ ownedCpuPercentPeak: Math.max(0, finite(steadyResources.ownedCpuPercentPeak)),
1282
+ ownedRssBytesPeak: Math.max(0, Math.round(finite(steadyResources.ownedRssBytesPeak))),
1283
+ ownedProcessCountPeak: Math.max(0, Math.round(finite(steadyResources.ownedProcessCountPeak))),
1284
+ ownedChildProcessCountPeak: Math.max(
1285
+ 0,
1286
+ Math.round(finite(steadyResources.ownedChildProcessCountPeak))
1287
+ ),
1288
+ captureHelperCountPeak: Math.max(0, Math.round(resourceHelperPeak))
1289
+ };
1290
+ const resourceWithinBudget = resourceBudgetObserved.steadySamples
1291
+ >= resourceBudget.minimumSteadySamples
1292
+ && resourceBudgetObserved.ownedCpuPercentP95 <= resourceBudget.ownedCpuPercentP95Max
1293
+ && resourceBudgetObserved.ownedCpuPercentPeak <= resourceBudget.ownedCpuPercentPeakMax
1294
+ && resourceBudgetObserved.ownedRssBytesPeak <= resourceBudget.ownedRssBytesPeakMax
1295
+ && resourceBudgetObserved.ownedProcessCountPeak <= resourceBudget.ownedProcessCountPeakMax
1296
+ && resourceBudgetObserved.ownedChildProcessCountPeak
1297
+ <= resourceBudget.ownedChildProcessCountPeakMax
1298
+ && resourceBudgetObserved.captureHelperCountPeak
1299
+ <= resourceBudget.captureHelperCountPeakMax
1300
+ && resourceRolesWithinBudget;
607
1301
  const highCpuProcesses = steadyProcesses
608
1302
  .filter(processInfo =>
609
- finite(processInfo?.cpuPercentP95) >= 80
610
- || finite(processInfo?.cpuPercentPeak) >= 100
1303
+ processInfo?.productProcess === true
1304
+ && (
1305
+ finite(processInfo?.cpuPercentP95) >= 80
1306
+ || finite(processInfo?.cpuPercentPeak) >= 100
1307
+ )
611
1308
  )
612
1309
  .map(processInfo => ({
613
1310
  role: String(processInfo?.role || ''),
@@ -657,12 +1354,88 @@ export function classifyMacPhysicalGate(snapshot = {}) {
657
1354
  const lifecycleRegistryFresh = Number.isFinite(lifecycleCompletedAtMs)
658
1355
  && Number.isFinite(registrySampledAtMs)
659
1356
  && registrySampledAtMs >= lifecycleCompletedAtMs;
1357
+ const videoToolboxSessionMaxConcurrent = finite(
1358
+ helper.videoToolboxSessionMaxConcurrent,
1359
+ -1
1360
+ );
1361
+ const videoToolboxSessionFinalCount = finite(
1362
+ helper.videoToolboxSessionFinalCount,
1363
+ -1
1364
+ );
1365
+ const nativeTerminalLifecycle = helper.nativeTerminalLifecycle || {};
1366
+ const finalHelperIdentity = lifecycle.finalHelperIdentity || {};
1367
+ const finalHelperStartedAtMs = Date.parse(String(finalHelperIdentity.startedAt || ''));
1368
+ const finalHelperObservedAtMs = Date.parse(String(finalHelperIdentity.observedAt || ''));
1369
+ const finalStopRequestedAtMs = Date.parse(String(lifecycle.finalStopRequestedAt || ''));
1370
+ const finalStopAcknowledgedAtMs = Date.parse(String(lifecycle.finalStopAcknowledgedAt || ''));
1371
+ const terminalObservedAtMs = Date.parse(
1372
+ String(nativeTerminalLifecycle.terminalObservedAt || '')
1373
+ );
1374
+ const nativeTerminalOwnerBound =
1375
+ Number.isInteger(Number(finalHelperIdentity.observationId))
1376
+ && finite(finalHelperIdentity.observationId) > 0
1377
+ && Number.isInteger(Number(finalHelperIdentity.processId))
1378
+ && finite(finalHelperIdentity.processId) > 1
1379
+ && Number.isInteger(Number(finalHelperIdentity.osProcessId))
1380
+ && finite(finalHelperIdentity.osProcessId) > 1
1381
+ && finite(finalHelperIdentity.osProcessId) === finite(finalHelperIdentity.processId)
1382
+ && helperPids.includes(finite(finalHelperIdentity.osProcessId))
1383
+ && String(finalHelperIdentity.profileName || '').startsWith('macos-screencapturekit')
1384
+ && Number.isFinite(finalHelperStartedAtMs)
1385
+ && Number.isFinite(finalHelperObservedAtMs)
1386
+ && finalHelperObservedAtMs >= finalHelperStartedAtMs
1387
+ && String(finalHelperIdentity.streamPurpose || '') === 'wall'
1388
+ && finite(finalHelperIdentity.captureGeneration)
1389
+ === finite(lifecycle.restoredWallGeneration)
1390
+ && String(finalHelperIdentity.streamId || '').length > 0
1391
+ && String(finalHelperIdentity.streamId || '')
1392
+ === String(lifecycle.finalStopStreamId || '')
1393
+ && finite(nativeTerminalLifecycle.observationId)
1394
+ === finite(finalHelperIdentity.observationId)
1395
+ && finite(nativeTerminalLifecycle.processId)
1396
+ === finite(finalHelperIdentity.processId)
1397
+ && String(nativeTerminalLifecycle.profileName || '')
1398
+ === String(finalHelperIdentity.profileName || '')
1399
+ && Date.parse(String(nativeTerminalLifecycle.startedAt || '')) === finalHelperStartedAtMs
1400
+ && Number.isFinite(finalStopRequestedAtMs)
1401
+ && Number.isFinite(finalStopAcknowledgedAtMs)
1402
+ && finalStopRequestedAtMs >= finalHelperObservedAtMs
1403
+ && finalStopAcknowledgedAtMs >= finalStopRequestedAtMs
1404
+ && lifecycleCompletedAtMs >= finalStopAcknowledgedAtMs
1405
+ && Number.isFinite(terminalObservedAtMs)
1406
+ && terminalObservedAtMs >= finalStopRequestedAtMs
1407
+ && terminalObservedAtMs <= lifecycleCompletedAtMs
1408
+ && registrySampledAtMs >= terminalObservedAtMs;
1409
+ const nativeTerminalClean = nativeTerminalLifecycle.terminalTelemetryAvailable === true
1410
+ && nativeTerminalLifecycle.terminalClean === true
1411
+ && finite(nativeTerminalLifecycle.terminalTelemetryVersion) >= 5
1412
+ && finite(nativeTerminalLifecycle.terminalEncodeFramesInFlight, -1) === 0
1413
+ && finite(nativeTerminalLifecycle.terminalPendingEncodeFrameCount, -1) === 0
1414
+ && finite(nativeTerminalLifecycle.terminalEncodeTimestampOwnerCount, -1) === 0
1415
+ && finite(nativeTerminalLifecycle.terminalEncodeTimestampOwnerHighWatermark, -1) >= 1
1416
+ && finite(nativeTerminalLifecycle.terminalEncodeTimestampOwnerHighWatermark, -1) <= 2
1417
+ && finite(nativeTerminalLifecycle.terminalVideoToolboxActiveSessionCount, -1) === 0
1418
+ && finite(nativeTerminalLifecycle.terminalVideoToolboxSessionHighWatermark, -1) === 1
1419
+ && Number.isFinite(Date.parse(String(nativeTerminalLifecycle.terminalObservedAt || '')));
660
1420
  const checks = [
661
1421
  check(
662
1422
  'report-schema',
663
- schemaVersion === 3,
1423
+ schemaVersion === 4,
664
1424
  `v${schemaVersion || 'missing'}`
665
1425
  ),
1426
+ check(
1427
+ 'active-motion-profile',
1428
+ activeMotionProfileMatches,
1429
+ `${finite(motion.width)}x${finite(motion.height)}@${requestedFps} `
1430
+ + `motion=${String(motion.kind || '-')} waiver=${motion.idleCadenceWaiverAllowed === true}`
1431
+ ),
1432
+ check(
1433
+ 'active-motion-operator',
1434
+ activeMotionOperatorConfirmed,
1435
+ activeMotionOperatorConfirmed
1436
+ ? motion.operatorRequirement
1437
+ : 'pass --confirm-active-motion after starting continuous motion on both displays'
1438
+ ),
666
1439
  check(
667
1440
  'diagnostic-run',
668
1441
  !runError,
@@ -712,6 +1485,13 @@ export function classifyMacPhysicalGate(snapshot = {}) {
712
1485
  + `max=${finite(lifecycle.registryMaxConcurrent, -1)} `
713
1486
  + `final=${finite(lifecycle.registryFinalHelperCount, -1)}`
714
1487
  ),
1488
+ check(
1489
+ 'capture-lifecycle-sequential-restarts',
1490
+ finite(lifecycle.registryHelperStartCount, -1) === 4
1491
+ && finite(lifecycle.registryHelperStopCount, -1) === 4,
1492
+ `starts=${finite(lifecycle.registryHelperStartCount, -1)} `
1493
+ + `stops=${finite(lifecycle.registryHelperStopCount, -1)} expected=4/4`
1494
+ ),
715
1495
  check(
716
1496
  'capture-lifecycle-stop',
717
1497
  lifecycle.controlStopConfirmed === true
@@ -734,6 +1514,16 @@ export function classifyMacPhysicalGate(snapshot = {}) {
734
1514
  stageEvidenceAvailable,
735
1515
  stageEvidenceAvailable ? requiredStageNames.join(',') : 'initial and switched stages required'
736
1516
  ),
1517
+ check(
1518
+ 'active-motion-proof',
1519
+ activeMotionProven,
1520
+ stageEvaluations.map(stage =>
1521
+ `${stage.name}=${stage.motionProven ? 'proven' : 'unproven'} `
1522
+ + `pixels=${finite(stage.motionProof.distinctDecodedPixelHashCount)} `
1523
+ + `au=${finite(stage.motionProof.distinctAccessUnitHashCount)} `
1524
+ + `native=${finite(stage.motionProof.captureSampleDelta)}`
1525
+ ).join(' ')
1526
+ ),
737
1527
  check(
738
1528
  'native-profile',
739
1529
  snapshot.platformProfile === 'macos-screencapturekit-nv12-videotoolbox',
@@ -760,6 +1550,18 @@ export function classifyMacPhysicalGate(snapshot = {}) {
760
1550
  .map(stage => `${stage.name}=${stage.hardwareAccelerated ? 'hardware' : 'not-confirmed'}`)
761
1551
  .join(' ')
762
1552
  ),
1553
+ check(
1554
+ 'native-encode-ownership',
1555
+ nativeEncodeOwnershipBounded,
1556
+ stageEvaluations.map(stage =>
1557
+ `${stage.name}=inFlight:${stage.encodeFramesInFlight}`
1558
+ + `/pending:${stage.pendingEncodeFrameCount}`
1559
+ + `/timestampOwners:${stage.encodeTimestampOwnerCount}`
1560
+ + `/timestampOwnerHigh:${stage.encodeTimestampOwnerHighWatermark}`
1561
+ + `/vt:${stage.videoToolboxActiveSessionCount}`
1562
+ + `/high:${stage.videoToolboxSessionHighWatermark}`
1563
+ ).join(' ')
1564
+ ),
763
1565
  check(
764
1566
  'screencapturekit-fps',
765
1567
  captureAtTarget,
@@ -791,7 +1593,37 @@ export function classifyMacPhysicalGate(snapshot = {}) {
791
1593
  'delivered-fps',
792
1594
  deliveredAtTarget,
793
1595
  `${stageMetricDetail('deliveredFps')} target=${requestedFps}`
794
- + `${idleOptimized ? ' idle-dominant-stage' : ''}`
1596
+ + `${idleOptimized ? ' idle-detected-active-gate' : ''}`
1597
+ ),
1598
+ check(
1599
+ 'encode-backpressure',
1600
+ backpressureFree,
1601
+ stageEvaluations
1602
+ .map(stage => `${stage.name}=${stage.backpressureSkipped}`)
1603
+ .join(' ')
1604
+ ),
1605
+ check(
1606
+ 'current-frame-queue-pressure',
1607
+ currentFrameQueuesBounded,
1608
+ stageEvaluations.map(stage =>
1609
+ `${stage.name}=nativeDrop:${stage.outputDropped}`
1610
+ + `/nativeOldest:${stage.outputOldestAgeMs}`
1611
+ + `/nativeAwait:${stage.telemetry.outputAwaitingKeyFrame === true ? 1 : 0}`
1612
+ + `/age:${stage.nativeAndPipeResidenceAgeMs}`
1613
+ + `/handoffDrop:${stage.handoffDropped}`
1614
+ + `/recovery:${stage.handoffRecoveryRequests}`
1615
+ + `/handoffOldest:${stage.handoffOldestAgeMs}`
1616
+ ).join(' ')
1617
+ ),
1618
+ check(
1619
+ 'payload-copy-accounting',
1620
+ payloadCopyAccountingCurrent,
1621
+ stageEvaluations.map(stage =>
1622
+ `${stage.name}=annexB:${stage.annexBAllocations}`
1623
+ + `/${stage.annexBPayloadCopies}/${stage.annexBPayloadCopiedBytes}`
1624
+ + ` pipe:${stage.pipePayloadAllocations}`
1625
+ + `/${stage.pipePayloadCopies}/${stage.pipePayloadCopiedBytes}`
1626
+ ).join(' ')
795
1627
  ),
796
1628
  check(
797
1629
  'single-helper',
@@ -799,17 +1631,70 @@ export function classifyMacPhysicalGate(snapshot = {}) {
799
1631
  `max=${resourceHelperPeak} pids=${helperPids.join(',') || '-'}`
800
1632
  ),
801
1633
  check('helper-drained', finite(helper.finalCount) === 0, `final=${finite(helper.finalCount)}`),
1634
+ check(
1635
+ 'single-videotoolbox-session',
1636
+ videoToolboxSessionMaxConcurrent === 1
1637
+ && videoToolboxSessionFinalCount === 0,
1638
+ `max=${videoToolboxSessionMaxConcurrent} final=${videoToolboxSessionFinalCount} `
1639
+ + 'proof=one-session-per-exact-helper'
1640
+ ),
1641
+ check(
1642
+ 'videotoolbox-terminal-cleanup',
1643
+ nativeTerminalClean,
1644
+ `available=${nativeTerminalLifecycle.terminalTelemetryAvailable === true} `
1645
+ + `clean=${nativeTerminalLifecycle.terminalClean === true} `
1646
+ + `v=${finite(nativeTerminalLifecycle.terminalTelemetryVersion)} `
1647
+ + `inFlight=${finite(nativeTerminalLifecycle.terminalEncodeFramesInFlight, -1)} `
1648
+ + `pending=${finite(nativeTerminalLifecycle.terminalPendingEncodeFrameCount, -1)} `
1649
+ + `timestampOwners=${finite(nativeTerminalLifecycle.terminalEncodeTimestampOwnerCount, -1)} `
1650
+ + `timestampOwnerHigh=${finite(nativeTerminalLifecycle.terminalEncodeTimestampOwnerHighWatermark, -1)} `
1651
+ + `vt=${finite(nativeTerminalLifecycle.terminalVideoToolboxActiveSessionCount, -1)} `
1652
+ + `high=${finite(nativeTerminalLifecycle.terminalVideoToolboxSessionHighWatermark, -1)}`
1653
+ ),
1654
+ check(
1655
+ 'videotoolbox-terminal-owner',
1656
+ nativeTerminalOwnerBound,
1657
+ `observation=${finite(finalHelperIdentity.observationId)}/${finite(nativeTerminalLifecycle.observationId)} `
1658
+ + `pid=${finite(finalHelperIdentity.processId)}/${finite(nativeTerminalLifecycle.processId)} `
1659
+ + `os=${finite(finalHelperIdentity.osProcessId)}`
1660
+ + `/${helperPids.includes(finite(finalHelperIdentity.osProcessId)) ? 'observed' : 'missing'} `
1661
+ + `profile=${String(finalHelperIdentity.profileName || '-') === String(nativeTerminalLifecycle.profileName || '-') ? 'match' : 'mismatch'} `
1662
+ + `generation=${finite(finalHelperIdentity.captureGeneration)}/${finite(lifecycle.restoredWallGeneration)} `
1663
+ + `purpose=${String(finalHelperIdentity.streamPurpose || '-')} `
1664
+ + `stream=${String(finalHelperIdentity.streamId || '') === String(lifecycle.finalStopStreamId || '') ? 'match' : 'mismatch'} `
1665
+ + `stop=${String(lifecycle.finalStopRequestedAt || '-')} `
1666
+ + `terminal=${String(nativeTerminalLifecycle.terminalObservedAt || '-')}`
1667
+ ),
802
1668
  check(
803
1669
  'process-telemetry',
804
1670
  resourceTelemetryAvailable,
805
1671
  resourceTelemetryAvailable
806
- ? `baseline=${finite(baselineResources.sampleCount)} steady=${finite(steadyResources.sampleCount)} cleanup=${finite(cleanupResources.sampleCount)}`
807
- : 'baseline, steady-capture, and after-cleanup samples are required'
1672
+ ? `baseline=${finite(baselineResources.sampleCount)} prime=${finite(startupPrimeResources.sampleCount)} steady=${finite(steadyResources.sampleCount)} cleanup=${finite(cleanupResources.sampleCount)}`
1673
+ : 'baseline, cold startup-prime, steady-capture, and after-cleanup samples are required'
1674
+ ),
1675
+ check(
1676
+ 'startup-prime-resource-budget',
1677
+ resourceTelemetryAvailable && startupPrimeWithinBudget,
1678
+ `samples=${startupPrimeObserved.samples}/${startupPrimeBudget.minimumSamples} `
1679
+ + `duration=${startupPrimeObserved.durationMs}/${startupPrimeBudget.maximumDurationMs}ms `
1680
+ + `cpu-p95=${startupPrimeObserved.ownedCpuPercentP95}`
1681
+ + `/${startupPrimeBudget.ownedCpuPercentP95Max}% `
1682
+ + `cpu-peak=${startupPrimeObserved.ownedCpuPercentPeak}`
1683
+ + `/${startupPrimeBudget.ownedCpuPercentPeakMax}% `
1684
+ + `rss=${startupPrimeObserved.ownedRssBytesPeak}`
1685
+ + `/${startupPrimeBudget.ownedRssBytesPeakMax} `
1686
+ + `processes=${startupPrimeObserved.ownedProcessCountPeak}`
1687
+ + `/${startupPrimeBudget.ownedProcessCountPeakMax} `
1688
+ + `children=${startupPrimeObserved.ownedChildProcessCountPeak}`
1689
+ + `/${startupPrimeBudget.ownedChildProcessCountPeakMax}`
808
1690
  ),
809
1691
  check(
810
1692
  'resource-ownership',
811
1693
  resourceTelemetryAvailable && resourceOwnershipCurrent,
812
- `baseline=${finite(baselineResources.totalLiveDeskProcessCountPeak)} `
1694
+ `model=${String(resources.ownershipModel || '-')} `
1695
+ + `labels=${resourceOwnershipLabelsCurrent ? 'exact' : 'invalid'} `
1696
+ + `topology=${exactSteadyTopologyCurrent ? '5/3' : `${steadyOwnedProcessCounts.join(',') || '-'}/${steadyOwnedChildCounts.join(',') || '-'}`} `
1697
+ + `baseline=${finite(baselineResources.totalLiveDeskProcessCountPeak)} `
813
1698
  + `steady=${finite(steadyResources.totalLiveDeskProcessCountPeak)} `
814
1699
  + `unowned=${Array.isArray(steadyResources.unownedLiveDeskPids) ? steadyResources.unownedLiveDeskPids.join(',') || '-' : '-'}`
815
1700
  ),
@@ -819,6 +1704,36 @@ export function classifyMacPhysicalGate(snapshot = {}) {
819
1704
  `final=${finite(cleanupResources.totalLiveDeskProcessCount)} `
820
1705
  + `peak=${finite(cleanupResources.totalLiveDeskProcessCountPeak)}`
821
1706
  ),
1707
+ check(
1708
+ 'active-motion-resource-budget',
1709
+ resourceTelemetryAvailable && resourceWithinBudget,
1710
+ `samples=${resourceBudgetObserved.steadySamples}/${resourceBudget.minimumSteadySamples} `
1711
+ + `cpu-p95=${resourceBudgetObserved.ownedCpuPercentP95}`
1712
+ + `/${resourceBudget.ownedCpuPercentP95Max}% `
1713
+ + `cpu-peak=${resourceBudgetObserved.ownedCpuPercentPeak}`
1714
+ + `/${resourceBudget.ownedCpuPercentPeakMax}% `
1715
+ + `rss=${resourceBudgetObserved.ownedRssBytesPeak}`
1716
+ + `/${resourceBudget.ownedRssBytesPeakMax} `
1717
+ + `processes=${resourceBudgetObserved.ownedProcessCountPeak}`
1718
+ + `/${resourceBudget.ownedProcessCountPeakMax} `
1719
+ + `children=${resourceBudgetObserved.ownedChildProcessCountPeak}`
1720
+ + `/${resourceBudget.ownedChildProcessCountPeakMax} `
1721
+ + resourceRoleChecks.map(roleInfo =>
1722
+ `${roleInfo.role}:cpu=${roleInfo.cpuPercentP95}/${roleInfo.budget.cpuPercentP95Max}`
1723
+ + ` rss=${roleInfo.rssBytesPeak}/${roleInfo.budget.rssBytesPeakMax}`
1724
+ + ` proc=${roleInfo.processCountPeak}/${roleInfo.budget.processCountPeakMax}`
1725
+ ).join(' ')
1726
+ ),
1727
+ check(
1728
+ 'resource-baseline-return',
1729
+ resourceTelemetryAvailable && resourceReturnedToBaseline,
1730
+ `processes=${finite(baselineResources.ownedProcessCountPeak)}`
1731
+ + `->${finite(cleanupResources.ownedProcessCountPeak)} `
1732
+ + `children=${finite(baselineResources.ownedChildProcessCountPeak)}`
1733
+ + `->${finite(cleanupResources.ownedChildProcessCountPeak)} `
1734
+ + `rss=${finite(baselineResources.ownedRssBytesPeak)}`
1735
+ + `->${finite(cleanupResources.ownedRssBytesPeak)}`
1736
+ ),
822
1737
  check('two-monitors', finite(monitor.count) >= 2, `count=${finite(monitor.count)}`),
823
1738
  check(
824
1739
  'monitor-generation',
@@ -862,6 +1777,8 @@ export function classifyMacPhysicalGate(snapshot = {}) {
862
1777
  const failed = checks.filter(item => !item.ok);
863
1778
  const bottleneckByCheck = {
864
1779
  'report-schema': 'report-schema',
1780
+ 'active-motion-profile': 'active-motion-profile',
1781
+ 'active-motion-operator': 'active-motion-operator',
865
1782
  'diagnostic-run': 'diagnostic-run',
866
1783
  'diagnostic-cleanup': 'diagnostic-cleanup',
867
1784
  'capture-lifecycle-order': 'capture-lifecycle-order',
@@ -869,22 +1786,34 @@ export function classifyMacPhysicalGate(snapshot = {}) {
869
1786
  'capture-lifecycle-pause': 'capture-pause-resume',
870
1787
  'capture-lifecycle-helper': 'capture-helper-lifecycle',
871
1788
  'capture-lifecycle-registry': 'capture-helper-registry',
1789
+ 'capture-lifecycle-sequential-restarts': 'capture-helper-restart-storm',
872
1790
  'capture-lifecycle-stop': 'capture-stop',
873
1791
  'first-frame': 'capture-startup',
874
1792
  'monitor-first-frame': 'monitor-restart',
875
1793
  'capture-stage-evidence': 'capture-stage-evidence',
1794
+ 'active-motion-proof': 'active-motion-not-proven',
876
1795
  'native-profile': 'capture-fallback',
877
1796
  'telemetry-current': 'native-telemetry-stale',
878
1797
  'hardware-videotoolbox': 'videotoolbox-software',
1798
+ 'native-encode-ownership': 'videotoolbox-ownership',
879
1799
  'screencapturekit-fps': 'screencapturekit',
880
1800
  'videotoolbox-fps': 'videotoolbox',
881
1801
  'stdout-fps': 'native-stdout',
882
1802
  'delivered-fps': 'agent-or-transport',
1803
+ 'encode-backpressure': 'videotoolbox-backpressure',
1804
+ 'current-frame-queue-pressure': 'current-frame-queue-pressure',
1805
+ 'payload-copy-accounting': 'payload-copy-accounting',
883
1806
  'single-helper': 'duplicate-helper',
884
1807
  'helper-drained': 'stale-helper',
1808
+ 'single-videotoolbox-session': 'videotoolbox-ownership',
1809
+ 'videotoolbox-terminal-cleanup': 'videotoolbox-terminal-cleanup',
1810
+ 'videotoolbox-terminal-owner': 'videotoolbox-terminal-owner',
885
1811
  'process-telemetry': 'process-telemetry',
1812
+ 'startup-prime-resource-budget': 'startup-prime-resource-budget',
886
1813
  'resource-ownership': 'resource-ownership',
887
1814
  'resource-drained': 'resource-not-drained',
1815
+ 'active-motion-resource-budget': 'resource-budget',
1816
+ 'resource-baseline-return': 'resource-not-returned',
888
1817
  'two-monitors': 'monitor-topology',
889
1818
  'monitor-generation': 'monitor-generation',
890
1819
  'monitor-native-identity': 'monitor-binding',
@@ -899,7 +1828,16 @@ export function classifyMacPhysicalGate(snapshot = {}) {
899
1828
  processTelemetry: {
900
1829
  available: resourceTelemetryAvailable,
901
1830
  highLoad: highCpuProcesses.length > 0,
902
- highLoadProcesses: highCpuProcesses
1831
+ highLoadProcesses: highCpuProcesses,
1832
+ budget: { ...resourceBudget },
1833
+ observed: resourceBudgetObserved,
1834
+ startupPrime: {
1835
+ budget: { ...startupPrimeBudget },
1836
+ observed: startupPrimeObserved,
1837
+ withinBudget: startupPrimeWithinBudget
1838
+ },
1839
+ roles: resourceRoleChecks,
1840
+ returnedToBaseline: resourceReturnedToBaseline
903
1841
  },
904
1842
  checks
905
1843
  };