@indigoai-us/hq-cloud 6.18.44-alpha.9 → 6.18.44
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/dist/bin/sync-runner-planning.d.ts +12 -0
- package/dist/bin/sync-runner-planning.d.ts.map +1 -1
- package/dist/bin/sync-runner-planning.js +18 -0
- package/dist/bin/sync-runner-planning.js.map +1 -1
- package/dist/bin/sync-runner-watch-loop.d.ts +18 -0
- package/dist/bin/sync-runner-watch-loop.d.ts.map +1 -1
- package/dist/bin/sync-runner-watch-loop.js +75 -6
- package/dist/bin/sync-runner-watch-loop.js.map +1 -1
- package/dist/bin/sync-runner.d.ts +22 -1
- package/dist/bin/sync-runner.d.ts.map +1 -1
- package/dist/bin/sync-runner.js +33 -4
- package/dist/bin/sync-runner.js.map +1 -1
- package/dist/cli/conflict.d.ts.map +1 -1
- package/dist/cli/conflict.js +2 -1
- package/dist/cli/conflict.js.map +1 -1
- package/dist/cli/rescue-core.d.ts +6 -0
- package/dist/cli/rescue-core.d.ts.map +1 -1
- package/dist/cli/rescue-core.js +10 -1
- package/dist/cli/rescue-core.js.map +1 -1
- package/dist/cli/rescue-windows-test-harness.d.ts +19 -0
- package/dist/cli/rescue-windows-test-harness.d.ts.map +1 -0
- package/dist/cli/rescue-windows-test-harness.js +443 -0
- package/dist/cli/rescue-windows-test-harness.js.map +1 -0
- package/dist/company-resolver.d.ts +22 -0
- package/dist/company-resolver.d.ts.map +1 -1
- package/dist/company-resolver.js +1 -1
- package/dist/company-resolver.js.map +1 -1
- package/dist/ignore.d.ts +4 -1
- package/dist/ignore.d.ts.map +1 -1
- package/dist/ignore.js +10 -12
- package/dist/ignore.js.map +1 -1
- package/dist/journal-snapshot-decoder.js +15 -2
- package/dist/journal-snapshot-decoder.js.map +1 -1
- package/dist/sync/durable-apply.d.ts +2 -0
- package/dist/sync/durable-apply.d.ts.map +1 -1
- package/dist/sync/durable-apply.js +9 -12
- package/dist/sync/durable-apply.js.map +1 -1
- package/dist/sync/fsync-compat.d.ts +13 -0
- package/dist/sync/fsync-compat.d.ts.map +1 -0
- package/dist/sync/fsync-compat.js +33 -0
- package/dist/sync/fsync-compat.js.map +1 -0
- package/dist/sync/multipart-uploader.d.ts.map +1 -1
- package/dist/sync/multipart-uploader.js +2 -1
- package/dist/sync/multipart-uploader.js.map +1 -1
- package/dist/sync/scope-inventory-client.d.ts.map +1 -1
- package/dist/sync/scope-inventory-client.js +7 -2
- package/dist/sync/scope-inventory-client.js.map +1 -1
- package/dist/telemetry.d.ts +31 -0
- package/dist/telemetry.d.ts.map +1 -1
- package/dist/telemetry.js +177 -17
- package/dist/telemetry.js.map +1 -1
- package/dist/usage-company-fallback.d.ts +104 -0
- package/dist/usage-company-fallback.d.ts.map +1 -0
- package/dist/usage-company-fallback.js +599 -0
- package/dist/usage-company-fallback.js.map +1 -0
- package/dist/watch-owner-lease.d.ts +3 -0
- package/dist/watch-owner-lease.d.ts.map +1 -1
- package/dist/watch-owner-lease.js +14 -3
- package/dist/watch-owner-lease.js.map +1 -1
- package/package.json +2 -2
- package/dist/active-company.test.d.ts +0 -2
- package/dist/active-company.test.d.ts.map +0 -1
- package/dist/active-company.test.js +0 -149
- package/dist/active-company.test.js.map +0 -1
- package/dist/agent-codex-instructions.test.d.ts +0 -2
- package/dist/agent-codex-instructions.test.d.ts.map +0 -1
- package/dist/agent-codex-instructions.test.js +0 -271
- package/dist/agent-codex-instructions.test.js.map +0 -1
- package/dist/backup-prune.test.d.ts +0 -2
- package/dist/backup-prune.test.d.ts.map +0 -1
- package/dist/backup-prune.test.js +0 -154
- package/dist/backup-prune.test.js.map +0 -1
- package/dist/bandwidth.test.d.ts +0 -2
- package/dist/bandwidth.test.d.ts.map +0 -1
- package/dist/bandwidth.test.js +0 -342
- package/dist/bandwidth.test.js.map +0 -1
- package/dist/best-effort-error.test.d.ts +0 -2
- package/dist/best-effort-error.test.d.ts.map +0 -1
- package/dist/best-effort-error.test.js +0 -54
- package/dist/best-effort-error.test.js.map +0 -1
- package/dist/bin/area-ledger-migration.test.d.ts +0 -2
- package/dist/bin/area-ledger-migration.test.d.ts.map +0 -1
- package/dist/bin/area-ledger-migration.test.js +0 -410
- package/dist/bin/area-ledger-migration.test.js.map +0 -1
- package/dist/bin/backup-prune-runner.test.d.ts +0 -2
- package/dist/bin/backup-prune-runner.test.d.ts.map +0 -1
- package/dist/bin/backup-prune-runner.test.js +0 -22
- package/dist/bin/backup-prune-runner.test.js.map +0 -1
- package/dist/bin/journal-repair.test.d.ts +0 -2
- package/dist/bin/journal-repair.test.d.ts.map +0 -1
- package/dist/bin/journal-repair.test.js +0 -53
- package/dist/bin/journal-repair.test.js.map +0 -1
- package/dist/bin/layout-compatibility.test.d.ts +0 -2
- package/dist/bin/layout-compatibility.test.d.ts.map +0 -1
- package/dist/bin/layout-compatibility.test.js +0 -87
- package/dist/bin/layout-compatibility.test.js.map +0 -1
- package/dist/bin/outpost-heartbeat.test.d.ts +0 -7
- package/dist/bin/outpost-heartbeat.test.d.ts.map +0 -1
- package/dist/bin/outpost-heartbeat.test.js +0 -84
- package/dist/bin/outpost-heartbeat.test.js.map +0 -1
- package/dist/bin/outpost-session-host.test.d.ts +0 -7
- package/dist/bin/outpost-session-host.test.d.ts.map +0 -1
- package/dist/bin/outpost-session-host.test.js +0 -52
- package/dist/bin/outpost-session-host.test.js.map +0 -1
- package/dist/bin/sync-mutation.test.d.ts +0 -2
- package/dist/bin/sync-mutation.test.d.ts.map +0 -1
- package/dist/bin/sync-mutation.test.js +0 -64
- package/dist/bin/sync-mutation.test.js.map +0 -1
- package/dist/bin/sync-runner-args.test.d.ts +0 -2
- package/dist/bin/sync-runner-args.test.d.ts.map +0 -1
- package/dist/bin/sync-runner-args.test.js +0 -82
- package/dist/bin/sync-runner-args.test.js.map +0 -1
- package/dist/bin/sync-runner-auth-wait.test.d.ts +0 -2
- package/dist/bin/sync-runner-auth-wait.test.d.ts.map +0 -1
- package/dist/bin/sync-runner-auth-wait.test.js +0 -406
- package/dist/bin/sync-runner-auth-wait.test.js.map +0 -1
- package/dist/bin/sync-runner-backoff.regression.test.d.ts +0 -29
- package/dist/bin/sync-runner-backoff.regression.test.d.ts.map +0 -1
- package/dist/bin/sync-runner-backoff.regression.test.js +0 -224
- package/dist/bin/sync-runner-backoff.regression.test.js.map +0 -1
- package/dist/bin/sync-runner-company.test.d.ts +0 -2
- package/dist/bin/sync-runner-company.test.d.ts.map +0 -1
- package/dist/bin/sync-runner-company.test.js +0 -2439
- package/dist/bin/sync-runner-company.test.js.map +0 -1
- package/dist/bin/sync-runner-delete-snapshot-mixed.bench.test.d.ts +0 -2
- package/dist/bin/sync-runner-delete-snapshot-mixed.bench.test.d.ts.map +0 -1
- package/dist/bin/sync-runner-delete-snapshot-mixed.bench.test.js +0 -648
- package/dist/bin/sync-runner-delete-snapshot-mixed.bench.test.js.map +0 -1
- package/dist/bin/sync-runner-delete-snapshot.bench.test.d.ts +0 -2
- package/dist/bin/sync-runner-delete-snapshot.bench.test.d.ts.map +0 -1
- package/dist/bin/sync-runner-delete-snapshot.bench.test.js +0 -329
- package/dist/bin/sync-runner-delete-snapshot.bench.test.js.map +0 -1
- package/dist/bin/sync-runner-events.test.d.ts +0 -2
- package/dist/bin/sync-runner-events.test.d.ts.map +0 -1
- package/dist/bin/sync-runner-events.test.js +0 -364
- package/dist/bin/sync-runner-events.test.js.map +0 -1
- package/dist/bin/sync-runner-heap-telemetry.test.d.ts +0 -2
- package/dist/bin/sync-runner-heap-telemetry.test.d.ts.map +0 -1
- package/dist/bin/sync-runner-heap-telemetry.test.js +0 -1966
- package/dist/bin/sync-runner-heap-telemetry.test.js.map +0 -1
- package/dist/bin/sync-runner-manifest.test.d.ts +0 -12
- package/dist/bin/sync-runner-manifest.test.d.ts.map +0 -1
- package/dist/bin/sync-runner-manifest.test.js +0 -921
- package/dist/bin/sync-runner-manifest.test.js.map +0 -1
- package/dist/bin/sync-runner-planning.test.d.ts +0 -2
- package/dist/bin/sync-runner-planning.test.d.ts.map +0 -1
- package/dist/bin/sync-runner-planning.test.js +0 -448
- package/dist/bin/sync-runner-planning.test.js.map +0 -1
- package/dist/bin/sync-runner-post-sync-flags.test.d.ts +0 -2
- package/dist/bin/sync-runner-post-sync-flags.test.d.ts.map +0 -1
- package/dist/bin/sync-runner-post-sync-flags.test.js +0 -64
- package/dist/bin/sync-runner-post-sync-flags.test.js.map +0 -1
- package/dist/bin/sync-runner-publish-expansion.test.d.ts +0 -2
- package/dist/bin/sync-runner-publish-expansion.test.d.ts.map +0 -1
- package/dist/bin/sync-runner-publish-expansion.test.js +0 -54
- package/dist/bin/sync-runner-publish-expansion.test.js.map +0 -1
- package/dist/bin/sync-runner-receiver-unresolved-company.test.d.ts +0 -2
- package/dist/bin/sync-runner-receiver-unresolved-company.test.d.ts.map +0 -1
- package/dist/bin/sync-runner-receiver-unresolved-company.test.js +0 -379
- package/dist/bin/sync-runner-receiver-unresolved-company.test.js.map +0 -1
- package/dist/bin/sync-runner-rollup.test.d.ts +0 -2
- package/dist/bin/sync-runner-rollup.test.d.ts.map +0 -1
- package/dist/bin/sync-runner-rollup.test.js +0 -24
- package/dist/bin/sync-runner-rollup.test.js.map +0 -1
- package/dist/bin/sync-runner-watch-loop.test.d.ts +0 -2
- package/dist/bin/sync-runner-watch-loop.test.d.ts.map +0 -1
- package/dist/bin/sync-runner-watch-loop.test.js +0 -734
- package/dist/bin/sync-runner-watch-loop.test.js.map +0 -1
- package/dist/bin/sync-runner-watch-routes.test.d.ts +0 -2
- package/dist/bin/sync-runner-watch-routes.test.d.ts.map +0 -1
- package/dist/bin/sync-runner-watch-routes.test.js +0 -181
- package/dist/bin/sync-runner-watch-routes.test.js.map +0 -1
- package/dist/bin/sync-runner.test.d.ts +0 -10
- package/dist/bin/sync-runner.test.d.ts.map +0 -1
- package/dist/bin/sync-runner.test.js +0 -18984
- package/dist/bin/sync-runner.test.js.map +0 -1
- package/dist/cli/case-alias.test.d.ts +0 -2
- package/dist/cli/case-alias.test.d.ts.map +0 -1
- package/dist/cli/case-alias.test.js +0 -96
- package/dist/cli/case-alias.test.js.map +0 -1
- package/dist/cli/conflict-recovery.test.d.ts +0 -2
- package/dist/cli/conflict-recovery.test.d.ts.map +0 -1
- package/dist/cli/conflict-recovery.test.js +0 -201
- package/dist/cli/conflict-recovery.test.js.map +0 -1
- package/dist/cli/conflict-winner.test.d.ts +0 -7
- package/dist/cli/conflict-winner.test.d.ts.map +0 -1
- package/dist/cli/conflict-winner.test.js +0 -98
- package/dist/cli/conflict-winner.test.js.map +0 -1
- package/dist/cli/doctor.test.d.ts +0 -13
- package/dist/cli/doctor.test.d.ts.map +0 -1
- package/dist/cli/doctor.test.js +0 -607
- package/dist/cli/doctor.test.js.map +0 -1
- package/dist/cli/invite.test.d.ts +0 -5
- package/dist/cli/invite.test.d.ts.map +0 -1
- package/dist/cli/invite.test.js +0 -178
- package/dist/cli/invite.test.js.map +0 -1
- package/dist/cli/onepass-pull-planner-regressions.test.d.ts +0 -2
- package/dist/cli/onepass-pull-planner-regressions.test.d.ts.map +0 -1
- package/dist/cli/onepass-pull-planner-regressions.test.js +0 -300
- package/dist/cli/onepass-pull-planner-regressions.test.js.map +0 -1
- package/dist/cli/onepass-pull-planner.test.d.ts +0 -2
- package/dist/cli/onepass-pull-planner.test.d.ts.map +0 -1
- package/dist/cli/onepass-pull-planner.test.js +0 -266
- package/dist/cli/onepass-pull-planner.test.js.map +0 -1
- package/dist/cli/push-scan-exclusions.test.d.ts +0 -2
- package/dist/cli/push-scan-exclusions.test.d.ts.map +0 -1
- package/dist/cli/push-scan-exclusions.test.js +0 -99
- package/dist/cli/push-scan-exclusions.test.js.map +0 -1
- package/dist/cli/reindex-knowledge.test.d.ts +0 -12
- package/dist/cli/reindex-knowledge.test.d.ts.map +0 -1
- package/dist/cli/reindex-knowledge.test.js +0 -260
- package/dist/cli/reindex-knowledge.test.js.map +0 -1
- package/dist/cli/reindex.test.d.ts +0 -11
- package/dist/cli/reindex.test.d.ts.map +0 -1
- package/dist/cli/reindex.test.js +0 -811
- package/dist/cli/reindex.test.js.map +0 -1
- package/dist/cli/rescue-breaker.test.d.ts +0 -2
- package/dist/cli/rescue-breaker.test.d.ts.map +0 -1
- package/dist/cli/rescue-breaker.test.js +0 -812
- package/dist/cli/rescue-breaker.test.js.map +0 -1
- package/dist/cli/rescue-classify-ordering.test.d.ts +0 -2
- package/dist/cli/rescue-classify-ordering.test.d.ts.map +0 -1
- package/dist/cli/rescue-classify-ordering.test.js +0 -500
- package/dist/cli/rescue-classify-ordering.test.js.map +0 -1
- package/dist/cli/rescue-clone-diagnostics.test.d.ts +0 -2
- package/dist/cli/rescue-clone-diagnostics.test.d.ts.map +0 -1
- package/dist/cli/rescue-clone-diagnostics.test.js +0 -101
- package/dist/cli/rescue-clone-diagnostics.test.js.map +0 -1
- package/dist/cli/rescue-clone-network-retry.test.d.ts +0 -2
- package/dist/cli/rescue-clone-network-retry.test.d.ts.map +0 -1
- package/dist/cli/rescue-clone-network-retry.test.js +0 -415
- package/dist/cli/rescue-clone-network-retry.test.js.map +0 -1
- package/dist/cli/rescue-core-stamp.test.d.ts +0 -2
- package/dist/cli/rescue-core-stamp.test.d.ts.map +0 -1
- package/dist/cli/rescue-core-stamp.test.js +0 -364
- package/dist/cli/rescue-core-stamp.test.js.map +0 -1
- package/dist/cli/rescue-drift-reconcile.test.d.ts +0 -2
- package/dist/cli/rescue-drift-reconcile.test.d.ts.map +0 -1
- package/dist/cli/rescue-drift-reconcile.test.js +0 -162
- package/dist/cli/rescue-drift-reconcile.test.js.map +0 -1
- package/dist/cli/rescue-drop-dir-symlink.test.d.ts +0 -2
- package/dist/cli/rescue-drop-dir-symlink.test.d.ts.map +0 -1
- package/dist/cli/rescue-drop-dir-symlink.test.js +0 -255
- package/dist/cli/rescue-drop-dir-symlink.test.js.map +0 -1
- package/dist/cli/rescue-exec-bit-preserve.test.d.ts +0 -2
- package/dist/cli/rescue-exec-bit-preserve.test.d.ts.map +0 -1
- package/dist/cli/rescue-exec-bit-preserve.test.js +0 -177
- package/dist/cli/rescue-exec-bit-preserve.test.js.map +0 -1
- package/dist/cli/rescue-hq-root-guard.test.d.ts +0 -2
- package/dist/cli/rescue-hq-root-guard.test.d.ts.map +0 -1
- package/dist/cli/rescue-hq-root-guard.test.js +0 -210
- package/dist/cli/rescue-hq-root-guard.test.js.map +0 -1
- package/dist/cli/rescue-journal-reconcile.test.d.ts +0 -2
- package/dist/cli/rescue-journal-reconcile.test.d.ts.map +0 -1
- package/dist/cli/rescue-journal-reconcile.test.js +0 -186
- package/dist/cli/rescue-journal-reconcile.test.js.map +0 -1
- package/dist/cli/rescue-keep-local-ignore.test.d.ts +0 -2
- package/dist/cli/rescue-keep-local-ignore.test.d.ts.map +0 -1
- package/dist/cli/rescue-keep-local-ignore.test.js +0 -315
- package/dist/cli/rescue-keep-local-ignore.test.js.map +0 -1
- package/dist/cli/rescue-move.test.d.ts +0 -2
- package/dist/cli/rescue-move.test.d.ts.map +0 -1
- package/dist/cli/rescue-move.test.js +0 -166
- package/dist/cli/rescue-move.test.js.map +0 -1
- package/dist/cli/rescue-mtime-preserve.test.d.ts +0 -2
- package/dist/cli/rescue-mtime-preserve.test.d.ts.map +0 -1
- package/dist/cli/rescue-mtime-preserve.test.js +0 -186
- package/dist/cli/rescue-mtime-preserve.test.js.map +0 -1
- package/dist/cli/rescue-preserve-copy-symlink.test.d.ts +0 -2
- package/dist/cli/rescue-preserve-copy-symlink.test.d.ts.map +0 -1
- package/dist/cli/rescue-preserve-copy-symlink.test.js +0 -483
- package/dist/cli/rescue-preserve-copy-symlink.test.js.map +0 -1
- package/dist/cli/rescue-realpath.test.d.ts +0 -2
- package/dist/cli/rescue-realpath.test.d.ts.map +0 -1
- package/dist/cli/rescue-realpath.test.js +0 -109
- package/dist/cli/rescue-realpath.test.js.map +0 -1
- package/dist/cli/rescue-restore-flag.test.d.ts +0 -2
- package/dist/cli/rescue-restore-flag.test.d.ts.map +0 -1
- package/dist/cli/rescue-restore-flag.test.js +0 -82
- package/dist/cli/rescue-restore-flag.test.js.map +0 -1
- package/dist/cli/rescue-rsync-preflight.test.d.ts +0 -2
- package/dist/cli/rescue-rsync-preflight.test.d.ts.map +0 -1
- package/dist/cli/rescue-rsync-preflight.test.js +0 -579
- package/dist/cli/rescue-rsync-preflight.test.js.map +0 -1
- package/dist/cli/rescue-rsync-status.test.d.ts +0 -2
- package/dist/cli/rescue-rsync-status.test.d.ts.map +0 -1
- package/dist/cli/rescue-rsync-status.test.js +0 -406
- package/dist/cli/rescue-rsync-status.test.js.map +0 -1
- package/dist/cli/rescue-settings-reconcile.test.d.ts +0 -2
- package/dist/cli/rescue-settings-reconcile.test.d.ts.map +0 -1
- package/dist/cli/rescue-settings-reconcile.test.js +0 -610
- package/dist/cli/rescue-settings-reconcile.test.js.map +0 -1
- package/dist/cli/rescue-snapshot-copy.test.d.ts +0 -2
- package/dist/cli/rescue-snapshot-copy.test.d.ts.map +0 -1
- package/dist/cli/rescue-snapshot-copy.test.js +0 -790
- package/dist/cli/rescue-snapshot-copy.test.js.map +0 -1
- package/dist/cli/rescue-snapshot-recovery.test.d.ts +0 -2
- package/dist/cli/rescue-snapshot-recovery.test.d.ts.map +0 -1
- package/dist/cli/rescue-snapshot-recovery.test.js +0 -265
- package/dist/cli/rescue-snapshot-recovery.test.js.map +0 -1
- package/dist/cli/rescue-snapshot.test.d.ts +0 -2
- package/dist/cli/rescue-snapshot.test.d.ts.map +0 -1
- package/dist/cli/rescue-snapshot.test.js +0 -46
- package/dist/cli/rescue-snapshot.test.js.map +0 -1
- package/dist/cli/rescue-source-switch-floor.test.d.ts +0 -2
- package/dist/cli/rescue-source-switch-floor.test.d.ts.map +0 -1
- package/dist/cli/rescue-source-switch-floor.test.js +0 -247
- package/dist/cli/rescue-source-switch-floor.test.js.map +0 -1
- package/dist/cli/rescue-unchanged-delete.test.d.ts +0 -2
- package/dist/cli/rescue-unchanged-delete.test.d.ts.map +0 -1
- package/dist/cli/rescue-unchanged-delete.test.js +0 -305
- package/dist/cli/rescue-unchanged-delete.test.js.map +0 -1
- package/dist/cli/rescue.reindex.test.d.ts +0 -2
- package/dist/cli/rescue.reindex.test.d.ts.map +0 -1
- package/dist/cli/rescue.reindex.test.js +0 -72
- package/dist/cli/rescue.reindex.test.js.map +0 -1
- package/dist/cli/rescue.test.d.ts +0 -2
- package/dist/cli/rescue.test.d.ts.map +0 -1
- package/dist/cli/rescue.test.js +0 -127
- package/dist/cli/rescue.test.js.map +0 -1
- package/dist/cli/share-plan-limit-presign.test.d.ts +0 -2
- package/dist/cli/share-plan-limit-presign.test.d.ts.map +0 -1
- package/dist/cli/share-plan-limit-presign.test.js +0 -271
- package/dist/cli/share-plan-limit-presign.test.js.map +0 -1
- package/dist/cli/share-presign-transport.test.d.ts +0 -22
- package/dist/cli/share-presign-transport.test.d.ts.map +0 -1
- package/dist/cli/share-presign-transport.test.js +0 -388
- package/dist/cli/share-presign-transport.test.js.map +0 -1
- package/dist/cli/share.test.d.ts +0 -5
- package/dist/cli/share.test.d.ts.map +0 -1
- package/dist/cli/share.test.js +0 -12335
- package/dist/cli/share.test.js.map +0 -1
- package/dist/cli/streaming-pull-planner.bench.test.d.ts +0 -15
- package/dist/cli/streaming-pull-planner.bench.test.d.ts.map +0 -1
- package/dist/cli/streaming-pull-planner.bench.test.js +0 -273
- package/dist/cli/streaming-pull-planner.bench.test.js.map +0 -1
- package/dist/cli/sync-scope.test.d.ts +0 -22
- package/dist/cli/sync-scope.test.d.ts.map +0 -1
- package/dist/cli/sync-scope.test.js +0 -903
- package/dist/cli/sync-scope.test.js.map +0 -1
- package/dist/cli/sync.test.d.ts +0 -5
- package/dist/cli/sync.test.d.ts.map +0 -1
- package/dist/cli/sync.test.js +0 -9028
- package/dist/cli/sync.test.js.map +0 -1
- package/dist/cli/tombstones.test.d.ts +0 -2
- package/dist/cli/tombstones.test.d.ts.map +0 -1
- package/dist/cli/tombstones.test.js +0 -120
- package/dist/cli/tombstones.test.js.map +0 -1
- package/dist/cli/watch-event-push-conflict.test.d.ts +0 -25
- package/dist/cli/watch-event-push-conflict.test.d.ts.map +0 -1
- package/dist/cli/watch-event-push-conflict.test.js +0 -230
- package/dist/cli/watch-event-push-conflict.test.js.map +0 -1
- package/dist/client-info.test.d.ts +0 -11
- package/dist/client-info.test.d.ts.map +0 -1
- package/dist/client-info.test.js +0 -168
- package/dist/client-info.test.js.map +0 -1
- package/dist/cognito-auth-lock-no-hard-links.test.d.ts +0 -11
- package/dist/cognito-auth-lock-no-hard-links.test.d.ts.map +0 -1
- package/dist/cognito-auth-lock-no-hard-links.test.js +0 -76
- package/dist/cognito-auth-lock-no-hard-links.test.js.map +0 -1
- package/dist/cognito-auth.test.d.ts +0 -9
- package/dist/cognito-auth.test.d.ts.map +0 -1
- package/dist/cognito-auth.test.js +0 -745
- package/dist/cognito-auth.test.js.map +0 -1
- package/dist/company-resolver.test.d.ts +0 -7
- package/dist/company-resolver.test.d.ts.map +0 -1
- package/dist/company-resolver.test.js +0 -533
- package/dist/company-resolver.test.js.map +0 -1
- package/dist/company-slug.test.d.ts +0 -7
- package/dist/company-slug.test.d.ts.map +0 -1
- package/dist/company-slug.test.js +0 -43
- package/dist/company-slug.test.js.map +0 -1
- package/dist/company-vault-transport.test.d.ts +0 -12
- package/dist/company-vault-transport.test.d.ts.map +0 -1
- package/dist/company-vault-transport.test.js +0 -97
- package/dist/company-vault-transport.test.js.map +0 -1
- package/dist/context.test.d.ts +0 -7
- package/dist/context.test.d.ts.map +0 -1
- package/dist/context.test.js +0 -646
- package/dist/context.test.js.map +0 -1
- package/dist/entity-resolver.test.d.ts +0 -10
- package/dist/entity-resolver.test.d.ts.map +0 -1
- package/dist/entity-resolver.test.js +0 -244
- package/dist/entity-resolver.test.js.map +0 -1
- package/dist/getindigo-dead-refs-contract.test.d.ts +0 -2
- package/dist/getindigo-dead-refs-contract.test.d.ts.map +0 -1
- package/dist/getindigo-dead-refs-contract.test.js +0 -58
- package/dist/getindigo-dead-refs-contract.test.js.map +0 -1
- package/dist/hermetic-test-env.test.d.ts +0 -2
- package/dist/hermetic-test-env.test.d.ts.map +0 -1
- package/dist/hermetic-test-env.test.js +0 -61
- package/dist/hermetic-test-env.test.js.map +0 -1
- package/dist/hq-root-recovery.test.d.ts +0 -2
- package/dist/hq-root-recovery.test.d.ts.map +0 -1
- package/dist/hq-root-recovery.test.js +0 -65
- package/dist/hq-root-recovery.test.js.map +0 -1
- package/dist/ignore.test.d.ts +0 -10
- package/dist/ignore.test.d.ts.map +0 -1
- package/dist/ignore.test.js +0 -926
- package/dist/ignore.test.js.map +0 -1
- package/dist/index.test.d.ts +0 -2
- package/dist/index.test.d.ts.map +0 -1
- package/dist/index.test.js +0 -26
- package/dist/index.test.js.map +0 -1
- package/dist/journal-area-ledger-read-boundary.test.d.ts +0 -2
- package/dist/journal-area-ledger-read-boundary.test.d.ts.map +0 -1
- package/dist/journal-area-ledger-read-boundary.test.js +0 -420
- package/dist/journal-area-ledger-read-boundary.test.js.map +0 -1
- package/dist/journal-baseline.test.d.ts +0 -2
- package/dist/journal-baseline.test.d.ts.map +0 -1
- package/dist/journal-baseline.test.js +0 -218
- package/dist/journal-baseline.test.js.map +0 -1
- package/dist/journal-decode-window.test.d.ts +0 -2
- package/dist/journal-decode-window.test.d.ts.map +0 -1
- package/dist/journal-decode-window.test.js +0 -99
- package/dist/journal-decode-window.test.js.map +0 -1
- package/dist/journal-delta-writes.test.d.ts +0 -2
- package/dist/journal-delta-writes.test.d.ts.map +0 -1
- package/dist/journal-delta-writes.test.js +0 -159
- package/dist/journal-delta-writes.test.js.map +0 -1
- package/dist/journal-ledger-index.test.d.ts +0 -15
- package/dist/journal-ledger-index.test.d.ts.map +0 -1
- package/dist/journal-ledger-index.test.js +0 -269
- package/dist/journal-ledger-index.test.js.map +0 -1
- package/dist/journal-public-boundary.test.d.ts +0 -2
- package/dist/journal-public-boundary.test.d.ts.map +0 -1
- package/dist/journal-public-boundary.test.js +0 -216
- package/dist/journal-public-boundary.test.js.map +0 -1
- package/dist/journal-row-store.test.d.ts +0 -2
- package/dist/journal-row-store.test.d.ts.map +0 -1
- package/dist/journal-row-store.test.js +0 -729
- package/dist/journal-row-store.test.js.map +0 -1
- package/dist/journal-snapshot-memory-helpers.d.ts +0 -33
- package/dist/journal-snapshot-memory-helpers.d.ts.map +0 -1
- package/dist/journal-snapshot-memory-helpers.js +0 -212
- package/dist/journal-snapshot-memory-helpers.js.map +0 -1
- package/dist/journal-snapshot-memory.test.d.ts +0 -2
- package/dist/journal-snapshot-memory.test.d.ts.map +0 -1
- package/dist/journal-snapshot-memory.test.js +0 -47
- package/dist/journal-snapshot-memory.test.js.map +0 -1
- package/dist/journal-snapshot-v4-memory.test.d.ts +0 -2
- package/dist/journal-snapshot-v4-memory.test.d.ts.map +0 -1
- package/dist/journal-snapshot-v4-memory.test.js +0 -24
- package/dist/journal-snapshot-v4-memory.test.js.map +0 -1
- package/dist/journal-snapshot-v4-writer-memory.test.d.ts +0 -2
- package/dist/journal-snapshot-v4-writer-memory.test.d.ts.map +0 -1
- package/dist/journal-snapshot-v4-writer-memory.test.js +0 -76
- package/dist/journal-snapshot-v4-writer-memory.test.js.map +0 -1
- package/dist/journal-snapshot-v4-writer.test.d.ts +0 -2
- package/dist/journal-snapshot-v4-writer.test.d.ts.map +0 -1
- package/dist/journal-snapshot-v4-writer.test.js +0 -282
- package/dist/journal-snapshot-v4-writer.test.js.map +0 -1
- package/dist/journal-snapshot-v4.test.d.ts +0 -2
- package/dist/journal-snapshot-v4.test.d.ts.map +0 -1
- package/dist/journal-snapshot-v4.test.js +0 -281
- package/dist/journal-snapshot-v4.test.js.map +0 -1
- package/dist/journal-store.test.d.ts +0 -2
- package/dist/journal-store.test.d.ts.map +0 -1
- package/dist/journal-store.test.js +0 -1227
- package/dist/journal-store.test.js.map +0 -1
- package/dist/journal-streaming.heap.test.d.ts +0 -2
- package/dist/journal-streaming.heap.test.d.ts.map +0 -1
- package/dist/journal-streaming.heap.test.js +0 -190
- package/dist/journal-streaming.heap.test.js.map +0 -1
- package/dist/journal.test.d.ts +0 -9
- package/dist/journal.test.d.ts.map +0 -1
- package/dist/journal.test.js +0 -1074
- package/dist/journal.test.js.map +0 -1
- package/dist/kms10-etag-safe-pull.test.d.ts +0 -2
- package/dist/kms10-etag-safe-pull.test.d.ts.map +0 -1
- package/dist/kms10-etag-safe-pull.test.js +0 -53
- package/dist/kms10-etag-safe-pull.test.js.map +0 -1
- package/dist/lib/cloud-authoritative.test.d.ts +0 -2
- package/dist/lib/cloud-authoritative.test.d.ts.map +0 -1
- package/dist/lib/cloud-authoritative.test.js +0 -42
- package/dist/lib/cloud-authoritative.test.js.map +0 -1
- package/dist/lib/conflict-reconcile.test.d.ts +0 -7
- package/dist/lib/conflict-reconcile.test.d.ts.map +0 -1
- package/dist/lib/conflict-reconcile.test.js +0 -246
- package/dist/lib/conflict-reconcile.test.js.map +0 -1
- package/dist/lib/conflict.test.d.ts +0 -9
- package/dist/lib/conflict.test.d.ts.map +0 -1
- package/dist/lib/conflict.test.js +0 -340
- package/dist/lib/conflict.test.js.map +0 -1
- package/dist/lib/describe-error.test.d.ts +0 -2
- package/dist/lib/describe-error.test.d.ts.map +0 -1
- package/dist/lib/describe-error.test.js +0 -148
- package/dist/lib/describe-error.test.js.map +0 -1
- package/dist/lib/machine-id.test.d.ts +0 -8
- package/dist/lib/machine-id.test.d.ts.map +0 -1
- package/dist/lib/machine-id.test.js +0 -228
- package/dist/lib/machine-id.test.js.map +0 -1
- package/dist/lib/net-errors.test.d.ts +0 -2
- package/dist/lib/net-errors.test.d.ts.map +0 -1
- package/dist/lib/net-errors.test.js +0 -111
- package/dist/lib/net-errors.test.js.map +0 -1
- package/dist/lib/ontology-entity-meta.test.d.ts +0 -2
- package/dist/lib/ontology-entity-meta.test.d.ts.map +0 -1
- package/dist/lib/ontology-entity-meta.test.js +0 -79
- package/dist/lib/ontology-entity-meta.test.js.map +0 -1
- package/dist/lib/ontology-entity-push.test.d.ts +0 -2
- package/dist/lib/ontology-entity-push.test.d.ts.map +0 -1
- package/dist/lib/ontology-entity-push.test.js +0 -156
- package/dist/lib/ontology-entity-push.test.js.map +0 -1
- package/dist/lib/personal-connector-settings-sync.test.d.ts +0 -2
- package/dist/lib/personal-connector-settings-sync.test.d.ts.map +0 -1
- package/dist/lib/personal-connector-settings-sync.test.js +0 -34
- package/dist/lib/personal-connector-settings-sync.test.js.map +0 -1
- package/dist/lib/readlink-safe.test.d.ts +0 -2
- package/dist/lib/readlink-safe.test.d.ts.map +0 -1
- package/dist/lib/readlink-safe.test.js +0 -34
- package/dist/lib/readlink-safe.test.js.map +0 -1
- package/dist/lib/s3-content-checksum.test.d.ts +0 -2
- package/dist/lib/s3-content-checksum.test.d.ts.map +0 -1
- package/dist/lib/s3-content-checksum.test.js +0 -26
- package/dist/lib/s3-content-checksum.test.js.map +0 -1
- package/dist/lib/skill-register-landed-bytes.test.d.ts +0 -2
- package/dist/lib/skill-register-landed-bytes.test.d.ts.map +0 -1
- package/dist/lib/skill-register-landed-bytes.test.js +0 -83
- package/dist/lib/skill-register-landed-bytes.test.js.map +0 -1
- package/dist/local-path-codec.test.d.ts +0 -14
- package/dist/local-path-codec.test.d.ts.map +0 -1
- package/dist/local-path-codec.test.js +0 -122
- package/dist/local-path-codec.test.js.map +0 -1
- package/dist/machine-auth.test.d.ts +0 -14
- package/dist/machine-auth.test.d.ts.map +0 -1
- package/dist/machine-auth.test.js +0 -1492
- package/dist/machine-auth.test.js.map +0 -1
- package/dist/machine-token-cache-isolation.test.d.ts +0 -24
- package/dist/machine-token-cache-isolation.test.d.ts.map +0 -1
- package/dist/machine-token-cache-isolation.test.js +0 -284
- package/dist/machine-token-cache-isolation.test.js.map +0 -1
- package/dist/manifest/US-002.acceptance.test.d.ts +0 -11
- package/dist/manifest/US-002.acceptance.test.d.ts.map +0 -1
- package/dist/manifest/US-002.acceptance.test.js +0 -160
- package/dist/manifest/US-002.acceptance.test.js.map +0 -1
- package/dist/manifest/US-004.acceptance.test.d.ts +0 -12
- package/dist/manifest/US-004.acceptance.test.d.ts.map +0 -1
- package/dist/manifest/US-004.acceptance.test.js +0 -173
- package/dist/manifest/US-004.acceptance.test.js.map +0 -1
- package/dist/manifest/build-manifest-resident-parity.test.d.ts +0 -2
- package/dist/manifest/build-manifest-resident-parity.test.d.ts.map +0 -1
- package/dist/manifest/build-manifest-resident-parity.test.js +0 -69
- package/dist/manifest/build-manifest-resident-parity.test.js.map +0 -1
- package/dist/manifest/build-manifest.bench.test.d.ts +0 -22
- package/dist/manifest/build-manifest.bench.test.d.ts.map +0 -1
- package/dist/manifest/build-manifest.bench.test.js +0 -930
- package/dist/manifest/build-manifest.bench.test.js.map +0 -1
- package/dist/manifest/build-manifest.test.d.ts +0 -10
- package/dist/manifest/build-manifest.test.d.ts.map +0 -1
- package/dist/manifest/build-manifest.test.js +0 -1217
- package/dist/manifest/build-manifest.test.js.map +0 -1
- package/dist/manifest/contract.test.d.ts +0 -17
- package/dist/manifest/contract.test.d.ts.map +0 -1
- package/dist/manifest/contract.test.js +0 -346
- package/dist/manifest/contract.test.js.map +0 -1
- package/dist/manifest/import-graph.test.d.ts +0 -25
- package/dist/manifest/import-graph.test.d.ts.map +0 -1
- package/dist/manifest/import-graph.test.js +0 -120
- package/dist/manifest/import-graph.test.js.map +0 -1
- package/dist/manifest/ledger-shard-isolation.test.d.ts +0 -18
- package/dist/manifest/ledger-shard-isolation.test.d.ts.map +0 -1
- package/dist/manifest/ledger-shard-isolation.test.js +0 -153
- package/dist/manifest/ledger-shard-isolation.test.js.map +0 -1
- package/dist/manifest/snapshot-store.test.d.ts +0 -11
- package/dist/manifest/snapshot-store.test.d.ts.map +0 -1
- package/dist/manifest/snapshot-store.test.js +0 -206
- package/dist/manifest/snapshot-store.test.js.map +0 -1
- package/dist/manifest/upload-manifest.test.d.ts +0 -10
- package/dist/manifest/upload-manifest.test.d.ts.map +0 -1
- package/dist/manifest/upload-manifest.test.js +0 -1335
- package/dist/manifest/upload-manifest.test.js.map +0 -1
- package/dist/manifest-reconcile.test.d.ts +0 -2
- package/dist/manifest-reconcile.test.d.ts.map +0 -1
- package/dist/manifest-reconcile.test.js +0 -1029
- package/dist/manifest-reconcile.test.js.map +0 -1
- package/dist/object-io.test.d.ts +0 -11
- package/dist/object-io.test.d.ts.map +0 -1
- package/dist/object-io.test.js +0 -2376
- package/dist/object-io.test.js.map +0 -1
- package/dist/operation-lock.test.d.ts +0 -5
- package/dist/operation-lock.test.d.ts.map +0 -1
- package/dist/operation-lock.test.js +0 -796
- package/dist/operation-lock.test.js.map +0 -1
- package/dist/outcome-telemetry.test.d.ts +0 -9
- package/dist/outcome-telemetry.test.d.ts.map +0 -1
- package/dist/outcome-telemetry.test.js +0 -663
- package/dist/outcome-telemetry.test.js.map +0 -1
- package/dist/outposts/client.test.d.ts +0 -9
- package/dist/outposts/client.test.d.ts.map +0 -1
- package/dist/outposts/client.test.js +0 -415
- package/dist/outposts/client.test.js.map +0 -1
- package/dist/outposts/command.test.d.ts +0 -18
- package/dist/outposts/command.test.d.ts.map +0 -1
- package/dist/outposts/command.test.js +0 -1230
- package/dist/outposts/command.test.js.map +0 -1
- package/dist/outposts/replica-sync.test.d.ts +0 -2
- package/dist/outposts/replica-sync.test.d.ts.map +0 -1
- package/dist/outposts/replica-sync.test.js +0 -224
- package/dist/outposts/replica-sync.test.js.map +0 -1
- package/dist/outposts/self-deploy.test.d.ts +0 -2
- package/dist/outposts/self-deploy.test.d.ts.map +0 -1
- package/dist/outposts/self-deploy.test.js +0 -218
- package/dist/outposts/self-deploy.test.js.map +0 -1
- package/dist/outposts/session-heartbeat-bounds.test.d.ts +0 -25
- package/dist/outposts/session-heartbeat-bounds.test.d.ts.map +0 -1
- package/dist/outposts/session-heartbeat-bounds.test.js +0 -156
- package/dist/outposts/session-heartbeat-bounds.test.js.map +0 -1
- package/dist/outposts/session-heartbeat-guard.test.d.ts +0 -16
- package/dist/outposts/session-heartbeat-guard.test.d.ts.map +0 -1
- package/dist/outposts/session-heartbeat-guard.test.js +0 -88
- package/dist/outposts/session-heartbeat-guard.test.js.map +0 -1
- package/dist/outposts/session-heartbeat-publisher.test.d.ts +0 -10
- package/dist/outposts/session-heartbeat-publisher.test.d.ts.map +0 -1
- package/dist/outposts/session-heartbeat-publisher.test.js +0 -153
- package/dist/outposts/session-heartbeat-publisher.test.js.map +0 -1
- package/dist/outposts/session-heartbeat-retain-guard.test.d.ts +0 -18
- package/dist/outposts/session-heartbeat-retain-guard.test.d.ts.map +0 -1
- package/dist/outposts/session-heartbeat-retain-guard.test.js +0 -106
- package/dist/outposts/session-heartbeat-retain-guard.test.js.map +0 -1
- package/dist/outposts/session-heartbeat-scan.test.d.ts +0 -20
- package/dist/outposts/session-heartbeat-scan.test.d.ts.map +0 -1
- package/dist/outposts/session-heartbeat-scan.test.js +0 -241
- package/dist/outposts/session-heartbeat-scan.test.js.map +0 -1
- package/dist/outposts/session-heartbeat.test.d.ts +0 -11
- package/dist/outposts/session-heartbeat.test.d.ts.map +0 -1
- package/dist/outposts/session-heartbeat.test.js +0 -370
- package/dist/outposts/session-heartbeat.test.js.map +0 -1
- package/dist/outposts/session-host-auth.test.d.ts +0 -16
- package/dist/outposts/session-host-auth.test.d.ts.map +0 -1
- package/dist/outposts/session-host-auth.test.js +0 -284
- package/dist/outposts/session-host-auth.test.js.map +0 -1
- package/dist/outposts/session-host-command.test.d.ts +0 -10
- package/dist/outposts/session-host-command.test.d.ts.map +0 -1
- package/dist/outposts/session-host-command.test.js +0 -180
- package/dist/outposts/session-host-command.test.js.map +0 -1
- package/dist/outposts/session-host-connection.test.d.ts +0 -18
- package/dist/outposts/session-host-connection.test.d.ts.map +0 -1
- package/dist/outposts/session-host-connection.test.js +0 -392
- package/dist/outposts/session-host-connection.test.js.map +0 -1
- package/dist/outposts/session-host-fakes.d.ts +0 -61
- package/dist/outposts/session-host-fakes.d.ts.map +0 -1
- package/dist/outposts/session-host-fakes.js +0 -115
- package/dist/outposts/session-host-fakes.js.map +0 -1
- package/dist/outposts/session-host-ndjson.test.d.ts +0 -11
- package/dist/outposts/session-host-ndjson.test.d.ts.map +0 -1
- package/dist/outposts/session-host-ndjson.test.js +0 -73
- package/dist/outposts/session-host-ndjson.test.js.map +0 -1
- package/dist/outposts/session-host-protocol.test.d.ts +0 -15
- package/dist/outposts/session-host-protocol.test.d.ts.map +0 -1
- package/dist/outposts/session-host-protocol.test.js +0 -0
- package/dist/outposts/session-host-protocol.test.js.map +0 -1
- package/dist/outposts/session-host-server.test.d.ts +0 -10
- package/dist/outposts/session-host-server.test.d.ts.map +0 -1
- package/dist/outposts/session-host-server.test.js +0 -118
- package/dist/outposts/session-host-server.test.js.map +0 -1
- package/dist/outposts/session-host-sessions.test.d.ts +0 -18
- package/dist/outposts/session-host-sessions.test.d.ts.map +0 -1
- package/dist/outposts/session-host-sessions.test.js +0 -439
- package/dist/outposts/session-host-sessions.test.js.map +0 -1
- package/dist/outposts/terminal.test.d.ts +0 -2
- package/dist/outposts/terminal.test.d.ts.map +0 -1
- package/dist/outposts/terminal.test.js +0 -146
- package/dist/outposts/terminal.test.js.map +0 -1
- package/dist/outposts/transport.test.d.ts +0 -10
- package/dist/outposts/transport.test.d.ts.map +0 -1
- package/dist/outposts/transport.test.js +0 -205
- package/dist/outposts/transport.test.js.map +0 -1
- package/dist/packed-journal-phase2.test.d.ts +0 -2
- package/dist/packed-journal-phase2.test.d.ts.map +0 -1
- package/dist/packed-journal-phase2.test.js +0 -273
- package/dist/packed-journal-phase2.test.js.map +0 -1
- package/dist/path-ancestors.test.d.ts +0 -2
- package/dist/path-ancestors.test.d.ts.map +0 -1
- package/dist/path-ancestors.test.js +0 -21
- package/dist/path-ancestors.test.js.map +0 -1
- package/dist/personal-vault-exclusions.test.d.ts +0 -22
- package/dist/personal-vault-exclusions.test.d.ts.map +0 -1
- package/dist/personal-vault-exclusions.test.js +0 -294
- package/dist/personal-vault-exclusions.test.js.map +0 -1
- package/dist/personal-vault.test.d.ts +0 -18
- package/dist/personal-vault.test.d.ts.map +0 -1
- package/dist/personal-vault.test.js +0 -1099
- package/dist/personal-vault.test.js.map +0 -1
- package/dist/plan-limit-pause.test.d.ts +0 -2
- package/dist/plan-limit-pause.test.d.ts.map +0 -1
- package/dist/plan-limit-pause.test.js +0 -150
- package/dist/plan-limit-pause.test.js.map +0 -1
- package/dist/plan-limit.fixtures.d.ts +0 -34
- package/dist/plan-limit.fixtures.d.ts.map +0 -1
- package/dist/plan-limit.fixtures.js +0 -96
- package/dist/plan-limit.fixtures.js.map +0 -1
- package/dist/plan-limit.test.d.ts +0 -2
- package/dist/plan-limit.test.d.ts.map +0 -1
- package/dist/plan-limit.test.js +0 -132
- package/dist/plan-limit.test.js.map +0 -1
- package/dist/prefix-coalesce.test.d.ts +0 -2
- package/dist/prefix-coalesce.test.d.ts.map +0 -1
- package/dist/prefix-coalesce.test.js +0 -260
- package/dist/prefix-coalesce.test.js.map +0 -1
- package/dist/private-folder-transport.test.d.ts +0 -2
- package/dist/private-folder-transport.test.d.ts.map +0 -1
- package/dist/private-folder-transport.test.js +0 -33
- package/dist/private-folder-transport.test.js.map +0 -1
- package/dist/public-surface.test.d.ts +0 -15
- package/dist/public-surface.test.d.ts.map +0 -1
- package/dist/public-surface.test.js +0 -110
- package/dist/public-surface.test.js.map +0 -1
- package/dist/qmd-reindex.test.d.ts +0 -14
- package/dist/qmd-reindex.test.d.ts.map +0 -1
- package/dist/qmd-reindex.test.js +0 -899
- package/dist/qmd-reindex.test.js.map +0 -1
- package/dist/read-only-state-dir.test.d.ts +0 -27
- package/dist/read-only-state-dir.test.d.ts.map +0 -1
- package/dist/read-only-state-dir.test.js +0 -167
- package/dist/read-only-state-dir.test.js.map +0 -1
- package/dist/remote-pull.test.d.ts +0 -2
- package/dist/remote-pull.test.d.ts.map +0 -1
- package/dist/remote-pull.test.js +0 -1147
- package/dist/remote-pull.test.js.map +0 -1
- package/dist/s3-download-stream-hash.test.d.ts +0 -2
- package/dist/s3-download-stream-hash.test.d.ts.map +0 -1
- package/dist/s3-download-stream-hash.test.js +0 -70
- package/dist/s3-download-stream-hash.test.js.map +0 -1
- package/dist/s3.list-cost.test.d.ts +0 -2
- package/dist/s3.list-cost.test.d.ts.map +0 -1
- package/dist/s3.list-cost.test.js +0 -51
- package/dist/s3.list-cost.test.js.map +0 -1
- package/dist/s3.rename-retry.test.d.ts +0 -2
- package/dist/s3.rename-retry.test.d.ts.map +0 -1
- package/dist/s3.rename-retry.test.js +0 -217
- package/dist/s3.rename-retry.test.js.map +0 -1
- package/dist/s3.symlink-materialize.test.d.ts +0 -18
- package/dist/s3.symlink-materialize.test.d.ts.map +0 -1
- package/dist/s3.symlink-materialize.test.js +0 -579
- package/dist/s3.symlink-materialize.test.js.map +0 -1
- package/dist/s3.test.d.ts +0 -9
- package/dist/s3.test.d.ts.map +0 -1
- package/dist/s3.test.js +0 -2047
- package/dist/s3.test.js.map +0 -1
- package/dist/s3.windows-portability.test.d.ts +0 -12
- package/dist/s3.windows-portability.test.d.ts.map +0 -1
- package/dist/s3.windows-portability.test.js +0 -223
- package/dist/s3.windows-portability.test.js.map +0 -1
- package/dist/schemas/signal-types.test.d.ts +0 -2
- package/dist/schemas/signal-types.test.d.ts.map +0 -1
- package/dist/schemas/signal-types.test.js +0 -65
- package/dist/schemas/signal-types.test.js.map +0 -1
- package/dist/schemas/source-channels.test.d.ts +0 -2
- package/dist/schemas/source-channels.test.d.ts.map +0 -1
- package/dist/schemas/source-channels.test.js +0 -65
- package/dist/schemas/source-channels.test.js.map +0 -1
- package/dist/scope-shrink.test.d.ts +0 -13
- package/dist/scope-shrink.test.d.ts.map +0 -1
- package/dist/scope-shrink.test.js +0 -558
- package/dist/scope-shrink.test.js.map +0 -1
- package/dist/session-log-capture.test.d.ts +0 -2
- package/dist/session-log-capture.test.d.ts.map +0 -1
- package/dist/session-log-capture.test.js +0 -533
- package/dist/session-log-capture.test.js.map +0 -1
- package/dist/session-log-retention.test.d.ts +0 -2
- package/dist/session-log-retention.test.d.ts.map +0 -1
- package/dist/session-log-retention.test.js +0 -383
- package/dist/session-log-retention.test.js.map +0 -1
- package/dist/signals/get.test.d.ts +0 -5
- package/dist/signals/get.test.d.ts.map +0 -1
- package/dist/signals/get.test.js +0 -254
- package/dist/signals/get.test.js.map +0 -1
- package/dist/signals/list.test.d.ts +0 -9
- package/dist/signals/list.test.d.ts.map +0 -1
- package/dist/signals/list.test.js +0 -330
- package/dist/signals/list.test.js.map +0 -1
- package/dist/skill-telemetry.test.d.ts +0 -2
- package/dist/skill-telemetry.test.d.ts.map +0 -1
- package/dist/skill-telemetry.test.js +0 -1484
- package/dist/skill-telemetry.test.js.map +0 -1
- package/dist/sources/get.test.d.ts +0 -5
- package/dist/sources/get.test.d.ts.map +0 -1
- package/dist/sources/get.test.js +0 -236
- package/dist/sources/get.test.js.map +0 -1
- package/dist/sources/list.test.d.ts +0 -8
- package/dist/sources/list.test.d.ts.map +0 -1
- package/dist/sources/list.test.js +0 -318
- package/dist/sources/list.test.js.map +0 -1
- package/dist/sts-credential-refresh.test.d.ts +0 -2
- package/dist/sts-credential-refresh.test.d.ts.map +0 -1
- package/dist/sts-credential-refresh.test.js +0 -181
- package/dist/sts-credential-refresh.test.js.map +0 -1
- package/dist/swallowed-error-sites.test.d.ts +0 -2
- package/dist/swallowed-error-sites.test.d.ts.map +0 -1
- package/dist/swallowed-error-sites.test.js +0 -199
- package/dist/swallowed-error-sites.test.js.map +0 -1
- package/dist/swallowed-error-w32.test.d.ts +0 -2
- package/dist/swallowed-error-w32.test.d.ts.map +0 -1
- package/dist/swallowed-error-w32.test.js +0 -171
- package/dist/swallowed-error-w32.test.js.map +0 -1
- package/dist/swallowed-error-w33.test.d.ts +0 -2
- package/dist/swallowed-error-w33.test.d.ts.map +0 -1
- package/dist/swallowed-error-w33.test.js +0 -125
- package/dist/swallowed-error-w33.test.js.map +0 -1
- package/dist/swallowed-error-w34.test.d.ts +0 -2
- package/dist/swallowed-error-w34.test.d.ts.map +0 -1
- package/dist/swallowed-error-w34.test.js +0 -110
- package/dist/swallowed-error-w34.test.js.map +0 -1
- package/dist/swallowed-error-w35.test.d.ts +0 -2
- package/dist/swallowed-error-w35.test.d.ts.map +0 -1
- package/dist/swallowed-error-w35.test.js +0 -223
- package/dist/swallowed-error-w35.test.js.map +0 -1
- package/dist/sync/area-ledger-auto-migration.test.d.ts +0 -2
- package/dist/sync/area-ledger-auto-migration.test.d.ts.map +0 -1
- package/dist/sync/area-ledger-auto-migration.test.js +0 -291
- package/dist/sync/area-ledger-auto-migration.test.js.map +0 -1
- package/dist/sync/area-ledger-migration.test.d.ts +0 -2
- package/dist/sync/area-ledger-migration.test.d.ts.map +0 -1
- package/dist/sync/area-ledger-migration.test.js +0 -554
- package/dist/sync/area-ledger-migration.test.js.map +0 -1
- package/dist/sync/area-ledger-recovery.bench.test.d.ts +0 -2
- package/dist/sync/area-ledger-recovery.bench.test.d.ts.map +0 -1
- package/dist/sync/area-ledger-recovery.bench.test.js +0 -60
- package/dist/sync/area-ledger-recovery.bench.test.js.map +0 -1
- package/dist/sync/area-ledger-writer-lock.bench.test.d.ts +0 -2
- package/dist/sync/area-ledger-writer-lock.bench.test.d.ts.map +0 -1
- package/dist/sync/area-ledger-writer-lock.bench.test.js +0 -57
- package/dist/sync/area-ledger-writer-lock.bench.test.js.map +0 -1
- package/dist/sync/area-ledger.test.d.ts +0 -2
- package/dist/sync/area-ledger.test.d.ts.map +0 -1
- package/dist/sync/area-ledger.test.js +0 -1401
- package/dist/sync/area-ledger.test.js.map +0 -1
- package/dist/sync/area-resolver.test.d.ts +0 -2
- package/dist/sync/area-resolver.test.d.ts.map +0 -1
- package/dist/sync/area-resolver.test.js +0 -234
- package/dist/sync/area-resolver.test.js.map +0 -1
- package/dist/sync/base-version-resolver.test.d.ts +0 -2
- package/dist/sync/base-version-resolver.test.d.ts.map +0 -1
- package/dist/sync/base-version-resolver.test.js +0 -24
- package/dist/sync/base-version-resolver.test.js.map +0 -1
- package/dist/sync/candidate-uploader.test.d.ts +0 -2
- package/dist/sync/candidate-uploader.test.d.ts.map +0 -1
- package/dist/sync/candidate-uploader.test.js +0 -132
- package/dist/sync/candidate-uploader.test.js.map +0 -1
- package/dist/sync/child-process-sync-worker-env.test.d.ts +0 -2
- package/dist/sync/child-process-sync-worker-env.test.d.ts.map +0 -1
- package/dist/sync/child-process-sync-worker-env.test.js +0 -38
- package/dist/sync/child-process-sync-worker-env.test.js.map +0 -1
- package/dist/sync/child-process-sync-worker-errors.test.d.ts +0 -2
- package/dist/sync/child-process-sync-worker-errors.test.d.ts.map +0 -1
- package/dist/sync/child-process-sync-worker-errors.test.js +0 -121
- package/dist/sync/child-process-sync-worker-errors.test.js.map +0 -1
- package/dist/sync/child-process-sync-worker.test.d.ts +0 -2
- package/dist/sync/child-process-sync-worker.test.d.ts.map +0 -1
- package/dist/sync/child-process-sync-worker.test.js +0 -262
- package/dist/sync/child-process-sync-worker.test.js.map +0 -1
- package/dist/sync/content-hash.test.d.ts +0 -2
- package/dist/sync/content-hash.test.d.ts.map +0 -1
- package/dist/sync/content-hash.test.js +0 -16
- package/dist/sync/content-hash.test.js.map +0 -1
- package/dist/sync/delta-client.test.d.ts +0 -2
- package/dist/sync/delta-client.test.d.ts.map +0 -1
- package/dist/sync/delta-client.test.js +0 -97
- package/dist/sync/delta-client.test.js.map +0 -1
- package/dist/sync/durable-apply.test.d.ts +0 -2
- package/dist/sync/durable-apply.test.d.ts.map +0 -1
- package/dist/sync/durable-apply.test.js +0 -180
- package/dist/sync/durable-apply.test.js.map +0 -1
- package/dist/sync/event-sync.test.d.ts +0 -14
- package/dist/sync/event-sync.test.d.ts.map +0 -1
- package/dist/sync/event-sync.test.js +0 -1714
- package/dist/sync/event-sync.test.js.map +0 -1
- package/dist/sync/feature-flags.test.d.ts +0 -24
- package/dist/sync/feature-flags.test.d.ts.map +0 -1
- package/dist/sync/feature-flags.test.js +0 -594
- package/dist/sync/feature-flags.test.js.map +0 -1
- package/dist/sync/full-pass-cap-governor.bench.test.d.ts +0 -2
- package/dist/sync/full-pass-cap-governor.bench.test.d.ts.map +0 -1
- package/dist/sync/full-pass-cap-governor.bench.test.js +0 -179
- package/dist/sync/full-pass-cap-governor.bench.test.js.map +0 -1
- package/dist/sync/full-pass-caps.test.d.ts +0 -2
- package/dist/sync/full-pass-caps.test.d.ts.map +0 -1
- package/dist/sync/full-pass-caps.test.js +0 -550
- package/dist/sync/full-pass-caps.test.js.map +0 -1
- package/dist/sync/idle-full-reconcile.test.d.ts +0 -2
- package/dist/sync/idle-full-reconcile.test.d.ts.map +0 -1
- package/dist/sync/idle-full-reconcile.test.js +0 -62
- package/dist/sync/idle-full-reconcile.test.js.map +0 -1
- package/dist/sync/inbound-event-admission.test.d.ts +0 -2
- package/dist/sync/inbound-event-admission.test.d.ts.map +0 -1
- package/dist/sync/inbound-event-admission.test.js +0 -125
- package/dist/sync/inbound-event-admission.test.js.map +0 -1
- package/dist/sync/journal-ledger-stream.test.d.ts +0 -12
- package/dist/sync/journal-ledger-stream.test.d.ts.map +0 -1
- package/dist/sync/journal-ledger-stream.test.js +0 -134
- package/dist/sync/journal-ledger-stream.test.js.map +0 -1
- package/dist/sync/lease-client.test.d.ts +0 -2
- package/dist/sync/lease-client.test.d.ts.map +0 -1
- package/dist/sync/lease-client.test.js +0 -119
- package/dist/sync/lease-client.test.js.map +0 -1
- package/dist/sync/logger.test.d.ts +0 -19
- package/dist/sync/logger.test.d.ts.map +0 -1
- package/dist/sync/logger.test.js +0 -204
- package/dist/sync/logger.test.js.map +0 -1
- package/dist/sync/metrics.test.d.ts +0 -19
- package/dist/sync/metrics.test.d.ts.map +0 -1
- package/dist/sync/metrics.test.js +0 -357
- package/dist/sync/metrics.test.js.map +0 -1
- package/dist/sync/multipart-uploader.test.d.ts +0 -2
- package/dist/sync/multipart-uploader.test.d.ts.map +0 -1
- package/dist/sync/multipart-uploader.test.js +0 -135
- package/dist/sync/multipart-uploader.test.js.map +0 -1
- package/dist/sync/mutation-client.test.d.ts +0 -2
- package/dist/sync/mutation-client.test.d.ts.map +0 -1
- package/dist/sync/mutation-client.test.js +0 -100
- package/dist/sync/mutation-client.test.js.map +0 -1
- package/dist/sync/mutation-session.test.d.ts +0 -2
- package/dist/sync/mutation-session.test.d.ts.map +0 -1
- package/dist/sync/mutation-session.test.js +0 -190
- package/dist/sync/mutation-session.test.js.map +0 -1
- package/dist/sync/parallel-sync-coordinator.test.d.ts +0 -2
- package/dist/sync/parallel-sync-coordinator.test.d.ts.map +0 -1
- package/dist/sync/parallel-sync-coordinator.test.js +0 -564
- package/dist/sync/parallel-sync-coordinator.test.js.map +0 -1
- package/dist/sync/path-leases.test.d.ts +0 -2
- package/dist/sync/path-leases.test.d.ts.map +0 -1
- package/dist/sync/path-leases.test.js +0 -51
- package/dist/sync/path-leases.test.js.map +0 -1
- package/dist/sync/per-area-maintenance.test.d.ts +0 -2
- package/dist/sync/per-area-maintenance.test.d.ts.map +0 -1
- package/dist/sync/per-area-maintenance.test.js +0 -100
- package/dist/sync/per-area-maintenance.test.js.map +0 -1
- package/dist/sync/push-event.test.d.ts +0 -15
- package/dist/sync/push-event.test.d.ts.map +0 -1
- package/dist/sync/push-event.test.js +0 -282
- package/dist/sync/push-event.test.js.map +0 -1
- package/dist/sync/push-receiver.test.d.ts +0 -25
- package/dist/sync/push-receiver.test.d.ts.map +0 -1
- package/dist/sync/push-receiver.test.js +0 -2851
- package/dist/sync/push-receiver.test.js.map +0 -1
- package/dist/sync/push-transport.test.d.ts +0 -2
- package/dist/sync/push-transport.test.d.ts.map +0 -1
- package/dist/sync/push-transport.test.js +0 -70
- package/dist/sync/push-transport.test.js.map +0 -1
- package/dist/sync/push-walk-matchers.bench.test.d.ts +0 -2
- package/dist/sync/push-walk-matchers.bench.test.d.ts.map +0 -1
- package/dist/sync/push-walk-matchers.bench.test.js +0 -230
- package/dist/sync/push-walk-matchers.bench.test.js.map +0 -1
- package/dist/sync/realtime-network-integration.test.d.ts +0 -2
- package/dist/sync/realtime-network-integration.test.d.ts.map +0 -1
- package/dist/sync/realtime-network-integration.test.js +0 -83
- package/dist/sync/realtime-network-integration.test.js.map +0 -1
- package/dist/sync/realtime-rollout.test.d.ts +0 -2
- package/dist/sync/realtime-rollout.test.d.ts.map +0 -1
- package/dist/sync/realtime-rollout.test.js +0 -94
- package/dist/sync/realtime-rollout.test.js.map +0 -1
- package/dist/sync/realtime-telemetry.test.d.ts +0 -2
- package/dist/sync/realtime-telemetry.test.d.ts.map +0 -1
- package/dist/sync/realtime-telemetry.test.js +0 -300
- package/dist/sync/realtime-telemetry.test.js.map +0 -1
- package/dist/sync/receiver-origin-config.test.d.ts +0 -2
- package/dist/sync/receiver-origin-config.test.d.ts.map +0 -1
- package/dist/sync/receiver-origin-config.test.js +0 -39
- package/dist/sync/receiver-origin-config.test.js.map +0 -1
- package/dist/sync/reconcile-cursor.test.d.ts +0 -2
- package/dist/sync/reconcile-cursor.test.d.ts.map +0 -1
- package/dist/sync/reconcile-cursor.test.js +0 -139
- package/dist/sync/reconcile-cursor.test.js.map +0 -1
- package/dist/sync/remote-file-spool.test.d.ts +0 -2
- package/dist/sync/remote-file-spool.test.d.ts.map +0 -1
- package/dist/sync/remote-file-spool.test.js +0 -77
- package/dist/sync/remote-file-spool.test.js.map +0 -1
- package/dist/sync/scope-inventory-client.test.d.ts +0 -2
- package/dist/sync/scope-inventory-client.test.d.ts.map +0 -1
- package/dist/sync/scope-inventory-client.test.js +0 -103
- package/dist/sync/scope-inventory-client.test.js.map +0 -1
- package/dist/sync/secret-path-policy.test.d.ts +0 -2
- package/dist/sync/secret-path-policy.test.d.ts.map +0 -1
- package/dist/sync/secret-path-policy.test.js +0 -183
- package/dist/sync/secret-path-policy.test.js.map +0 -1
- package/dist/sync/snapshot-client.test.d.ts +0 -2
- package/dist/sync/snapshot-client.test.d.ts.map +0 -1
- package/dist/sync/snapshot-client.test.js +0 -169
- package/dist/sync/snapshot-client.test.js.map +0 -1
- package/dist/sync/state-store.test.d.ts +0 -2
- package/dist/sync/state-store.test.d.ts.map +0 -1
- package/dist/sync/state-store.test.js +0 -1556
- package/dist/sync/state-store.test.js.map +0 -1
- package/dist/sync/sync-contract.test.d.ts +0 -2
- package/dist/sync/sync-contract.test.d.ts.map +0 -1
- package/dist/sync/sync-contract.test.js +0 -141
- package/dist/sync/sync-contract.test.js.map +0 -1
- package/dist/sync/targeted-pull-prefixes.test.d.ts +0 -2
- package/dist/sync/targeted-pull-prefixes.test.d.ts.map +0 -1
- package/dist/sync/targeted-pull-prefixes.test.js +0 -33
- package/dist/sync/targeted-pull-prefixes.test.js.map +0 -1
- package/dist/sync/targeted-upsert-exact-list.test.d.ts +0 -2
- package/dist/sync/targeted-upsert-exact-list.test.d.ts.map +0 -1
- package/dist/sync/targeted-upsert-exact-list.test.js +0 -247
- package/dist/sync/targeted-upsert-exact-list.test.js.map +0 -1
- package/dist/sync/unchanged-push-events.test.d.ts +0 -2
- package/dist/sync/unchanged-push-events.test.d.ts.map +0 -1
- package/dist/sync/unchanged-push-events.test.js +0 -76
- package/dist/sync/unchanged-push-events.test.js.map +0 -1
- package/dist/sync/uploader-finalization.test.d.ts +0 -2
- package/dist/sync/uploader-finalization.test.d.ts.map +0 -1
- package/dist/sync/uploader-finalization.test.js +0 -107
- package/dist/sync/uploader-finalization.test.js.map +0 -1
- package/dist/sync-core.test.d.ts +0 -2
- package/dist/sync-core.test.d.ts.map +0 -1
- package/dist/sync-core.test.js +0 -189
- package/dist/sync-core.test.js.map +0 -1
- package/dist/sync-progress.test.d.ts +0 -2
- package/dist/sync-progress.test.d.ts.map +0 -1
- package/dist/sync-progress.test.js +0 -277
- package/dist/sync-progress.test.js.map +0 -1
- package/dist/telemetry-events.test.d.ts +0 -2
- package/dist/telemetry-events.test.d.ts.map +0 -1
- package/dist/telemetry-events.test.js +0 -113
- package/dist/telemetry-events.test.js.map +0 -1
- package/dist/telemetry.test.d.ts +0 -11
- package/dist/telemetry.test.d.ts.map +0 -1
- package/dist/telemetry.test.js +0 -1603
- package/dist/telemetry.test.js.map +0 -1
- package/dist/vault-client.test.d.ts +0 -7
- package/dist/vault-client.test.d.ts.map +0 -1
- package/dist/vault-client.test.js +0 -1334
- package/dist/vault-client.test.js.map +0 -1
- package/dist/watch-coverage.test.d.ts +0 -2
- package/dist/watch-coverage.test.d.ts.map +0 -1
- package/dist/watch-coverage.test.js +0 -56
- package/dist/watch-coverage.test.js.map +0 -1
- package/dist/watch-owner-lease.test.d.ts +0 -2
- package/dist/watch-owner-lease.test.d.ts.map +0 -1
- package/dist/watch-owner-lease.test.js +0 -353
- package/dist/watch-owner-lease.test.js.map +0 -1
- package/dist/watch-parent.test.d.ts +0 -2
- package/dist/watch-parent.test.d.ts.map +0 -1
- package/dist/watch-parent.test.js +0 -131
- package/dist/watch-parent.test.js.map +0 -1
- package/dist/watch-roots.test.d.ts +0 -18
- package/dist/watch-roots.test.d.ts.map +0 -1
- package/dist/watch-roots.test.js +0 -395
- package/dist/watch-roots.test.js.map +0 -1
- package/dist/watcher-continuity-hot-path.test.d.ts +0 -2
- package/dist/watcher-continuity-hot-path.test.d.ts.map +0 -1
- package/dist/watcher-continuity-hot-path.test.js +0 -40
- package/dist/watcher-continuity-hot-path.test.js.map +0 -1
- package/dist/watcher-event-gate.test.d.ts +0 -16
- package/dist/watcher-event-gate.test.d.ts.map +0 -1
- package/dist/watcher-event-gate.test.js +0 -191
- package/dist/watcher-event-gate.test.js.map +0 -1
- package/dist/watcher-replan-sweep.test.d.ts +0 -2
- package/dist/watcher-replan-sweep.test.d.ts.map +0 -1
- package/dist/watcher-replan-sweep.test.js +0 -245
- package/dist/watcher-replan-sweep.test.js.map +0 -1
- package/dist/watcher-stdout.test.d.ts +0 -2
- package/dist/watcher-stdout.test.d.ts.map +0 -1
- package/dist/watcher-stdout.test.js +0 -86
- package/dist/watcher-stdout.test.js.map +0 -1
- package/dist/watcher.test.d.ts +0 -2
- package/dist/watcher.test.d.ts.map +0 -1
- package/dist/watcher.test.js +0 -4301
- package/dist/watcher.test.js.map +0 -1
- package/dist/watcher.warmup-cancel.test.d.ts +0 -2
- package/dist/watcher.warmup-cancel.test.d.ts.map +0 -1
- package/dist/watcher.warmup-cancel.test.js +0 -59
- package/dist/watcher.warmup-cancel.test.js.map +0 -1
- package/dist/watcher.warmup-responsive.test.d.ts +0 -2
- package/dist/watcher.warmup-responsive.test.d.ts.map +0 -1
- package/dist/watcher.warmup-responsive.test.js +0 -62
- package/dist/watcher.warmup-responsive.test.js.map +0 -1
|
@@ -1,1966 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it, vi } from "vitest";
|
|
2
|
-
import { HeapCensus, RunnerHeapTelemetry, sampleRunnerHeap, heapSampleCloudProperties, heapPressureCloudProperties, heapRecycleCloudProperties, heapPressureStderrLine, heapRecycleStderrLine, performHeapHardExit, startRunnerHeapSampling, nodeMajorGcObserver, isFreshGcReading, MAX_FRESH_GC_READING_MS, resolveHeapPressureFraction, resolveHeapSampleIntervalMs, resolveHeapRecycleEnabled, resolveHeapRecycleLiveFraction, resolveHeapRecycleEmergencyFraction, resolveHeapRecycleSustainWindowMs, resolveHeapRecycleMinUptimeMs, resolveHeapCheckpointMs, resolveHeapPinnedFraction, resolveHeapPinnedSustainMs, resolveHeapHardExitFraction, resolveHeapRecycleLatchHardExitMs, resolveHeapFootprintRecycleEnabled, resolveHeapFootprintRecycleBytes, resolveHeapFootprintHardExitBytes, resolveDeclaredHeapCeilingBytes, DEFAULT_HEAP_RECYCLE_LIVE_FRACTION, DEFAULT_HEAP_RECYCLE_EMERGENCY_FRACTION, DEFAULT_HEAP_RECYCLE_SUSTAIN_WINDOW_MS, DEFAULT_HEAP_RECYCLE_MIN_UPTIME_MS, DEFAULT_HEAP_CHECKPOINT_MS, DEFAULT_HEAP_PINNED_FRACTION, DEFAULT_HEAP_PINNED_SUSTAIN_MS, DEFAULT_HEAP_HARD_EXIT_FRACTION, DEFAULT_HEAP_RECYCLE_LATCH_HARD_EXIT_MS, DEFAULT_HEAP_FOOTPRINT_RECYCLE_BYTES, DEFAULT_HEAP_FOOTPRINT_HARD_EXIT_BYTES, } from "./sync-runner-heap-telemetry.js";
|
|
3
|
-
import { emitCloudTelemetry } from "../telemetry-events.js";
|
|
4
|
-
function mutableReaders() {
|
|
5
|
-
const state = {
|
|
6
|
-
used: 0,
|
|
7
|
-
total: 0,
|
|
8
|
-
limit: 1000,
|
|
9
|
-
externalV8: 0,
|
|
10
|
-
rss: 0,
|
|
11
|
-
external: 0,
|
|
12
|
-
arrayBuffers: 0,
|
|
13
|
-
now: 0,
|
|
14
|
-
};
|
|
15
|
-
const readers = {
|
|
16
|
-
getHeapStatistics: () => ({
|
|
17
|
-
used_heap_size: state.used,
|
|
18
|
-
total_heap_size: state.total,
|
|
19
|
-
heap_size_limit: state.limit,
|
|
20
|
-
external_memory: state.externalV8,
|
|
21
|
-
}),
|
|
22
|
-
memoryUsage: () => ({
|
|
23
|
-
rss: state.rss,
|
|
24
|
-
heapTotal: state.total,
|
|
25
|
-
heapUsed: state.used,
|
|
26
|
-
external: state.external,
|
|
27
|
-
arrayBuffers: state.arrayBuffers,
|
|
28
|
-
}),
|
|
29
|
-
monotonicNowMs: () => state.now,
|
|
30
|
-
};
|
|
31
|
-
return { state, readers };
|
|
32
|
-
}
|
|
33
|
-
function fakeScheduler() {
|
|
34
|
-
let handler = null;
|
|
35
|
-
let cleared = false;
|
|
36
|
-
let unrefCalls = 0;
|
|
37
|
-
const scheduler = {
|
|
38
|
-
setInterval: (fn) => {
|
|
39
|
-
handler = fn;
|
|
40
|
-
return {
|
|
41
|
-
unref: () => {
|
|
42
|
-
unrefCalls += 1;
|
|
43
|
-
},
|
|
44
|
-
};
|
|
45
|
-
},
|
|
46
|
-
clearInterval: () => {
|
|
47
|
-
cleared = true;
|
|
48
|
-
handler = null;
|
|
49
|
-
},
|
|
50
|
-
};
|
|
51
|
-
return {
|
|
52
|
-
scheduler,
|
|
53
|
-
tick: () => handler?.(),
|
|
54
|
-
isCleared: () => cleared,
|
|
55
|
-
unrefCalls: () => unrefCalls,
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
/** A manual GC observer whose registered callback the test fires by hand. */
|
|
59
|
-
function fakeGcObserver() {
|
|
60
|
-
let cb = null;
|
|
61
|
-
let started = false;
|
|
62
|
-
let stopped = false;
|
|
63
|
-
const observer = {
|
|
64
|
-
start: (onMajorGc) => {
|
|
65
|
-
cb = onMajorGc;
|
|
66
|
-
started = true;
|
|
67
|
-
},
|
|
68
|
-
stop: () => {
|
|
69
|
-
stopped = true;
|
|
70
|
-
cb = null;
|
|
71
|
-
},
|
|
72
|
-
};
|
|
73
|
-
return {
|
|
74
|
-
observer,
|
|
75
|
-
fire: () => cb?.(),
|
|
76
|
-
started: () => started,
|
|
77
|
-
stopped: () => stopped,
|
|
78
|
-
};
|
|
79
|
-
}
|
|
80
|
-
describe("sampleRunnerHeap", () => {
|
|
81
|
-
it("maps injected v8/process readings to a flat numeric record", () => {
|
|
82
|
-
const census = new HeapCensus();
|
|
83
|
-
census.register("mapA", () => 3);
|
|
84
|
-
census.register("setB", () => 5);
|
|
85
|
-
const { state, readers } = mutableReaders();
|
|
86
|
-
Object.assign(state, {
|
|
87
|
-
used: 111,
|
|
88
|
-
total: 222,
|
|
89
|
-
limit: 2048,
|
|
90
|
-
externalV8: 33,
|
|
91
|
-
rss: 444,
|
|
92
|
-
external: 55,
|
|
93
|
-
arrayBuffers: 66,
|
|
94
|
-
now: 5000,
|
|
95
|
-
});
|
|
96
|
-
const sample = sampleRunnerHeap({ readers, census, sessionStartedAtMs: 1000 });
|
|
97
|
-
expect(sample).toMatchObject({
|
|
98
|
-
usedHeapBytes: 111,
|
|
99
|
-
totalHeapBytes: 222,
|
|
100
|
-
heapLimitBytes: 2048,
|
|
101
|
-
externalV8Bytes: 33,
|
|
102
|
-
rssBytes: 444,
|
|
103
|
-
externalBytes: 55,
|
|
104
|
-
arrayBuffersBytes: 66,
|
|
105
|
-
uptimeMs: 4000,
|
|
106
|
-
censusTotal: 8,
|
|
107
|
-
});
|
|
108
|
-
expect(sample.census).toEqual([
|
|
109
|
-
{ site: "mapA", count: 3 },
|
|
110
|
-
{ site: "setB", count: 5 },
|
|
111
|
-
]);
|
|
112
|
-
});
|
|
113
|
-
it("emits only numeric properties that survive the LOCAL telemetry sanitizer", async () => {
|
|
114
|
-
// NOTE: this exercises hq-cloud's own emit-side sanitizer, which keys on the
|
|
115
|
-
// VALUE type — a numeric value survives regardless of key. It is a DIFFERENT
|
|
116
|
-
// and more permissive layer than hq-pro's ingest contract (the vault's
|
|
117
|
-
// redactProperties, which drops any key not ending in _count/_bytes/_ms or
|
|
118
|
-
// containing a denied substring). The regression this module fixes is that
|
|
119
|
-
// the old `census_<token>` spelling passed HERE but was silently dropped at
|
|
120
|
-
// ingest; the deny-safe `_count` spelling — pinned by the ingest-key-contract
|
|
121
|
-
// test below — is what makes the attribution actually reach the store.
|
|
122
|
-
const census = new HeapCensus();
|
|
123
|
-
census.register("publishedDigestGenerations", () => 12345);
|
|
124
|
-
census.register("knownKinds", () => 10);
|
|
125
|
-
const { state, readers } = mutableReaders();
|
|
126
|
-
Object.assign(state, {
|
|
127
|
-
used: 1,
|
|
128
|
-
total: 2,
|
|
129
|
-
limit: 3,
|
|
130
|
-
externalV8: 4,
|
|
131
|
-
rss: 5,
|
|
132
|
-
external: 6,
|
|
133
|
-
arrayBuffers: 7,
|
|
134
|
-
now: 10,
|
|
135
|
-
});
|
|
136
|
-
const props = heapSampleCloudProperties(sampleRunnerHeap({ readers, census, sessionStartedAtMs: 0 }));
|
|
137
|
-
for (const value of Object.values(props)) {
|
|
138
|
-
expect(typeof value).toBe("number");
|
|
139
|
-
}
|
|
140
|
-
const captured = [];
|
|
141
|
-
const client = {
|
|
142
|
-
postTelemetryEvents: async (batch) => {
|
|
143
|
-
for (const event of batch.events)
|
|
144
|
-
captured.push(event);
|
|
145
|
-
return { accepted: batch.events.length };
|
|
146
|
-
},
|
|
147
|
-
};
|
|
148
|
-
await emitCloudTelemetry(client, {
|
|
149
|
-
eventName: "sync_runner_heap_sample",
|
|
150
|
-
source: "sync-runner-heap",
|
|
151
|
-
properties: props,
|
|
152
|
-
});
|
|
153
|
-
const survived = captured[0]?.properties ?? {};
|
|
154
|
-
expect(survived.census_publishedDigestGenerations_count).toBe(12345);
|
|
155
|
-
expect(survived.heap_used_bytes).toBe(1);
|
|
156
|
-
// No property was dropped by the sanitizer.
|
|
157
|
-
expect(Object.keys(survived).length).toBe(Object.keys(props).length);
|
|
158
|
-
});
|
|
159
|
-
});
|
|
160
|
-
describe("HeapCensus", () => {
|
|
161
|
-
it("rejects non-fixed census tokens and overwrites duplicates so the registry stays bounded", () => {
|
|
162
|
-
const census = new HeapCensus();
|
|
163
|
-
expect(() => census.register("has space", () => 1)).toThrow();
|
|
164
|
-
expect(() => census.register("../path", () => 1)).toThrow();
|
|
165
|
-
census.register("dupe", () => 1);
|
|
166
|
-
census.register("dupe", () => 2);
|
|
167
|
-
expect(census.siteCount).toBe(1);
|
|
168
|
-
expect(census.snapshot()).toEqual([{ site: "dupe", count: 2 }]);
|
|
169
|
-
});
|
|
170
|
-
it("reports 0 for a faulty reader rather than throwing", () => {
|
|
171
|
-
const census = new HeapCensus();
|
|
172
|
-
census.register("ok", () => 4);
|
|
173
|
-
census.register("boom", () => {
|
|
174
|
-
throw new Error("reader exploded");
|
|
175
|
-
});
|
|
176
|
-
expect(census.snapshot()).toEqual([
|
|
177
|
-
{ site: "ok", count: 4 },
|
|
178
|
-
{ site: "boom", count: 0 },
|
|
179
|
-
]);
|
|
180
|
-
});
|
|
181
|
-
});
|
|
182
|
-
describe("RunnerHeapTelemetry", () => {
|
|
183
|
-
it("computes census growth against the session's first sample", () => {
|
|
184
|
-
let a = 1;
|
|
185
|
-
const b = 100;
|
|
186
|
-
const census = new HeapCensus();
|
|
187
|
-
census.register("aMap", () => a);
|
|
188
|
-
census.register("bMap", () => b);
|
|
189
|
-
const { state, readers } = mutableReaders();
|
|
190
|
-
state.limit = 1000;
|
|
191
|
-
state.used = 10;
|
|
192
|
-
const controller = new RunnerHeapTelemetry({
|
|
193
|
-
readers,
|
|
194
|
-
census,
|
|
195
|
-
sessionStartedAtMs: 0,
|
|
196
|
-
pressureThresholdFraction: 0.75,
|
|
197
|
-
});
|
|
198
|
-
controller.sample(); // baseline a=1, b=100
|
|
199
|
-
a = 5000;
|
|
200
|
-
state.used = 800; // 0.8 of limit -> crosses 0.75
|
|
201
|
-
const { pressure } = controller.sample();
|
|
202
|
-
expect(pressure?.topSites[0]).toMatchObject({ site: "aMap", growth: 4999 });
|
|
203
|
-
});
|
|
204
|
-
it("keeps only baseline and latest across many samples (no unbounded history)", () => {
|
|
205
|
-
let n = 0;
|
|
206
|
-
const census = new HeapCensus();
|
|
207
|
-
census.register("n", () => n);
|
|
208
|
-
const { state, readers } = mutableReaders();
|
|
209
|
-
state.limit = 1000;
|
|
210
|
-
state.used = 1;
|
|
211
|
-
const controller = new RunnerHeapTelemetry({
|
|
212
|
-
readers,
|
|
213
|
-
census,
|
|
214
|
-
sessionStartedAtMs: 0,
|
|
215
|
-
pressureThresholdFraction: 0.99,
|
|
216
|
-
});
|
|
217
|
-
for (n = 0; n < 100; n += 1) {
|
|
218
|
-
state.now = n;
|
|
219
|
-
controller.sample();
|
|
220
|
-
}
|
|
221
|
-
expect(controller.baselineSample?.census).toEqual([{ site: "n", count: 0 }]);
|
|
222
|
-
expect(controller.latestSample?.census).toEqual([{ site: "n", count: 99 }]);
|
|
223
|
-
});
|
|
224
|
-
it("emits the pressure record exactly once per rising crossing, calibrated to the injected heap limit", () => {
|
|
225
|
-
const census = new HeapCensus();
|
|
226
|
-
census.register("x", () => 1);
|
|
227
|
-
const { state, readers } = mutableReaders();
|
|
228
|
-
state.limit = 2048 * 1024 * 1024; // desktop-injected 2048 MB ceiling
|
|
229
|
-
const controller = new RunnerHeapTelemetry({
|
|
230
|
-
readers,
|
|
231
|
-
census,
|
|
232
|
-
sessionStartedAtMs: 0,
|
|
233
|
-
pressureThresholdFraction: 0.75,
|
|
234
|
-
});
|
|
235
|
-
state.used = state.limit * 0.5;
|
|
236
|
-
expect(controller.sample().pressure).toBeNull(); // below threshold
|
|
237
|
-
state.used = Math.round(state.limit * 0.76);
|
|
238
|
-
const crossing = controller.sample().pressure; // rising crossing -> one record
|
|
239
|
-
expect(crossing).not.toBeNull();
|
|
240
|
-
expect(crossing?.thresholdFraction).toBe(0.75);
|
|
241
|
-
expect(crossing?.usedFraction).toBeCloseTo(0.76, 2);
|
|
242
|
-
state.used = Math.round(state.limit * 0.8);
|
|
243
|
-
expect(controller.sample().pressure).toBeNull(); // still above -> no repeat
|
|
244
|
-
state.used = Math.round(state.limit * 0.5);
|
|
245
|
-
expect(controller.sample().pressure).toBeNull(); // dropped below -> re-arm
|
|
246
|
-
state.used = Math.round(state.limit * 0.9);
|
|
247
|
-
expect(controller.sample().pressure).not.toBeNull(); // second crossing -> record
|
|
248
|
-
});
|
|
249
|
-
});
|
|
250
|
-
describe("startRunnerHeapSampling", () => {
|
|
251
|
-
it("unrefs the timer, clears it on stop, and never lets a fault reach the caller", () => {
|
|
252
|
-
const census = new HeapCensus();
|
|
253
|
-
// A faulty reader must be swallowed to 0, never thrown out of the sampler.
|
|
254
|
-
census.register("x", () => {
|
|
255
|
-
throw new Error("reader boom");
|
|
256
|
-
});
|
|
257
|
-
const { state, readers } = mutableReaders();
|
|
258
|
-
state.limit = 1000;
|
|
259
|
-
state.used = 900; // 0.9 -> above the 0.75 threshold -> pressure fires
|
|
260
|
-
const fake = fakeScheduler();
|
|
261
|
-
const onSample = vi.fn(() => {
|
|
262
|
-
throw new Error("sample emit boom");
|
|
263
|
-
});
|
|
264
|
-
const onPressure = vi.fn(() => {
|
|
265
|
-
throw new Error("pressure emit boom");
|
|
266
|
-
});
|
|
267
|
-
const handle = startRunnerHeapSampling({
|
|
268
|
-
readers,
|
|
269
|
-
census,
|
|
270
|
-
sessionStartedAtMs: 0,
|
|
271
|
-
pressureThresholdFraction: 0.75,
|
|
272
|
-
intervalMs: 60_000,
|
|
273
|
-
scheduler: fake.scheduler,
|
|
274
|
-
onSample,
|
|
275
|
-
onPressure,
|
|
276
|
-
log: () => { },
|
|
277
|
-
});
|
|
278
|
-
expect(fake.unrefCalls()).toBe(1);
|
|
279
|
-
// A tick with a throwing reader AND throwing emit callbacks must not throw.
|
|
280
|
-
expect(() => fake.tick()).not.toThrow();
|
|
281
|
-
expect(onSample).toHaveBeenCalledTimes(1);
|
|
282
|
-
expect(onPressure).toHaveBeenCalledTimes(1);
|
|
283
|
-
handle.stop();
|
|
284
|
-
expect(fake.isCleared()).toBe(true);
|
|
285
|
-
// Ticks after stop are inert.
|
|
286
|
-
fake.tick();
|
|
287
|
-
expect(onSample).toHaveBeenCalledTimes(1);
|
|
288
|
-
});
|
|
289
|
-
it("drives repeated samples so at least two heap records reach the sink", () => {
|
|
290
|
-
const census = new HeapCensus();
|
|
291
|
-
census.register("x", () => 1);
|
|
292
|
-
const { state, readers } = mutableReaders();
|
|
293
|
-
state.limit = 1000;
|
|
294
|
-
state.used = 1; // never crosses -> only heap samples, no pressure
|
|
295
|
-
const fake = fakeScheduler();
|
|
296
|
-
const samples = [];
|
|
297
|
-
startRunnerHeapSampling({
|
|
298
|
-
readers,
|
|
299
|
-
census,
|
|
300
|
-
sessionStartedAtMs: 0,
|
|
301
|
-
pressureThresholdFraction: 0.75,
|
|
302
|
-
intervalMs: 1,
|
|
303
|
-
scheduler: fake.scheduler,
|
|
304
|
-
onSample: (sample) => samples.push(sample.usedHeapBytes),
|
|
305
|
-
onPressure: () => {
|
|
306
|
-
throw new Error("no crossing expected");
|
|
307
|
-
},
|
|
308
|
-
});
|
|
309
|
-
fake.tick();
|
|
310
|
-
fake.tick();
|
|
311
|
-
expect(samples).toHaveLength(2);
|
|
312
|
-
});
|
|
313
|
-
it("primes the growth baseline at start, so a first crossing already above the threshold still reports real growth", () => {
|
|
314
|
-
let count = 100;
|
|
315
|
-
const census = new HeapCensus();
|
|
316
|
-
census.register("x", () => count);
|
|
317
|
-
const { state, readers } = mutableReaders();
|
|
318
|
-
state.limit = 1000;
|
|
319
|
-
state.used = 900; // already above the 0.75 threshold at the very first tick
|
|
320
|
-
const fake = fakeScheduler();
|
|
321
|
-
const pressures = [];
|
|
322
|
-
startRunnerHeapSampling({
|
|
323
|
-
readers,
|
|
324
|
-
census,
|
|
325
|
-
sessionStartedAtMs: 0,
|
|
326
|
-
pressureThresholdFraction: 0.75,
|
|
327
|
-
intervalMs: 1000,
|
|
328
|
-
scheduler: fake.scheduler,
|
|
329
|
-
onSample: () => { },
|
|
330
|
-
onPressure: (record) => pressures.push(record),
|
|
331
|
-
});
|
|
332
|
-
// Baseline was primed at count=100 before the first interval. Grow, then the
|
|
333
|
-
// first scheduled sample crosses the threshold.
|
|
334
|
-
count = 5000;
|
|
335
|
-
fake.tick();
|
|
336
|
-
expect(pressures).toHaveLength(1);
|
|
337
|
-
// Without priming the baseline would equal the crossing sample and growth
|
|
338
|
-
// would be reported as 0, losing the attribution.
|
|
339
|
-
expect(pressures[0].topSites[0]).toMatchObject({ site: "x", growth: 4900 });
|
|
340
|
-
});
|
|
341
|
-
});
|
|
342
|
-
describe("resolveHeapPressureFraction", () => {
|
|
343
|
-
it("parses a valid fraction and falls back to the default on garbage or out-of-range input", () => {
|
|
344
|
-
expect(resolveHeapPressureFraction("0.6")).toBe(0.6);
|
|
345
|
-
expect(resolveHeapPressureFraction("1")).toBe(1);
|
|
346
|
-
expect(resolveHeapPressureFraction("")).toBe(0.75);
|
|
347
|
-
expect(resolveHeapPressureFraction("2")).toBe(0.75);
|
|
348
|
-
expect(resolveHeapPressureFraction("-1")).toBe(0.75);
|
|
349
|
-
expect(resolveHeapPressureFraction("0.5x")).toBe(0.75); // trailing junk rejected
|
|
350
|
-
expect(resolveHeapPressureFraction(undefined)).toBe(0.75);
|
|
351
|
-
});
|
|
352
|
-
});
|
|
353
|
-
describe("resolveHeapSampleIntervalMs", () => {
|
|
354
|
-
it("parses whole milliseconds and rejects a malformed value like '60s' rather than accepting its prefix", () => {
|
|
355
|
-
expect(resolveHeapSampleIntervalMs("60000")).toBe(60_000);
|
|
356
|
-
expect(resolveHeapSampleIntervalMs("500")).toBe(500);
|
|
357
|
-
// The natural-but-invalid "60s" must NOT become a 60 ms interval.
|
|
358
|
-
expect(resolveHeapSampleIntervalMs("60s")).toBe(60_000);
|
|
359
|
-
expect(resolveHeapSampleIntervalMs("1_000")).toBe(60_000);
|
|
360
|
-
expect(resolveHeapSampleIntervalMs("")).toBe(60_000);
|
|
361
|
-
expect(resolveHeapSampleIntervalMs("0")).toBe(60_000);
|
|
362
|
-
expect(resolveHeapSampleIntervalMs(undefined)).toBe(60_000);
|
|
363
|
-
});
|
|
364
|
-
});
|
|
365
|
-
describe("heapPressureStderrLine", () => {
|
|
366
|
-
it("renders a compact numeric-only pressure line with fixed-token sites", () => {
|
|
367
|
-
const line = heapPressureStderrLine({
|
|
368
|
-
usedHeapBytes: 100,
|
|
369
|
-
heapLimitBytes: 200,
|
|
370
|
-
effectiveLimitBytes: 200,
|
|
371
|
-
declaredCeilingBytes: 0,
|
|
372
|
-
usedFraction: 0.5,
|
|
373
|
-
thresholdFraction: 0.75,
|
|
374
|
-
rssBytes: 300,
|
|
375
|
-
externalBytes: 40,
|
|
376
|
-
uptimeMs: 1000,
|
|
377
|
-
topSites: [{ site: "knownKinds", count: 5, growth: 3 }],
|
|
378
|
-
});
|
|
379
|
-
expect(line).toContain("used=100");
|
|
380
|
-
expect(line).toContain("limit=200");
|
|
381
|
-
expect(line).toContain("uptime_ms=1000");
|
|
382
|
-
expect(line).toContain("top=knownKinds:3");
|
|
383
|
-
});
|
|
384
|
-
});
|
|
385
|
-
// --- Self-recycling governor (GC-anchored post-GC live floor) -----------------
|
|
386
|
-
const MB = 1024 * 1024;
|
|
387
|
-
// Comfortably past the 35-minute uptime floor, so the floor never gates a test
|
|
388
|
-
// that is exercising a different property.
|
|
389
|
-
const PAST_FLOOR_MS = DEFAULT_HEAP_RECYCLE_MIN_UPTIME_MS + 5 * 60_000;
|
|
390
|
-
function governor(overrides = {}) {
|
|
391
|
-
const census = overrides.census ?? new HeapCensus();
|
|
392
|
-
if (!overrides.census)
|
|
393
|
-
census.register("knownKinds", () => 1);
|
|
394
|
-
const { state, readers } = mutableReaders();
|
|
395
|
-
state.limit = overrides.limitBytes ?? 2048 * MB;
|
|
396
|
-
const controller = new RunnerHeapTelemetry({
|
|
397
|
-
readers,
|
|
398
|
-
census,
|
|
399
|
-
sessionStartedAtMs: 0,
|
|
400
|
-
pressureThresholdFraction: 0.75,
|
|
401
|
-
recycleLiveFraction: overrides.liveFraction ?? 0.6,
|
|
402
|
-
recycleSustainWindowMs: overrides.windowMs ?? 120_000,
|
|
403
|
-
recycleEmergencyFraction: overrides.emergencyFraction ?? 0.9,
|
|
404
|
-
recycleMinUptimeMs: overrides.minUptimeMs ?? DEFAULT_HEAP_RECYCLE_MIN_UPTIME_MS,
|
|
405
|
-
recycleEnabled: overrides.recycleEnabled ?? true,
|
|
406
|
-
...(overrides.declaredCeilingBytes !== undefined
|
|
407
|
-
? { declaredCeilingBytes: overrides.declaredCeilingBytes }
|
|
408
|
-
: {}),
|
|
409
|
-
...(overrides.pinnedFraction !== undefined
|
|
410
|
-
? { recyclePinnedFraction: overrides.pinnedFraction }
|
|
411
|
-
: {}),
|
|
412
|
-
...(overrides.pinnedSustainWindowMs !== undefined
|
|
413
|
-
? { recyclePinnedSustainWindowMs: overrides.pinnedSustainWindowMs }
|
|
414
|
-
: {}),
|
|
415
|
-
...(overrides.hardExitFraction !== undefined
|
|
416
|
-
? { hardExitFraction: overrides.hardExitFraction }
|
|
417
|
-
: {}),
|
|
418
|
-
...(overrides.latchHardExitMs !== undefined
|
|
419
|
-
? { latchHardExitMs: overrides.latchHardExitMs }
|
|
420
|
-
: {}),
|
|
421
|
-
...(overrides.latchHardExitObservations !== undefined
|
|
422
|
-
? { latchHardExitObservations: overrides.latchHardExitObservations }
|
|
423
|
-
: {}),
|
|
424
|
-
...(overrides.footprintRecycleEnabled !== undefined
|
|
425
|
-
? { footprintRecycleEnabled: overrides.footprintRecycleEnabled }
|
|
426
|
-
: {}),
|
|
427
|
-
...(overrides.footprintRecycleBytes !== undefined
|
|
428
|
-
? { footprintRecycleBytes: overrides.footprintRecycleBytes }
|
|
429
|
-
: {}),
|
|
430
|
-
...(overrides.footprintHardExitBytes !== undefined
|
|
431
|
-
? { footprintHardExitBytes: overrides.footprintHardExitBytes }
|
|
432
|
-
: {}),
|
|
433
|
-
});
|
|
434
|
-
const setAt = (fraction, nowMs) => {
|
|
435
|
-
state.used = Math.round(state.limit * fraction);
|
|
436
|
-
state.now = nowMs;
|
|
437
|
-
};
|
|
438
|
-
// Footprint driver: a HIGH resident rss (absolute bytes) with a QUIET heap
|
|
439
|
-
// (usedFraction default 0.30, below every heap floor), so only the footprint
|
|
440
|
-
// tier can produce a verdict.
|
|
441
|
-
const setFootprint = (rssBytes, nowMs, usedFraction = 0.3) => {
|
|
442
|
-
state.rss = rssBytes;
|
|
443
|
-
state.used = Math.round(state.limit * usedFraction);
|
|
444
|
-
state.now = nowMs;
|
|
445
|
-
};
|
|
446
|
-
// The cooperative recycle channel (backward-compatible with the shipped tests).
|
|
447
|
-
const observeAt = (fraction, nowMs) => {
|
|
448
|
-
setAt(fraction, nowMs);
|
|
449
|
-
return controller.observeMajorGc().recycle;
|
|
450
|
-
};
|
|
451
|
-
const sampleAt = (fraction, nowMs) => {
|
|
452
|
-
setAt(fraction, nowMs);
|
|
453
|
-
return controller.sample();
|
|
454
|
-
};
|
|
455
|
-
const evaluateAt = (fraction, nowMs) => {
|
|
456
|
-
setAt(fraction, nowMs);
|
|
457
|
-
return controller.evaluate().recycle;
|
|
458
|
-
};
|
|
459
|
-
// The full verdict channel (both cooperative recycle AND hard exit).
|
|
460
|
-
const observeFullAt = (fraction, nowMs) => {
|
|
461
|
-
setAt(fraction, nowMs);
|
|
462
|
-
return controller.observeMajorGc();
|
|
463
|
-
};
|
|
464
|
-
const evaluateFullAt = (fraction, nowMs) => {
|
|
465
|
-
setAt(fraction, nowMs);
|
|
466
|
-
return controller.evaluate();
|
|
467
|
-
};
|
|
468
|
-
return {
|
|
469
|
-
state,
|
|
470
|
-
readers,
|
|
471
|
-
census,
|
|
472
|
-
controller,
|
|
473
|
-
setAt,
|
|
474
|
-
setFootprint,
|
|
475
|
-
observeAt,
|
|
476
|
-
sampleAt,
|
|
477
|
-
evaluateAt,
|
|
478
|
-
observeFullAt,
|
|
479
|
-
evaluateFullAt,
|
|
480
|
-
};
|
|
481
|
-
}
|
|
482
|
-
describe("RunnerHeapTelemetry — recycle governor (primary post-GC live path)", () => {
|
|
483
|
-
it("recycles on a sustained post-GC live floor across the window, past the uptime floor", () => {
|
|
484
|
-
const g = governor();
|
|
485
|
-
// First qualifying post-GC observation at/above the live floor: anchors only.
|
|
486
|
-
expect(g.observeAt(0.62, PAST_FLOOR_MS)).toBeNull();
|
|
487
|
-
// Too soon (less than the 120 s window since the anchor): still no verdict.
|
|
488
|
-
expect(g.observeAt(0.65, PAST_FLOOR_MS + 119_000)).toBeNull();
|
|
489
|
-
// A window later, still at/above the floor: recycle, trigger "live".
|
|
490
|
-
const recycle = g.observeAt(0.66, PAST_FLOOR_MS + 121_000);
|
|
491
|
-
expect(recycle).not.toBeNull();
|
|
492
|
-
expect(recycle?.trigger).toBe("live");
|
|
493
|
-
expect(recycle?.liveFraction).toBeCloseTo(0.66, 2);
|
|
494
|
-
expect(recycle?.observedSustainMs).toBe(121_000);
|
|
495
|
-
expect(recycle?.recycleLiveFraction).toBe(0.6);
|
|
496
|
-
});
|
|
497
|
-
it("is immune to an instantaneous sawtooth: neither the sample path nor a below-floor live reading ever recycles", () => {
|
|
498
|
-
// The exact defect: instantaneous used_heap_size oscillates high (0.60..0.89,
|
|
499
|
-
// never at the 0.90 emergency line) while the POST-GC live floor stays low
|
|
500
|
-
// (0.30). The old rule watched the instantaneous series and would have tripped;
|
|
501
|
-
// the GC-anchored rule watches only the post-GC floor and never does.
|
|
502
|
-
const g = governor();
|
|
503
|
-
const inst = [0.62, 0.30, 0.88, 0.35, 0.89, 0.28, 0.87, 0.40];
|
|
504
|
-
let t = PAST_FLOOR_MS;
|
|
505
|
-
for (const f of inst) {
|
|
506
|
-
// The interval sampler sees the instantaneous sawtooth — emergency only,
|
|
507
|
-
// and 0.89 < 0.90, so it never fires.
|
|
508
|
-
expect(g.sampleAt(f, t).recycle).toBeNull();
|
|
509
|
-
// The post-GC live floor is flat at 0.30, well below the 0.60 floor.
|
|
510
|
-
expect(g.observeAt(0.3, t)).toBeNull();
|
|
511
|
-
t += 60_000;
|
|
512
|
-
}
|
|
513
|
-
});
|
|
514
|
-
it("requires the window between the TWO qualifying observations, and resets the anchor on a dip", () => {
|
|
515
|
-
const g = governor();
|
|
516
|
-
// Two qualifying observations less than a window apart never recycle.
|
|
517
|
-
expect(g.observeAt(0.7, PAST_FLOOR_MS)).toBeNull();
|
|
518
|
-
expect(g.observeAt(0.7, PAST_FLOOR_MS + 60_000)).toBeNull(); // 60 s < 120 s
|
|
519
|
-
// A dip below the floor resets the anchor: the clock since the FIRST
|
|
520
|
-
// observation no longer counts.
|
|
521
|
-
expect(g.observeAt(0.4, PAST_FLOOR_MS + 90_000)).toBeNull(); // dip -> anchor cleared
|
|
522
|
-
expect(g.observeAt(0.7, PAST_FLOOR_MS + 100_000)).toBeNull(); // re-anchor here
|
|
523
|
-
expect(g.observeAt(0.7, PAST_FLOOR_MS + 180_000)).toBeNull(); // only 80 s since re-anchor
|
|
524
|
-
const recycle = g.observeAt(0.7, PAST_FLOOR_MS + 221_000); // 121 s since re-anchor
|
|
525
|
-
expect(recycle).not.toBeNull();
|
|
526
|
-
// The sustain span is measured from the RE-ANCHOR, not the first observation.
|
|
527
|
-
expect(recycle?.observedSustainMs).toBe(121_000);
|
|
528
|
-
});
|
|
529
|
-
it("never recycles (live path) before the uptime floor, however high the live floor climbs", () => {
|
|
530
|
-
const g = governor();
|
|
531
|
-
// Live floor pinned at 0.80 for many observations, but every one is below the
|
|
532
|
-
// 35-minute floor: no verdict, so the desktop's crash streak is never touched.
|
|
533
|
-
let t = 60_000;
|
|
534
|
-
for (let i = 0; i < 40; i += 1) {
|
|
535
|
-
expect(g.observeAt(0.8, t)).toBeNull();
|
|
536
|
-
t += 30_000; // stays under DEFAULT_HEAP_RECYCLE_MIN_UPTIME_MS (35 min)
|
|
537
|
-
}
|
|
538
|
-
// Cross the floor with the window already satisfied -> recycle.
|
|
539
|
-
const recycle = g.observeAt(0.8, DEFAULT_HEAP_RECYCLE_MIN_UPTIME_MS + 1);
|
|
540
|
-
expect(recycle).not.toBeNull();
|
|
541
|
-
expect(recycle?.trigger).toBe("live");
|
|
542
|
-
});
|
|
543
|
-
it("keeps the uptime floor strictly above hq-desktop-app's 30-minute HEALTHY_RUN_WINDOW", () => {
|
|
544
|
-
// A recycle must OUTLIVE the desktop's 30-minute window so should_reset_after_recovery
|
|
545
|
-
// clears the crash streak instead of pacing respawn backoff. The literal is
|
|
546
|
-
// quoted so a future change that would drop the floor to/under it fails here.
|
|
547
|
-
const DESKTOP_HEALTHY_RUN_WINDOW_MS = 1_800_000;
|
|
548
|
-
expect(DEFAULT_HEAP_RECYCLE_MIN_UPTIME_MS).toBe(2_100_000); // 35 min
|
|
549
|
-
expect(DEFAULT_HEAP_RECYCLE_MIN_UPTIME_MS).toBeGreaterThan(DESKTOP_HEALTHY_RUN_WINDOW_MS);
|
|
550
|
-
});
|
|
551
|
-
it("scales the live floor with the reported heap limit, so a larger ceiling never recycles prematurely", () => {
|
|
552
|
-
// 1700 MB is 0.83 of a 2048 MB ceiling (recycles) but only 0.415 of a 4096 MB
|
|
553
|
-
// ceiling (below the 0.60 live floor -> never), because the floor is a
|
|
554
|
-
// fraction of the injected heap_size_limit, not an absolute figure.
|
|
555
|
-
const small = governor({ limitBytes: 2048 * MB });
|
|
556
|
-
small.state.used = 1700 * MB;
|
|
557
|
-
small.state.now = PAST_FLOOR_MS;
|
|
558
|
-
expect(small.controller.observeMajorGc().recycle).toBeNull(); // anchor
|
|
559
|
-
small.state.now = PAST_FLOOR_MS + 121_000;
|
|
560
|
-
expect(small.controller.observeMajorGc().recycle).not.toBeNull();
|
|
561
|
-
const large = governor({ limitBytes: 4096 * MB });
|
|
562
|
-
let t = PAST_FLOOR_MS;
|
|
563
|
-
for (let i = 0; i < 10; i += 1) {
|
|
564
|
-
large.state.used = 1700 * MB; // 0.415 of 4096 MB
|
|
565
|
-
large.state.now = t;
|
|
566
|
-
expect(large.controller.observeMajorGc().recycle).toBeNull();
|
|
567
|
-
t += 121_000;
|
|
568
|
-
}
|
|
569
|
-
});
|
|
570
|
-
it("maintains the anchor on every major GC, so a close below-floor recovery resets the sustain run", () => {
|
|
571
|
-
// The anchor is updated on EVERY observed major GC — there is no throttle
|
|
572
|
-
// that could skip a below-floor recovery and leave a stale anchor faking a
|
|
573
|
-
// continuous run (the reviewed defect).
|
|
574
|
-
const g = governor();
|
|
575
|
-
expect(g.observeAt(0.7, PAST_FLOOR_MS)).toBeNull(); // anchor
|
|
576
|
-
expect(g.observeAt(0.4, PAST_FLOOR_MS + 1_000)).toBeNull(); // 1 s later, below floor -> reset
|
|
577
|
-
expect(g.observeAt(0.7, PAST_FLOOR_MS + 2_000)).toBeNull(); // re-anchor here
|
|
578
|
-
// 121 s past the FIRST anchor but only 119 s past the RE-anchor: no recycle.
|
|
579
|
-
expect(g.observeAt(0.7, PAST_FLOOR_MS + 121_000)).toBeNull();
|
|
580
|
-
// A window past the re-anchor -> recycle, sustain measured from the re-anchor.
|
|
581
|
-
const recycle = g.observeAt(0.7, PAST_FLOOR_MS + 123_000);
|
|
582
|
-
expect(recycle).not.toBeNull();
|
|
583
|
-
expect(recycle?.observedSustainMs).toBe(121_000);
|
|
584
|
-
});
|
|
585
|
-
it("attaches census growth and rss/external/arrayBuffers deltas to the recycle record", () => {
|
|
586
|
-
let entries = 100;
|
|
587
|
-
const census = new HeapCensus();
|
|
588
|
-
census.register("watcherJournalStores", () => entries);
|
|
589
|
-
const g = governor({ census });
|
|
590
|
-
// Prime the growth baseline at session start (entries=100, rss=0), exactly as
|
|
591
|
-
// startRunnerHeapSampling does before the first interval.
|
|
592
|
-
g.controller.primeBaseline();
|
|
593
|
-
expect(g.observeAt(0.7, PAST_FLOOR_MS)).toBeNull(); // anchor
|
|
594
|
-
entries = 900;
|
|
595
|
-
g.state.rss = 1_500 * MB;
|
|
596
|
-
g.state.external = 40 * MB;
|
|
597
|
-
g.state.arrayBuffers = 12 * MB;
|
|
598
|
-
const recycle = g.observeAt(0.7, PAST_FLOOR_MS + 121_000);
|
|
599
|
-
expect(recycle).not.toBeNull();
|
|
600
|
-
expect(recycle?.topSites[0]).toMatchObject({ site: "watcherJournalStores", growth: 800 });
|
|
601
|
-
expect(recycle?.rssGrowthBytes).toBe(1_500 * MB);
|
|
602
|
-
expect(recycle?.externalGrowthBytes).toBe(40 * MB);
|
|
603
|
-
expect(recycle?.arrayBuffersGrowthBytes).toBe(12 * MB);
|
|
604
|
-
expect(recycle?.censusTotal).toBe(900);
|
|
605
|
-
});
|
|
606
|
-
});
|
|
607
|
-
describe("RunnerHeapTelemetry — emergency backstop", () => {
|
|
608
|
-
it("recycles on a single instantaneous sample at/above the emergency fraction, even below the uptime floor", () => {
|
|
609
|
-
const g = governor();
|
|
610
|
-
// Well below the uptime floor, but a single instantaneous sample above the
|
|
611
|
-
// 0.90 emergency line: at that reading V8 is minutes from aborting, recycle now.
|
|
612
|
-
const { recycle } = g.sampleAt(0.92, 30_000);
|
|
613
|
-
expect(recycle).not.toBeNull();
|
|
614
|
-
expect(recycle?.trigger).toBe("emergency");
|
|
615
|
-
expect(recycle?.observedSustainMs).toBe(0);
|
|
616
|
-
});
|
|
617
|
-
it("does not fire the emergency backstop below the emergency fraction", () => {
|
|
618
|
-
const g = governor();
|
|
619
|
-
// 0.89 instantaneous, sustained across many samples below the floor: the
|
|
620
|
-
// emergency arm never fires (and the live path is off — no GC observations).
|
|
621
|
-
let t = 30_000;
|
|
622
|
-
for (let i = 0; i < 20; i += 1) {
|
|
623
|
-
expect(g.sampleAt(0.89, t).recycle).toBeNull();
|
|
624
|
-
t += 60_000;
|
|
625
|
-
}
|
|
626
|
-
});
|
|
627
|
-
it("fires the emergency path even when a post-GC observation is already abort-adjacent (bypassing the window)", () => {
|
|
628
|
-
const g = governor();
|
|
629
|
-
// A post-GC live reading already at 0.95 recycles immediately, without the
|
|
630
|
-
// sustain window and without a prior anchor.
|
|
631
|
-
const recycle = g.observeAt(0.95, 20_000);
|
|
632
|
-
expect(recycle).not.toBeNull();
|
|
633
|
-
expect(recycle?.trigger).toBe("emergency");
|
|
634
|
-
});
|
|
635
|
-
it("evaluate() is a loop-top backstop that runs the emergency check off the timer path", () => {
|
|
636
|
-
const g = governor();
|
|
637
|
-
// The interval sampler never ticked, but a loop-top evaluate() catches an
|
|
638
|
-
// abort-adjacent instantaneous reading — the starved-timer safety net.
|
|
639
|
-
expect(g.evaluateAt(0.6, 30_000)).toBeNull(); // below emergency, no window seen
|
|
640
|
-
const recycle = g.evaluateAt(0.92, 60_000);
|
|
641
|
-
expect(recycle).not.toBeNull();
|
|
642
|
-
expect(recycle?.trigger).toBe("emergency");
|
|
643
|
-
});
|
|
644
|
-
});
|
|
645
|
-
describe("RunnerHeapTelemetry — recycle governor invariants", () => {
|
|
646
|
-
it("emits at most one recycle verdict per session, across every path", () => {
|
|
647
|
-
const g = governor();
|
|
648
|
-
expect(g.observeAt(0.7, PAST_FLOOR_MS)).toBeNull();
|
|
649
|
-
const first = g.observeAt(0.7, PAST_FLOOR_MS + 121_000);
|
|
650
|
-
expect(first).not.toBeNull();
|
|
651
|
-
// No second verdict from any path.
|
|
652
|
-
expect(g.observeAt(0.95, PAST_FLOOR_MS + 200_000)).toBeNull();
|
|
653
|
-
expect(g.sampleAt(0.99, PAST_FLOOR_MS + 260_000).recycle).toBeNull();
|
|
654
|
-
expect(g.evaluateAt(0.99, PAST_FLOOR_MS + 320_000)).toBeNull();
|
|
655
|
-
});
|
|
656
|
-
it("the kill switch disables every recycle path; pressure still behaves as base", () => {
|
|
657
|
-
const g = governor({ recycleEnabled: false });
|
|
658
|
-
let pressures = 0;
|
|
659
|
-
let recycles = 0;
|
|
660
|
-
let t = PAST_FLOOR_MS;
|
|
661
|
-
for (let i = 0; i < 20; i += 1) {
|
|
662
|
-
const { pressure, recycle } = g.sampleAt(0.99, t); // far above any fraction
|
|
663
|
-
if (pressure)
|
|
664
|
-
pressures += 1;
|
|
665
|
-
if (recycle)
|
|
666
|
-
recycles += 1;
|
|
667
|
-
if (g.observeAt(0.99, t))
|
|
668
|
-
recycles += 1;
|
|
669
|
-
if (g.evaluateAt(0.99, t))
|
|
670
|
-
recycles += 1;
|
|
671
|
-
t += 60_000;
|
|
672
|
-
}
|
|
673
|
-
expect(recycles).toBe(0); // governor off — never recycles on ANY path
|
|
674
|
-
expect(pressures).toBe(1); // pressure record still fires once per crossing
|
|
675
|
-
});
|
|
676
|
-
it("a recycle record and stderr line carry only fixed tokens and integers — never a runner byte", () => {
|
|
677
|
-
const SECRET_PATH = "/Users/hassaan/hq/companies/acme-corp/vault/report.md";
|
|
678
|
-
const SLUG = "acme-corp";
|
|
679
|
-
const FILENAME = "report.md";
|
|
680
|
-
const backing = new Map();
|
|
681
|
-
backing.set(SECRET_PATH, 1);
|
|
682
|
-
backing.set(`companies/${SLUG}/notes`, 2);
|
|
683
|
-
backing.set(FILENAME, 3);
|
|
684
|
-
const census = new HeapCensus();
|
|
685
|
-
census.register("watcherJournalStores", () => backing.size);
|
|
686
|
-
census.register("publishedDigestGenerations", () => backing.size);
|
|
687
|
-
const g = governor({ census });
|
|
688
|
-
const recycle = g.sampleAt(0.95, PAST_FLOOR_MS).recycle; // emergency path
|
|
689
|
-
expect(recycle).not.toBeNull();
|
|
690
|
-
const line = heapRecycleStderrLine(recycle);
|
|
691
|
-
const json = JSON.stringify(recycle);
|
|
692
|
-
for (const secret of [SECRET_PATH, SLUG, FILENAME, "/Users/", "companies/"]) {
|
|
693
|
-
expect(line).not.toContain(secret);
|
|
694
|
-
expect(json).not.toContain(secret);
|
|
695
|
-
}
|
|
696
|
-
expect(json).toContain("watcherJournalStores");
|
|
697
|
-
});
|
|
698
|
-
it("the recycle stderr line matches NONE of hq-desktop-app's fatal stderr markers", () => {
|
|
699
|
-
const DESKTOP_FATAL_MARKERS = [
|
|
700
|
-
"javascript heap out of memory",
|
|
701
|
-
"ineffective mark-compacts",
|
|
702
|
-
"fatal error",
|
|
703
|
-
"uncaught exception",
|
|
704
|
-
"unhandledrejection",
|
|
705
|
-
"panicked at",
|
|
706
|
-
"assertion failed",
|
|
707
|
-
"npm error ",
|
|
708
|
-
"npm err! ",
|
|
709
|
-
"enospc",
|
|
710
|
-
"permission denied",
|
|
711
|
-
"no such file or directory",
|
|
712
|
-
"command not found",
|
|
713
|
-
];
|
|
714
|
-
const census = new HeapCensus();
|
|
715
|
-
for (const token of [
|
|
716
|
-
"pendingAppliedUpserts",
|
|
717
|
-
"watcherJournalStores",
|
|
718
|
-
"publishedDigestGenerations",
|
|
719
|
-
"knownKinds",
|
|
720
|
-
"deleteSnapshotEpochs",
|
|
721
|
-
]) {
|
|
722
|
-
census.register(token, () => 7);
|
|
723
|
-
}
|
|
724
|
-
const g = governor({ census });
|
|
725
|
-
expect(g.observeAt(0.7, PAST_FLOOR_MS)).toBeNull();
|
|
726
|
-
const recycle = g.observeAt(0.7, PAST_FLOOR_MS + 121_000);
|
|
727
|
-
const line = heapRecycleStderrLine(recycle);
|
|
728
|
-
const lowered = line.toLowerCase();
|
|
729
|
-
for (const marker of DESKTOP_FATAL_MARKERS) {
|
|
730
|
-
expect(lowered).not.toContain(marker);
|
|
731
|
-
}
|
|
732
|
-
expect(line.trimStart().startsWith("#")).toBe(false);
|
|
733
|
-
expect(line).toContain("hq-sync-runner: heap-recycle");
|
|
734
|
-
expect(line).toContain("trigger=live");
|
|
735
|
-
expect(line).toContain("sustain_ms=");
|
|
736
|
-
});
|
|
737
|
-
});
|
|
738
|
-
describe("RunnerHeapTelemetry — recorded production series replay (regression reopen)", () => {
|
|
739
|
-
// The aborting generation's own recorded telemetry (person#prs_01KQ2TZQMA...,
|
|
740
|
-
// 2026-09-01 18:57..19:48Z). heap_size_limit was 2348810240 B; the SIGABRT
|
|
741
|
-
// landed at uptime 55m16s = 3_316_000 ms.
|
|
742
|
-
const RECORDED_LIMIT_BYTES = 2_348_810_240;
|
|
743
|
-
const ABORT_UPTIME_MS = 3_316_000;
|
|
744
|
-
// The recorded per-sample INSTANTANEOUS used/limit fractions. Max is 0.741 —
|
|
745
|
-
// it never reaches the old 0.78 rule and never two-in-a-row, and never the
|
|
746
|
-
// 0.90 emergency line, so the instantaneous path is inert (the base failure).
|
|
747
|
-
const INSTANTANEOUS = [
|
|
748
|
-
0.072, 0.106, 0.109, 0.458, 0.546, 0.251, 0.592, 0.564, 0.559, 0.427, 0.457,
|
|
749
|
-
0.564, 0.397, 0.584, 0.438, 0.523, 0.64, 0.548, 0.66, 0.664, 0.512, 0.513,
|
|
750
|
-
0.69, 0.703, 0.741,
|
|
751
|
-
];
|
|
752
|
-
it("base instantaneous logic is inert: no sample-path recycle across the entire recorded series", () => {
|
|
753
|
-
const g = governor({ limitBytes: RECORDED_LIMIT_BYTES });
|
|
754
|
-
let recycles = 0;
|
|
755
|
-
let t = 4.24 * 60_000;
|
|
756
|
-
for (const f of INSTANTANEOUS) {
|
|
757
|
-
if (g.sampleAt(f, t).recycle)
|
|
758
|
-
recycles += 1;
|
|
759
|
-
t += 2 * 60_000;
|
|
760
|
-
}
|
|
761
|
-
expect(recycles).toBe(0); // the exact inertness the regression exhibited
|
|
762
|
-
});
|
|
763
|
-
it("GC-anchored logic produces exactly one live recycle before the recorded 55m16s abort", () => {
|
|
764
|
-
// The implied POST-GC live floor: monotonic, crossing 0.60 between ~36.7 and
|
|
765
|
-
// ~39 min (the lower envelope of the instantaneous series). One major-GC
|
|
766
|
-
// observation per minute.
|
|
767
|
-
const LIVE_FLOOR = [
|
|
768
|
-
{ uptimeMin: 4, fraction: 0.07 },
|
|
769
|
-
{ uptimeMin: 12, fraction: 0.11 },
|
|
770
|
-
{ uptimeMin: 20, fraction: 0.25 },
|
|
771
|
-
{ uptimeMin: 28, fraction: 0.42 },
|
|
772
|
-
{ uptimeMin: 34, fraction: 0.51 },
|
|
773
|
-
{ uptimeMin: 37, fraction: 0.6 }, // crosses the floor here -> anchor
|
|
774
|
-
{ uptimeMin: 39, fraction: 0.63 }, // >= 120 s later -> recycle
|
|
775
|
-
{ uptimeMin: 46, fraction: 0.69 },
|
|
776
|
-
{ uptimeMin: 52, fraction: 0.74 },
|
|
777
|
-
];
|
|
778
|
-
const g = governor({ limitBytes: RECORDED_LIMIT_BYTES });
|
|
779
|
-
const verdicts = [];
|
|
780
|
-
for (const point of LIVE_FLOOR) {
|
|
781
|
-
const recycle = g.observeAt(point.fraction, point.uptimeMin * 60_000);
|
|
782
|
-
if (recycle)
|
|
783
|
-
verdicts.push(recycle);
|
|
784
|
-
}
|
|
785
|
-
expect(verdicts).toHaveLength(1);
|
|
786
|
-
expect(verdicts[0].trigger).toBe("live");
|
|
787
|
-
expect(verdicts[0].uptimeMs).toBeLessThan(ABORT_UPTIME_MS); // wins the race to V8
|
|
788
|
-
expect(verdicts[0].uptimeMs).toBeGreaterThanOrEqual(DEFAULT_HEAP_RECYCLE_MIN_UPTIME_MS);
|
|
789
|
-
// ~16 minutes of headroom before the recorded abort.
|
|
790
|
-
expect(ABORT_UPTIME_MS - verdicts[0].uptimeMs).toBeGreaterThan(10 * 60_000);
|
|
791
|
-
});
|
|
792
|
-
});
|
|
793
|
-
describe("ingest key contract — every emitted property key survives hq-pro redactProperties", () => {
|
|
794
|
-
// hq-pro's raw-telemetry-envelope ingest keeps a numeric property ONLY when its
|
|
795
|
-
// key matches this shape, ends in _count/_bytes/_ms, and contains none of these
|
|
796
|
-
// denied substrings. Quoted here as literals: a future key that violates the
|
|
797
|
-
// contract (the exact defect that dropped every census field to date) fails.
|
|
798
|
-
const KEY_SHAPE = /^[A-Za-z][A-Za-z0-9_]{0,63}$/;
|
|
799
|
-
const DENY_PARTS = [
|
|
800
|
-
"authorization",
|
|
801
|
-
"body",
|
|
802
|
-
"content",
|
|
803
|
-
"cookie",
|
|
804
|
-
"email",
|
|
805
|
-
"file",
|
|
806
|
-
"message",
|
|
807
|
-
"password",
|
|
808
|
-
"path",
|
|
809
|
-
"prompt",
|
|
810
|
-
"query",
|
|
811
|
-
"secret",
|
|
812
|
-
"text",
|
|
813
|
-
"token",
|
|
814
|
-
"transcript",
|
|
815
|
-
];
|
|
816
|
-
// Every FIXED census token the production runner registers (watch-loop ownMaps
|
|
817
|
-
// + the decoupled emitter/watcher tokens). If a new token is added, add it here.
|
|
818
|
-
const PRODUCTION_CENSUS_TOKENS = [
|
|
819
|
-
"pendingAppliedUpserts",
|
|
820
|
-
"pendingWatcherEntries",
|
|
821
|
-
"pendingWatcherChanges",
|
|
822
|
-
"pendingWatcherDroppedRouteHints",
|
|
823
|
-
"quarantinedDeleteRoots",
|
|
824
|
-
"watcherJournalStores",
|
|
825
|
-
"watcherJournalRows",
|
|
826
|
-
"targetedJournalSessions",
|
|
827
|
-
"targetedJournalRows",
|
|
828
|
-
"cachedAreaJournalMigrations",
|
|
829
|
-
"pendingSlowPasses",
|
|
830
|
-
"pendingFastPasses",
|
|
831
|
-
"lastPublishedDigests",
|
|
832
|
-
"publishedDigestGenerations",
|
|
833
|
-
"deferredPublishes",
|
|
834
|
-
"durablePublishedHashRetries",
|
|
835
|
-
"initialPublishedDigests",
|
|
836
|
-
"knownKinds",
|
|
837
|
-
"deleteSnapshotEpochs",
|
|
838
|
-
"ignoreFilterMemo",
|
|
839
|
-
];
|
|
840
|
-
function assertIngestSafe(props) {
|
|
841
|
-
for (const key of Object.keys(props)) {
|
|
842
|
-
const lower = key.toLowerCase();
|
|
843
|
-
expect(KEY_SHAPE.test(key), `key shape: ${key}`).toBe(true);
|
|
844
|
-
expect(lower.endsWith("count") || lower.endsWith("bytes") || lower.endsWith("ms"), `key suffix: ${key}`).toBe(true);
|
|
845
|
-
for (const part of DENY_PARTS) {
|
|
846
|
-
expect(lower.includes(part), `deny part "${part}" in ${key}`).toBe(false);
|
|
847
|
-
}
|
|
848
|
-
}
|
|
849
|
-
}
|
|
850
|
-
function fullCensus() {
|
|
851
|
-
const census = new HeapCensus();
|
|
852
|
-
for (const token of PRODUCTION_CENSUS_TOKENS)
|
|
853
|
-
census.register(token, () => 7);
|
|
854
|
-
return census;
|
|
855
|
-
}
|
|
856
|
-
it("heapSampleCloudProperties keys are all ingest-safe", () => {
|
|
857
|
-
const { readers } = mutableReaders();
|
|
858
|
-
const sample = sampleRunnerHeap({ readers, census: fullCensus(), sessionStartedAtMs: 0 });
|
|
859
|
-
assertIngestSafe(heapSampleCloudProperties(sample));
|
|
860
|
-
});
|
|
861
|
-
it("heapPressureCloudProperties keys are all ingest-safe", () => {
|
|
862
|
-
const census = fullCensus();
|
|
863
|
-
const { state, readers } = mutableReaders();
|
|
864
|
-
state.limit = 2048 * MB;
|
|
865
|
-
state.used = Math.round(2048 * MB * 0.8);
|
|
866
|
-
const controller = new RunnerHeapTelemetry({
|
|
867
|
-
readers,
|
|
868
|
-
census,
|
|
869
|
-
sessionStartedAtMs: 0,
|
|
870
|
-
pressureThresholdFraction: 0.75,
|
|
871
|
-
});
|
|
872
|
-
const { pressure } = controller.sample();
|
|
873
|
-
expect(pressure).not.toBeNull();
|
|
874
|
-
assertIngestSafe(heapPressureCloudProperties(pressure));
|
|
875
|
-
});
|
|
876
|
-
it("heapRecycleCloudProperties keys are all ingest-safe (every trigger)", () => {
|
|
877
|
-
const emergency = governor({ census: fullCensus() }).sampleAt(0.95, PAST_FLOOR_MS).recycle;
|
|
878
|
-
expect(emergency).not.toBeNull();
|
|
879
|
-
assertIngestSafe(heapRecycleCloudProperties(emergency));
|
|
880
|
-
const g = governor({ census: fullCensus() });
|
|
881
|
-
expect(g.observeAt(0.7, PAST_FLOOR_MS)).toBeNull();
|
|
882
|
-
const live = g.observeAt(0.7, PAST_FLOOR_MS + 121_000);
|
|
883
|
-
expect(live).not.toBeNull();
|
|
884
|
-
assertIngestSafe(heapRecycleCloudProperties(live));
|
|
885
|
-
// A footprint recycle carries the three NEW keys (trigger flag + two byte
|
|
886
|
-
// floors); they must satisfy the same contract or ingest would drop them.
|
|
887
|
-
const gFoot = governor({ census: fullCensus(), minUptimeMs: 0 });
|
|
888
|
-
gFoot.setFootprint(4600 * MB, 30_000); // > 4096 recycle floor, < 4864 hard floor
|
|
889
|
-
const footprint = gFoot.controller.evaluate().recycle;
|
|
890
|
-
expect(footprint?.trigger).toBe("footprint");
|
|
891
|
-
const footProps = heapRecycleCloudProperties(footprint);
|
|
892
|
-
assertIngestSafe(footProps);
|
|
893
|
-
expect(footProps.recycle_footprint_trigger_count).toBe(1);
|
|
894
|
-
expect(footProps.recycle_footprint_recycle_floor_bytes).toBe(DEFAULT_HEAP_FOOTPRINT_RECYCLE_BYTES);
|
|
895
|
-
expect(footProps.recycle_footprint_hard_exit_floor_bytes).toBe(DEFAULT_HEAP_FOOTPRINT_HARD_EXIT_BYTES);
|
|
896
|
-
// A heap trigger reports 0 for the footprint trigger flag.
|
|
897
|
-
expect(heapRecycleCloudProperties(live).recycle_footprint_trigger_count).toBe(0);
|
|
898
|
-
});
|
|
899
|
-
});
|
|
900
|
-
describe("startRunnerHeapSampling — GC observer + recycle callback", () => {
|
|
901
|
-
it("drives the governor to a LIVE recycle via the injected GC observer and swallows a throwing onRecycle", () => {
|
|
902
|
-
const census = new HeapCensus();
|
|
903
|
-
census.register("x", () => 1);
|
|
904
|
-
const { state, readers } = mutableReaders();
|
|
905
|
-
state.limit = 2048 * MB;
|
|
906
|
-
const fake = fakeScheduler();
|
|
907
|
-
const gc = fakeGcObserver();
|
|
908
|
-
const recycles = [];
|
|
909
|
-
const handle = startRunnerHeapSampling({
|
|
910
|
-
readers,
|
|
911
|
-
census,
|
|
912
|
-
sessionStartedAtMs: 0,
|
|
913
|
-
pressureThresholdFraction: 0.75,
|
|
914
|
-
recycleLiveFraction: 0.6,
|
|
915
|
-
recycleSustainWindowMs: 120_000,
|
|
916
|
-
recycleEmergencyFraction: 0.9,
|
|
917
|
-
recycleMinUptimeMs: DEFAULT_HEAP_RECYCLE_MIN_UPTIME_MS,
|
|
918
|
-
intervalMs: 1,
|
|
919
|
-
scheduler: fake.scheduler,
|
|
920
|
-
gcObserver: gc.observer,
|
|
921
|
-
onSample: () => { },
|
|
922
|
-
onPressure: () => { },
|
|
923
|
-
onRecycle: (record) => {
|
|
924
|
-
recycles.push(record);
|
|
925
|
-
throw new Error("recycle emit boom"); // must be swallowed
|
|
926
|
-
},
|
|
927
|
-
log: () => { },
|
|
928
|
-
});
|
|
929
|
-
expect(gc.started()).toBe(true);
|
|
930
|
-
// First major GC at/above the live floor: anchors, no verdict.
|
|
931
|
-
state.used = Math.round(state.limit * 0.7);
|
|
932
|
-
state.now = PAST_FLOOR_MS;
|
|
933
|
-
expect(() => gc.fire()).not.toThrow();
|
|
934
|
-
expect(recycles).toHaveLength(0);
|
|
935
|
-
// A window later: the second major GC produces the live recycle (throw swallowed).
|
|
936
|
-
state.now = PAST_FLOOR_MS + 121_000;
|
|
937
|
-
expect(() => gc.fire()).not.toThrow();
|
|
938
|
-
expect(recycles).toHaveLength(1);
|
|
939
|
-
expect(recycles[0].trigger).toBe("live");
|
|
940
|
-
// At most once per session, even as GCs continue.
|
|
941
|
-
state.now = PAST_FLOOR_MS + 300_000;
|
|
942
|
-
expect(() => gc.fire()).not.toThrow();
|
|
943
|
-
expect(recycles).toHaveLength(1);
|
|
944
|
-
handle.stop();
|
|
945
|
-
expect(gc.stopped()).toBe(true);
|
|
946
|
-
});
|
|
947
|
-
it("the loop-top evaluate() fires the emergency backstop when the interval timer never ticks", () => {
|
|
948
|
-
const census = new HeapCensus();
|
|
949
|
-
census.register("x", () => 1);
|
|
950
|
-
const { state, readers } = mutableReaders();
|
|
951
|
-
state.limit = 2048 * MB;
|
|
952
|
-
const fake = fakeScheduler();
|
|
953
|
-
const recycles = [];
|
|
954
|
-
const handle = startRunnerHeapSampling({
|
|
955
|
-
readers,
|
|
956
|
-
census,
|
|
957
|
-
sessionStartedAtMs: 0,
|
|
958
|
-
pressureThresholdFraction: 0.75,
|
|
959
|
-
recycleEmergencyFraction: 0.9,
|
|
960
|
-
intervalMs: 60_000,
|
|
961
|
-
scheduler: fake.scheduler,
|
|
962
|
-
onSample: () => { },
|
|
963
|
-
onPressure: () => { },
|
|
964
|
-
onRecycle: (record) => recycles.push(record),
|
|
965
|
-
log: () => { },
|
|
966
|
-
});
|
|
967
|
-
// The timer never ticked. An abort-adjacent instantaneous heap: the loop-top
|
|
968
|
-
// evaluate() catches it anyway.
|
|
969
|
-
state.used = Math.round(state.limit * 0.93);
|
|
970
|
-
state.now = 30_000;
|
|
971
|
-
handle.evaluate();
|
|
972
|
-
expect(recycles).toHaveLength(1);
|
|
973
|
-
expect(recycles[0].trigger).toBe("emergency");
|
|
974
|
-
});
|
|
975
|
-
it("produces no recycle when the governor is disabled by option", () => {
|
|
976
|
-
const census = new HeapCensus();
|
|
977
|
-
census.register("x", () => 1);
|
|
978
|
-
const { state, readers } = mutableReaders();
|
|
979
|
-
state.limit = 1000;
|
|
980
|
-
state.used = 999;
|
|
981
|
-
state.now = PAST_FLOOR_MS;
|
|
982
|
-
const fake = fakeScheduler();
|
|
983
|
-
const gc = fakeGcObserver();
|
|
984
|
-
const recycles = [];
|
|
985
|
-
const handle = startRunnerHeapSampling({
|
|
986
|
-
readers,
|
|
987
|
-
census,
|
|
988
|
-
sessionStartedAtMs: 0,
|
|
989
|
-
pressureThresholdFraction: 0.75,
|
|
990
|
-
recycleEnabled: false,
|
|
991
|
-
recycleMinUptimeMs: 0,
|
|
992
|
-
intervalMs: 1,
|
|
993
|
-
scheduler: fake.scheduler,
|
|
994
|
-
gcObserver: gc.observer,
|
|
995
|
-
onSample: () => { },
|
|
996
|
-
onPressure: () => { },
|
|
997
|
-
onRecycle: (record) => recycles.push(record),
|
|
998
|
-
log: () => { },
|
|
999
|
-
});
|
|
1000
|
-
for (let i = 0; i < 10; i += 1) {
|
|
1001
|
-
fake.tick();
|
|
1002
|
-
gc.fire();
|
|
1003
|
-
handle.evaluate();
|
|
1004
|
-
}
|
|
1005
|
-
expect(recycles).toHaveLength(0);
|
|
1006
|
-
});
|
|
1007
|
-
});
|
|
1008
|
-
describe("isFreshGcReading", () => {
|
|
1009
|
-
it("accepts a promptly-captured reading and rejects a stale (blocked-loop) one", () => {
|
|
1010
|
-
expect(isFreshGcReading(1000, 1000)).toBe(true); // captured at the GC end
|
|
1011
|
-
expect(isFreshGcReading(1000, 1000 + MAX_FRESH_GC_READING_MS)).toBe(true); // exactly at the bound
|
|
1012
|
-
expect(isFreshGcReading(1000, 1000 + MAX_FRESH_GC_READING_MS + 1)).toBe(false); // just over
|
|
1013
|
-
expect(isFreshGcReading(1000, 5000)).toBe(false); // blocked loop: 4 s stale reading
|
|
1014
|
-
expect(isFreshGcReading(1000, 900)).toBe(true); // reading at/just before the GC end
|
|
1015
|
-
// Missing entry timing degrades to "not fresh" (skip), never a false accept.
|
|
1016
|
-
expect(isFreshGcReading(Number.NaN, 1000)).toBe(false);
|
|
1017
|
-
expect(isFreshGcReading(1000, Number.NaN)).toBe(false);
|
|
1018
|
-
expect(isFreshGcReading(1000, 1400, 500)).toBe(true); // custom threshold honored
|
|
1019
|
-
});
|
|
1020
|
-
});
|
|
1021
|
-
describe("nodeMajorGcObserver", () => {
|
|
1022
|
-
it("constructs, observes major GCs, and disconnects without throwing", () => {
|
|
1023
|
-
const observer = nodeMajorGcObserver();
|
|
1024
|
-
let fires = 0;
|
|
1025
|
-
expect(() => observer.start(() => (fires += 1))).not.toThrow();
|
|
1026
|
-
// Force a real major GC if the runtime exposes it; otherwise this is a no-op
|
|
1027
|
-
// and the observer simply never fires — both are acceptable (degrade to
|
|
1028
|
-
// silence). The contract under test is that neither start nor stop throws.
|
|
1029
|
-
const globalGc = globalThis.gc;
|
|
1030
|
-
if (typeof globalGc === "function") {
|
|
1031
|
-
try {
|
|
1032
|
-
globalGc({ type: "major" });
|
|
1033
|
-
}
|
|
1034
|
-
catch {
|
|
1035
|
-
globalGc();
|
|
1036
|
-
}
|
|
1037
|
-
}
|
|
1038
|
-
expect(() => observer.stop()).not.toThrow();
|
|
1039
|
-
expect(fires).toBeGreaterThanOrEqual(0);
|
|
1040
|
-
});
|
|
1041
|
-
it("start() twice and stop() before start() never throw (degrade to silence)", () => {
|
|
1042
|
-
const observer = nodeMajorGcObserver();
|
|
1043
|
-
expect(() => observer.stop()).not.toThrow(); // stop before start
|
|
1044
|
-
expect(() => observer.start(() => { })).not.toThrow();
|
|
1045
|
-
expect(() => observer.start(() => { })).not.toThrow(); // re-start
|
|
1046
|
-
expect(() => observer.stop()).not.toThrow();
|
|
1047
|
-
expect(() => observer.stop()).not.toThrow(); // double stop
|
|
1048
|
-
});
|
|
1049
|
-
});
|
|
1050
|
-
describe("resolveHeapRecycleEnabled", () => {
|
|
1051
|
-
it("is ON by default and disabled only by an explicit whole-string off value", () => {
|
|
1052
|
-
expect(resolveHeapRecycleEnabled(undefined)).toBe(true);
|
|
1053
|
-
expect(resolveHeapRecycleEnabled("")).toBe(true);
|
|
1054
|
-
expect(resolveHeapRecycleEnabled("1")).toBe(true);
|
|
1055
|
-
expect(resolveHeapRecycleEnabled("true")).toBe(true);
|
|
1056
|
-
expect(resolveHeapRecycleEnabled("0")).toBe(false);
|
|
1057
|
-
expect(resolveHeapRecycleEnabled("false")).toBe(false);
|
|
1058
|
-
expect(resolveHeapRecycleEnabled("NO")).toBe(false);
|
|
1059
|
-
expect(resolveHeapRecycleEnabled(" off ")).toBe(false);
|
|
1060
|
-
// A typo keeps the governor armed rather than silently disabling it.
|
|
1061
|
-
expect(resolveHeapRecycleEnabled("disabled")).toBe(true);
|
|
1062
|
-
});
|
|
1063
|
-
});
|
|
1064
|
-
describe("resolveHeapRecycleLiveFraction", () => {
|
|
1065
|
-
it("parses a valid fraction and falls back to 0.60 on garbage or out-of-range input", () => {
|
|
1066
|
-
expect(resolveHeapRecycleLiveFraction("0.5")).toBe(0.5);
|
|
1067
|
-
expect(resolveHeapRecycleLiveFraction("1")).toBe(1);
|
|
1068
|
-
expect(resolveHeapRecycleLiveFraction(undefined)).toBe(DEFAULT_HEAP_RECYCLE_LIVE_FRACTION);
|
|
1069
|
-
expect(resolveHeapRecycleLiveFraction("")).toBe(DEFAULT_HEAP_RECYCLE_LIVE_FRACTION);
|
|
1070
|
-
expect(resolveHeapRecycleLiveFraction("2")).toBe(DEFAULT_HEAP_RECYCLE_LIVE_FRACTION);
|
|
1071
|
-
expect(resolveHeapRecycleLiveFraction("-1")).toBe(DEFAULT_HEAP_RECYCLE_LIVE_FRACTION);
|
|
1072
|
-
expect(resolveHeapRecycleLiveFraction("0.6x")).toBe(DEFAULT_HEAP_RECYCLE_LIVE_FRACTION);
|
|
1073
|
-
});
|
|
1074
|
-
});
|
|
1075
|
-
describe("resolveHeapRecycleEmergencyFraction", () => {
|
|
1076
|
-
it("parses a valid fraction and falls back to 0.90 on garbage or out-of-range input", () => {
|
|
1077
|
-
expect(resolveHeapRecycleEmergencyFraction("0.95")).toBe(0.95);
|
|
1078
|
-
expect(resolveHeapRecycleEmergencyFraction("1")).toBe(1);
|
|
1079
|
-
expect(resolveHeapRecycleEmergencyFraction(undefined)).toBe(DEFAULT_HEAP_RECYCLE_EMERGENCY_FRACTION);
|
|
1080
|
-
expect(resolveHeapRecycleEmergencyFraction("")).toBe(DEFAULT_HEAP_RECYCLE_EMERGENCY_FRACTION);
|
|
1081
|
-
expect(resolveHeapRecycleEmergencyFraction("2")).toBe(DEFAULT_HEAP_RECYCLE_EMERGENCY_FRACTION);
|
|
1082
|
-
expect(resolveHeapRecycleEmergencyFraction("0.9x")).toBe(DEFAULT_HEAP_RECYCLE_EMERGENCY_FRACTION);
|
|
1083
|
-
});
|
|
1084
|
-
});
|
|
1085
|
-
describe("resolveHeapRecycleSustainWindowMs", () => {
|
|
1086
|
-
it("parses whole ms >= 0 (0 removes the window) and rejects '2m' rather than its prefix", () => {
|
|
1087
|
-
expect(resolveHeapRecycleSustainWindowMs("120000")).toBe(120_000);
|
|
1088
|
-
expect(resolveHeapRecycleSustainWindowMs("0")).toBe(0); // 0 is a valid window
|
|
1089
|
-
expect(resolveHeapRecycleSustainWindowMs(undefined)).toBe(DEFAULT_HEAP_RECYCLE_SUSTAIN_WINDOW_MS);
|
|
1090
|
-
expect(resolveHeapRecycleSustainWindowMs("2m")).toBe(DEFAULT_HEAP_RECYCLE_SUSTAIN_WINDOW_MS);
|
|
1091
|
-
expect(resolveHeapRecycleSustainWindowMs("-1")).toBe(DEFAULT_HEAP_RECYCLE_SUSTAIN_WINDOW_MS);
|
|
1092
|
-
expect(resolveHeapRecycleSustainWindowMs("")).toBe(DEFAULT_HEAP_RECYCLE_SUSTAIN_WINDOW_MS);
|
|
1093
|
-
});
|
|
1094
|
-
});
|
|
1095
|
-
describe("resolveHeapRecycleMinUptimeMs", () => {
|
|
1096
|
-
it("parses whole ms >= 0 (0 disables the floor) and rejects '35m' rather than its prefix", () => {
|
|
1097
|
-
expect(resolveHeapRecycleMinUptimeMs("60000")).toBe(60_000);
|
|
1098
|
-
expect(resolveHeapRecycleMinUptimeMs("0")).toBe(0); // 0 is a valid floor
|
|
1099
|
-
expect(resolveHeapRecycleMinUptimeMs(undefined)).toBe(DEFAULT_HEAP_RECYCLE_MIN_UPTIME_MS);
|
|
1100
|
-
expect(resolveHeapRecycleMinUptimeMs("35m")).toBe(DEFAULT_HEAP_RECYCLE_MIN_UPTIME_MS);
|
|
1101
|
-
expect(resolveHeapRecycleMinUptimeMs("-1")).toBe(DEFAULT_HEAP_RECYCLE_MIN_UPTIME_MS);
|
|
1102
|
-
expect(resolveHeapRecycleMinUptimeMs("")).toBe(DEFAULT_HEAP_RECYCLE_MIN_UPTIME_MS);
|
|
1103
|
-
});
|
|
1104
|
-
});
|
|
1105
|
-
// --- v2-layered governor: effective limit, pinned + hard-exit tiers ----------
|
|
1106
|
-
describe("RunnerHeapTelemetry — effective-limit re-denomination", () => {
|
|
1107
|
-
it("divides every fraction by min(declared, reported) so a declared ceiling makes the trigger real", () => {
|
|
1108
|
-
const declared = 2048 * MB;
|
|
1109
|
-
const reported = 2233 * MB; // Node-25 reported limit sits above the declared ceiling
|
|
1110
|
-
const withDeclared = governor({
|
|
1111
|
-
declaredCeilingBytes: declared,
|
|
1112
|
-
limitBytes: reported,
|
|
1113
|
-
minUptimeMs: 0,
|
|
1114
|
-
});
|
|
1115
|
-
// 0.91 of the DECLARED ceiling: 1863 MB. Over effective (declared) that is
|
|
1116
|
-
// 0.91 >= the 0.90 emergency line, so a single post-GC reading recycles.
|
|
1117
|
-
withDeclared.state.used = Math.round(declared * 0.91);
|
|
1118
|
-
withDeclared.state.now = 30_000;
|
|
1119
|
-
const armed = withDeclared.controller.observeMajorGc().recycle;
|
|
1120
|
-
expect(armed).not.toBeNull();
|
|
1121
|
-
expect(armed?.trigger).toBe("emergency");
|
|
1122
|
-
expect(armed?.effectiveLimitBytes).toBe(declared);
|
|
1123
|
-
expect(armed?.declaredCeilingBytes).toBe(declared);
|
|
1124
|
-
expect(armed?.heapLimitBytes).toBe(reported); // reported kept for provenance
|
|
1125
|
-
expect(armed?.usedFraction).toBeCloseTo(0.91, 2);
|
|
1126
|
-
});
|
|
1127
|
-
it("the SAME bytes never reach emergency when the reported limit is the denominator (no declared)", () => {
|
|
1128
|
-
const declared = 2048 * MB;
|
|
1129
|
-
const reported = 2233 * MB;
|
|
1130
|
-
const noDeclared = governor({ declaredCeilingBytes: null, limitBytes: reported, minUptimeMs: 0 });
|
|
1131
|
-
// 1863 MB is 0.834 of the reported limit — below the 0.90 emergency line, so
|
|
1132
|
-
// the post-GC path only anchors the live floor (no immediate recycle).
|
|
1133
|
-
noDeclared.state.used = Math.round(declared * 0.91);
|
|
1134
|
-
noDeclared.state.now = 30_000;
|
|
1135
|
-
expect(noDeclared.controller.observeMajorGc().recycle).toBeNull();
|
|
1136
|
-
});
|
|
1137
|
-
it("falls back to the reported limit (bit-identical shipped semantics) when no ceiling is declared", () => {
|
|
1138
|
-
const g = governor({ declaredCeilingBytes: null });
|
|
1139
|
-
expect(g.observeAt(0.62, PAST_FLOOR_MS)).toBeNull();
|
|
1140
|
-
const recycle = g.observeAt(0.66, PAST_FLOOR_MS + 121_000);
|
|
1141
|
-
expect(recycle?.trigger).toBe("live");
|
|
1142
|
-
expect(recycle?.effectiveLimitBytes).toBe(recycle?.heapLimitBytes);
|
|
1143
|
-
expect(recycle?.declaredCeilingBytes).toBe(0);
|
|
1144
|
-
});
|
|
1145
|
-
});
|
|
1146
|
-
describe("RunnerHeapTelemetry — pinned tier (T2, instantaneous sustained)", () => {
|
|
1147
|
-
it("arms on an instantaneous fraction held at/above the floor across the window, past the uptime floor", () => {
|
|
1148
|
-
const g = governor({ pinnedFraction: 0.84, pinnedSustainWindowMs: 20_000, minUptimeMs: 0 });
|
|
1149
|
-
expect(g.sampleAt(0.85, 0).recycle).toBeNull(); // anchor (count 1)
|
|
1150
|
-
expect(g.sampleAt(0.85, 10_000).recycle).toBeNull(); // 10 s < 20 s window
|
|
1151
|
-
const { recycle } = g.sampleAt(0.85, 21_000); // 21 s >= window, count >= 2
|
|
1152
|
-
expect(recycle).not.toBeNull();
|
|
1153
|
-
expect(recycle?.trigger).toBe("pinned");
|
|
1154
|
-
expect(recycle?.reason).toBe("cooperative");
|
|
1155
|
-
expect(recycle?.pinnedFraction).toBe(0.84);
|
|
1156
|
-
});
|
|
1157
|
-
it("resets its anchor on any single below-floor observation (a healthy sawtooth never sustains)", () => {
|
|
1158
|
-
const g = governor({ pinnedFraction: 0.84, pinnedSustainWindowMs: 20_000, minUptimeMs: 0 });
|
|
1159
|
-
expect(g.sampleAt(0.85, 0).recycle).toBeNull(); // anchor
|
|
1160
|
-
expect(g.sampleAt(0.80, 5_000).recycle).toBeNull(); // dip -> anchor reset
|
|
1161
|
-
expect(g.sampleAt(0.85, 25_000).recycle).toBeNull(); // re-anchor here (count 1)
|
|
1162
|
-
const { recycle } = g.sampleAt(0.85, 46_000); // 21 s past the re-anchor
|
|
1163
|
-
expect(recycle?.trigger).toBe("pinned");
|
|
1164
|
-
});
|
|
1165
|
-
it("honours the 35-minute uptime floor (unlike the emergency tier)", () => {
|
|
1166
|
-
const g = governor({ pinnedFraction: 0.84, pinnedSustainWindowMs: 0, minUptimeMs: 100_000 });
|
|
1167
|
-
expect(g.sampleAt(0.85, 0).recycle).toBeNull(); // anchor
|
|
1168
|
-
expect(g.sampleAt(0.85, 50_000).recycle).toBeNull(); // window satisfied but 50 s < 100 s floor
|
|
1169
|
-
const { recycle } = g.sampleAt(0.85, 120_000); // past the floor
|
|
1170
|
-
expect(recycle?.trigger).toBe("pinned");
|
|
1171
|
-
});
|
|
1172
|
-
it("never fires off the post-GC live path, and the live path never fires 'pinned'", () => {
|
|
1173
|
-
// Instantaneous readings drive pinned; post-GC readings drive live. They keep
|
|
1174
|
-
// separate anchors, so neither impersonates the other.
|
|
1175
|
-
const gPinned = governor({ pinnedFraction: 0.84, pinnedSustainWindowMs: 0, minUptimeMs: 0 });
|
|
1176
|
-
gPinned.sampleAt(0.85, 0); // instantaneous only
|
|
1177
|
-
expect(gPinned.sampleAt(0.85, 1_000).recycle?.trigger).toBe("pinned");
|
|
1178
|
-
const gLive = governor({ minUptimeMs: 0 });
|
|
1179
|
-
gLive.observeAt(0.65, 0);
|
|
1180
|
-
expect(gLive.observeAt(0.65, 121_000)?.trigger).toBe("live");
|
|
1181
|
-
});
|
|
1182
|
-
});
|
|
1183
|
-
describe("RunnerHeapTelemetry — hard-exit tiers (T4)", () => {
|
|
1184
|
-
it("hard_fraction: a single instantaneous observation at/above the hard line hard-exits with no prior latch", () => {
|
|
1185
|
-
const g = governor({ minUptimeMs: DEFAULT_HEAP_RECYCLE_MIN_UPTIME_MS });
|
|
1186
|
-
// 0.96 >= 0.955, well BELOW the uptime floor: the hard tier bypasses the floor
|
|
1187
|
-
// exactly like a SIGABRT would, and preempts the cooperative recycle.
|
|
1188
|
-
const { recycle, hardExit } = g.evaluateFullAt(0.96, 10_000);
|
|
1189
|
-
expect(recycle).toBeNull();
|
|
1190
|
-
expect(hardExit).not.toBeNull();
|
|
1191
|
-
expect(hardExit?.reason).toBe("hard_fraction");
|
|
1192
|
-
expect(hardExit?.trigger).toBe("emergency"); // no prior latch -> instantaneous family
|
|
1193
|
-
expect(hardExit?.hardExitFraction).toBe(DEFAULT_HEAP_HARD_EXIT_FRACTION);
|
|
1194
|
-
});
|
|
1195
|
-
it("latch_timeout: an unhonoured cooperative latch ages out by TIME into a hard exit", () => {
|
|
1196
|
-
const g = governor({ minUptimeMs: 0, latchHardExitMs: 60_000, latchHardExitObservations: 1_000_000 });
|
|
1197
|
-
const armed = g.evaluateFullAt(0.92, 0); // cooperative emergency latch
|
|
1198
|
-
expect(armed.recycle?.reason).toBe("cooperative");
|
|
1199
|
-
expect(armed.recycle?.trigger).toBe("emergency");
|
|
1200
|
-
expect(g.evaluateFullAt(0.92, 30_000).hardExit).toBeNull(); // 30 s < 60 s deadline
|
|
1201
|
-
const late = g.evaluateFullAt(0.92, 61_000); // 61 s >= deadline
|
|
1202
|
-
expect(late.recycle).toBeNull();
|
|
1203
|
-
expect(late.hardExit?.reason).toBe("latch_timeout");
|
|
1204
|
-
expect(late.hardExit?.trigger).toBe("emergency"); // inherits the latched tier
|
|
1205
|
-
expect(late.hardExit?.postLatchObservations).toBeGreaterThanOrEqual(1);
|
|
1206
|
-
});
|
|
1207
|
-
it("latch_timeout: ages out by observation COUNT before the time deadline", () => {
|
|
1208
|
-
const g = governor({ minUptimeMs: 0, latchHardExitMs: 10 * 60_000, latchHardExitObservations: 3 });
|
|
1209
|
-
g.evaluateFullAt(0.92, 0); // cooperative emergency latch
|
|
1210
|
-
expect(g.evaluateFullAt(0.7, 1_000).hardExit).toBeNull(); // post-latch obs 1 < 3
|
|
1211
|
-
expect(g.evaluateFullAt(0.7, 2_000).hardExit).toBeNull(); // post-latch obs 2 < 3
|
|
1212
|
-
const third = g.evaluateFullAt(0.7, 3_000); // post-latch obs 3 >= 3
|
|
1213
|
-
expect(third.hardExit?.reason).toBe("latch_timeout");
|
|
1214
|
-
});
|
|
1215
|
-
it("the latch-age arm fires from EVERY observation path (interval sample, loop-top, post-GC)", () => {
|
|
1216
|
-
const g = governor({ minUptimeMs: 0, latchHardExitMs: 0, latchHardExitObservations: 1_000_000 });
|
|
1217
|
-
// latchHardExitMs=0: the first post-latch observation from any path hard-exits.
|
|
1218
|
-
g.observeFullAt(0.92, 0); // post-GC emergency cooperative latch
|
|
1219
|
-
const viaSample = g.sampleAt(0.7, 1);
|
|
1220
|
-
expect(viaSample.hardExit?.reason).toBe("latch_timeout");
|
|
1221
|
-
});
|
|
1222
|
-
it("fires at most one hard exit per session and never a cooperative verdict afterwards", () => {
|
|
1223
|
-
const g = governor({ minUptimeMs: 0, latchHardExitMs: 0, latchHardExitObservations: 1_000_000 });
|
|
1224
|
-
g.evaluateFullAt(0.92, 0); // cooperative latch
|
|
1225
|
-
expect(g.evaluateFullAt(0.7, 1).hardExit?.reason).toBe("latch_timeout"); // first hard exit
|
|
1226
|
-
expect(g.evaluateFullAt(0.99, 2).hardExit).toBeNull(); // already hard-signalled
|
|
1227
|
-
expect(g.evaluateFullAt(0.99, 3).recycle).toBeNull();
|
|
1228
|
-
});
|
|
1229
|
-
});
|
|
1230
|
-
describe("RunnerHeapTelemetry — tier ordering and derivation pin (guard-constant policy)", () => {
|
|
1231
|
-
it("pins 0.60 < 0.84 < 0.90 < 0.955 and each against the observed abort band", () => {
|
|
1232
|
-
expect(DEFAULT_HEAP_RECYCLE_LIVE_FRACTION).toBe(0.6);
|
|
1233
|
-
expect(DEFAULT_HEAP_PINNED_FRACTION).toBe(0.84);
|
|
1234
|
-
expect(DEFAULT_HEAP_RECYCLE_EMERGENCY_FRACTION).toBe(0.9);
|
|
1235
|
-
expect(DEFAULT_HEAP_HARD_EXIT_FRACTION).toBe(0.955);
|
|
1236
|
-
const order = [
|
|
1237
|
-
DEFAULT_HEAP_RECYCLE_LIVE_FRACTION,
|
|
1238
|
-
DEFAULT_HEAP_PINNED_FRACTION,
|
|
1239
|
-
DEFAULT_HEAP_RECYCLE_EMERGENCY_FRACTION,
|
|
1240
|
-
DEFAULT_HEAP_HARD_EXIT_FRACTION,
|
|
1241
|
-
];
|
|
1242
|
-
for (let i = 1; i < order.length; i += 1)
|
|
1243
|
-
expect(order[i]).toBeGreaterThan(order[i - 1]);
|
|
1244
|
-
// Pinned sits above the max healthy instantaneous evidence (~0.816 of declared)
|
|
1245
|
-
// and below the legacy abort floor (1788/2048 = 0.873).
|
|
1246
|
-
expect(DEFAULT_HEAP_PINNED_FRACTION).toBeGreaterThan(0.816);
|
|
1247
|
-
expect(DEFAULT_HEAP_PINNED_FRACTION).toBeLessThan(1788 / 2048);
|
|
1248
|
-
// Emergency and hard-exit both sit strictly below the minimum post-#471
|
|
1249
|
-
// observed abort ratio (1995/2048 = 0.974), so both preempt the SIGABRT.
|
|
1250
|
-
expect(DEFAULT_HEAP_RECYCLE_EMERGENCY_FRACTION).toBeLessThan(1995 / 2048);
|
|
1251
|
-
expect(DEFAULT_HEAP_HARD_EXIT_FRACTION).toBeLessThan(1995 / 2048);
|
|
1252
|
-
});
|
|
1253
|
-
});
|
|
1254
|
-
describe("RunnerHeapTelemetry — kill switch disables every tier (incl. hard)", () => {
|
|
1255
|
-
it("disables live, pinned, emergency, hard_fraction, AND latch_timeout at once", () => {
|
|
1256
|
-
const g = governor({ recycleEnabled: false, minUptimeMs: 0 });
|
|
1257
|
-
let t = 0;
|
|
1258
|
-
for (let i = 0; i < 20; i += 1) {
|
|
1259
|
-
// 0.99 would fire hard_fraction; 0.85 sustained would fire pinned — none do.
|
|
1260
|
-
const viaSample = g.sampleAt(0.99, t);
|
|
1261
|
-
expect(viaSample.recycle).toBeNull();
|
|
1262
|
-
expect(viaSample.hardExit).toBeNull();
|
|
1263
|
-
const viaEval = g.evaluateFullAt(0.99, t + 1);
|
|
1264
|
-
expect(viaEval.recycle).toBeNull();
|
|
1265
|
-
expect(viaEval.hardExit).toBeNull();
|
|
1266
|
-
const viaGc = g.observeFullAt(0.99, t + 2);
|
|
1267
|
-
expect(viaGc.recycle).toBeNull();
|
|
1268
|
-
expect(viaGc.hardExit).toBeNull();
|
|
1269
|
-
t += 60_000;
|
|
1270
|
-
}
|
|
1271
|
-
});
|
|
1272
|
-
});
|
|
1273
|
-
// --- Resident-footprint tier (T5): the regression-reopen fix ------------------
|
|
1274
|
-
//
|
|
1275
|
-
// The four heap tiers denominate every fraction in min(declaredCeiling,
|
|
1276
|
-
// heap_size_limit) — the V8 old-space cap. A non-heap (external/ArrayBuffer)
|
|
1277
|
-
// burst outruns that cap while used_heap_size stays quiet, so NONE of them can
|
|
1278
|
-
// see a 5,598 MB resident process with a calm heap; the desktop's whole-tree
|
|
1279
|
-
// footprint backstop is left to pre-empt it, which IS this Sentry issue. The
|
|
1280
|
-
// footprint tier adds an absolute rss BUDGET the heap tiers never had.
|
|
1281
|
-
describe("RunnerHeapTelemetry — resident-footprint tier (T5)", () => {
|
|
1282
|
-
// hq-desktop-app crates/hq-desktop-core: WATCHER_FOOTPRINT_HARD_CEILING_MB.
|
|
1283
|
-
const DESKTOP_FOOTPRINT_HARD_CEILING_MB = 5120;
|
|
1284
|
-
// Max npx launcher sibling across the 8 decomposed events (8-84 MB observed).
|
|
1285
|
-
const LAUNCHER_HEADROOM_MB = 84;
|
|
1286
|
-
// Highest healthy cold-ramp tree peak that must NOT recycle.
|
|
1287
|
-
const HIGHEST_COLD_RAMP_MB = 3623;
|
|
1288
|
-
// Drive ONE footprint reading (rss in bytes, quiet heap) through a chosen path
|
|
1289
|
-
// on a fresh session, so the one-verdict-per-session latch never carries over.
|
|
1290
|
-
function footprintVerdict(rssBytes, path) {
|
|
1291
|
-
const g = governor({ minUptimeMs: 0 });
|
|
1292
|
-
g.setFootprint(rssBytes, 30_000);
|
|
1293
|
-
if (path === "sample") {
|
|
1294
|
-
const r = g.controller.sample();
|
|
1295
|
-
return { recycle: r.recycle, hardExit: r.hardExit };
|
|
1296
|
-
}
|
|
1297
|
-
if (path === "evaluate")
|
|
1298
|
-
return g.controller.evaluate();
|
|
1299
|
-
return g.controller.observeMajorGc();
|
|
1300
|
-
}
|
|
1301
|
-
it("pins the budgets from the production traces and hq-desktop-app's published constants", () => {
|
|
1302
|
-
expect(DEFAULT_HEAP_FOOTPRINT_RECYCLE_BYTES).toBe(4096 * MB);
|
|
1303
|
-
expect(DEFAULT_HEAP_FOOTPRINT_HARD_EXIT_BYTES).toBe(4864 * MB);
|
|
1304
|
-
// Both budgets recycle the runner's OWN rss STRICTLY below the desktop's
|
|
1305
|
-
// whole-tree pre-empt line (5120 MB) minus the launcher-sibling headroom.
|
|
1306
|
-
const preemptLineBytes = (DESKTOP_FOOTPRINT_HARD_CEILING_MB - LAUNCHER_HEADROOM_MB) * MB;
|
|
1307
|
-
expect(DEFAULT_HEAP_FOOTPRINT_RECYCLE_BYTES).toBeLessThan(preemptLineBytes);
|
|
1308
|
-
expect(DEFAULT_HEAP_FOOTPRINT_HARD_EXIT_BYTES).toBeLessThan(preemptLineBytes);
|
|
1309
|
-
// Cooperative < hard: a moderate climb recycles cooperatively; only an
|
|
1310
|
-
// abort-adjacent reading hard-exits.
|
|
1311
|
-
expect(DEFAULT_HEAP_FOOTPRINT_RECYCLE_BYTES).toBeLessThan(DEFAULT_HEAP_FOOTPRINT_HARD_EXIT_BYTES);
|
|
1312
|
-
// The cooperative floor sits above the highest healthy cold ramp, so a large
|
|
1313
|
-
// sync never trades this issue for respawn churn (the r1 failure mode).
|
|
1314
|
-
expect(DEFAULT_HEAP_FOOTPRINT_RECYCLE_BYTES).toBeGreaterThan(HIGHEST_COLD_RAMP_MB * MB);
|
|
1315
|
-
});
|
|
1316
|
-
it("replays the evented production traces: runaways recycle/hard-exit, cold ramps never do", () => {
|
|
1317
|
-
// The four cold ramps (healthy large syncs) must NEVER produce a verdict, on
|
|
1318
|
-
// ANY path. On BASE the tier does not exist and these produce no verdict
|
|
1319
|
-
// either — but neither do the runaways below, which is the base defect.
|
|
1320
|
-
for (const treeMb of [2776, 3080, 3337, 3623]) {
|
|
1321
|
-
for (const path of ["sample", "evaluate", "gc"]) {
|
|
1322
|
-
const v = footprintVerdict(treeMb * MB, path);
|
|
1323
|
-
expect(v.recycle, `cold ramp ${treeMb} via ${path}`).toBeNull();
|
|
1324
|
-
expect(v.hardExit, `cold ramp ${treeMb} via ${path}`).toBeNull();
|
|
1325
|
-
}
|
|
1326
|
-
}
|
|
1327
|
-
// 4493 MB (below the hard floor): a COOPERATIVE footprint recycle.
|
|
1328
|
-
for (const path of ["sample", "evaluate", "gc"]) {
|
|
1329
|
-
const v = footprintVerdict(4493 * MB, path);
|
|
1330
|
-
expect(v.recycle?.trigger, `4493 via ${path}`).toBe("footprint");
|
|
1331
|
-
expect(v.recycle?.reason).toBe("cooperative");
|
|
1332
|
-
expect(v.recycle?.observedSustainMs).toBe(0); // single observation, no window
|
|
1333
|
-
expect(v.hardExit).toBeNull();
|
|
1334
|
-
}
|
|
1335
|
-
// 5598 and 7949 MB (at/above the hard floor, already past the desktop line on
|
|
1336
|
-
// the runner's own rss): a footprint HARD EXIT, no waiting for a pass boundary.
|
|
1337
|
-
for (const rssMb of [5598, 7949]) {
|
|
1338
|
-
for (const path of ["sample", "evaluate", "gc"]) {
|
|
1339
|
-
const v = footprintVerdict(rssMb * MB, path);
|
|
1340
|
-
expect(v.hardExit?.trigger, `${rssMb} via ${path}`).toBe("footprint");
|
|
1341
|
-
expect(v.hardExit?.reason).toBe("hard_fraction");
|
|
1342
|
-
expect(v.recycle).toBeNull();
|
|
1343
|
-
}
|
|
1344
|
-
}
|
|
1345
|
-
});
|
|
1346
|
-
it("arms on a SINGLE observation with no sustain window and below the uptime floor", () => {
|
|
1347
|
-
// Default 35-minute uptime floor; a burst at 5 s uptime still recycles, so an
|
|
1348
|
-
// 83-1142 MB/s climb can never outrun the tier the way a sustain window would.
|
|
1349
|
-
const g = governor(); // default minUptimeMs = 35 min
|
|
1350
|
-
g.setFootprint(4200 * MB, 5_000);
|
|
1351
|
-
const { recycle } = g.controller.evaluate();
|
|
1352
|
-
expect(recycle?.trigger).toBe("footprint");
|
|
1353
|
-
expect(recycle?.reason).toBe("cooperative");
|
|
1354
|
-
expect(recycle?.observedSustainMs).toBe(0);
|
|
1355
|
-
expect(recycle?.uptimeMs).toBe(5_000);
|
|
1356
|
-
});
|
|
1357
|
-
it("carries rss/external/arrayBuffers, their growth deltas, and census attribution", () => {
|
|
1358
|
-
let entries = 100;
|
|
1359
|
-
const census = new HeapCensus();
|
|
1360
|
-
census.register("watcherJournalStores", () => entries);
|
|
1361
|
-
const g = governor({ census, minUptimeMs: 0 });
|
|
1362
|
-
g.controller.primeBaseline(); // baseline at entries=100, rss=0
|
|
1363
|
-
entries = 900;
|
|
1364
|
-
g.state.external = 40 * MB;
|
|
1365
|
-
g.state.arrayBuffers = 2000 * MB; // the non-heap growth that the heap cap misses
|
|
1366
|
-
g.setFootprint(4600 * MB, 30_000);
|
|
1367
|
-
const { recycle } = g.controller.evaluate();
|
|
1368
|
-
expect(recycle?.trigger).toBe("footprint");
|
|
1369
|
-
expect(recycle?.rssBytes).toBe(4600 * MB);
|
|
1370
|
-
expect(recycle?.rssGrowthBytes).toBe(4600 * MB);
|
|
1371
|
-
expect(recycle?.arrayBuffersGrowthBytes).toBe(2000 * MB);
|
|
1372
|
-
expect(recycle?.externalGrowthBytes).toBe(40 * MB);
|
|
1373
|
-
expect(recycle?.topSites[0]).toMatchObject({ site: "watcherJournalStores", growth: 800 });
|
|
1374
|
-
});
|
|
1375
|
-
it("adds a verdict only where the heap tiers are silent — a heap verdict always wins", () => {
|
|
1376
|
-
// Quiet heap + high rss -> footprint fires (heap silent).
|
|
1377
|
-
expect(footprintVerdict(4200 * MB, "evaluate").recycle?.trigger).toBe("footprint");
|
|
1378
|
-
// A heap emergency with rss ALSO over the footprint budget keeps trigger
|
|
1379
|
-
// "emergency": the heap tiers are evaluated first and their verdict is returned
|
|
1380
|
-
// unchanged, so the footprint tier never suppresses or re-denominates it.
|
|
1381
|
-
const g = governor({ minUptimeMs: 0 });
|
|
1382
|
-
g.state.rss = 9000 * MB;
|
|
1383
|
-
g.state.used = Math.round(g.state.limit * 0.95); // heap emergency line
|
|
1384
|
-
g.state.now = 30_000;
|
|
1385
|
-
expect(g.controller.evaluate().recycle?.trigger).toBe("emergency");
|
|
1386
|
-
// A heap HARD-fraction with rss over the footprint budget stays a heap hard
|
|
1387
|
-
// exit (trigger from the instantaneous family, not "footprint").
|
|
1388
|
-
const gh = governor({ minUptimeMs: 0 });
|
|
1389
|
-
gh.state.rss = 9000 * MB;
|
|
1390
|
-
gh.state.used = Math.round(gh.state.limit * 0.96); // hard-exit fraction
|
|
1391
|
-
gh.state.now = 30_000;
|
|
1392
|
-
const hv = gh.controller.evaluate();
|
|
1393
|
-
expect(hv.hardExit?.reason).toBe("hard_fraction");
|
|
1394
|
-
expect(hv.hardExit?.trigger).toBe("emergency"); // NOT footprint
|
|
1395
|
-
});
|
|
1396
|
-
it("fires from evaluate() (loop-top) when the interval timer never ticks and no GC is observed", () => {
|
|
1397
|
-
// The starved-sampler backstop: the module's own 11-minute-hole safety net now
|
|
1398
|
-
// covers footprint too. Only evaluate() is ever called here.
|
|
1399
|
-
const g = governor({ minUptimeMs: 0 });
|
|
1400
|
-
g.setFootprint(4200 * MB, 30_000);
|
|
1401
|
-
expect(g.controller.evaluate().recycle?.trigger).toBe("footprint");
|
|
1402
|
-
});
|
|
1403
|
-
it("hard-exits on a single reading at/above the footprint hard budget", () => {
|
|
1404
|
-
const g = governor({ minUptimeMs: 0 });
|
|
1405
|
-
g.setFootprint(DEFAULT_HEAP_FOOTPRINT_HARD_EXIT_BYTES, 30_000); // exactly the floor
|
|
1406
|
-
const { recycle, hardExit } = g.controller.evaluate();
|
|
1407
|
-
expect(recycle).toBeNull();
|
|
1408
|
-
expect(hardExit?.trigger).toBe("footprint");
|
|
1409
|
-
expect(hardExit?.reason).toBe("hard_fraction");
|
|
1410
|
-
expect(hardExit?.rssBytes).toBe(DEFAULT_HEAP_FOOTPRINT_HARD_EXIT_BYTES);
|
|
1411
|
-
});
|
|
1412
|
-
it("ages an unhonoured footprint cooperative latch into a footprint latch_timeout hard exit", () => {
|
|
1413
|
-
const g = governor({ minUptimeMs: 0, latchHardExitMs: 0, latchHardExitObservations: 1_000_000 });
|
|
1414
|
-
g.setFootprint(4200 * MB, 0); // cooperative footprint latch (below the hard floor)
|
|
1415
|
-
expect(g.controller.evaluate().recycle?.trigger).toBe("footprint");
|
|
1416
|
-
// latchHardExitMs=0: the next observation ages the latch out. The hard exit
|
|
1417
|
-
// inherits the footprint trigger from the latched tier.
|
|
1418
|
-
g.setFootprint(4200 * MB, 1);
|
|
1419
|
-
const late = g.controller.evaluate();
|
|
1420
|
-
expect(late.recycle).toBeNull();
|
|
1421
|
-
expect(late.hardExit?.reason).toBe("latch_timeout");
|
|
1422
|
-
expect(late.hardExit?.trigger).toBe("footprint");
|
|
1423
|
-
});
|
|
1424
|
-
it("recycles at most once per session across every path, then never again", () => {
|
|
1425
|
-
const g = governor({ minUptimeMs: 0 });
|
|
1426
|
-
g.setFootprint(4200 * MB, 0);
|
|
1427
|
-
expect(g.controller.evaluate().recycle?.trigger).toBe("footprint"); // the one recycle
|
|
1428
|
-
g.setFootprint(4600 * MB, 60_000);
|
|
1429
|
-
expect(g.controller.sample().recycle).toBeNull();
|
|
1430
|
-
g.setFootprint(4600 * MB, 120_000);
|
|
1431
|
-
expect(g.controller.observeMajorGc().recycle).toBeNull();
|
|
1432
|
-
});
|
|
1433
|
-
it("the footprint-specific disable silences ONLY the footprint tier; heap tiers still fire", () => {
|
|
1434
|
-
const g = governor({ footprintRecycleEnabled: false, minUptimeMs: 0 });
|
|
1435
|
-
g.setFootprint(9000 * MB, 30_000); // way over both budgets, quiet heap
|
|
1436
|
-
const off = g.controller.evaluate();
|
|
1437
|
-
expect(off.recycle).toBeNull();
|
|
1438
|
-
expect(off.hardExit).toBeNull();
|
|
1439
|
-
// A heap emergency on the same governor still fires — base behaviour intact.
|
|
1440
|
-
g.state.used = Math.round(g.state.limit * 0.95);
|
|
1441
|
-
g.state.now = 60_000;
|
|
1442
|
-
expect(g.controller.evaluate().recycle?.trigger).toBe("emergency");
|
|
1443
|
-
});
|
|
1444
|
-
it("the global kill switch also disables the footprint tier", () => {
|
|
1445
|
-
const g = governor({ recycleEnabled: false, minUptimeMs: 0 });
|
|
1446
|
-
g.setFootprint(9000 * MB, 30_000);
|
|
1447
|
-
const v = g.controller.evaluate();
|
|
1448
|
-
expect(v.recycle).toBeNull();
|
|
1449
|
-
expect(v.hardExit).toBeNull();
|
|
1450
|
-
});
|
|
1451
|
-
it("a disabled footprint tier reports 0 for both budget floors in the verdict record", () => {
|
|
1452
|
-
// A reader must be able to tell an OFF tier (0) from a live one (the budget).
|
|
1453
|
-
const g = governor({ footprintRecycleEnabled: false, minUptimeMs: 0 });
|
|
1454
|
-
const emg = g.sampleAt(0.95, PAST_FLOOR_MS).recycle; // heap emergency still fires
|
|
1455
|
-
expect(emg?.footprintRecycleBytes).toBe(0);
|
|
1456
|
-
expect(emg?.footprintHardExitBytes).toBe(0);
|
|
1457
|
-
// With the tier ON, a heap verdict still carries the configured budgets.
|
|
1458
|
-
const g2 = governor({ minUptimeMs: 0 });
|
|
1459
|
-
const emg2 = g2.sampleAt(0.95, PAST_FLOOR_MS).recycle;
|
|
1460
|
-
expect(emg2?.footprintRecycleBytes).toBe(DEFAULT_HEAP_FOOTPRINT_RECYCLE_BYTES);
|
|
1461
|
-
expect(emg2?.footprintHardExitBytes).toBe(DEFAULT_HEAP_FOOTPRINT_HARD_EXIT_BYTES);
|
|
1462
|
-
});
|
|
1463
|
-
it("honours a custom footprint budget and never fires just below it", () => {
|
|
1464
|
-
const g = governor({ footprintRecycleBytes: 3000 * MB, footprintHardExitBytes: 4000 * MB, minUptimeMs: 0 });
|
|
1465
|
-
g.setFootprint(2999 * MB, 10_000);
|
|
1466
|
-
expect(g.controller.evaluate().recycle).toBeNull(); // 1 MB under the floor
|
|
1467
|
-
g.setFootprint(3000 * MB, 20_000);
|
|
1468
|
-
expect(g.controller.evaluate().recycle?.trigger).toBe("footprint"); // exactly the floor
|
|
1469
|
-
});
|
|
1470
|
-
it("a disabled footprint tier adds NO rss read, so a throwing memoryUsage never breaks a quiet-heap observation", () => {
|
|
1471
|
-
// Regression: the kill switch must restore the exact prior observation path.
|
|
1472
|
-
// With the tier off and a quiet heap (no verdict), evaluate()/observeMajorGc()
|
|
1473
|
-
// must not touch memoryUsage() at all — so a throwing reader cannot swallow the
|
|
1474
|
-
// observation before the heap tiers run.
|
|
1475
|
-
const census = new HeapCensus();
|
|
1476
|
-
census.register("x", () => 1);
|
|
1477
|
-
let memReads = 0;
|
|
1478
|
-
const readers = {
|
|
1479
|
-
getHeapStatistics: () => ({
|
|
1480
|
-
used_heap_size: Math.round(2048 * MB * 0.3), // quiet heap: no heap verdict
|
|
1481
|
-
total_heap_size: 0,
|
|
1482
|
-
heap_size_limit: 2048 * MB,
|
|
1483
|
-
external_memory: 0,
|
|
1484
|
-
}),
|
|
1485
|
-
memoryUsage: () => {
|
|
1486
|
-
memReads += 1;
|
|
1487
|
-
throw new Error("memoryUsage boom");
|
|
1488
|
-
},
|
|
1489
|
-
monotonicNowMs: () => 30_000,
|
|
1490
|
-
};
|
|
1491
|
-
const controller = new RunnerHeapTelemetry({
|
|
1492
|
-
readers,
|
|
1493
|
-
census,
|
|
1494
|
-
sessionStartedAtMs: 0,
|
|
1495
|
-
pressureThresholdFraction: 0.75,
|
|
1496
|
-
recycleMinUptimeMs: 0,
|
|
1497
|
-
footprintRecycleEnabled: false,
|
|
1498
|
-
});
|
|
1499
|
-
expect(() => controller.evaluate()).not.toThrow();
|
|
1500
|
-
expect(() => controller.observeMajorGc()).not.toThrow();
|
|
1501
|
-
expect(controller.evaluate().recycle).toBeNull();
|
|
1502
|
-
expect(controller.observeMajorGc().recycle).toBeNull();
|
|
1503
|
-
expect(memReads).toBe(0); // the disabled tier read rss zero times
|
|
1504
|
-
});
|
|
1505
|
-
it("records the rss value that CROSSED the budget, not a later drifted re-read", () => {
|
|
1506
|
-
// The decision reads rss once; the verdict sample re-reads memoryUsage a hair
|
|
1507
|
-
// later. A reader whose rss drops between the two reads must not make the
|
|
1508
|
-
// durable record claim an rss below the floor that triggered the hard exit.
|
|
1509
|
-
const census = new HeapCensus();
|
|
1510
|
-
census.register("x", () => 1);
|
|
1511
|
-
const rssSequence = [5000 * MB, 100 * MB]; // decision sees 5000 (>= hard floor); re-read drifts to 100
|
|
1512
|
-
let i = 0;
|
|
1513
|
-
const readers = {
|
|
1514
|
-
getHeapStatistics: () => ({
|
|
1515
|
-
used_heap_size: Math.round(2048 * MB * 0.3), // quiet heap
|
|
1516
|
-
total_heap_size: 0,
|
|
1517
|
-
heap_size_limit: 2048 * MB,
|
|
1518
|
-
external_memory: 0,
|
|
1519
|
-
}),
|
|
1520
|
-
memoryUsage: () => ({
|
|
1521
|
-
rss: rssSequence[Math.min(i++, rssSequence.length - 1)],
|
|
1522
|
-
heapTotal: 0,
|
|
1523
|
-
heapUsed: 0,
|
|
1524
|
-
external: 0,
|
|
1525
|
-
arrayBuffers: 0,
|
|
1526
|
-
}),
|
|
1527
|
-
monotonicNowMs: () => 30_000,
|
|
1528
|
-
};
|
|
1529
|
-
const controller = new RunnerHeapTelemetry({
|
|
1530
|
-
readers,
|
|
1531
|
-
census,
|
|
1532
|
-
sessionStartedAtMs: 0,
|
|
1533
|
-
pressureThresholdFraction: 0.75,
|
|
1534
|
-
recycleMinUptimeMs: 0,
|
|
1535
|
-
});
|
|
1536
|
-
const { hardExit } = controller.evaluate();
|
|
1537
|
-
expect(hardExit?.trigger).toBe("footprint");
|
|
1538
|
-
expect(hardExit?.reason).toBe("hard_fraction");
|
|
1539
|
-
// Stamped with the checked 5000 MB, never the drifted 100 MB re-read.
|
|
1540
|
-
expect(hardExit?.rssBytes).toBe(5000 * MB);
|
|
1541
|
-
expect(hardExit?.rssBytes).toBeGreaterThanOrEqual(DEFAULT_HEAP_FOOTPRINT_HARD_EXIT_BYTES);
|
|
1542
|
-
});
|
|
1543
|
-
it("the footprint recycle stderr line matches NONE of hq-desktop-app's fatal markers", () => {
|
|
1544
|
-
const DESKTOP_FATAL_MARKERS = [
|
|
1545
|
-
"javascript heap out of memory",
|
|
1546
|
-
"ineffective mark-compacts",
|
|
1547
|
-
"fatal error",
|
|
1548
|
-
"uncaught exception",
|
|
1549
|
-
"unhandledrejection",
|
|
1550
|
-
"panicked at",
|
|
1551
|
-
"assertion failed",
|
|
1552
|
-
"npm error ",
|
|
1553
|
-
"npm err! ",
|
|
1554
|
-
"enospc",
|
|
1555
|
-
"permission denied",
|
|
1556
|
-
"no such file or directory",
|
|
1557
|
-
"command not found",
|
|
1558
|
-
];
|
|
1559
|
-
const g = governor({ minUptimeMs: 0 });
|
|
1560
|
-
g.setFootprint(4200 * MB, 30_000);
|
|
1561
|
-
const line = heapRecycleStderrLine(g.controller.evaluate().recycle);
|
|
1562
|
-
const lowered = line.toLowerCase();
|
|
1563
|
-
for (const marker of DESKTOP_FATAL_MARKERS)
|
|
1564
|
-
expect(lowered).not.toContain(marker);
|
|
1565
|
-
expect(line.trimStart().startsWith("#")).toBe(false);
|
|
1566
|
-
expect(line).toContain("trigger=footprint");
|
|
1567
|
-
});
|
|
1568
|
-
});
|
|
1569
|
-
describe("startRunnerHeapSampling — footprint tier", () => {
|
|
1570
|
-
it("drives a footprint cooperative recycle through the interval tick and swallows a throwing onRecycle", () => {
|
|
1571
|
-
const census = new HeapCensus();
|
|
1572
|
-
census.register("x", () => 1);
|
|
1573
|
-
const { state, readers } = mutableReaders();
|
|
1574
|
-
state.limit = 2048 * MB;
|
|
1575
|
-
state.used = Math.round(state.limit * 0.3); // quiet heap
|
|
1576
|
-
state.rss = 4300 * MB; // over the 4096 MB recycle floor, under the hard floor
|
|
1577
|
-
const fake = fakeScheduler();
|
|
1578
|
-
const recycles = [];
|
|
1579
|
-
startRunnerHeapSampling({
|
|
1580
|
-
readers,
|
|
1581
|
-
census,
|
|
1582
|
-
sessionStartedAtMs: 0,
|
|
1583
|
-
pressureThresholdFraction: 0.99,
|
|
1584
|
-
recycleMinUptimeMs: 0,
|
|
1585
|
-
intervalMs: 60_000,
|
|
1586
|
-
scheduler: fake.scheduler,
|
|
1587
|
-
onSample: () => { },
|
|
1588
|
-
onPressure: () => { },
|
|
1589
|
-
onRecycle: (record) => {
|
|
1590
|
-
recycles.push(record);
|
|
1591
|
-
throw new Error("recycle emit boom"); // must be swallowed
|
|
1592
|
-
},
|
|
1593
|
-
log: () => { },
|
|
1594
|
-
});
|
|
1595
|
-
state.now = 30_000;
|
|
1596
|
-
expect(() => fake.tick()).not.toThrow();
|
|
1597
|
-
expect(recycles).toHaveLength(1);
|
|
1598
|
-
expect(recycles[0].trigger).toBe("footprint");
|
|
1599
|
-
});
|
|
1600
|
-
it("delivers a footprint hard exit to onHardExit (never onRecycle)", () => {
|
|
1601
|
-
const census = new HeapCensus();
|
|
1602
|
-
census.register("x", () => 1);
|
|
1603
|
-
const { state, readers } = mutableReaders();
|
|
1604
|
-
state.limit = 2048 * MB;
|
|
1605
|
-
state.used = Math.round(state.limit * 0.3); // quiet heap
|
|
1606
|
-
state.rss = 5000 * MB; // over the 4864 MB hard floor
|
|
1607
|
-
const fake = fakeScheduler();
|
|
1608
|
-
const recycles = [];
|
|
1609
|
-
const hardExits = [];
|
|
1610
|
-
const handle = startRunnerHeapSampling({
|
|
1611
|
-
readers,
|
|
1612
|
-
census,
|
|
1613
|
-
sessionStartedAtMs: 0,
|
|
1614
|
-
pressureThresholdFraction: 0.99,
|
|
1615
|
-
recycleMinUptimeMs: 0,
|
|
1616
|
-
intervalMs: 60_000,
|
|
1617
|
-
checkpointMs: 10,
|
|
1618
|
-
scheduler: fake.scheduler,
|
|
1619
|
-
onSample: () => { },
|
|
1620
|
-
onPressure: () => { },
|
|
1621
|
-
onRecycle: (record) => recycles.push(record),
|
|
1622
|
-
onHardExit: (record) => hardExits.push(record),
|
|
1623
|
-
log: () => { },
|
|
1624
|
-
});
|
|
1625
|
-
state.now = 0;
|
|
1626
|
-
handle.maybeObserve();
|
|
1627
|
-
expect(hardExits).toHaveLength(1);
|
|
1628
|
-
expect(hardExits[0].trigger).toBe("footprint");
|
|
1629
|
-
expect(hardExits[0].reason).toBe("hard_fraction");
|
|
1630
|
-
expect(recycles).toHaveLength(0);
|
|
1631
|
-
handle.stop();
|
|
1632
|
-
});
|
|
1633
|
-
it("the loop-top evaluate() fires the footprint tier when the interval timer never ticks", () => {
|
|
1634
|
-
const census = new HeapCensus();
|
|
1635
|
-
census.register("x", () => 1);
|
|
1636
|
-
const { state, readers } = mutableReaders();
|
|
1637
|
-
state.limit = 2048 * MB;
|
|
1638
|
-
state.used = Math.round(state.limit * 0.3);
|
|
1639
|
-
state.rss = 4300 * MB;
|
|
1640
|
-
const fake = fakeScheduler();
|
|
1641
|
-
const recycles = [];
|
|
1642
|
-
const handle = startRunnerHeapSampling({
|
|
1643
|
-
readers,
|
|
1644
|
-
census,
|
|
1645
|
-
sessionStartedAtMs: 0,
|
|
1646
|
-
pressureThresholdFraction: 0.99,
|
|
1647
|
-
recycleMinUptimeMs: 0,
|
|
1648
|
-
intervalMs: 60_000,
|
|
1649
|
-
scheduler: fake.scheduler,
|
|
1650
|
-
onSample: () => { },
|
|
1651
|
-
onPressure: () => { },
|
|
1652
|
-
onRecycle: (record) => recycles.push(record),
|
|
1653
|
-
log: () => { },
|
|
1654
|
-
});
|
|
1655
|
-
state.now = 30_000;
|
|
1656
|
-
handle.evaluate(); // timer never ticked
|
|
1657
|
-
expect(recycles).toHaveLength(1);
|
|
1658
|
-
expect(recycles[0].trigger).toBe("footprint");
|
|
1659
|
-
handle.stop();
|
|
1660
|
-
});
|
|
1661
|
-
it("a throwing memoryUsage reader degrades to silence on every path (never throws into a watch pass)", () => {
|
|
1662
|
-
const census = new HeapCensus();
|
|
1663
|
-
census.register("x", () => 1);
|
|
1664
|
-
const readers = {
|
|
1665
|
-
getHeapStatistics: () => ({
|
|
1666
|
-
used_heap_size: 10,
|
|
1667
|
-
total_heap_size: 20,
|
|
1668
|
-
heap_size_limit: 2048 * MB,
|
|
1669
|
-
external_memory: 0,
|
|
1670
|
-
}),
|
|
1671
|
-
memoryUsage: () => {
|
|
1672
|
-
throw new Error("memoryUsage boom");
|
|
1673
|
-
},
|
|
1674
|
-
monotonicNowMs: () => 30_000,
|
|
1675
|
-
};
|
|
1676
|
-
const fake = fakeScheduler();
|
|
1677
|
-
const gc = fakeGcObserver();
|
|
1678
|
-
const handle = startRunnerHeapSampling({
|
|
1679
|
-
readers,
|
|
1680
|
-
census,
|
|
1681
|
-
sessionStartedAtMs: 0,
|
|
1682
|
-
pressureThresholdFraction: 0.99,
|
|
1683
|
-
recycleMinUptimeMs: 0,
|
|
1684
|
-
intervalMs: 60_000,
|
|
1685
|
-
scheduler: fake.scheduler,
|
|
1686
|
-
gcObserver: gc.observer,
|
|
1687
|
-
onSample: () => { },
|
|
1688
|
-
onPressure: () => { },
|
|
1689
|
-
onRecycle: () => { },
|
|
1690
|
-
onHardExit: () => { },
|
|
1691
|
-
log: () => { },
|
|
1692
|
-
});
|
|
1693
|
-
expect(() => fake.tick()).not.toThrow();
|
|
1694
|
-
expect(() => handle.evaluate()).not.toThrow();
|
|
1695
|
-
expect(() => gc.fire()).not.toThrow();
|
|
1696
|
-
handle.stop();
|
|
1697
|
-
});
|
|
1698
|
-
it("a footprint value in the record survives the LOCAL telemetry sanitizer as numbers only", async () => {
|
|
1699
|
-
const g = governor({ minUptimeMs: 0 });
|
|
1700
|
-
g.setFootprint(4300 * MB, 30_000);
|
|
1701
|
-
const record = g.controller.evaluate().recycle;
|
|
1702
|
-
const props = heapRecycleCloudProperties(record);
|
|
1703
|
-
for (const value of Object.values(props))
|
|
1704
|
-
expect(typeof value).toBe("number");
|
|
1705
|
-
const captured = [];
|
|
1706
|
-
const client = {
|
|
1707
|
-
postTelemetryEvents: async (batch) => {
|
|
1708
|
-
for (const event of batch.events)
|
|
1709
|
-
captured.push(event);
|
|
1710
|
-
return { accepted: batch.events.length };
|
|
1711
|
-
},
|
|
1712
|
-
};
|
|
1713
|
-
await emitCloudTelemetry(client, {
|
|
1714
|
-
eventName: "sync_runner_heap_recycle",
|
|
1715
|
-
source: "sync-runner-heap",
|
|
1716
|
-
properties: props,
|
|
1717
|
-
});
|
|
1718
|
-
const survived = captured[0]?.properties ?? {};
|
|
1719
|
-
expect(survived.recycle_footprint_trigger_count).toBe(1);
|
|
1720
|
-
expect(Object.keys(survived).length).toBe(Object.keys(props).length);
|
|
1721
|
-
});
|
|
1722
|
-
});
|
|
1723
|
-
describe("resolve* parsers for the footprint knobs", () => {
|
|
1724
|
-
it("resolveHeapFootprintRecycleEnabled: ON by default, disabled only by an explicit off value", () => {
|
|
1725
|
-
expect(resolveHeapFootprintRecycleEnabled(undefined)).toBe(true);
|
|
1726
|
-
expect(resolveHeapFootprintRecycleEnabled("")).toBe(true);
|
|
1727
|
-
expect(resolveHeapFootprintRecycleEnabled("1")).toBe(true);
|
|
1728
|
-
expect(resolveHeapFootprintRecycleEnabled("0")).toBe(false);
|
|
1729
|
-
expect(resolveHeapFootprintRecycleEnabled("false")).toBe(false);
|
|
1730
|
-
expect(resolveHeapFootprintRecycleEnabled(" OFF ")).toBe(false);
|
|
1731
|
-
// A typo keeps the tier armed rather than silently disabling protection.
|
|
1732
|
-
expect(resolveHeapFootprintRecycleEnabled("disabled")).toBe(true);
|
|
1733
|
-
});
|
|
1734
|
-
it("resolveHeapFootprintRecycleBytes: whole MB -> bytes, 4096 MB default, rejects a numeric prefix", () => {
|
|
1735
|
-
expect(resolveHeapFootprintRecycleBytes("4096")).toBe(4096 * MB);
|
|
1736
|
-
expect(resolveHeapFootprintRecycleBytes("1000")).toBe(1000 * MB);
|
|
1737
|
-
expect(resolveHeapFootprintRecycleBytes(undefined)).toBe(DEFAULT_HEAP_FOOTPRINT_RECYCLE_BYTES);
|
|
1738
|
-
expect(resolveHeapFootprintRecycleBytes("")).toBe(DEFAULT_HEAP_FOOTPRINT_RECYCLE_BYTES);
|
|
1739
|
-
expect(resolveHeapFootprintRecycleBytes("0")).toBe(DEFAULT_HEAP_FOOTPRINT_RECYCLE_BYTES);
|
|
1740
|
-
// A malformed value can NEVER silently lower the budget or disable the tier.
|
|
1741
|
-
expect(resolveHeapFootprintRecycleBytes("4096x")).toBe(DEFAULT_HEAP_FOOTPRINT_RECYCLE_BYTES);
|
|
1742
|
-
expect(resolveHeapFootprintRecycleBytes("4gb")).toBe(DEFAULT_HEAP_FOOTPRINT_RECYCLE_BYTES);
|
|
1743
|
-
});
|
|
1744
|
-
it("resolveHeapFootprintHardExitBytes: whole MB -> bytes, 4864 MB default, rejects garbage", () => {
|
|
1745
|
-
expect(resolveHeapFootprintHardExitBytes("4864")).toBe(4864 * MB);
|
|
1746
|
-
expect(resolveHeapFootprintHardExitBytes(undefined)).toBe(DEFAULT_HEAP_FOOTPRINT_HARD_EXIT_BYTES);
|
|
1747
|
-
expect(resolveHeapFootprintHardExitBytes("0")).toBe(DEFAULT_HEAP_FOOTPRINT_HARD_EXIT_BYTES);
|
|
1748
|
-
expect(resolveHeapFootprintHardExitBytes("4864x")).toBe(DEFAULT_HEAP_FOOTPRINT_HARD_EXIT_BYTES);
|
|
1749
|
-
});
|
|
1750
|
-
});
|
|
1751
|
-
describe("startRunnerHeapSampling — work-driven checkpoint gate", () => {
|
|
1752
|
-
function pinnedHandle(overrides) {
|
|
1753
|
-
const census = new HeapCensus();
|
|
1754
|
-
census.register("x", () => 1);
|
|
1755
|
-
const { state, readers } = mutableReaders();
|
|
1756
|
-
state.limit = 2048 * MB;
|
|
1757
|
-
state.used = Math.round(state.limit * 0.85); // pinned range
|
|
1758
|
-
const fake = fakeScheduler();
|
|
1759
|
-
const recs = [];
|
|
1760
|
-
const handle = startRunnerHeapSampling({
|
|
1761
|
-
readers,
|
|
1762
|
-
census,
|
|
1763
|
-
sessionStartedAtMs: 0,
|
|
1764
|
-
pressureThresholdFraction: 0.99,
|
|
1765
|
-
recyclePinnedFraction: 0.84,
|
|
1766
|
-
recyclePinnedSustainWindowMs: 0, // any two gated observations suffice
|
|
1767
|
-
recycleMinUptimeMs: 0,
|
|
1768
|
-
intervalMs: 60_000,
|
|
1769
|
-
checkpointMs: overrides.checkpointMs,
|
|
1770
|
-
scheduler: fake.scheduler,
|
|
1771
|
-
onSample: () => { },
|
|
1772
|
-
onPressure: () => { },
|
|
1773
|
-
onRecycle: (record, counters) => recs.push({ record, counters }),
|
|
1774
|
-
log: () => { },
|
|
1775
|
-
});
|
|
1776
|
-
return { state, handle, recs };
|
|
1777
|
-
}
|
|
1778
|
-
it("observes only when the gate is open (>= checkpointMs since the last observation)", () => {
|
|
1779
|
-
const { state, handle, recs } = pinnedHandle({ checkpointMs: 100 });
|
|
1780
|
-
state.now = 0;
|
|
1781
|
-
handle.maybeObserve(); // first checkpoint: no prior observation -> observes (pinned count 1)
|
|
1782
|
-
state.now = 50;
|
|
1783
|
-
handle.maybeObserve(); // 50 < 100 -> below the gate, no observation
|
|
1784
|
-
state.now = 150;
|
|
1785
|
-
handle.maybeObserve(); // 150 >= 100 -> observes (pinned count 2 -> recycle)
|
|
1786
|
-
expect(recs).toHaveLength(1);
|
|
1787
|
-
expect(recs[0].record.trigger).toBe("pinned");
|
|
1788
|
-
// Exactly two checkpoint observations (the 50 ms one was gated out); the 60 s
|
|
1789
|
-
// interval timer never ticked, proving the feed was work-driven, not the timer.
|
|
1790
|
-
expect(recs[0].counters.heapCheckpointObservationsCount).toBe(2);
|
|
1791
|
-
expect(recs[0].counters.heapIntervalObservationsCount).toBe(0);
|
|
1792
|
-
expect(recs[0].counters.msSincePreviousObservationMs).toBe(150);
|
|
1793
|
-
});
|
|
1794
|
-
it("checkpointMs=0 disables the work-driven checkpoint but keeps the loop-top evaluate()", () => {
|
|
1795
|
-
const { state, handle, recs } = pinnedHandle({ checkpointMs: 0 });
|
|
1796
|
-
state.now = 0;
|
|
1797
|
-
handle.maybeObserve();
|
|
1798
|
-
state.now = 100_000;
|
|
1799
|
-
handle.maybeObserve(); // both disabled -> never observes
|
|
1800
|
-
expect(recs).toHaveLength(0);
|
|
1801
|
-
// The loop-top evaluate() is ungated and still works with checkpoints off.
|
|
1802
|
-
state.used = Math.round(2048 * MB * 0.96); // hard-fraction range
|
|
1803
|
-
// ... but this handle has no onHardExit, so route through a recycle instead:
|
|
1804
|
-
state.used = Math.round(2048 * MB * 0.92); // emergency cooperative
|
|
1805
|
-
state.now = 200_000;
|
|
1806
|
-
handle.evaluate();
|
|
1807
|
-
expect(recs).toHaveLength(1);
|
|
1808
|
-
expect(recs[0].record.trigger).toBe("emergency");
|
|
1809
|
-
});
|
|
1810
|
-
it("delivers a hard exit to onHardExit (never onRecycle) and preempts the cooperative verdict", () => {
|
|
1811
|
-
const census = new HeapCensus();
|
|
1812
|
-
census.register("x", () => 1);
|
|
1813
|
-
const { state, readers } = mutableReaders();
|
|
1814
|
-
state.limit = 2048 * MB;
|
|
1815
|
-
state.used = Math.round(state.limit * 0.96); // hard-fraction range
|
|
1816
|
-
const fake = fakeScheduler();
|
|
1817
|
-
const recycles = [];
|
|
1818
|
-
const hardExits = [];
|
|
1819
|
-
const handle = startRunnerHeapSampling({
|
|
1820
|
-
readers,
|
|
1821
|
-
census,
|
|
1822
|
-
sessionStartedAtMs: 0,
|
|
1823
|
-
pressureThresholdFraction: 0.99,
|
|
1824
|
-
recycleMinUptimeMs: 0,
|
|
1825
|
-
intervalMs: 60_000,
|
|
1826
|
-
checkpointMs: 10,
|
|
1827
|
-
scheduler: fake.scheduler,
|
|
1828
|
-
onSample: () => { },
|
|
1829
|
-
onPressure: () => { },
|
|
1830
|
-
onRecycle: (record) => recycles.push(record),
|
|
1831
|
-
onHardExit: (record) => hardExits.push(record),
|
|
1832
|
-
log: () => { },
|
|
1833
|
-
});
|
|
1834
|
-
state.now = 0;
|
|
1835
|
-
handle.maybeObserve();
|
|
1836
|
-
expect(hardExits).toHaveLength(1);
|
|
1837
|
-
expect(hardExits[0].reason).toBe("hard_fraction");
|
|
1838
|
-
expect(recycles).toHaveLength(0);
|
|
1839
|
-
handle.stop();
|
|
1840
|
-
});
|
|
1841
|
-
});
|
|
1842
|
-
describe("performHeapHardExit", () => {
|
|
1843
|
-
function hardFractionRecord() {
|
|
1844
|
-
const record = governor({ minUptimeMs: 0 }).sampleAt(0.96, 10_000).hardExit;
|
|
1845
|
-
expect(record).not.toBeNull();
|
|
1846
|
-
return record;
|
|
1847
|
-
}
|
|
1848
|
-
it("writes the NDJSON record to stdout, then the marker to stderr, then exits — in that order", () => {
|
|
1849
|
-
const calls = [];
|
|
1850
|
-
performHeapHardExit(hardFractionRecord(), {
|
|
1851
|
-
writeStdout: (line) => calls.push(`out:${line}`),
|
|
1852
|
-
writeStderr: (line) => calls.push(`err:${line}`),
|
|
1853
|
-
exit: (code) => calls.push(`exit:${code}`),
|
|
1854
|
-
}, 1);
|
|
1855
|
-
expect(calls).toHaveLength(3);
|
|
1856
|
-
expect(calls[0]).toContain("out:");
|
|
1857
|
-
expect(calls[0]).toContain('"type":"heap-recycle"');
|
|
1858
|
-
expect(calls[0]).toContain('"reason":"hard_fraction"');
|
|
1859
|
-
expect(calls[1]).toContain("err:");
|
|
1860
|
-
expect(calls[1]).toContain("hq-sync-runner: heap-recycle");
|
|
1861
|
-
expect(calls[2]).toBe("exit:1");
|
|
1862
|
-
});
|
|
1863
|
-
it("still exits even if BOTH stream writes throw (a closed/blocked pipe)", () => {
|
|
1864
|
-
let exited = -1;
|
|
1865
|
-
performHeapHardExit(hardFractionRecord(), {
|
|
1866
|
-
writeStdout: () => {
|
|
1867
|
-
throw new Error("EPIPE");
|
|
1868
|
-
},
|
|
1869
|
-
writeStderr: () => {
|
|
1870
|
-
throw new Error("EPIPE");
|
|
1871
|
-
},
|
|
1872
|
-
exit: (code) => {
|
|
1873
|
-
exited = code;
|
|
1874
|
-
},
|
|
1875
|
-
}, 1);
|
|
1876
|
-
expect(exited).toBe(1);
|
|
1877
|
-
});
|
|
1878
|
-
it("the hard-exit stderr marker matches NONE of hq-desktop-app's fatal classifiers (both hard reasons)", () => {
|
|
1879
|
-
const DESKTOP_FATAL_MARKERS = [
|
|
1880
|
-
"javascript heap out of memory",
|
|
1881
|
-
"ineffective mark-compacts",
|
|
1882
|
-
"fatal error",
|
|
1883
|
-
"uncaught exception",
|
|
1884
|
-
"unhandledrejection",
|
|
1885
|
-
"panicked at",
|
|
1886
|
-
"assertion failed",
|
|
1887
|
-
"npm error ",
|
|
1888
|
-
"npm err! ",
|
|
1889
|
-
"enospc",
|
|
1890
|
-
"permission denied",
|
|
1891
|
-
"no such file or directory",
|
|
1892
|
-
"command not found",
|
|
1893
|
-
];
|
|
1894
|
-
const hardFraction = hardFractionRecord();
|
|
1895
|
-
const latch = (() => {
|
|
1896
|
-
const g = governor({ minUptimeMs: 0, latchHardExitMs: 0, latchHardExitObservations: 1_000_000 });
|
|
1897
|
-
g.evaluateFullAt(0.92, 0);
|
|
1898
|
-
return g.evaluateFullAt(0.7, 1).hardExit;
|
|
1899
|
-
})();
|
|
1900
|
-
for (const record of [hardFraction, latch]) {
|
|
1901
|
-
const line = heapRecycleStderrLine(record).toLowerCase();
|
|
1902
|
-
for (const marker of DESKTOP_FATAL_MARKERS) {
|
|
1903
|
-
expect(line, `reason ${record.reason} must not contain "${marker}"`).not.toContain(marker);
|
|
1904
|
-
}
|
|
1905
|
-
expect(line.trimStart().startsWith("#")).toBe(false);
|
|
1906
|
-
expect(line).toContain("hq-sync-runner: heap-recycle");
|
|
1907
|
-
}
|
|
1908
|
-
expect(heapRecycleStderrLine(hardFraction)).toContain("reason=hard_fraction");
|
|
1909
|
-
expect(heapRecycleStderrLine(latch)).toContain("reason=latch_timeout");
|
|
1910
|
-
});
|
|
1911
|
-
});
|
|
1912
|
-
describe("resolveDeclaredHeapCeilingBytes", () => {
|
|
1913
|
-
const MBb = 1024 * 1024;
|
|
1914
|
-
it("parses --max-old-space-size from execArgv in both forms, last occurrence wins", () => {
|
|
1915
|
-
expect(resolveDeclaredHeapCeilingBytes(["--max-old-space-size=2048"], undefined)).toBe(2048 * MBb);
|
|
1916
|
-
expect(resolveDeclaredHeapCeilingBytes(["--max-old-space-size", "2048"], undefined)).toBe(2048 * MBb);
|
|
1917
|
-
expect(resolveDeclaredHeapCeilingBytes(["--max-old-space-size=1024", "--max-old-space-size=2048"], undefined)).toBe(2048 * MBb);
|
|
1918
|
-
expect(resolveDeclaredHeapCeilingBytes(["--max_old_space_size=2048"], undefined)).toBe(2048 * MBb);
|
|
1919
|
-
});
|
|
1920
|
-
it("falls back to NODE_OPTIONS only when execArgv has none, honours one pair of quotes, execArgv wins", () => {
|
|
1921
|
-
expect(resolveDeclaredHeapCeilingBytes([], "--max-old-space-size=2048")).toBe(2048 * MBb);
|
|
1922
|
-
expect(resolveDeclaredHeapCeilingBytes([], '--max-old-space-size="2048"')).toBe(2048 * MBb);
|
|
1923
|
-
expect(resolveDeclaredHeapCeilingBytes(["--max-old-space-size=4096"], "--max-old-space-size=2048")).toBe(4096 * MBb);
|
|
1924
|
-
});
|
|
1925
|
-
it("returns null on absent/garbage/zero so the governor falls back to the reported limit", () => {
|
|
1926
|
-
expect(resolveDeclaredHeapCeilingBytes([], undefined)).toBeNull();
|
|
1927
|
-
expect(resolveDeclaredHeapCeilingBytes(["--max-old-space-size=abc"], undefined)).toBeNull();
|
|
1928
|
-
expect(resolveDeclaredHeapCeilingBytes(["--max-old-space-size=0"], undefined)).toBeNull();
|
|
1929
|
-
expect(resolveDeclaredHeapCeilingBytes(["--other=5"], " ")).toBeNull();
|
|
1930
|
-
});
|
|
1931
|
-
});
|
|
1932
|
-
describe("resolve* parsers for the new governor knobs", () => {
|
|
1933
|
-
it("resolveHeapCheckpointMs: 250 default, 0 valid (disables), rejects '250x'", () => {
|
|
1934
|
-
expect(resolveHeapCheckpointMs("250")).toBe(250);
|
|
1935
|
-
expect(resolveHeapCheckpointMs("0")).toBe(0);
|
|
1936
|
-
expect(resolveHeapCheckpointMs(undefined)).toBe(DEFAULT_HEAP_CHECKPOINT_MS);
|
|
1937
|
-
expect(resolveHeapCheckpointMs("250x")).toBe(DEFAULT_HEAP_CHECKPOINT_MS);
|
|
1938
|
-
expect(resolveHeapCheckpointMs("-1")).toBe(DEFAULT_HEAP_CHECKPOINT_MS);
|
|
1939
|
-
});
|
|
1940
|
-
it("resolveHeapPinnedFraction: 0.84 default, out-of-range/garbage falls back", () => {
|
|
1941
|
-
expect(resolveHeapPinnedFraction("0.84")).toBe(0.84);
|
|
1942
|
-
expect(resolveHeapPinnedFraction("1")).toBe(1);
|
|
1943
|
-
expect(resolveHeapPinnedFraction(undefined)).toBe(DEFAULT_HEAP_PINNED_FRACTION);
|
|
1944
|
-
expect(resolveHeapPinnedFraction("2")).toBe(DEFAULT_HEAP_PINNED_FRACTION);
|
|
1945
|
-
expect(resolveHeapPinnedFraction("0.84x")).toBe(DEFAULT_HEAP_PINNED_FRACTION);
|
|
1946
|
-
});
|
|
1947
|
-
it("resolveHeapPinnedSustainMs: 20000 default, 0 valid, rejects '20s'", () => {
|
|
1948
|
-
expect(resolveHeapPinnedSustainMs("20000")).toBe(20_000);
|
|
1949
|
-
expect(resolveHeapPinnedSustainMs("0")).toBe(0);
|
|
1950
|
-
expect(resolveHeapPinnedSustainMs(undefined)).toBe(DEFAULT_HEAP_PINNED_SUSTAIN_MS);
|
|
1951
|
-
expect(resolveHeapPinnedSustainMs("20s")).toBe(DEFAULT_HEAP_PINNED_SUSTAIN_MS);
|
|
1952
|
-
});
|
|
1953
|
-
it("resolveHeapHardExitFraction: 0.955 default, out-of-range/garbage falls back", () => {
|
|
1954
|
-
expect(resolveHeapHardExitFraction("0.955")).toBe(0.955);
|
|
1955
|
-
expect(resolveHeapHardExitFraction(undefined)).toBe(DEFAULT_HEAP_HARD_EXIT_FRACTION);
|
|
1956
|
-
expect(resolveHeapHardExitFraction("2")).toBe(DEFAULT_HEAP_HARD_EXIT_FRACTION);
|
|
1957
|
-
expect(resolveHeapHardExitFraction("0.955x")).toBe(DEFAULT_HEAP_HARD_EXIT_FRACTION);
|
|
1958
|
-
});
|
|
1959
|
-
it("resolveHeapRecycleLatchHardExitMs: 60000 default, 0 valid, rejects '60s'", () => {
|
|
1960
|
-
expect(resolveHeapRecycleLatchHardExitMs("60000")).toBe(60_000);
|
|
1961
|
-
expect(resolveHeapRecycleLatchHardExitMs("0")).toBe(0);
|
|
1962
|
-
expect(resolveHeapRecycleLatchHardExitMs(undefined)).toBe(DEFAULT_HEAP_RECYCLE_LATCH_HARD_EXIT_MS);
|
|
1963
|
-
expect(resolveHeapRecycleLatchHardExitMs("60s")).toBe(DEFAULT_HEAP_RECYCLE_LATCH_HARD_EXIT_MS);
|
|
1964
|
-
});
|
|
1965
|
-
});
|
|
1966
|
-
//# sourceMappingURL=sync-runner-heap-telemetry.test.js.map
|