diff --git a/docs/adb-device-analysis.md b/docs/adb-device-analysis.md index b18e7b4fbe..7e10ac33d6 100644 --- a/docs/adb-device-analysis.md +++ b/docs/adb-device-analysis.md @@ -153,8 +153,10 @@ and during shutdown. - **Mutations are explicit.** `push_adb_file`, `install_adb_package`, and `uninstall_adb_package` declare `mutatesTarget` and never run implicitly: pushes check for existing device files before transfer and refuse them - unless `overwrite` is set. The check is not atomic, so a different process - can create the path before `adb push` writes it. When available, the device's + unless `overwrite` is set. A failed or unexpected existence probe stops + transfer even with `overwrite`; only a clean `test -e` exit of 1 with no + output is interpreted as absence. The check is not atomic, so a different + process can create the path before `adb push` writes it. When available, the device's `sha256sum` verifies the transfer; installs preserve the device's `Failure [...]` reason verbatim. - **Producer identity stays attached.** Every result embeds the producing diff --git a/docs/electron-observation.md b/docs/electron-observation.md index 9b478d9dc4..791e4d86e3 100644 --- a/docs/electron-observation.md +++ b/docs/electron-observation.md @@ -127,6 +127,46 @@ If cleanup cannot be verified, the error retains collected observations in `details.partial_observation` and marks their application cleanup `unverified`. Cleanup uncertainty does not authorize signaling unrelated processes. +Main-process IPC recording keeps original-function removal for `on` and `once` +registrations and records a fired `once` callback once. Native Node.js owns +removal order and traversal, including listeners registered by removal observers; +the hook translates only its own recording wrappers at the removal boundary. +Wrapper metadata uses weak keys and does not retain removed registrations. +Wrapper metadata operations, private collection methods and traversal constructors are captured +before target startup; later replacement of their global bindings does not +change listener registration or removal. + +An `on` listener's application-defined `.apply` receives the original argument-list +object unchanged. Borrowing the exposed method preserves its dynamic receiver. +Because that method owns argument interpretation and may not +invoke the callback, the hook records a bounded `ipc-observation-gap` timeline +entry with the registered channel rather than an IPC invocation. Its empty +`argument_shapes` means unobserved arguments, not a zero-argument callback; +callback execution and result remain unknown. Coverage and retention include +these gaps even when their individual records are dropped. Normal native +dispatch continues to record actual invocation frames. Evidence snapshots copy +retained arrays and counters without invoking application iterators or Array +species. + +Listener-chain recognition distinguishes owned, unowned and unknown metadata. +Data links may be inherited; absence is established only after completing the +bounded prototype lookup. Exhausting either walk, encountering a getter or seeing a Proxy leaves +ownership unknown. The hook preserves native registration without adding another +recording wrapper and retains a `listener-chain` observation gap after successful +registration. This gap does not prove IPC delivery. An existing inner recording +wrapper can still report its actual invocation; otherwise callback activity +remains unobserved. Setter-only properties have a known undefined read. +Data-descriptor parsing is shared with removal normalization. + +This does not establish complete JavaScript function introspection equivalence. +Caller-defined `.listener` properties can change the identity exposed by +`listeners()` and meta-events; accessors and Proxy metadata are not followed for +wrapper recognition. Re-registering a raw recording wrapper on another channel +retains the original registration's channel attribution. +Mutating `.listener` on a raw recording wrapper can also prevent removal through +application decorators; the reproduced identity conflict is tracked in +[#4513](https://github.com/morluto/rea/issues/4513). + ### Target fuse prerequisites Active scenario capture requires that the target Electron executable honors Node CLI inspector flags (`--inspect` or `--remote-debugging-port`) so that Playwright can establish its DevTools protocol session. diff --git a/docs/ghidra-provider.md b/docs/ghidra-provider.md index 434d822309..4d1090ea00 100644 --- a/docs/ghidra-provider.md +++ b/docs/ghidra-provider.md @@ -145,6 +145,13 @@ New attempts wait for that cleanup; failed cleanup retains its original error and owned directories until a successful close. Closing the provider reports a provider-state failure to live callers rather than cancelling their signals. +Configured analysis extensions and seeds use owned file reads during profile +resolution and launch snapshots. A changed read extent or descriptor state +refuses the commitment. Failed source or snapshot descriptor closes remain +owned and block new admission until cleanup succeeds; the physical failure +and exact cancellation reason remain available. Provider shutdown joins all +profile stages before retrying their retained descriptors. + | Concern | Ghidra contract | | ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Decompiler lifetime | One persistent `DecompInterface` is opened for the imported Program and disposed during bridge shutdown. Native decompilation has no fixed per-function deadline. External functions or functions without bodies return `null`; cancellation and native failure remain distinct. | diff --git a/docs/tool-design.md b/docs/tool-design.md index 237fb8bb06..055daf1483 100644 --- a/docs/tool-design.md +++ b/docs/tool-design.md @@ -132,6 +132,14 @@ providers need not expose identical state machines. while it remains unresolved. Link the originating cleanup error to its exact owner so confirmed release resolves its cleanup marker while preserving the earlier diagnostic. Shared CLI/MCP adapters cannot infer that confirmation. +- Provider profile selection owns every asynchronous candidate through its + terminal result and finalization. The registry owns profile-file cleanup, + joins all automatic-selection children, and closes those file owners before + reporting candidate shutdown. Final caller cancellation joins the selection; + an adapter that ignores cancellation remains active and owned until it settles. + Block new admission during shutdown and while cleanup remains unresolved; + reopen after confirmed retry. Preserve failed results before considering a + later signal, and retain every child's attributed cleanup in mixed outcomes. - Release child descriptors before removing their containing root. Retain a composite owner when either stage fails so retry preserves that ordering. A Windows command leader's exit does not establish Job Object closure. Keep diff --git a/docs/verification/electron-ipc-listeners-2026-10-16.json b/docs/verification/electron-ipc-listeners-2026-10-16.json new file mode 100644 index 0000000000..e83ed0e791 --- /dev/null +++ b/docs/verification/electron-ipc-listeners-2026-10-16.json @@ -0,0 +1,10377 @@ +{ + "issue": 4390, + "pr": 4389, + "umbrella": 4027, + "date": "2026-10-16", + "source_pins": { + "candidate_revision": "d02cc934c28812367be1d0a256f08a45c51af453", + "baseline_revision": "180ba03bfddd5916251e81e97a500b1ede7a9eea", + "source_sha256": { + "scripts/electron-active-hook.cjs": "550223fa1d33b0ea4046ce1b19c07a15b4036b6b1875ca330cd53f5a460cae64", + "tests/boundary/browser/electronActiveHook.test.ts": "7a7f0aa110e78fe08df98993cbcea183fb41a6a2e776a77f093b2be2350846b0", + "experiments/analysis-efficiency/drawio-electron-lifecycle.mjs": "d484b1550704b3be1bf8b4fb1980ab50b81924a917ffae8659c84dea036fd96a" + }, + "affected_compiled_modules_and_catalog_byte_equal": true + }, + "original_pr_head": "efd452948c2940ff0234ce4d918bba966e3c6eb6", + "inputs": { + "official_release": "https://github.com/jgraph/drawio-desktop/releases/tag/v32.4.1", + "appimage": { + "browser_download_url": "https://github.com/jgraph/drawio-desktop/releases/download/v32.4.1/drawio-x86_64-32.4.1.AppImage", + "digest": "sha256:c687a9f1ddcaa146b78547452d264c03c2afcf125639cba5deeb65f3e9554c9e", + "name": "drawio-x86_64-32.4.1.AppImage", + "size": 152095313 + }, + "asar_bytes": 62941673, + "asar_sha256": "e64b9650c0b8241ca9a7d33b56386a385bceee63270cec701d3a467347b697e6", + "generic_electron_version": "44.7.0", + "vendor_inspector_fuse": "disabled; no vendor Inspector attempt", + "vendor_executable_sha256": "7148eedc52bfae9c1a0535b4d381b6999fea4d7de54852702de0102d59c35e16" + }, + "conditions": { + "cpu_affinity": [10], + "cpu_quota_percent": 100, + "memory_hard_bytes": 8589934592, + "memory_early_stop_bytes": 6442450944, + "swap_bytes": 0, + "tasks_max": 192, + "order": "ABBA; two fresh processes/profiles per variant, same ASAR/runtime/script/actions", + "cache_scope": "OS/build caches uncontrolled. First/repeat process runs, not verified cold/warm system pairs.", + "counter_scope": "Cgroup family counters at payload exit while observer remains alive; not final whole-scope counters. Memory includes file cache, not RSS. External scope shutdown is not provider cleanup." + }, + "checks": { + "original_regressions_red": { + "passed": 14, + "failed": 2, + "receipt": { + "name": "two-boundaries-red", + "source_manifest": null, + "harness_hash_at_run": "NOT_ENOUGH_INFO: not captured before this diagnostic", + "log_sha256": "b714327508e02b26bb1e19b1945f55eeb6106564bc0265d38b2a1b8092c75679", + "observations": [], + "resource_observation": { + "scope": "rea-efficiency-12610f9d4bb047378ca331968d955759.scope", + "elapsed_seconds": 3.482, + "abort": null, + "sampled_peak_cgroup_bytes": 319447040, + "payload_exit": { + "phase": "payload-exited-observer-alive", + "ownership_token": "rea-efficiency-12610f9d4bb047378ca331968d955759.scope", + "payload_exit_code": 1, + "observer_pid": 3701997, + "remaining_processes": [3701997], + "payload_cleanup_verified": true, + "cpu_stat": { + "usage_usec": 3234414, + "user_usec": 2107057, + "system_usec": 1127356, + "nr_periods": 33, + "nr_throttled": 0, + "throttled_usec": 0, + "nr_bursts": 0, + "burst_usec": 0 + }, + "memory_peak_bytes": 341299200, + "control_group": "user.slice/user-1004.slice/user@1004.service/app.slice/rea-efficiency-12610f9d4bb047378ca331968d955759.scope", + "resource_events": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + } + }, + "payload_resource_observation_verified": true, + "final_whole_scope_counters_verified": false, + "observer_source_sha256": { + "resource-run.py": "7728862171415e7dee2c11af2ead2b9a54265fccaf82a7113956d36b29065e33", + "resource_payload.py": "6f089e645459eba80c5d73e39c21606c78173a44daf49fabc3b88c4631ce51c6" + } + }, + "post_scope_state": "ActiveState=inactive\nSubState=dead", + "xvfb_teardown_failure": false + } + }, + "intermediate_green": { + "passed": 16, + "receipt": { + "name": "two-boundaries-green", + "source_manifest": null, + "harness_hash_at_run": "NOT_ENOUGH_INFO: not captured before this diagnostic", + "log_sha256": "e33f2c10506a705d6d3f5e254d990b9039ed3a8db37473bf0ff75a8a7617b645", + "observations": [], + "resource_observation": { + "scope": "rea-efficiency-4f9680a23af948e7b3be561ca17121ee.scope", + "elapsed_seconds": 3.386, + "abort": null, + "sampled_peak_cgroup_bytes": 316997632, + "payload_exit": { + "phase": "payload-exited-observer-alive", + "ownership_token": "rea-efficiency-4f9680a23af948e7b3be561ca17121ee.scope", + "payload_exit_code": 0, + "observer_pid": 3702279, + "remaining_processes": [3702279], + "payload_cleanup_verified": true, + "cpu_stat": { + "usage_usec": 3121426, + "user_usec": 2001007, + "system_usec": 1120418, + "nr_periods": 32, + "nr_throttled": 0, + "throttled_usec": 0, + "nr_bursts": 0, + "burst_usec": 0 + }, + "memory_peak_bytes": 343248896, + "control_group": "user.slice/user-1004.slice/user@1004.service/app.slice/rea-efficiency-4f9680a23af948e7b3be561ca17121ee.scope", + "resource_events": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + } + }, + "payload_resource_observation_verified": true, + "final_whole_scope_counters_verified": false, + "observer_source_sha256": { + "resource-run.py": "7728862171415e7dee2c11af2ead2b9a54265fccaf82a7113956d36b29065e33", + "resource_payload.py": "6f089e645459eba80c5d73e39c21606c78173a44daf49fabc3b88c4631ce51c6" + } + }, + "post_scope_state": "ActiveState=inactive\nSubState=dead", + "xvfb_teardown_failure": false + } + }, + "proxy_red": { + "passed": 16, + "failed": 1, + "receipt": { + "name": "proxy-red", + "source_manifest": null, + "harness_hash_at_run": "NOT_ENOUGH_INFO: not captured before this diagnostic", + "log_sha256": "ca121412215db02ae6ea62f5c5b06d94c4681381b784601b2a4a9335b9a1e8ce", + "observations": [], + "resource_observation": { + "scope": "rea-efficiency-e58967e1ae514824900541e8c0226fab.scope", + "elapsed_seconds": 6.438, + "abort": null, + "sampled_peak_cgroup_bytes": 324837376, + "payload_exit": { + "phase": "payload-exited-observer-alive", + "ownership_token": "rea-efficiency-e58967e1ae514824900541e8c0226fab.scope", + "payload_exit_code": 1, + "observer_pid": 3706175, + "remaining_processes": [3706175], + "payload_cleanup_verified": true, + "cpu_stat": { + "usage_usec": 6161255, + "user_usec": 3756977, + "system_usec": 2404277, + "nr_periods": 62, + "nr_throttled": 0, + "throttled_usec": 0, + "nr_bursts": 0, + "burst_usec": 0 + }, + "memory_peak_bytes": 340758528, + "control_group": "user.slice/user-1004.slice/user@1004.service/app.slice/rea-efficiency-e58967e1ae514824900541e8c0226fab.scope", + "resource_events": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + } + }, + "payload_resource_observation_verified": true, + "final_whole_scope_counters_verified": false, + "observer_source_sha256": { + "resource-run.py": "7728862171415e7dee2c11af2ead2b9a54265fccaf82a7113956d36b29065e33", + "resource_payload.py": "6f089e645459eba80c5d73e39c21606c78173a44daf49fabc3b88c4631ce51c6" + } + }, + "post_scope_state": "ActiveState=inactive\nSubState=dead", + "xvfb_teardown_failure": false + } + }, + "current_focused": { + "passed": 17, + "files": 1, + "receipt": { + "name": "proxy-green", + "source_manifest": null, + "harness_hash_at_run": "NOT_ENOUGH_INFO: not captured before this diagnostic", + "log_sha256": "5a7f01e795d62f6f7fef4e12ad17b655b61e8b5a14fd19c3f2c123be908f6dd2", + "observations": [], + "resource_observation": { + "scope": "rea-efficiency-1610d79f372a4991966a9ce9bd36af0f.scope", + "elapsed_seconds": 6.345, + "abort": null, + "sampled_peak_cgroup_bytes": 343515136, + "payload_exit": { + "phase": "payload-exited-observer-alive", + "ownership_token": "rea-efficiency-1610d79f372a4991966a9ce9bd36af0f.scope", + "payload_exit_code": 0, + "observer_pid": 3706487, + "remaining_processes": [3706487], + "payload_cleanup_verified": true, + "cpu_stat": { + "usage_usec": 6050967, + "user_usec": 3868866, + "system_usec": 2182100, + "nr_periods": 62, + "nr_throttled": 0, + "throttled_usec": 0, + "nr_bursts": 0, + "burst_usec": 0 + }, + "memory_peak_bytes": 344432640, + "control_group": "user.slice/user-1004.slice/user@1004.service/app.slice/rea-efficiency-1610d79f372a4991966a9ce9bd36af0f.scope", + "resource_events": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + } + }, + "payload_resource_observation_verified": true, + "final_whole_scope_counters_verified": false, + "observer_source_sha256": { + "resource-run.py": "7728862171415e7dee2c11af2ead2b9a54265fccaf82a7113956d36b29065e33", + "resource_payload.py": "6f089e645459eba80c5d73e39c21606c78173a44daf49fabc3b88c4631ce51c6" + } + }, + "post_scope_state": "ActiveState=inactive\nSubState=dead", + "xvfb_teardown_failure": false + } + }, + "static": { + "tasks_passed": 2, + "tasks_cached": 0, + "receipt": { + "name": "check", + "source_manifest": null, + "harness_hash_at_run": "NOT_ENOUGH_INFO: not captured before this diagnostic", + "log_sha256": "b81bad8a32aa34a725ddc73e994724081016a825efc17b277f6bb08d9f56cd18", + "observations": [], + "resource_observation": { + "scope": "rea-efficiency-6c54ef0d4b5d4064848d1d5205612966.scope", + "elapsed_seconds": 89.441, + "abort": null, + "sampled_peak_cgroup_bytes": 4321443840, + "payload_exit": { + "phase": "payload-exited-observer-alive", + "ownership_token": "rea-efficiency-6c54ef0d4b5d4064848d1d5205612966.scope", + "payload_exit_code": 0, + "observer_pid": 3715753, + "remaining_processes": [3715753], + "payload_cleanup_verified": true, + "cpu_stat": { + "usage_usec": 88630288, + "user_usec": 84986470, + "system_usec": 3643818, + "nr_periods": 893, + "nr_throttled": 41, + "throttled_usec": 19672, + "nr_bursts": 0, + "burst_usec": 0 + }, + "memory_peak_bytes": 4337172480, + "control_group": "user.slice/user-1004.slice/user@1004.service/app.slice/rea-efficiency-6c54ef0d4b5d4064848d1d5205612966.scope", + "resource_events": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + } + }, + "payload_resource_observation_verified": true, + "final_whole_scope_counters_verified": false, + "observer_source_sha256": { + "resource-run.py": "7728862171415e7dee2c11af2ead2b9a54265fccaf82a7113956d36b29065e33", + "resource_payload.py": "6f089e645459eba80c5d73e39c21606c78173a44daf49fabc3b88c4631ce51c6" + } + }, + "post_scope_state": "ActiveState=inactive\nSubState=dead", + "xvfb_teardown_failure": false + } + }, + "docs": { + "tasks_passed": 6, + "readme_cases_passed": 5, + "log_sha256": "bb9efe251fca7d694947e2ef7474c939c2ba4b16406c82f78298650e646b705f", + "payload_exit": { + "phase": "payload-exited-observer-alive", + "ownership_token": "rea-efficiency-313ac8927257445e9ff18c774177304e.scope", + "payload_exit_code": 0, + "observer_pid": 3719376, + "remaining_processes": [3719376], + "payload_cleanup_verified": true, + "cpu_stat": { + "usage_usec": 13505614, + "user_usec": 10977822, + "system_usec": 2527791, + "nr_periods": 136, + "nr_throttled": 3, + "throttled_usec": 3117, + "nr_bursts": 0, + "burst_usec": 0 + }, + "memory_peak_bytes": 518815744, + "control_group": "user.slice/user-1004.slice/user@1004.service/app.slice/rea-efficiency-313ac8927257445e9ff18c774177304e.scope", + "resource_events": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + } + }, + "post_scope_state": "ActiveState=inactive\nSubState=dead" + } + }, + "real_app_trials": [ + { + "name": "drawio-baseline-1", + "source_manifest": "d02cc934c28812367be1d0a256f08a45c51af453", + "harness_hash_at_run": "pinned before run", + "log_sha256": "4e50247f744a2f1a620494ec959c43e79e4d4aa1588e704ef0bcb6cf9c771773", + "observations": [ + { + "profile": "/tmp/rea-4390-realapps-20261016/profiles/drawio-ui-vUvPks", + "runtime": "/home/yann/yann/rea-electron-cancellation-main-control-20261016", + "elapsed_ms": 10006.81695, + "deadline_triggered": false, + "selected_shape": ".geSidebar a.geItem:visible >> nth=0", + "before": { + "title": "Untitled Diagram - draw.io", + "invalid_file_dialog": true, + "unsaved_changes": false, + "diagram_svg_elements": 0, + "cells": 0 + }, + "after": { + "title": "Untitled Diagram - draw.io", + "invalid_file_dialog": false, + "unsaved_changes": true, + "diagram_svg_elements": 2, + "cells": 0 + }, + "actions_completed": true, + "shape_inserted": true, + "provider_ok": false, + "cleanup_verified": false, + "reason": "cleanup_failed", + "coverage": { + "status": "partial_attach", + "observed_event_families": [ + "app-lifecycle", + "ipc", + "navigation", + "preload-configuration", + "web-contents-lifecycle", + "window-lifecycle" + ], + "unavailable_event_families": [ + "download", + "error", + "native-addon", + "permission", + "popup-attempt", + "preload", + "process-lifecycle", + "protocol", + "renderer-ipc", + "shell-attempt", + "updater" + ], + "observed_roles": ["browser", "main", "web_contents", "window"], + "pre_capture_activity": "unavailable" + }, + "retention": { + "budget_bytes": 16777216, + "estimated_retained_bytes": 203982, + "event_serialized_byte_upper_bound": 48666, + "retained": 110, + "dropped": 0, + "dropped_event_families": [], + "dropped_event_roles": [], + "observed_ipc": 8, + "dropped_ipc": 0, + "observed_runtime": 102, + "dropped_runtime": 0 + }, + "ipc": { + "events": [ + { + "sequence": 77, + "correlation_id": "capture:3714016:77", + "kind": "main-event-invocation", + "event": null, + "phase": "observed", + "channel": "rendererReq", + "direction": "renderer-to-main", + "sender": "webContents:1", + "receiver": "main", + "frame": "frame:1", + "target": null, + "argument_shapes": ["object"], + "result_shape": null, + "process_type": "main", + "source": "electron-active-hook", + "capture_method": "api-wrapper", + "artifact_path": null, + "artifact_sha256": null, + "error": false + }, + { + "sequence": 84, + "correlation_id": "capture:3714016:84", + "kind": "main-event-invocation", + "event": null, + "phase": "observed", + "channel": "rendererReq", + "direction": "renderer-to-main", + "sender": "webContents:1", + "receiver": "main", + "frame": "frame:1", + "target": null, + "argument_shapes": ["object"], + "result_shape": null, + "process_type": "main", + "source": "electron-active-hook", + "capture_method": "api-wrapper", + "artifact_path": null, + "artifact_sha256": null, + "error": false + }, + { + "sequence": 87, + "correlation_id": "capture:3714016:87", + "kind": "main-event-invocation", + "event": null, + "phase": "observed", + "channel": "app-load-finished", + "direction": "renderer-to-main", + "sender": "webContents:1", + "receiver": "main", + "frame": "frame:1", + "target": null, + "argument_shapes": ["null"], + "result_shape": null, + "process_type": "main", + "source": "electron-active-hook", + "capture_method": "api-wrapper", + "artifact_path": null, + "artifact_sha256": null, + "error": false + }, + { + "sequence": 88, + "correlation_id": "capture:3714016:88", + "kind": "main-event-invocation", + "event": null, + "phase": "observed", + "channel": "app-load-finished", + "direction": "renderer-to-main", + "sender": "webContents:1", + "receiver": "main", + "frame": "frame:1", + "target": null, + "argument_shapes": ["null"], + "result_shape": null, + "process_type": "main", + "source": "electron-active-hook", + "capture_method": "api-wrapper", + "artifact_path": null, + "artifact_sha256": null, + "error": false + }, + { + "sequence": 89, + "correlation_id": "capture:3714016:89", + "kind": "main-event-invocation", + "event": null, + "phase": "observed", + "channel": "app-load-finished", + "direction": "renderer-to-main", + "sender": "webContents:1", + "receiver": "main", + "frame": "frame:1", + "target": null, + "argument_shapes": ["null"], + "result_shape": null, + "process_type": "main", + "source": "electron-active-hook", + "capture_method": "api-wrapper", + "artifact_path": null, + "artifact_sha256": null, + "error": false + }, + { + "sequence": 90, + "correlation_id": "capture:3714016:90", + "kind": "ipc-main-to-renderer", + "event": null, + "phase": "observed", + "channel": "args-obj", + "direction": "main-to-renderer", + "sender": "main", + "receiver": "webContents:1", + "frame": null, + "target": "webContents:1", + "argument_shapes": ["object"], + "result_shape": "undefined", + "process_type": "main", + "source": "electron-active-hook", + "capture_method": "api-wrapper", + "artifact_path": null, + "artifact_sha256": null, + "error": false + }, + { + "sequence": 93, + "correlation_id": "capture:3714016:93", + "kind": "main-event-invocation", + "event": null, + "phase": "observed", + "channel": "rendererReq", + "direction": "renderer-to-main", + "sender": "webContents:1", + "receiver": "main", + "frame": "frame:1", + "target": null, + "argument_shapes": ["object"], + "result_shape": null, + "process_type": "main", + "source": "electron-active-hook", + "capture_method": "api-wrapper", + "artifact_path": null, + "artifact_sha256": null, + "error": false + }, + { + "sequence": 97, + "correlation_id": "capture:3714016:97", + "kind": "main-event-invocation", + "event": null, + "phase": "observed", + "channel": "rendererReq", + "direction": "renderer-to-main", + "sender": "webContents:1", + "receiver": "main", + "frame": "frame:1", + "target": null, + "argument_shapes": ["object"], + "result_shape": null, + "process_type": "main", + "source": "electron-active-hook", + "capture_method": "api-wrapper", + "artifact_path": null, + "artifact_sha256": null, + "error": false + } + ], + "observed": 8, + "dropped": 0 + }, + "actions": [ + { + "step_id": "dismiss-invalid-file", + "kind": "click", + "window_index": 0, + "target": "window:0", + "elapsed_ms": 83, + "status": "completed", + "error": null + }, + { + "step_id": "insert-shape", + "kind": "click", + "window_index": 0, + "target": "window:0", + "elapsed_ms": 136, + "status": "completed", + "error": null + }, + { + "step_id": "settle-renderer", + "kind": "wait", + "window_index": 0, + "target": "window:0", + "elapsed_ms": 501, + "status": "completed", + "error": null + } + ], + "workflow_status": "partial-or-failed" + } + ], + "resource_observation": { + "scope": "rea-efficiency-bb9be4d57d7b48448c0aa72c272306b0.scope", + "elapsed_seconds": 11.084, + "abort": null, + "sampled_peak_cgroup_bytes": 560349184, + "payload_exit": { + "phase": "payload-exited-observer-alive", + "ownership_token": "rea-efficiency-bb9be4d57d7b48448c0aa72c272306b0.scope", + "payload_exit_code": 1, + "observer_pid": 3713982, + "remaining_processes": [3713982], + "payload_cleanup_verified": true, + "cpu_stat": { + "usage_usec": 6270053, + "user_usec": 4660681, + "system_usec": 1609371, + "nr_periods": 110, + "nr_throttled": 3, + "throttled_usec": 1262, + "nr_bursts": 0, + "burst_usec": 0 + }, + "memory_peak_bytes": 560615424, + "control_group": "user.slice/user-1004.slice/user@1004.service/app.slice/rea-efficiency-bb9be4d57d7b48448c0aa72c272306b0.scope", + "resource_events": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + } + }, + "payload_resource_observation_verified": true, + "final_whole_scope_counters_verified": false, + "observer_source_sha256": { + "resource-run.py": "7728862171415e7dee2c11af2ead2b9a54265fccaf82a7113956d36b29065e33", + "resource_payload.py": "6f089e645459eba80c5d73e39c21606c78173a44daf49fabc3b88c4631ce51c6" + } + }, + "post_scope_state": "ActiveState=inactive\nSubState=dead", + "xvfb_teardown_failure": false, + "harness_revision": "d02cc934c28812367be1d0a256f08a45c51af453", + "runtime_revision": "180ba03bfddd5916251e81e97a500b1ede7a9eea" + }, + { + "name": "drawio-candidate-1", + "source_manifest": "d02cc934c28812367be1d0a256f08a45c51af453", + "harness_hash_at_run": "pinned before run", + "log_sha256": "e62a085d6c6a829070eb048ba2488df34f15f6d0f8e861604efc98b18ebafed5", + "observations": [ + { + "profile": "/tmp/rea-4390-realapps-20261016/profiles/drawio-ui-KcFwvc", + "runtime": "/home/yann/yann/rea-electron-ipc-listener-4390-20261016", + "elapsed_ms": 10029.79165, + "deadline_triggered": false, + "selected_shape": ".geSidebar a.geItem:visible >> nth=0", + "before": { + "title": "Untitled Diagram - draw.io", + "invalid_file_dialog": true, + "unsaved_changes": false, + "diagram_svg_elements": 0, + "cells": 0 + }, + "after": { + "title": "Untitled Diagram - draw.io", + "invalid_file_dialog": false, + "unsaved_changes": true, + "diagram_svg_elements": 2, + "cells": 0 + }, + "actions_completed": true, + "shape_inserted": true, + "provider_ok": false, + "cleanup_verified": false, + "reason": "cleanup_failed", + "coverage": { + "status": "partial_attach", + "observed_event_families": [ + "app-lifecycle", + "ipc", + "navigation", + "preload-configuration", + "web-contents-lifecycle", + "window-lifecycle" + ], + "unavailable_event_families": [ + "download", + "error", + "native-addon", + "permission", + "popup-attempt", + "preload", + "process-lifecycle", + "protocol", + "renderer-ipc", + "shell-attempt", + "updater" + ], + "observed_roles": ["browser", "main", "web_contents", "window"], + "pre_capture_activity": "unavailable" + }, + "retention": { + "budget_bytes": 16777216, + "estimated_retained_bytes": 202114, + "event_serialized_byte_upper_bound": 48214, + "retained": 109, + "dropped": 0, + "dropped_event_families": [], + "dropped_event_roles": [], + "observed_ipc": 7, + "dropped_ipc": 0, + "observed_runtime": 102, + "dropped_runtime": 0 + }, + "ipc": { + "events": [ + { + "sequence": 77, + "correlation_id": "capture:3714160:77", + "kind": "main-event-invocation", + "event": null, + "phase": "observed", + "channel": "rendererReq", + "direction": "renderer-to-main", + "sender": "webContents:1", + "receiver": "main", + "frame": "frame:1", + "target": null, + "argument_shapes": ["object"], + "result_shape": null, + "process_type": "main", + "source": "electron-active-hook", + "capture_method": "api-wrapper", + "artifact_path": null, + "artifact_sha256": null, + "error": false + }, + { + "sequence": 84, + "correlation_id": "capture:3714160:84", + "kind": "main-event-invocation", + "event": null, + "phase": "observed", + "channel": "rendererReq", + "direction": "renderer-to-main", + "sender": "webContents:1", + "receiver": "main", + "frame": "frame:1", + "target": null, + "argument_shapes": ["object"], + "result_shape": null, + "process_type": "main", + "source": "electron-active-hook", + "capture_method": "api-wrapper", + "artifact_path": null, + "artifact_sha256": null, + "error": false + }, + { + "sequence": 87, + "correlation_id": "capture:3714160:87", + "kind": "main-event-invocation", + "event": null, + "phase": "observed", + "channel": "app-load-finished", + "direction": "renderer-to-main", + "sender": "webContents:1", + "receiver": "main", + "frame": "frame:1", + "target": null, + "argument_shapes": ["null"], + "result_shape": null, + "process_type": "main", + "source": "electron-active-hook", + "capture_method": "api-wrapper", + "artifact_path": null, + "artifact_sha256": null, + "error": false + }, + { + "sequence": 88, + "correlation_id": "capture:3714160:88", + "kind": "main-event-invocation", + "event": null, + "phase": "observed", + "channel": "app-load-finished", + "direction": "renderer-to-main", + "sender": "webContents:1", + "receiver": "main", + "frame": "frame:1", + "target": null, + "argument_shapes": ["null"], + "result_shape": null, + "process_type": "main", + "source": "electron-active-hook", + "capture_method": "api-wrapper", + "artifact_path": null, + "artifact_sha256": null, + "error": false + }, + { + "sequence": 89, + "correlation_id": "capture:3714160:89", + "kind": "ipc-main-to-renderer", + "event": null, + "phase": "observed", + "channel": "args-obj", + "direction": "main-to-renderer", + "sender": "main", + "receiver": "webContents:1", + "frame": null, + "target": "webContents:1", + "argument_shapes": ["object"], + "result_shape": "undefined", + "process_type": "main", + "source": "electron-active-hook", + "capture_method": "api-wrapper", + "artifact_path": null, + "artifact_sha256": null, + "error": false + }, + { + "sequence": 92, + "correlation_id": "capture:3714160:92", + "kind": "main-event-invocation", + "event": null, + "phase": "observed", + "channel": "rendererReq", + "direction": "renderer-to-main", + "sender": "webContents:1", + "receiver": "main", + "frame": "frame:1", + "target": null, + "argument_shapes": ["object"], + "result_shape": null, + "process_type": "main", + "source": "electron-active-hook", + "capture_method": "api-wrapper", + "artifact_path": null, + "artifact_sha256": null, + "error": false + }, + { + "sequence": 96, + "correlation_id": "capture:3714160:96", + "kind": "main-event-invocation", + "event": null, + "phase": "observed", + "channel": "rendererReq", + "direction": "renderer-to-main", + "sender": "webContents:1", + "receiver": "main", + "frame": "frame:1", + "target": null, + "argument_shapes": ["object"], + "result_shape": null, + "process_type": "main", + "source": "electron-active-hook", + "capture_method": "api-wrapper", + "artifact_path": null, + "artifact_sha256": null, + "error": false + } + ], + "observed": 7, + "dropped": 0 + }, + "actions": [ + { + "step_id": "dismiss-invalid-file", + "kind": "click", + "window_index": 0, + "target": "window:0", + "elapsed_ms": 75, + "status": "completed", + "error": null + }, + { + "step_id": "insert-shape", + "kind": "click", + "window_index": 0, + "target": "window:0", + "elapsed_ms": 106, + "status": "completed", + "error": null + }, + { + "step_id": "settle-renderer", + "kind": "wait", + "window_index": 0, + "target": "window:0", + "elapsed_ms": 501, + "status": "completed", + "error": null + } + ], + "workflow_status": "partial-or-failed" + } + ], + "resource_observation": { + "scope": "rea-efficiency-20cceec5ea874911ab2ec88cacbc08a3.scope", + "elapsed_seconds": 11.135, + "abort": null, + "sampled_peak_cgroup_bytes": 550121472, + "payload_exit": { + "phase": "payload-exited-observer-alive", + "ownership_token": "rea-efficiency-20cceec5ea874911ab2ec88cacbc08a3.scope", + "payload_exit_code": 1, + "observer_pid": 3714132, + "remaining_processes": [3714132, 3714144, 3714365, 3714367], + "payload_cleanup_verified": false, + "cpu_stat": { + "usage_usec": 6395461, + "user_usec": 4852435, + "system_usec": 1543025, + "nr_periods": 110, + "nr_throttled": 1, + "throttled_usec": 1324, + "nr_bursts": 0, + "burst_usec": 0 + }, + "memory_peak_bytes": 550383616, + "control_group": "user.slice/user-1004.slice/user@1004.service/app.slice/rea-efficiency-20cceec5ea874911ab2ec88cacbc08a3.scope", + "resource_events": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + } + }, + "payload_resource_observation_verified": false, + "final_whole_scope_counters_verified": false, + "observer_source_sha256": { + "resource-run.py": "7728862171415e7dee2c11af2ead2b9a54265fccaf82a7113956d36b29065e33", + "resource_payload.py": "6f089e645459eba80c5d73e39c21606c78173a44daf49fabc3b88c4631ce51c6" + } + }, + "post_scope_state": "ActiveState=inactive\nSubState=dead", + "xvfb_teardown_failure": false, + "harness_revision": "d02cc934c28812367be1d0a256f08a45c51af453", + "runtime_revision": "d02cc934c28812367be1d0a256f08a45c51af453" + }, + { + "name": "drawio-candidate-2", + "source_manifest": "d02cc934c28812367be1d0a256f08a45c51af453", + "harness_hash_at_run": "pinned before run", + "log_sha256": "d2c14556672ca26758e7fd0228ded03caa9aead39b9974756962bff40322b162", + "observations": [ + { + "profile": "/tmp/rea-4390-realapps-20261016/profiles/drawio-ui-KBvynV", + "runtime": "/home/yann/yann/rea-electron-ipc-listener-4390-20261016", + "elapsed_ms": 10714.616337, + "deadline_triggered": false, + "selected_shape": ".geSidebar a.geItem:visible >> nth=0", + "before": { + "title": "Untitled Diagram - draw.io", + "invalid_file_dialog": true, + "unsaved_changes": false, + "diagram_svg_elements": 0, + "cells": 0 + }, + "after": { + "title": "Untitled Diagram - draw.io", + "invalid_file_dialog": false, + "unsaved_changes": true, + "diagram_svg_elements": 2, + "cells": 0 + }, + "actions_completed": true, + "shape_inserted": true, + "provider_ok": false, + "cleanup_verified": false, + "reason": "cleanup_failed", + "coverage": { + "status": "partial_attach", + "observed_event_families": [ + "app-lifecycle", + "ipc", + "navigation", + "preload-configuration", + "web-contents-lifecycle", + "window-lifecycle" + ], + "unavailable_event_families": [ + "download", + "error", + "native-addon", + "permission", + "popup-attempt", + "preload", + "process-lifecycle", + "protocol", + "renderer-ipc", + "shell-attempt", + "updater" + ], + "observed_roles": ["browser", "main", "web_contents", "window"], + "pre_capture_activity": "unavailable" + }, + "retention": { + "budget_bytes": 16777216, + "estimated_retained_bytes": 202114, + "event_serialized_byte_upper_bound": 48214, + "retained": 109, + "dropped": 0, + "dropped_event_families": [], + "dropped_event_roles": [], + "observed_ipc": 7, + "dropped_ipc": 0, + "observed_runtime": 102, + "dropped_runtime": 0 + }, + "ipc": { + "events": [ + { + "sequence": 77, + "correlation_id": "capture:3714730:77", + "kind": "main-event-invocation", + "event": null, + "phase": "observed", + "channel": "rendererReq", + "direction": "renderer-to-main", + "sender": "webContents:1", + "receiver": "main", + "frame": "frame:1", + "target": null, + "argument_shapes": ["object"], + "result_shape": null, + "process_type": "main", + "source": "electron-active-hook", + "capture_method": "api-wrapper", + "artifact_path": null, + "artifact_sha256": null, + "error": false + }, + { + "sequence": 84, + "correlation_id": "capture:3714730:84", + "kind": "main-event-invocation", + "event": null, + "phase": "observed", + "channel": "rendererReq", + "direction": "renderer-to-main", + "sender": "webContents:1", + "receiver": "main", + "frame": "frame:1", + "target": null, + "argument_shapes": ["object"], + "result_shape": null, + "process_type": "main", + "source": "electron-active-hook", + "capture_method": "api-wrapper", + "artifact_path": null, + "artifact_sha256": null, + "error": false + }, + { + "sequence": 87, + "correlation_id": "capture:3714730:87", + "kind": "main-event-invocation", + "event": null, + "phase": "observed", + "channel": "app-load-finished", + "direction": "renderer-to-main", + "sender": "webContents:1", + "receiver": "main", + "frame": "frame:1", + "target": null, + "argument_shapes": ["null"], + "result_shape": null, + "process_type": "main", + "source": "electron-active-hook", + "capture_method": "api-wrapper", + "artifact_path": null, + "artifact_sha256": null, + "error": false + }, + { + "sequence": 88, + "correlation_id": "capture:3714730:88", + "kind": "main-event-invocation", + "event": null, + "phase": "observed", + "channel": "app-load-finished", + "direction": "renderer-to-main", + "sender": "webContents:1", + "receiver": "main", + "frame": "frame:1", + "target": null, + "argument_shapes": ["null"], + "result_shape": null, + "process_type": "main", + "source": "electron-active-hook", + "capture_method": "api-wrapper", + "artifact_path": null, + "artifact_sha256": null, + "error": false + }, + { + "sequence": 89, + "correlation_id": "capture:3714730:89", + "kind": "ipc-main-to-renderer", + "event": null, + "phase": "observed", + "channel": "args-obj", + "direction": "main-to-renderer", + "sender": "main", + "receiver": "webContents:1", + "frame": null, + "target": "webContents:1", + "argument_shapes": ["object"], + "result_shape": "undefined", + "process_type": "main", + "source": "electron-active-hook", + "capture_method": "api-wrapper", + "artifact_path": null, + "artifact_sha256": null, + "error": false + }, + { + "sequence": 92, + "correlation_id": "capture:3714730:92", + "kind": "main-event-invocation", + "event": null, + "phase": "observed", + "channel": "rendererReq", + "direction": "renderer-to-main", + "sender": "webContents:1", + "receiver": "main", + "frame": "frame:1", + "target": null, + "argument_shapes": ["object"], + "result_shape": null, + "process_type": "main", + "source": "electron-active-hook", + "capture_method": "api-wrapper", + "artifact_path": null, + "artifact_sha256": null, + "error": false + }, + { + "sequence": 96, + "correlation_id": "capture:3714730:96", + "kind": "main-event-invocation", + "event": null, + "phase": "observed", + "channel": "rendererReq", + "direction": "renderer-to-main", + "sender": "webContents:1", + "receiver": "main", + "frame": "frame:1", + "target": null, + "argument_shapes": ["object"], + "result_shape": null, + "process_type": "main", + "source": "electron-active-hook", + "capture_method": "api-wrapper", + "artifact_path": null, + "artifact_sha256": null, + "error": false + } + ], + "observed": 7, + "dropped": 0 + }, + "actions": [ + { + "step_id": "dismiss-invalid-file", + "kind": "click", + "window_index": 0, + "target": "window:0", + "elapsed_ms": 81, + "status": "completed", + "error": null + }, + { + "step_id": "insert-shape", + "kind": "click", + "window_index": 0, + "target": "window:0", + "elapsed_ms": 107, + "status": "completed", + "error": null + }, + { + "step_id": "settle-renderer", + "kind": "wait", + "window_index": 0, + "target": "window:0", + "elapsed_ms": 500, + "status": "completed", + "error": null + } + ], + "workflow_status": "partial-or-failed" + } + ], + "resource_observation": { + "scope": "rea-efficiency-654c4187b2b94ef4a396905c99986817.scope", + "elapsed_seconds": 11.898, + "abort": null, + "sampled_peak_cgroup_bytes": 525230080, + "payload_exit": { + "phase": "payload-exited-observer-alive", + "ownership_token": "rea-efficiency-654c4187b2b94ef4a396905c99986817.scope", + "payload_exit_code": 1, + "observer_pid": 3714370, + "remaining_processes": [3714370, 3714384], + "payload_cleanup_verified": false, + "cpu_stat": { + "usage_usec": 6907123, + "user_usec": 5032253, + "system_usec": 1874870, + "nr_periods": 118, + "nr_throttled": 8, + "throttled_usec": 12530, + "nr_bursts": 0, + "burst_usec": 0 + }, + "memory_peak_bytes": 525631488, + "control_group": "user.slice/user-1004.slice/user@1004.service/app.slice/rea-efficiency-654c4187b2b94ef4a396905c99986817.scope", + "resource_events": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + } + }, + "payload_resource_observation_verified": false, + "final_whole_scope_counters_verified": false, + "observer_source_sha256": { + "resource-run.py": "7728862171415e7dee2c11af2ead2b9a54265fccaf82a7113956d36b29065e33", + "resource_payload.py": "6f089e645459eba80c5d73e39c21606c78173a44daf49fabc3b88c4631ce51c6" + } + }, + "post_scope_state": "ActiveState=inactive\nSubState=dead", + "xvfb_teardown_failure": false, + "harness_revision": "d02cc934c28812367be1d0a256f08a45c51af453", + "runtime_revision": "d02cc934c28812367be1d0a256f08a45c51af453" + }, + { + "name": "drawio-baseline-2", + "source_manifest": "d02cc934c28812367be1d0a256f08a45c51af453", + "harness_hash_at_run": "pinned before run", + "log_sha256": "42363adf845571718b3abec7977f30c0abba3698b2f02da13a5b894f7521f676", + "observations": [ + { + "profile": "/tmp/rea-4390-realapps-20261016/profiles/drawio-ui-FdWSLl", + "runtime": "/home/yann/yann/rea-electron-cancellation-main-control-20261016", + "elapsed_ms": 10293.676588, + "deadline_triggered": false, + "selected_shape": ".geSidebar a.geItem:visible >> nth=0", + "before": { + "title": "Untitled Diagram - draw.io", + "invalid_file_dialog": true, + "unsaved_changes": false, + "diagram_svg_elements": 0, + "cells": 0 + }, + "after": { + "title": "Untitled Diagram - draw.io", + "invalid_file_dialog": false, + "unsaved_changes": true, + "diagram_svg_elements": 2, + "cells": 0 + }, + "actions_completed": true, + "shape_inserted": true, + "provider_ok": false, + "cleanup_verified": false, + "reason": "cleanup_failed", + "coverage": { + "status": "partial_attach", + "observed_event_families": [ + "app-lifecycle", + "ipc", + "navigation", + "preload-configuration", + "web-contents-lifecycle", + "window-lifecycle" + ], + "unavailable_event_families": [ + "download", + "error", + "native-addon", + "permission", + "popup-attempt", + "preload", + "process-lifecycle", + "protocol", + "renderer-ipc", + "shell-attempt", + "updater" + ], + "observed_roles": ["browser", "main", "web_contents", "window"], + "pre_capture_activity": "unavailable" + }, + "retention": { + "budget_bytes": 16777216, + "estimated_retained_bytes": 203982, + "event_serialized_byte_upper_bound": 48666, + "retained": 110, + "dropped": 0, + "dropped_event_families": [], + "dropped_event_roles": [], + "observed_ipc": 8, + "dropped_ipc": 0, + "observed_runtime": 102, + "dropped_runtime": 0 + }, + "ipc": { + "events": [ + { + "sequence": 77, + "correlation_id": "capture:3715117:77", + "kind": "main-event-invocation", + "event": null, + "phase": "observed", + "channel": "rendererReq", + "direction": "renderer-to-main", + "sender": "webContents:1", + "receiver": "main", + "frame": "frame:1", + "target": null, + "argument_shapes": ["object"], + "result_shape": null, + "process_type": "main", + "source": "electron-active-hook", + "capture_method": "api-wrapper", + "artifact_path": null, + "artifact_sha256": null, + "error": false + }, + { + "sequence": 84, + "correlation_id": "capture:3715117:84", + "kind": "main-event-invocation", + "event": null, + "phase": "observed", + "channel": "rendererReq", + "direction": "renderer-to-main", + "sender": "webContents:1", + "receiver": "main", + "frame": "frame:1", + "target": null, + "argument_shapes": ["object"], + "result_shape": null, + "process_type": "main", + "source": "electron-active-hook", + "capture_method": "api-wrapper", + "artifact_path": null, + "artifact_sha256": null, + "error": false + }, + { + "sequence": 87, + "correlation_id": "capture:3715117:87", + "kind": "main-event-invocation", + "event": null, + "phase": "observed", + "channel": "app-load-finished", + "direction": "renderer-to-main", + "sender": "webContents:1", + "receiver": "main", + "frame": "frame:1", + "target": null, + "argument_shapes": ["null"], + "result_shape": null, + "process_type": "main", + "source": "electron-active-hook", + "capture_method": "api-wrapper", + "artifact_path": null, + "artifact_sha256": null, + "error": false + }, + { + "sequence": 88, + "correlation_id": "capture:3715117:88", + "kind": "main-event-invocation", + "event": null, + "phase": "observed", + "channel": "app-load-finished", + "direction": "renderer-to-main", + "sender": "webContents:1", + "receiver": "main", + "frame": "frame:1", + "target": null, + "argument_shapes": ["null"], + "result_shape": null, + "process_type": "main", + "source": "electron-active-hook", + "capture_method": "api-wrapper", + "artifact_path": null, + "artifact_sha256": null, + "error": false + }, + { + "sequence": 89, + "correlation_id": "capture:3715117:89", + "kind": "main-event-invocation", + "event": null, + "phase": "observed", + "channel": "app-load-finished", + "direction": "renderer-to-main", + "sender": "webContents:1", + "receiver": "main", + "frame": "frame:1", + "target": null, + "argument_shapes": ["null"], + "result_shape": null, + "process_type": "main", + "source": "electron-active-hook", + "capture_method": "api-wrapper", + "artifact_path": null, + "artifact_sha256": null, + "error": false + }, + { + "sequence": 90, + "correlation_id": "capture:3715117:90", + "kind": "ipc-main-to-renderer", + "event": null, + "phase": "observed", + "channel": "args-obj", + "direction": "main-to-renderer", + "sender": "main", + "receiver": "webContents:1", + "frame": null, + "target": "webContents:1", + "argument_shapes": ["object"], + "result_shape": "undefined", + "process_type": "main", + "source": "electron-active-hook", + "capture_method": "api-wrapper", + "artifact_path": null, + "artifact_sha256": null, + "error": false + }, + { + "sequence": 93, + "correlation_id": "capture:3715117:93", + "kind": "main-event-invocation", + "event": null, + "phase": "observed", + "channel": "rendererReq", + "direction": "renderer-to-main", + "sender": "webContents:1", + "receiver": "main", + "frame": "frame:1", + "target": null, + "argument_shapes": ["object"], + "result_shape": null, + "process_type": "main", + "source": "electron-active-hook", + "capture_method": "api-wrapper", + "artifact_path": null, + "artifact_sha256": null, + "error": false + }, + { + "sequence": 97, + "correlation_id": "capture:3715117:97", + "kind": "main-event-invocation", + "event": null, + "phase": "observed", + "channel": "rendererReq", + "direction": "renderer-to-main", + "sender": "webContents:1", + "receiver": "main", + "frame": "frame:1", + "target": null, + "argument_shapes": ["object"], + "result_shape": null, + "process_type": "main", + "source": "electron-active-hook", + "capture_method": "api-wrapper", + "artifact_path": null, + "artifact_sha256": null, + "error": false + } + ], + "observed": 8, + "dropped": 0 + }, + "actions": [ + { + "step_id": "dismiss-invalid-file", + "kind": "click", + "window_index": 0, + "target": "window:0", + "elapsed_ms": 93, + "status": "completed", + "error": null + }, + { + "step_id": "insert-shape", + "kind": "click", + "window_index": 0, + "target": "window:0", + "elapsed_ms": 129, + "status": "completed", + "error": null + }, + { + "step_id": "settle-renderer", + "kind": "wait", + "window_index": 0, + "target": "window:0", + "elapsed_ms": 501, + "status": "completed", + "error": null + } + ], + "workflow_status": "partial-or-failed" + } + ], + "resource_observation": { + "scope": "rea-efficiency-d6f65716ca5743ee98a9efc4591dd55c.scope", + "elapsed_seconds": 11.469, + "abort": null, + "sampled_peak_cgroup_bytes": 558194688, + "payload_exit": { + "phase": "payload-exited-observer-alive", + "ownership_token": "rea-efficiency-d6f65716ca5743ee98a9efc4591dd55c.scope", + "payload_exit_code": 5, + "observer_pid": 3715059, + "remaining_processes": [3715059, 3715089], + "payload_cleanup_verified": false, + "cpu_stat": { + "usage_usec": 6526674, + "user_usec": 4920982, + "system_usec": 1605691, + "nr_periods": 113, + "nr_throttled": 0, + "throttled_usec": 0, + "nr_bursts": 0, + "burst_usec": 0 + }, + "memory_peak_bytes": 558456832, + "control_group": "user.slice/user-1004.slice/user@1004.service/app.slice/rea-efficiency-d6f65716ca5743ee98a9efc4591dd55c.scope", + "resource_events": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + } + }, + "payload_resource_observation_verified": false, + "final_whole_scope_counters_verified": false, + "observer_source_sha256": { + "resource-run.py": "7728862171415e7dee2c11af2ead2b9a54265fccaf82a7113956d36b29065e33", + "resource_payload.py": "6f089e645459eba80c5d73e39c21606c78173a44daf49fabc3b88c4631ce51c6" + } + }, + "post_scope_state": "ActiveState=inactive\nSubState=dead", + "xvfb_teardown_failure": true, + "harness_revision": "d02cc934c28812367be1d0a256f08a45c51af453", + "runtime_revision": "180ba03bfddd5916251e81e97a500b1ede7a9eea" + } + ], + "historical_diagnostics": [ + { + "name": "drawio-smoke", + "source_manifest": null, + "harness_hash_at_run": "NOT_ENOUGH_INFO: not captured before this diagnostic", + "log_sha256": "01beb2b3547e2e32dab985489b6e0b949496660176bb45ff7a97a105a9395ac8", + "observations": [ + { + "request": { + "executable_path": "/tmp/rea-docs-audit-20261012/runtime/electron-fixture-tools/node_modules/electron/dist/electron", + "application_path": "/tmp/rea-4390-realapps-20261016/drawio/resources/app.asar", + "args": [ + "--no-sandbox", + "--disable-gpu", + "--user-data-dir=/tmp/rea-4390-realapps-20261016/profiles/drawio-smoke-1fd15m_o" + ], + "actions": [ + { + "kind": "wait", + "step_id": "editor-startup", + "duration_ms": 3000 + } + ] + }, + "profile": "/tmp/rea-4390-realapps-20261016/profiles/drawio-smoke-1fd15m_o", + "exit_code": 1, + "timed_out": false, + "elapsed_seconds": 8.421080463100225, + "reply_code": "cleanup_incomplete", + "reply_message": "Electron cleanup could not be verified: process lineage contains an unowned or PID-reused process; helper cleanup was not proven. Collected observations are retained in partial_observation. Inspect the reported process resources before launching another capture.", + "capture": { + "application": { + "executable_path": "/tmp/rea-docs-audit-20261012/runtime/electron-fixture-tools/node_modules/electron/dist/electron", + "application_path": "/tmp/rea-4390-realapps-20261016/drawio/resources/app.asar", + "electron_version": "44.7.0", + "process_ownership": "provider-owned", + "cleanup": "unverified" + }, + "actions": [ + { + "step_id": "editor-startup", + "kind": "wait", + "window_index": null, + "target": null, + "elapsed_ms": 3023, + "status": "completed", + "error": null + } + ], + "windows": [ + { + "window_id": "window:1", + "web_contents_id": "webContents:1", + "url": "file:///tmp/rea-4390-realapps-20261016/drawio/resources/app.asar/drawio/src/main/webapp/index.html?dev=0&test=0&gapi=0&db=0&od=0&gh=0&gl=0&tr=0&browser=0&picker=0&mode=device&export=https%3A%2F%2Fconvert.diagrams.net%2Fnode%2Fexport&disableUpdate=1&enableSpellCheck=0&enableStoreBkp=1&isGoogleFontsEnabled=0&appLang=en-US&autoUpdate=1", + "title": "Untitled Diagram - draw.io", + "visible": true, + "destroyed": false + } + ], + "ipc": { + "events": [ + { + "sequence": 77, + "correlation_id": "capture:3705582:77", + "kind": "main-event-invocation", + "event": null, + "phase": "observed", + "channel": "rendererReq", + "direction": "renderer-to-main", + "sender": "webContents:1", + "receiver": "main", + "frame": "frame:1", + "target": null, + "argument_shapes": ["object"], + "result_shape": null, + "process_type": "main", + "source": "electron-active-hook", + "capture_method": "api-wrapper", + "artifact_path": null, + "artifact_sha256": null, + "error": false + }, + { + "sequence": 84, + "correlation_id": "capture:3705582:84", + "kind": "main-event-invocation", + "event": null, + "phase": "observed", + "channel": "rendererReq", + "direction": "renderer-to-main", + "sender": "webContents:1", + "receiver": "main", + "frame": "frame:1", + "target": null, + "argument_shapes": ["object"], + "result_shape": null, + "process_type": "main", + "source": "electron-active-hook", + "capture_method": "api-wrapper", + "artifact_path": null, + "artifact_sha256": null, + "error": false + }, + { + "sequence": 87, + "correlation_id": "capture:3705582:87", + "kind": "main-event-invocation", + "event": null, + "phase": "observed", + "channel": "app-load-finished", + "direction": "renderer-to-main", + "sender": "webContents:1", + "receiver": "main", + "frame": "frame:1", + "target": null, + "argument_shapes": ["null"], + "result_shape": null, + "process_type": "main", + "source": "electron-active-hook", + "capture_method": "api-wrapper", + "artifact_path": null, + "artifact_sha256": null, + "error": false + }, + { + "sequence": 88, + "correlation_id": "capture:3705582:88", + "kind": "main-event-invocation", + "event": null, + "phase": "observed", + "channel": "app-load-finished", + "direction": "renderer-to-main", + "sender": "webContents:1", + "receiver": "main", + "frame": "frame:1", + "target": null, + "argument_shapes": ["null"], + "result_shape": null, + "process_type": "main", + "source": "electron-active-hook", + "capture_method": "api-wrapper", + "artifact_path": null, + "artifact_sha256": null, + "error": false + }, + { + "sequence": 89, + "correlation_id": "capture:3705582:89", + "kind": "ipc-main-to-renderer", + "event": null, + "phase": "observed", + "channel": "args-obj", + "direction": "main-to-renderer", + "sender": "main", + "receiver": "webContents:1", + "frame": null, + "target": "webContents:1", + "argument_shapes": ["object"], + "result_shape": "undefined", + "process_type": "main", + "source": "electron-active-hook", + "capture_method": "api-wrapper", + "artifact_path": null, + "artifact_sha256": null, + "error": false + }, + { + "sequence": 92, + "correlation_id": "capture:3705582:92", + "kind": "main-event-invocation", + "event": null, + "phase": "observed", + "channel": "rendererReq", + "direction": "renderer-to-main", + "sender": "webContents:1", + "receiver": "main", + "frame": "frame:1", + "target": null, + "argument_shapes": ["object"], + "result_shape": null, + "process_type": "main", + "source": "electron-active-hook", + "capture_method": "api-wrapper", + "artifact_path": null, + "artifact_sha256": null, + "error": false + }, + { + "sequence": 96, + "correlation_id": "capture:3705582:96", + "kind": "main-event-invocation", + "event": null, + "phase": "observed", + "channel": "rendererReq", + "direction": "renderer-to-main", + "sender": "webContents:1", + "receiver": "main", + "frame": "frame:1", + "target": null, + "argument_shapes": ["object"], + "result_shape": null, + "process_type": "main", + "source": "electron-active-hook", + "capture_method": "api-wrapper", + "artifact_path": null, + "artifact_sha256": null, + "error": false + } + ], + "observed": 7, + "dropped": 0 + }, + "retention": { + "budget_bytes": 16777216, + "estimated_retained_bytes": 177739, + "event_serialized_byte_upper_bound": 42377, + "retained": 96, + "dropped": 0, + "dropped_event_families": [], + "dropped_event_roles": [], + "observed_ipc": 7, + "dropped_ipc": 0, + "observed_runtime": 89, + "dropped_runtime": 0 + }, + "coverage": { + "status": "partial_attach", + "observed_event_families": [ + "app-lifecycle", + "ipc", + "navigation", + "preload-configuration", + "web-contents-lifecycle", + "window-lifecycle" + ], + "unavailable_event_families": [ + "download", + "error", + "native-addon", + "permission", + "popup-attempt", + "preload", + "process-lifecycle", + "protocol", + "renderer-ipc", + "shell-attempt", + "updater" + ], + "observed_roles": ["browser", "main", "web_contents", "window"], + "pre_capture_activity": "unavailable" + }, + "limitations": [ + "IPC payloads are represented by value shapes; payload values are never retained.", + "IPC direction and sender/receiver identifiers are observed only where Electron exposes them at the hooked boundary.", + "The runtime timeline records lifecycle, navigation, shell, permission, popup, download, protocol, preload, native-addon, process, and IPC events; activity before hook installation is unavailable.", + "The preload and renderer process contexts are not instrumented by the main-process -r hook; preload configuration and contextBridge API-shape events are main-boundary observations, not proof of renderer-side execution.", + "Process metrics are an Electron API snapshot and do not prove hostile-local-user isolation.", + "External shell opens, external navigation, permission grants, downloads, popup windows, updater relaunches, and OS integration are blocked and recorded by the active hook; other application filesystem and network behavior is not sandboxed by this provider." + ] + }, + "stderr_tail": "" + } + ], + "resource_observation": { + "scope": "rea-efficiency-4f1fdbaca8dd4bb98565ac60c02f7fce.scope", + "elapsed_seconds": 8.811, + "abort": null, + "sampled_peak_cgroup_bytes": 700981248, + "payload_exit": { + "phase": "payload-exited-observer-alive", + "ownership_token": "rea-efficiency-4f1fdbaca8dd4bb98565ac60c02f7fce.scope", + "payload_exit_code": 1, + "observer_pid": 3705541, + "remaining_processes": [3705541], + "payload_cleanup_verified": true, + "cpu_stat": { + "usage_usec": 7830412, + "user_usec": 6216934, + "system_usec": 1613478, + "nr_periods": 86, + "nr_throttled": 6, + "throttled_usec": 18286, + "nr_bursts": 0, + "burst_usec": 0 + }, + "memory_peak_bytes": 703377408, + "control_group": "user.slice/user-1004.slice/user@1004.service/app.slice/rea-efficiency-4f1fdbaca8dd4bb98565ac60c02f7fce.scope", + "resource_events": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + } + }, + "payload_resource_observation_verified": true, + "final_whole_scope_counters_verified": false, + "observer_source_sha256": { + "resource-run.py": "7728862171415e7dee2c11af2ead2b9a54265fccaf82a7113956d36b29065e33", + "resource_payload.py": "6f089e645459eba80c5d73e39c21606c78173a44daf49fabc3b88c4631ce51c6" + } + }, + "post_scope_state": "ActiveState=inactive\nSubState=dead", + "xvfb_teardown_failure": false + }, + { + "name": "drawio-inspect", + "source_manifest": null, + "harness_hash_at_run": "NOT_ENOUGH_INFO: not captured before this diagnostic", + "log_sha256": "fda20a90e50147f59c67538ff6a58aca797e57a417f1445055251e783ebd9272", + "observations": [ + { + "profile": "/tmp/rea-4390-realapps-20261016/profiles/drawio-ui-QF9lE2", + "runtime": "/home/yann/yann/rea-electron-ipc-listener-4390-20261016", + "elapsed_ms": 5001.670024, + "selected_shape": null, + "before": { + "title": "Untitled Diagram - draw.io", + "diagram_svg_elements": 0, + "cells": 0 + }, + "after": { + "title": "Untitled Diagram - draw.io", + "diagram_svg_elements": 0, + "cells": 0 + }, + "actions_completed": true, + "shape_inserted": null, + "provider_ok": false, + "cleanup_verified": false, + "reason": "cleanup_failed", + "coverage": { + "status": "partial_attach", + "observed_event_families": [ + "app-lifecycle", + "ipc", + "navigation", + "preload-configuration", + "web-contents-lifecycle", + "window-lifecycle" + ], + "unavailable_event_families": [ + "download", + "error", + "native-addon", + "permission", + "popup-attempt", + "preload", + "process-lifecycle", + "protocol", + "renderer-ipc", + "shell-attempt", + "updater" + ], + "observed_roles": ["browser", "main", "web_contents", "window"], + "pre_capture_activity": "unavailable" + }, + "retention": { + "budget_bytes": 16777216, + "estimated_retained_bytes": 177739, + "event_serialized_byte_upper_bound": 42377, + "retained": 96, + "dropped": 0, + "dropped_event_families": [], + "dropped_event_roles": [], + "observed_ipc": 7, + "dropped_ipc": 0, + "observed_runtime": 89, + "dropped_runtime": 0 + }, + "ipc": { + "events": [ + { + "sequence": 77, + "correlation_id": "capture:3707496:77", + "kind": "main-event-invocation", + "event": null, + "phase": "observed", + "channel": "rendererReq", + "direction": "renderer-to-main", + "sender": "webContents:1", + "receiver": "main", + "frame": "frame:1", + "target": null, + "argument_shapes": ["object"], + "result_shape": null, + "process_type": "main", + "source": "electron-active-hook", + "capture_method": "api-wrapper", + "artifact_path": null, + "artifact_sha256": null, + "error": false + }, + { + "sequence": 84, + "correlation_id": "capture:3707496:84", + "kind": "main-event-invocation", + "event": null, + "phase": "observed", + "channel": "rendererReq", + "direction": "renderer-to-main", + "sender": "webContents:1", + "receiver": "main", + "frame": "frame:1", + "target": null, + "argument_shapes": ["object"], + "result_shape": null, + "process_type": "main", + "source": "electron-active-hook", + "capture_method": "api-wrapper", + "artifact_path": null, + "artifact_sha256": null, + "error": false + }, + { + "sequence": 87, + "correlation_id": "capture:3707496:87", + "kind": "main-event-invocation", + "event": null, + "phase": "observed", + "channel": "app-load-finished", + "direction": "renderer-to-main", + "sender": "webContents:1", + "receiver": "main", + "frame": "frame:1", + "target": null, + "argument_shapes": ["null"], + "result_shape": null, + "process_type": "main", + "source": "electron-active-hook", + "capture_method": "api-wrapper", + "artifact_path": null, + "artifact_sha256": null, + "error": false + }, + { + "sequence": 88, + "correlation_id": "capture:3707496:88", + "kind": "main-event-invocation", + "event": null, + "phase": "observed", + "channel": "app-load-finished", + "direction": "renderer-to-main", + "sender": "webContents:1", + "receiver": "main", + "frame": "frame:1", + "target": null, + "argument_shapes": ["null"], + "result_shape": null, + "process_type": "main", + "source": "electron-active-hook", + "capture_method": "api-wrapper", + "artifact_path": null, + "artifact_sha256": null, + "error": false + }, + { + "sequence": 89, + "correlation_id": "capture:3707496:89", + "kind": "ipc-main-to-renderer", + "event": null, + "phase": "observed", + "channel": "args-obj", + "direction": "main-to-renderer", + "sender": "main", + "receiver": "webContents:1", + "frame": null, + "target": "webContents:1", + "argument_shapes": ["object"], + "result_shape": "undefined", + "process_type": "main", + "source": "electron-active-hook", + "capture_method": "api-wrapper", + "artifact_path": null, + "artifact_sha256": null, + "error": false + }, + { + "sequence": 92, + "correlation_id": "capture:3707496:92", + "kind": "main-event-invocation", + "event": null, + "phase": "observed", + "channel": "rendererReq", + "direction": "renderer-to-main", + "sender": "webContents:1", + "receiver": "main", + "frame": "frame:1", + "target": null, + "argument_shapes": ["object"], + "result_shape": null, + "process_type": "main", + "source": "electron-active-hook", + "capture_method": "api-wrapper", + "artifact_path": null, + "artifact_sha256": null, + "error": false + }, + { + "sequence": 96, + "correlation_id": "capture:3707496:96", + "kind": "main-event-invocation", + "event": null, + "phase": "observed", + "channel": "rendererReq", + "direction": "renderer-to-main", + "sender": "webContents:1", + "receiver": "main", + "frame": "frame:1", + "target": null, + "argument_shapes": ["object"], + "result_shape": null, + "process_type": "main", + "source": "electron-active-hook", + "capture_method": "api-wrapper", + "artifact_path": null, + "artifact_sha256": null, + "error": false + } + ], + "observed": 7, + "dropped": 0 + }, + "actions": [ + { + "step_id": "settle-renderer", + "kind": "wait", + "window_index": 0, + "target": "window:0", + "elapsed_ms": 507, + "status": "completed", + "error": null + } + ], + "workflow_status": "partial-or-failed" + } + ], + "resource_observation": { + "scope": "rea-efficiency-662d209969474cb982f6faaaa7290ddb.scope", + "elapsed_seconds": 6.012, + "abort": null, + "sampled_peak_cgroup_bytes": 483291136, + "payload_exit": { + "phase": "payload-exited-observer-alive", + "ownership_token": "rea-efficiency-662d209969474cb982f6faaaa7290ddb.scope", + "payload_exit_code": 1, + "observer_pid": 3707466, + "remaining_processes": [3707466, 3707478], + "payload_cleanup_verified": false, + "cpu_stat": { + "usage_usec": 5323847, + "user_usec": 3915375, + "system_usec": 1408471, + "nr_periods": 59, + "nr_throttled": 0, + "throttled_usec": 0, + "nr_bursts": 0, + "burst_usec": 0 + }, + "memory_peak_bytes": 486182912, + "control_group": "user.slice/user-1004.slice/user@1004.service/app.slice/rea-efficiency-662d209969474cb982f6faaaa7290ddb.scope", + "resource_events": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + } + }, + "payload_resource_observation_verified": false, + "final_whole_scope_counters_verified": false, + "observer_source_sha256": { + "resource-run.py": "7728862171415e7dee2c11af2ead2b9a54265fccaf82a7113956d36b29065e33", + "resource_payload.py": "6f089e645459eba80c5d73e39c21606c78173a44daf49fabc3b88c4631ce51c6" + } + }, + "post_scope_state": "ActiveState=inactive\nSubState=dead", + "xvfb_teardown_failure": false + }, + { + "name": "drawio-insert-diagnostic", + "source_manifest": null, + "harness_hash_at_run": "NOT_ENOUGH_INFO: not captured before this diagnostic", + "log_sha256": "ba22d945858a56fe53b9b2438eab5d066af2899b6c53c3b0e1d79477b44bfa6f", + "observations": [ + { + "profile": "/tmp/rea-4390-realapps-20261016/profiles/drawio-ui-9r2HQd", + "runtime": "/home/yann/yann/rea-electron-ipc-listener-4390-20261016", + "elapsed_ms": 40738.874155, + "selected_shape": ".geSidebar a.geItem >> nth=0", + "before": { + "title": "Untitled Diagram - draw.io", + "diagram_svg_elements": 0, + "cells": 0 + }, + "after": { + "title": "Untitled Diagram - draw.io", + "diagram_svg_elements": 0, + "cells": 0 + }, + "actions_completed": false, + "shape_inserted": false, + "provider_ok": false, + "cleanup_verified": false, + "reason": "cleanup_failed", + "coverage": { + "status": "partial_attach", + "observed_event_families": [ + "app-lifecycle", + "ipc", + "navigation", + "preload-configuration", + "web-contents-lifecycle", + "window-lifecycle" + ], + "unavailable_event_families": [ + "download", + "error", + "native-addon", + "permission", + "popup-attempt", + "preload", + "process-lifecycle", + "protocol", + "renderer-ipc", + "shell-attempt", + "updater" + ], + "observed_roles": ["browser", "main", "web_contents", "window"], + "pre_capture_activity": "unavailable" + }, + "retention": { + "budget_bytes": 16777216, + "estimated_retained_bytes": 177739, + "event_serialized_byte_upper_bound": 42377, + "retained": 96, + "dropped": 0, + "dropped_event_families": [], + "dropped_event_roles": [], + "observed_ipc": 7, + "dropped_ipc": 0, + "observed_runtime": 89, + "dropped_runtime": 0 + }, + "ipc": { + "events": [ + { + "sequence": 77, + "correlation_id": "capture:3708003:77", + "kind": "main-event-invocation", + "event": null, + "phase": "observed", + "channel": "rendererReq", + "direction": "renderer-to-main", + "sender": "webContents:1", + "receiver": "main", + "frame": "frame:1", + "target": null, + "argument_shapes": ["object"], + "result_shape": null, + "process_type": "main", + "source": "electron-active-hook", + "capture_method": "api-wrapper", + "artifact_path": null, + "artifact_sha256": null, + "error": false + }, + { + "sequence": 84, + "correlation_id": "capture:3708003:84", + "kind": "main-event-invocation", + "event": null, + "phase": "observed", + "channel": "rendererReq", + "direction": "renderer-to-main", + "sender": "webContents:1", + "receiver": "main", + "frame": "frame:1", + "target": null, + "argument_shapes": ["object"], + "result_shape": null, + "process_type": "main", + "source": "electron-active-hook", + "capture_method": "api-wrapper", + "artifact_path": null, + "artifact_sha256": null, + "error": false + }, + { + "sequence": 87, + "correlation_id": "capture:3708003:87", + "kind": "main-event-invocation", + "event": null, + "phase": "observed", + "channel": "app-load-finished", + "direction": "renderer-to-main", + "sender": "webContents:1", + "receiver": "main", + "frame": "frame:1", + "target": null, + "argument_shapes": ["null"], + "result_shape": null, + "process_type": "main", + "source": "electron-active-hook", + "capture_method": "api-wrapper", + "artifact_path": null, + "artifact_sha256": null, + "error": false + }, + { + "sequence": 88, + "correlation_id": "capture:3708003:88", + "kind": "main-event-invocation", + "event": null, + "phase": "observed", + "channel": "app-load-finished", + "direction": "renderer-to-main", + "sender": "webContents:1", + "receiver": "main", + "frame": "frame:1", + "target": null, + "argument_shapes": ["null"], + "result_shape": null, + "process_type": "main", + "source": "electron-active-hook", + "capture_method": "api-wrapper", + "artifact_path": null, + "artifact_sha256": null, + "error": false + }, + { + "sequence": 89, + "correlation_id": "capture:3708003:89", + "kind": "ipc-main-to-renderer", + "event": null, + "phase": "observed", + "channel": "args-obj", + "direction": "main-to-renderer", + "sender": "main", + "receiver": "webContents:1", + "frame": null, + "target": "webContents:1", + "argument_shapes": ["object"], + "result_shape": "undefined", + "process_type": "main", + "source": "electron-active-hook", + "capture_method": "api-wrapper", + "artifact_path": null, + "artifact_sha256": null, + "error": false + }, + { + "sequence": 92, + "correlation_id": "capture:3708003:92", + "kind": "main-event-invocation", + "event": null, + "phase": "observed", + "channel": "rendererReq", + "direction": "renderer-to-main", + "sender": "webContents:1", + "receiver": "main", + "frame": "frame:1", + "target": null, + "argument_shapes": ["object"], + "result_shape": null, + "process_type": "main", + "source": "electron-active-hook", + "capture_method": "api-wrapper", + "artifact_path": null, + "artifact_sha256": null, + "error": false + }, + { + "sequence": 96, + "correlation_id": "capture:3708003:96", + "kind": "main-event-invocation", + "event": null, + "phase": "observed", + "channel": "rendererReq", + "direction": "renderer-to-main", + "sender": "webContents:1", + "receiver": "main", + "frame": "frame:1", + "target": null, + "argument_shapes": ["object"], + "result_shape": null, + "process_type": "main", + "source": "electron-active-hook", + "capture_method": "api-wrapper", + "artifact_path": null, + "artifact_sha256": null, + "error": false + } + ], + "observed": 7, + "dropped": 0 + }, + "actions": [ + { + "step_id": "insert-shape", + "kind": "click", + "window_index": 0, + "target": "window:0", + "elapsed_ms": 35848, + "status": "cancelled", + "error": "Browser observation capture_electron_scenario failed: cancelled" + } + ], + "workflow_status": "partial-or-failed" + } + ], + "resource_observation": { + "scope": "rea-efficiency-8ed23b8e39f240fb9a5b729042d10dd2.scope", + "elapsed_seconds": 41.806, + "abort": null, + "sampled_peak_cgroup_bytes": 530280448, + "payload_exit": { + "phase": "payload-exited-observer-alive", + "ownership_token": "rea-efficiency-8ed23b8e39f240fb9a5b729042d10dd2.scope", + "payload_exit_code": 1, + "observer_pid": 3707963, + "remaining_processes": [3707963], + "payload_cleanup_verified": true, + "cpu_stat": { + "usage_usec": 9410989, + "user_usec": 6810684, + "system_usec": 2600305, + "nr_periods": 400, + "nr_throttled": 1, + "throttled_usec": 12789, + "nr_bursts": 0, + "burst_usec": 0 + }, + "memory_peak_bytes": 536735744, + "control_group": "user.slice/user-1004.slice/user@1004.service/app.slice/rea-efficiency-8ed23b8e39f240fb9a5b729042d10dd2.scope", + "resource_events": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + } + }, + "payload_resource_observation_verified": true, + "final_whole_scope_counters_verified": false, + "observer_source_sha256": { + "resource-run.py": "7728862171415e7dee2c11af2ead2b9a54265fccaf82a7113956d36b29065e33", + "resource_payload.py": "6f089e645459eba80c5d73e39c21606c78173a44daf49fabc3b88c4631ce51c6" + } + }, + "post_scope_state": "ActiveState=inactive\nSubState=dead", + "xvfb_teardown_failure": false + }, + { + "name": "drawio-visible-insert", + "source_manifest": null, + "harness_hash_at_run": "NOT_ENOUGH_INFO: not captured before this diagnostic", + "log_sha256": "4e95793afdc13831677bc00df09ee29269637b73f5aea66a945afb315d2d62b1", + "observations": [ + { + "profile": "/tmp/rea-4390-realapps-20261016/profiles/drawio-ui-uFneVi", + "runtime": "/home/yann/yann/rea-electron-ipc-listener-4390-20261016", + "elapsed_ms": 40862.040049999996, + "selected_shape": ".geSidebar a.geItem:visible >> nth=0", + "before": { + "title": "Untitled Diagram - draw.io", + "diagram_svg_elements": 0, + "cells": 0, + "invalid_file_dialog_text_observed": false + }, + "after": { + "title": "Untitled Diagram - draw.io", + "diagram_svg_elements": 0, + "cells": 0, + "invalid_file_dialog_text_observed": true + }, + "actions_completed": false, + "shape_inserted": false, + "provider_ok": false, + "cleanup_verified": false, + "reason": "cleanup_failed", + "coverage": { + "status": "partial_attach", + "observed_event_families": [ + "app-lifecycle", + "ipc", + "navigation", + "preload-configuration", + "web-contents-lifecycle", + "window-lifecycle" + ], + "unavailable_event_families": [ + "download", + "error", + "native-addon", + "permission", + "popup-attempt", + "preload", + "process-lifecycle", + "protocol", + "renderer-ipc", + "shell-attempt", + "updater" + ], + "observed_roles": ["browser", "main", "web_contents", "window"], + "pre_capture_activity": "unavailable" + }, + "retention": { + "budget_bytes": 16777216, + "estimated_retained_bytes": 177739, + "event_serialized_byte_upper_bound": 42377, + "retained": 96, + "dropped": 0, + "dropped_event_families": [], + "dropped_event_roles": [], + "observed_ipc": 7, + "dropped_ipc": 0, + "observed_runtime": 89, + "dropped_runtime": 0 + }, + "ipc": { + "events": [ + { + "sequence": 77, + "correlation_id": "capture:3709834:77", + "kind": "main-event-invocation", + "event": null, + "phase": "observed", + "channel": "rendererReq", + "direction": "renderer-to-main", + "sender": "webContents:1", + "receiver": "main", + "frame": "frame:1", + "target": null, + "argument_shapes": ["object"], + "result_shape": null, + "process_type": "main", + "source": "electron-active-hook", + "capture_method": "api-wrapper", + "artifact_path": null, + "artifact_sha256": null, + "error": false + }, + { + "sequence": 84, + "correlation_id": "capture:3709834:84", + "kind": "main-event-invocation", + "event": null, + "phase": "observed", + "channel": "rendererReq", + "direction": "renderer-to-main", + "sender": "webContents:1", + "receiver": "main", + "frame": "frame:1", + "target": null, + "argument_shapes": ["object"], + "result_shape": null, + "process_type": "main", + "source": "electron-active-hook", + "capture_method": "api-wrapper", + "artifact_path": null, + "artifact_sha256": null, + "error": false + }, + { + "sequence": 87, + "correlation_id": "capture:3709834:87", + "kind": "main-event-invocation", + "event": null, + "phase": "observed", + "channel": "app-load-finished", + "direction": "renderer-to-main", + "sender": "webContents:1", + "receiver": "main", + "frame": "frame:1", + "target": null, + "argument_shapes": ["null"], + "result_shape": null, + "process_type": "main", + "source": "electron-active-hook", + "capture_method": "api-wrapper", + "artifact_path": null, + "artifact_sha256": null, + "error": false + }, + { + "sequence": 88, + "correlation_id": "capture:3709834:88", + "kind": "main-event-invocation", + "event": null, + "phase": "observed", + "channel": "app-load-finished", + "direction": "renderer-to-main", + "sender": "webContents:1", + "receiver": "main", + "frame": "frame:1", + "target": null, + "argument_shapes": ["null"], + "result_shape": null, + "process_type": "main", + "source": "electron-active-hook", + "capture_method": "api-wrapper", + "artifact_path": null, + "artifact_sha256": null, + "error": false + }, + { + "sequence": 89, + "correlation_id": "capture:3709834:89", + "kind": "ipc-main-to-renderer", + "event": null, + "phase": "observed", + "channel": "args-obj", + "direction": "main-to-renderer", + "sender": "main", + "receiver": "webContents:1", + "frame": null, + "target": "webContents:1", + "argument_shapes": ["object"], + "result_shape": "undefined", + "process_type": "main", + "source": "electron-active-hook", + "capture_method": "api-wrapper", + "artifact_path": null, + "artifact_sha256": null, + "error": false + }, + { + "sequence": 92, + "correlation_id": "capture:3709834:92", + "kind": "main-event-invocation", + "event": null, + "phase": "observed", + "channel": "rendererReq", + "direction": "renderer-to-main", + "sender": "webContents:1", + "receiver": "main", + "frame": "frame:1", + "target": null, + "argument_shapes": ["object"], + "result_shape": null, + "process_type": "main", + "source": "electron-active-hook", + "capture_method": "api-wrapper", + "artifact_path": null, + "artifact_sha256": null, + "error": false + }, + { + "sequence": 96, + "correlation_id": "capture:3709834:96", + "kind": "main-event-invocation", + "event": null, + "phase": "observed", + "channel": "rendererReq", + "direction": "renderer-to-main", + "sender": "webContents:1", + "receiver": "main", + "frame": "frame:1", + "target": null, + "argument_shapes": ["object"], + "result_shape": null, + "process_type": "main", + "source": "electron-active-hook", + "capture_method": "api-wrapper", + "artifact_path": null, + "artifact_sha256": null, + "error": false + } + ], + "observed": 7, + "dropped": 0 + }, + "actions": [ + { + "step_id": "insert-shape", + "kind": "click", + "window_index": 0, + "target": "window:0", + "elapsed_ms": 35977, + "status": "cancelled", + "error": "Browser observation capture_electron_scenario failed: cancelled" + } + ], + "workflow_status": "partial-or-failed" + } + ], + "resource_observation": { + "scope": "rea-efficiency-b1883a8592a5466baae309560b287bc7.scope", + "elapsed_seconds": 41.849, + "abort": null, + "sampled_peak_cgroup_bytes": 534786048, + "payload_exit": { + "phase": "payload-exited-observer-alive", + "ownership_token": "rea-efficiency-b1883a8592a5466baae309560b287bc7.scope", + "payload_exit_code": 1, + "observer_pid": 3709805, + "remaining_processes": [3709805], + "payload_cleanup_verified": true, + "cpu_stat": { + "usage_usec": 8851268, + "user_usec": 6282326, + "system_usec": 2568942, + "nr_periods": 396, + "nr_throttled": 4, + "throttled_usec": 15206, + "nr_bursts": 0, + "burst_usec": 0 + }, + "memory_peak_bytes": 539242496, + "control_group": "user.slice/user-1004.slice/user@1004.service/app.slice/rea-efficiency-b1883a8592a5466baae309560b287bc7.scope", + "resource_events": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + } + }, + "payload_resource_observation_verified": true, + "final_whole_scope_counters_verified": false, + "observer_source_sha256": { + "resource-run.py": "7728862171415e7dee2c11af2ead2b9a54265fccaf82a7113956d36b29065e33", + "resource_payload.py": "6f089e645459eba80c5d73e39c21606c78173a44daf49fabc3b88c4631ce51c6" + } + }, + "post_scope_state": "ActiveState=inactive\nSubState=dead", + "xvfb_teardown_failure": false + }, + { + "name": "drawio-recovery-insert", + "source_manifest": null, + "harness_hash_at_run": "NOT_ENOUGH_INFO: not captured before this diagnostic", + "log_sha256": "b3da509c2c1d5e374b0947a01cc6f432ebbd8b98394a1cc03333bbe6052ec9ad", + "observations": [ + { + "profile": "/tmp/rea-4390-realapps-20261016/profiles/drawio-ui-9iII3M", + "runtime": "/home/yann/yann/rea-electron-ipc-listener-4390-20261016", + "elapsed_ms": 10202.265695999999, + "selected_shape": ".geSidebar a.geItem:visible >> nth=0", + "before": { + "title": "Untitled Diagram - draw.io", + "diagram_svg_elements": 0, + "cells": 0, + "main_process": { + "argv": [ + "/tmp/rea-docs-audit-20261012/runtime/electron-fixture-tools/node_modules/electron/dist/electron", + "--inspect=0", + "--remote-debugging-port=0", + "-r", + "/home/yann/yann/rea-electron-ipc-listener-4390-20261016/scripts/electron-active-hook.cjs", + "/tmp/rea-4390-realapps-20261016/drawio/resources/app.asar", + "--no-sandbox", + "--disable-gpu", + "--user-data-dir=/tmp/rea-4390-realapps-20261016/profiles/drawio-ui-9iII3M" + ], + "default_app": true + }, + "invalid_file_dialog_text_observed": false + }, + "after": { + "title": "Untitled Diagram - draw.io", + "diagram_svg_elements": 2, + "cells": 0, + "invalid_file_dialog_text_observed": false + }, + "actions_completed": true, + "shape_inserted": true, + "provider_ok": false, + "cleanup_verified": false, + "reason": "cleanup_failed", + "coverage": { + "status": "partial_attach", + "observed_event_families": [ + "app-lifecycle", + "ipc", + "navigation", + "preload-configuration", + "web-contents-lifecycle", + "window-lifecycle" + ], + "unavailable_event_families": [ + "download", + "error", + "native-addon", + "permission", + "popup-attempt", + "preload", + "process-lifecycle", + "protocol", + "renderer-ipc", + "shell-attempt", + "updater" + ], + "observed_roles": ["browser", "main", "web_contents", "window"], + "pre_capture_activity": "unavailable" + }, + "retention": { + "budget_bytes": 16777216, + "estimated_retained_bytes": 202114, + "event_serialized_byte_upper_bound": 48214, + "retained": 109, + "dropped": 0, + "dropped_event_families": [], + "dropped_event_roles": [], + "observed_ipc": 7, + "dropped_ipc": 0, + "observed_runtime": 102, + "dropped_runtime": 0 + }, + "ipc": { + "events": [ + { + "sequence": 77, + "correlation_id": "capture:3711700:77", + "kind": "main-event-invocation", + "event": null, + "phase": "observed", + "channel": "rendererReq", + "direction": "renderer-to-main", + "sender": "webContents:1", + "receiver": "main", + "frame": "frame:1", + "target": null, + "argument_shapes": ["object"], + "result_shape": null, + "process_type": "main", + "source": "electron-active-hook", + "capture_method": "api-wrapper", + "artifact_path": null, + "artifact_sha256": null, + "error": false + }, + { + "sequence": 84, + "correlation_id": "capture:3711700:84", + "kind": "main-event-invocation", + "event": null, + "phase": "observed", + "channel": "rendererReq", + "direction": "renderer-to-main", + "sender": "webContents:1", + "receiver": "main", + "frame": "frame:1", + "target": null, + "argument_shapes": ["object"], + "result_shape": null, + "process_type": "main", + "source": "electron-active-hook", + "capture_method": "api-wrapper", + "artifact_path": null, + "artifact_sha256": null, + "error": false + }, + { + "sequence": 87, + "correlation_id": "capture:3711700:87", + "kind": "main-event-invocation", + "event": null, + "phase": "observed", + "channel": "app-load-finished", + "direction": "renderer-to-main", + "sender": "webContents:1", + "receiver": "main", + "frame": "frame:1", + "target": null, + "argument_shapes": ["null"], + "result_shape": null, + "process_type": "main", + "source": "electron-active-hook", + "capture_method": "api-wrapper", + "artifact_path": null, + "artifact_sha256": null, + "error": false + }, + { + "sequence": 88, + "correlation_id": "capture:3711700:88", + "kind": "main-event-invocation", + "event": null, + "phase": "observed", + "channel": "app-load-finished", + "direction": "renderer-to-main", + "sender": "webContents:1", + "receiver": "main", + "frame": "frame:1", + "target": null, + "argument_shapes": ["null"], + "result_shape": null, + "process_type": "main", + "source": "electron-active-hook", + "capture_method": "api-wrapper", + "artifact_path": null, + "artifact_sha256": null, + "error": false + }, + { + "sequence": 89, + "correlation_id": "capture:3711700:89", + "kind": "ipc-main-to-renderer", + "event": null, + "phase": "observed", + "channel": "args-obj", + "direction": "main-to-renderer", + "sender": "main", + "receiver": "webContents:1", + "frame": null, + "target": "webContents:1", + "argument_shapes": ["object"], + "result_shape": "undefined", + "process_type": "main", + "source": "electron-active-hook", + "capture_method": "api-wrapper", + "artifact_path": null, + "artifact_sha256": null, + "error": false + }, + { + "sequence": 92, + "correlation_id": "capture:3711700:92", + "kind": "main-event-invocation", + "event": null, + "phase": "observed", + "channel": "rendererReq", + "direction": "renderer-to-main", + "sender": "webContents:1", + "receiver": "main", + "frame": "frame:1", + "target": null, + "argument_shapes": ["object"], + "result_shape": null, + "process_type": "main", + "source": "electron-active-hook", + "capture_method": "api-wrapper", + "artifact_path": null, + "artifact_sha256": null, + "error": false + }, + { + "sequence": 96, + "correlation_id": "capture:3711700:96", + "kind": "main-event-invocation", + "event": null, + "phase": "observed", + "channel": "rendererReq", + "direction": "renderer-to-main", + "sender": "webContents:1", + "receiver": "main", + "frame": "frame:1", + "target": null, + "argument_shapes": ["object"], + "result_shape": null, + "process_type": "main", + "source": "electron-active-hook", + "capture_method": "api-wrapper", + "artifact_path": null, + "artifact_sha256": null, + "error": false + } + ], + "observed": 7, + "dropped": 0 + }, + "actions": [ + { + "step_id": "dismiss-invalid-file", + "kind": "click", + "window_index": 0, + "target": "window:0", + "elapsed_ms": 100, + "status": "completed", + "error": null + }, + { + "step_id": "insert-shape", + "kind": "click", + "window_index": 0, + "target": "window:0", + "elapsed_ms": 125, + "status": "completed", + "error": null + }, + { + "step_id": "settle-renderer", + "kind": "wait", + "window_index": 0, + "target": "window:0", + "elapsed_ms": 501, + "status": "completed", + "error": null + } + ], + "workflow_status": "partial-or-failed" + } + ], + "resource_observation": { + "scope": "rea-efficiency-a5a61de8bca74116922c6c4d60f6e498.scope", + "elapsed_seconds": 11.259, + "abort": null, + "sampled_peak_cgroup_bytes": 528416768, + "payload_exit": { + "phase": "payload-exited-observer-alive", + "ownership_token": "rea-efficiency-a5a61de8bca74116922c6c4d60f6e498.scope", + "payload_exit_code": 1, + "observer_pid": 3711672, + "remaining_processes": [3711672, 3711684, 3711801, 3711803], + "payload_cleanup_verified": false, + "cpu_stat": { + "usage_usec": 6297848, + "user_usec": 4677339, + "system_usec": 1620508, + "nr_periods": 111, + "nr_throttled": 0, + "throttled_usec": 0, + "nr_bursts": 0, + "burst_usec": 0 + }, + "memory_peak_bytes": 529313792, + "control_group": "user.slice/user-1004.slice/user@1004.service/app.slice/rea-efficiency-a5a61de8bca74116922c6c4d60f6e498.scope", + "resource_events": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + } + }, + "payload_resource_observation_verified": false, + "final_whole_scope_counters_verified": false, + "observer_source_sha256": { + "resource-run.py": "7728862171415e7dee2c11af2ead2b9a54265fccaf82a7113956d36b29065e33", + "resource_payload.py": "6f089e645459eba80c5d73e39c21606c78173a44daf49fabc3b88c4631ce51c6" + } + }, + "post_scope_state": "ActiveState=inactive\nSubState=dead", + "xvfb_teardown_failure": false + } + ], + "related_blockers": { + "4494": "Invalid-file startup dialog and injected arguments; exact causality not proven", + "4282": "Linux provider process-lineage/cleanup uncertainty", + "2974": "Related packaged Windows launch/Inspector limitations; different runtime scope" + }, + "limits": [ + "All four real trials complete the declared dialog recovery and shape insertion but fail overall provider cleanup; no complete-workflow pass or latency/CPU/memory speedup is established.", + "Baseline-2 also has Xvfb temporary-directory cleanup failure (payload exit5, outer wrapper failure). Its directory identity was not captured; no broad path-based cleanup was attempted.", + "DOM cells counts refer only to data-cell-id attributes, which this app does not expose; they do not measure the mxGraph model. The independent insertion oracle uses SVG elements and the actual unsaved-changes UI.", + "The official packaged runtime refuses Inspector via fuse; results apply only to its unchanged ASAR on the selected generic runtime.", + "The recovery scenario intentionally dismisses the observed invalid-file dialog; it does not establish normal startup compatibility.", + "Live-model tokens, agent context, whole-task tool-schema/call costs, actual MCP-wire parity and completed-task performance remain unmeasured. Fewer retained events do not establish token savings.", + "Earlier diagnostic helper hashes were not captured. Only the four formal trials have the pre-run manifest; failed attempts were not overwritten.", + "Application images, extracted resources/profiles and full Result files are regenerable local inputs/outputs, not committed data.", + "Historical captures omit unrelated full timeline/process arrays and exploratory palette/body listings; original logs remain identified by digest. Boolean invalid-file text projections are derived from the recorded renderer excerpt." + ], + "review_followup": { + "reviewed_head": "f3bb9b9ed736ca7076559cda5c0cc7e5bff9bda5", + "finding_thread": "PRRT_kwDOSB6hVc6tPP5b", + "root_cause": "Translation inside a patched removeListener happened after application decorators had already received the raw on wrapper.", + "structural_fix": "Native removeAllListeners runs through a private forwarding receiver sharing the real emitter state; owned on entries are normalized before current app method/decorator invocation. Real receivers, reentrant replacement, readonly methods, getters and Proxy decorators are preserved. Public removeListener/off patches are removed. Canonical removal targets are held in the weak registry independently of mutable public metadata; changed getters run at the native deletion stage.", + "decorator_red": { + "passed": 17, + "failed": 2, + "receipt": { + "name": "removal-decorator-red", + "log_sha256": "3fe1b148b717cb59ede675047b38d00c3a380791239859bd755ef440cd9386b2", + "execution_status": "executed", + "observations": [], + "resource_observation": { + "scope": "rea-efficiency-d6ad36626706407b8b78f90836d83542.scope", + "elapsed_seconds": 3.588, + "abort": null, + "payload_exit": { + "phase": "payload-exited-observer-alive", + "ownership_token": "rea-efficiency-d6ad36626706407b8b78f90836d83542.scope", + "payload_exit_code": 1, + "observer_pid": 3728302, + "remaining_processes": [3728302], + "payload_cleanup_verified": true, + "cpu_stat": { + "usage_usec": 3365700, + "user_usec": 2242425, + "system_usec": 1123274, + "nr_periods": 35, + "nr_throttled": 0, + "throttled_usec": 0, + "nr_bursts": 0, + "burst_usec": 0 + }, + "memory_peak_bytes": 341663744, + "control_group": "user.slice/user-1004.slice/user@1004.service/app.slice/rea-efficiency-d6ad36626706407b8b78f90836d83542.scope", + "resource_events": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + } + }, + "payload_resource_observation_verified": true, + "final_whole_scope_counters_verified": false + }, + "post_scope_state": "ActiveState=inactive\nSubState=dead", + "xvfb_teardown_failure": false + } + }, + "mutated_metadata_red": { + "passed": 22, + "failed": 1, + "receipt": { + "name": "raw-metadata-red", + "log_sha256": "54342240b486549bc2b3e941ee775269ef15cf34b4619766bd74dd9c381d03db", + "execution_status": "executed", + "observations": [], + "resource_observation": { + "scope": "rea-efficiency-fc3a1632b0da47d598557d98455eb25e.scope", + "elapsed_seconds": 4.028, + "abort": null, + "payload_exit": { + "phase": "payload-exited-observer-alive", + "ownership_token": "rea-efficiency-fc3a1632b0da47d598557d98455eb25e.scope", + "payload_exit_code": 1, + "observer_pid": 3743076, + "remaining_processes": [3743076], + "payload_cleanup_verified": true, + "cpu_stat": { + "usage_usec": 3600018, + "user_usec": 2431019, + "system_usec": 1168998, + "nr_periods": 38, + "nr_throttled": 2, + "throttled_usec": 3178, + "nr_bursts": 0, + "burst_usec": 0 + }, + "memory_peak_bytes": 347525120, + "control_group": "user.slice/user-1004.slice/user@1004.service/app.slice/rea-efficiency-fc3a1632b0da47d598557d98455eb25e.scope", + "resource_events": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + } + }, + "payload_resource_observation_verified": true, + "final_whole_scope_counters_verified": false + }, + "post_scope_state": "ActiveState=inactive\nSubState=dead", + "xvfb_teardown_failure": false + } + }, + "final_focused": { + "passed": 23, + "receipt": { + "name": "raw-metadata-green", + "log_sha256": "85492543307ccd121c7c93bc877ffca1e25c4a1f861c91a558021b8070601d69", + "execution_status": "executed", + "observations": [], + "resource_observation": { + "scope": "rea-efficiency-3915d88b7ccc47e582431c2ce598c6c4.scope", + "elapsed_seconds": 3.658, + "abort": null, + "payload_exit": { + "phase": "payload-exited-observer-alive", + "ownership_token": "rea-efficiency-3915d88b7ccc47e582431c2ce598c6c4.scope", + "payload_exit_code": 0, + "observer_pid": 3744278, + "remaining_processes": [3744278], + "payload_cleanup_verified": true, + "cpu_stat": { + "usage_usec": 3436723, + "user_usec": 2322900, + "system_usec": 1113823, + "nr_periods": 35, + "nr_throttled": 0, + "throttled_usec": 0, + "nr_bursts": 0, + "burst_usec": 0 + }, + "memory_peak_bytes": 310366208, + "control_group": "user.slice/user-1004.slice/user@1004.service/app.slice/rea-efficiency-3915d88b7ccc47e582431c2ce598c6c4.scope", + "resource_events": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + } + }, + "payload_resource_observation_verified": true, + "final_whole_scope_counters_verified": false + }, + "post_scope_state": "ActiveState=inactive\nSubState=dead", + "xvfb_teardown_failure": false + } + }, + "intermediate_source_pins": { + "candidate_revision": "c8c494b5c225a4fe1e1b6d22cd632088b5f3e132", + "baseline_revision": "180ba03bfddd5916251e81e97a500b1ede7a9eea", + "source_sha256": { + "scripts/electron-active-hook.cjs": "3fca1c597477dfb19b5873a8ee4be8291ca0bf3e3634cef1b1e8fefb3f7597aa", + "tests/boundary/browser/electronActiveHook.test.ts": "ac0ceeb4b739ebdac7e0b4863635c8e35b9feabd69b1de1a463cba27bf369bd3", + "experiments/analysis-efficiency/drawio-electron-lifecycle.mjs": "d484b1550704b3be1bf8b4fb1980ab50b81924a917ffae8659c84dea036fd96a" + } + }, + "intermediate_abba_attempts": [ + { + "name": "drawio-review-baseline-1", + "log_sha256": "aa1c5b34621865c4075602647eaca310a243eeebf7d9aa7075e1b297899f1013", + "execution_status": "executed", + "observations": [ + { + "profile": "/tmp/rea-4390-realapps-20261016/profiles/drawio-ui-lhshti", + "runtime": "/home/yann/yann/rea-electron-cancellation-main-control-20261016", + "elapsed_ms": 15030.539359, + "deadline_triggered": false, + "selected_shape": ".geSidebar a.geItem:visible >> nth=0", + "before": { + "title": "Untitled Diagram - draw.io", + "invalid_file_dialog": true, + "unsaved_changes": false, + "diagram_svg_elements": 0, + "cells": 0 + }, + "after": { + "title": "Untitled Diagram - draw.io", + "invalid_file_dialog": false, + "unsaved_changes": true, + "diagram_svg_elements": 2, + "cells": 0 + }, + "actions_completed": true, + "shape_inserted": true, + "provider_ok": false, + "cleanup_verified": false, + "reason": "cleanup_failed", + "coverage": { + "status": "partial_attach", + "observed_event_families": [ + "app-lifecycle", + "ipc", + "navigation", + "preload-configuration", + "web-contents-lifecycle", + "window-lifecycle" + ], + "unavailable_event_families": [ + "download", + "error", + "native-addon", + "permission", + "popup-attempt", + "preload", + "process-lifecycle", + "protocol", + "renderer-ipc", + "shell-attempt", + "updater" + ], + "observed_roles": ["browser", "main", "web_contents", "window"], + "pre_capture_activity": "unavailable" + }, + "retention": { + "budget_bytes": 16777216, + "estimated_retained_bytes": 203982, + "event_serialized_byte_upper_bound": 48666, + "retained": 110, + "dropped": 0, + "dropped_event_families": [], + "dropped_event_roles": [], + "observed_ipc": 8, + "dropped_ipc": 0, + "observed_runtime": 102, + "dropped_runtime": 0 + }, + "ipc": { + "events": [ + { + "sequence": 77, + "correlation_id": "capture:3738060:77", + "kind": "main-event-invocation", + "event": null, + "phase": "observed", + "channel": "rendererReq", + "direction": "renderer-to-main", + "sender": "webContents:1", + "receiver": "main", + "frame": "frame:1", + "target": null, + "argument_shapes": ["object"], + "result_shape": null, + "process_type": "main", + "source": "electron-active-hook", + "capture_method": "api-wrapper", + "artifact_path": null, + "artifact_sha256": null, + "error": false + }, + { + "sequence": 84, + "correlation_id": "capture:3738060:84", + "kind": "main-event-invocation", + "event": null, + "phase": "observed", + "channel": "rendererReq", + "direction": "renderer-to-main", + "sender": "webContents:1", + "receiver": "main", + "frame": "frame:1", + "target": null, + "argument_shapes": ["object"], + "result_shape": null, + "process_type": "main", + "source": "electron-active-hook", + "capture_method": "api-wrapper", + "artifact_path": null, + "artifact_sha256": null, + "error": false + }, + { + "sequence": 87, + "correlation_id": "capture:3738060:87", + "kind": "main-event-invocation", + "event": null, + "phase": "observed", + "channel": "app-load-finished", + "direction": "renderer-to-main", + "sender": "webContents:1", + "receiver": "main", + "frame": "frame:1", + "target": null, + "argument_shapes": ["null"], + "result_shape": null, + "process_type": "main", + "source": "electron-active-hook", + "capture_method": "api-wrapper", + "artifact_path": null, + "artifact_sha256": null, + "error": false + }, + { + "sequence": 88, + "correlation_id": "capture:3738060:88", + "kind": "main-event-invocation", + "event": null, + "phase": "observed", + "channel": "app-load-finished", + "direction": "renderer-to-main", + "sender": "webContents:1", + "receiver": "main", + "frame": "frame:1", + "target": null, + "argument_shapes": ["null"], + "result_shape": null, + "process_type": "main", + "source": "electron-active-hook", + "capture_method": "api-wrapper", + "artifact_path": null, + "artifact_sha256": null, + "error": false + }, + { + "sequence": 89, + "correlation_id": "capture:3738060:89", + "kind": "main-event-invocation", + "event": null, + "phase": "observed", + "channel": "app-load-finished", + "direction": "renderer-to-main", + "sender": "webContents:1", + "receiver": "main", + "frame": "frame:1", + "target": null, + "argument_shapes": ["null"], + "result_shape": null, + "process_type": "main", + "source": "electron-active-hook", + "capture_method": "api-wrapper", + "artifact_path": null, + "artifact_sha256": null, + "error": false + }, + { + "sequence": 90, + "correlation_id": "capture:3738060:90", + "kind": "ipc-main-to-renderer", + "event": null, + "phase": "observed", + "channel": "args-obj", + "direction": "main-to-renderer", + "sender": "main", + "receiver": "webContents:1", + "frame": null, + "target": "webContents:1", + "argument_shapes": ["object"], + "result_shape": "undefined", + "process_type": "main", + "source": "electron-active-hook", + "capture_method": "api-wrapper", + "artifact_path": null, + "artifact_sha256": null, + "error": false + }, + { + "sequence": 93, + "correlation_id": "capture:3738060:93", + "kind": "main-event-invocation", + "event": null, + "phase": "observed", + "channel": "rendererReq", + "direction": "renderer-to-main", + "sender": "webContents:1", + "receiver": "main", + "frame": "frame:1", + "target": null, + "argument_shapes": ["object"], + "result_shape": null, + "process_type": "main", + "source": "electron-active-hook", + "capture_method": "api-wrapper", + "artifact_path": null, + "artifact_sha256": null, + "error": false + }, + { + "sequence": 97, + "correlation_id": "capture:3738060:97", + "kind": "main-event-invocation", + "event": null, + "phase": "observed", + "channel": "rendererReq", + "direction": "renderer-to-main", + "sender": "webContents:1", + "receiver": "main", + "frame": "frame:1", + "target": null, + "argument_shapes": ["object"], + "result_shape": null, + "process_type": "main", + "source": "electron-active-hook", + "capture_method": "api-wrapper", + "artifact_path": null, + "artifact_sha256": null, + "error": false + } + ], + "observed": 8, + "dropped": 0 + }, + "actions": [ + { + "step_id": "dismiss-invalid-file", + "kind": "click", + "window_index": 0, + "target": "window:0", + "elapsed_ms": 124, + "status": "completed", + "error": null + }, + { + "step_id": "insert-shape", + "kind": "click", + "window_index": 0, + "target": "window:0", + "elapsed_ms": 174, + "status": "completed", + "error": null + }, + { + "step_id": "settle-renderer", + "kind": "wait", + "window_index": 0, + "target": "window:0", + "elapsed_ms": 503, + "status": "completed", + "error": null + } + ], + "workflow_status": "partial-or-failed", + "serialized_result_sha256": "5be1733640c6f09ea8615fdc36623e590eb3ee7a8b83e4274387af76ecd9f408", + "provider_error": { + "_tag": "BrowserObservationError", + "operation": "capture_electron_scenario", + "reason": "cleanup_failed", + "userMessage": "Electron cleanup could not be verified: process ownership token mismatch for PID(s): 3738062, 3738063, 3738096, 3738097, 3738121. Collected observations are retained in partial_observation. Inspect the reported process resources before launching another capture.", + "cleanup": { + "reason": "process ownership token mismatch for PID(s): 3738062, 3738063, 3738096, 3738097, 3738121", + "resources": [ + "owned_process_group", + "run:961f5596-11c2-47fa-b060-9b2a707f6e57", + "process-group:3738060" + ] + }, + "cleanupIncomplete": true + } + } + ], + "resource_observation": { + "scope": "rea-efficiency-15cacfddc00e4458aa785198a0ce3d9a.scope", + "elapsed_seconds": 17.014, + "abort": null, + "payload_exit": { + "phase": "payload-exited-observer-alive", + "ownership_token": "rea-efficiency-15cacfddc00e4458aa785198a0ce3d9a.scope", + "payload_exit_code": 1, + "observer_pid": 3737995, + "remaining_processes": [3737995, 3738007, 3738245, 3738247], + "payload_cleanup_verified": false, + "cpu_stat": { + "usage_usec": 12788998, + "user_usec": 9235429, + "system_usec": 3553568, + "nr_periods": 168, + "nr_throttled": 4, + "throttled_usec": 15852, + "nr_bursts": 0, + "burst_usec": 0 + }, + "memory_peak_bytes": 519233536, + "control_group": "user.slice/user-1004.slice/user@1004.service/app.slice/rea-efficiency-15cacfddc00e4458aa785198a0ce3d9a.scope", + "resource_events": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + } + }, + "payload_resource_observation_verified": false, + "final_whole_scope_counters_verified": false + }, + "post_scope_state": "ActiveState=inactive\nSubState=dead", + "xvfb_teardown_failure": false, + "requested_runtime_revision": "180ba03bfddd5916251e81e97a500b1ede7a9eea", + "runtime_revision": "180ba03bfddd5916251e81e97a500b1ede7a9eea" + }, + { + "name": "drawio-review-candidate-1", + "log_sha256": "3f7280a31108f0e8908089baca5c76fff2f01db1b439be0e7c5495d29720f07a", + "execution_status": "not_started", + "reason": "memory admission denied: requested 8 GiB plus 4 GiB host reserve unavailable", + "observations": [], + "resource_observation": null, + "requested_runtime_revision": "c8c494b5c225a4fe1e1b6d22cd632088b5f3e132", + "runtime_revision": null + }, + { + "name": "drawio-review-candidate-2", + "log_sha256": "3f7280a31108f0e8908089baca5c76fff2f01db1b439be0e7c5495d29720f07a", + "execution_status": "not_started", + "reason": "memory admission denied: requested 8 GiB plus 4 GiB host reserve unavailable", + "observations": [], + "resource_observation": null, + "requested_runtime_revision": "c8c494b5c225a4fe1e1b6d22cd632088b5f3e132", + "runtime_revision": null + }, + { + "name": "drawio-review-baseline-2", + "log_sha256": "3f7280a31108f0e8908089baca5c76fff2f01db1b439be0e7c5495d29720f07a", + "execution_status": "not_started", + "reason": "memory admission denied: requested 8 GiB plus 4 GiB host reserve unavailable", + "observations": [], + "resource_observation": null, + "requested_runtime_revision": "180ba03bfddd5916251e81e97a500b1ede7a9eea", + "runtime_revision": null + } + ], + "final_source_pins": { + "candidate_revision": "5a094a0dfa5a2d7f0145f6bb63d2b0476fa6bd3e", + "baseline_revision": "180ba03bfddd5916251e81e97a500b1ede7a9eea", + "source_sha256": { + "scripts/electron-active-hook.cjs": "58266134f1a8ca83f6a9a4da3a1c36880cd394a272bf250e504dff53cf852858", + "tests/boundary/browser/electronActiveHook.test.ts": "850f7ed4d5dbde52d1c987caf1cd2bed3f1c8668db4a953fd35dbc0113337e0a", + "experiments/analysis-efficiency/drawio-electron-lifecycle.mjs": "d484b1550704b3be1bf8b4fb1980ab50b81924a917ffae8659c84dea036fd96a" + } + }, + "final_real_app": { + "name": "drawio-review-final-candidate", + "log_sha256": "80a36000dabcbe0f2e0c879a243f1b344654b81c7e36ed75da68719c10491624", + "execution_status": "executed", + "observations": [ + { + "profile": "/tmp/rea-4390-realapps-20261016/profiles/drawio-ui-4WKSqs", + "runtime": "/home/yann/yann/rea-electron-ipc-listener-4390-20261016", + "elapsed_ms": 11172.099482, + "deadline_triggered": false, + "selected_shape": ".geSidebar a.geItem:visible >> nth=0", + "before": { + "title": "Untitled Diagram - draw.io", + "invalid_file_dialog": true, + "unsaved_changes": false, + "diagram_svg_elements": 0, + "cells": 0 + }, + "after": { + "title": "Untitled Diagram - draw.io", + "invalid_file_dialog": false, + "unsaved_changes": true, + "diagram_svg_elements": 2, + "cells": 0 + }, + "actions_completed": true, + "shape_inserted": true, + "provider_ok": false, + "cleanup_verified": false, + "reason": "cleanup_failed", + "coverage": { + "status": "partial_attach", + "observed_event_families": [ + "app-lifecycle", + "ipc", + "navigation", + "preload-configuration", + "web-contents-lifecycle", + "window-lifecycle" + ], + "unavailable_event_families": [ + "download", + "error", + "native-addon", + "permission", + "popup-attempt", + "preload", + "process-lifecycle", + "protocol", + "renderer-ipc", + "shell-attempt", + "updater" + ], + "observed_roles": ["browser", "main", "web_contents", "window"], + "pre_capture_activity": "unavailable" + }, + "retention": { + "budget_bytes": 16777216, + "estimated_retained_bytes": 202114, + "event_serialized_byte_upper_bound": 48214, + "retained": 109, + "dropped": 0, + "dropped_event_families": [], + "dropped_event_roles": [], + "observed_ipc": 7, + "dropped_ipc": 0, + "observed_runtime": 102, + "dropped_runtime": 0 + }, + "ipc": { + "events": [ + { + "sequence": 77, + "correlation_id": "capture:3749543:77", + "kind": "main-event-invocation", + "event": null, + "phase": "observed", + "channel": "rendererReq", + "direction": "renderer-to-main", + "sender": "webContents:1", + "receiver": "main", + "frame": "frame:1", + "target": null, + "argument_shapes": ["object"], + "result_shape": null, + "process_type": "main", + "source": "electron-active-hook", + "capture_method": "api-wrapper", + "artifact_path": null, + "artifact_sha256": null, + "error": false + }, + { + "sequence": 84, + "correlation_id": "capture:3749543:84", + "kind": "main-event-invocation", + "event": null, + "phase": "observed", + "channel": "rendererReq", + "direction": "renderer-to-main", + "sender": "webContents:1", + "receiver": "main", + "frame": "frame:1", + "target": null, + "argument_shapes": ["object"], + "result_shape": null, + "process_type": "main", + "source": "electron-active-hook", + "capture_method": "api-wrapper", + "artifact_path": null, + "artifact_sha256": null, + "error": false + }, + { + "sequence": 87, + "correlation_id": "capture:3749543:87", + "kind": "main-event-invocation", + "event": null, + "phase": "observed", + "channel": "app-load-finished", + "direction": "renderer-to-main", + "sender": "webContents:1", + "receiver": "main", + "frame": "frame:1", + "target": null, + "argument_shapes": ["null"], + "result_shape": null, + "process_type": "main", + "source": "electron-active-hook", + "capture_method": "api-wrapper", + "artifact_path": null, + "artifact_sha256": null, + "error": false + }, + { + "sequence": 88, + "correlation_id": "capture:3749543:88", + "kind": "main-event-invocation", + "event": null, + "phase": "observed", + "channel": "app-load-finished", + "direction": "renderer-to-main", + "sender": "webContents:1", + "receiver": "main", + "frame": "frame:1", + "target": null, + "argument_shapes": ["null"], + "result_shape": null, + "process_type": "main", + "source": "electron-active-hook", + "capture_method": "api-wrapper", + "artifact_path": null, + "artifact_sha256": null, + "error": false + }, + { + "sequence": 89, + "correlation_id": "capture:3749543:89", + "kind": "ipc-main-to-renderer", + "event": null, + "phase": "observed", + "channel": "args-obj", + "direction": "main-to-renderer", + "sender": "main", + "receiver": "webContents:1", + "frame": null, + "target": "webContents:1", + "argument_shapes": ["object"], + "result_shape": "undefined", + "process_type": "main", + "source": "electron-active-hook", + "capture_method": "api-wrapper", + "artifact_path": null, + "artifact_sha256": null, + "error": false + }, + { + "sequence": 92, + "correlation_id": "capture:3749543:92", + "kind": "main-event-invocation", + "event": null, + "phase": "observed", + "channel": "rendererReq", + "direction": "renderer-to-main", + "sender": "webContents:1", + "receiver": "main", + "frame": "frame:1", + "target": null, + "argument_shapes": ["object"], + "result_shape": null, + "process_type": "main", + "source": "electron-active-hook", + "capture_method": "api-wrapper", + "artifact_path": null, + "artifact_sha256": null, + "error": false + }, + { + "sequence": 96, + "correlation_id": "capture:3749543:96", + "kind": "main-event-invocation", + "event": null, + "phase": "observed", + "channel": "rendererReq", + "direction": "renderer-to-main", + "sender": "webContents:1", + "receiver": "main", + "frame": "frame:1", + "target": null, + "argument_shapes": ["object"], + "result_shape": null, + "process_type": "main", + "source": "electron-active-hook", + "capture_method": "api-wrapper", + "artifact_path": null, + "artifact_sha256": null, + "error": false + } + ], + "observed": 7, + "dropped": 0 + }, + "actions": [ + { + "step_id": "dismiss-invalid-file", + "kind": "click", + "window_index": 0, + "target": "window:0", + "elapsed_ms": 124, + "status": "completed", + "error": null + }, + { + "step_id": "insert-shape", + "kind": "click", + "window_index": 0, + "target": "window:0", + "elapsed_ms": 117, + "status": "completed", + "error": null + }, + { + "step_id": "settle-renderer", + "kind": "wait", + "window_index": 0, + "target": "window:0", + "elapsed_ms": 502, + "status": "completed", + "error": null + } + ], + "workflow_status": "partial-or-failed", + "serialized_result_sha256": "97991c178260b1562a08ebc2281c82b7307e68ee640da38386e300205a698c5f", + "provider_error": { + "_tag": "BrowserObservationError", + "operation": "capture_electron_scenario", + "reason": "cleanup_failed", + "userMessage": "Electron cleanup could not be verified: process ownership token mismatch for PID(s): 3749547, 3749548, 3749585, 3749587, 3749605. Collected observations are retained in partial_observation. Inspect the reported process resources before launching another capture.", + "cleanup": { + "reason": "process ownership token mismatch for PID(s): 3749547, 3749548, 3749585, 3749587, 3749605", + "resources": [ + "owned_process_group", + "run:750e2da3-8f6a-4662-921e-5d86ba40b018", + "process-group:3749543" + ] + }, + "cleanupIncomplete": true + } + } + ], + "resource_observation": { + "scope": "rea-efficiency-1b1d76dffc5d402385b9b164a1fd774d.scope", + "elapsed_seconds": 12.3, + "abort": null, + "payload_exit": { + "phase": "payload-exited-observer-alive", + "ownership_token": "rea-efficiency-1b1d76dffc5d402385b9b164a1fd774d.scope", + "payload_exit_code": 5, + "observer_pid": 3749467, + "remaining_processes": [3749467, 3749479], + "payload_cleanup_verified": false, + "cpu_stat": { + "usage_usec": 7213035, + "user_usec": 4961284, + "system_usec": 2251750, + "nr_periods": 121, + "nr_throttled": 3, + "throttled_usec": 8438, + "nr_bursts": 0, + "burst_usec": 0 + }, + "memory_peak_bytes": 546140160, + "control_group": "user.slice/user-1004.slice/user@1004.service/app.slice/rea-efficiency-1b1d76dffc5d402385b9b164a1fd774d.scope", + "resource_events": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + } + }, + "payload_resource_observation_verified": false, + "final_whole_scope_counters_verified": false + }, + "post_scope_state": "ActiveState=inactive\nSubState=dead", + "xvfb_teardown_failure": true, + "runtime_revision": "5a094a0dfa5a2d7f0145f6bb63d2b0476fa6bd3e" + }, + "static": { + "tasks_passed": 2, + "tasks_cached": 0, + "receipt": { + "name": "review-check", + "log_sha256": "72073f3ed7a75a36f6005932974aa5b283498cb608b029a4f316c079323bc44d", + "execution_status": "executed", + "observations": [], + "resource_observation": { + "scope": "rea-efficiency-b393f8ccbb774849979e1c349bf943c4.scope", + "elapsed_seconds": 135.173, + "abort": null, + "payload_exit": { + "phase": "payload-exited-observer-alive", + "ownership_token": "rea-efficiency-b393f8ccbb774849979e1c349bf943c4.scope", + "payload_exit_code": 0, + "observer_pid": 3753488, + "remaining_processes": [3753488], + "payload_cleanup_verified": true, + "cpu_stat": { + "usage_usec": 133786166, + "user_usec": 124480122, + "system_usec": 9306043, + "nr_periods": 1350, + "nr_throttled": 32, + "throttled_usec": 10768, + "nr_bursts": 0, + "burst_usec": 0 + }, + "memory_peak_bytes": 4274634752, + "control_group": "user.slice/user-1004.slice/user@1004.service/app.slice/rea-efficiency-b393f8ccbb774849979e1c349bf943c4.scope", + "resource_events": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + } + }, + "payload_resource_observation_verified": true, + "final_whole_scope_counters_verified": false + }, + "post_scope_state": "ActiveState=inactive\nSubState=dead", + "xvfb_teardown_failure": false + } + }, + "docs_check": { + "tasks_passed": 6, + "readme_cases_passed": 5, + "log_sha256": "d19d6e8a8631f9d4dbae7fea66f90ea17225326758b67d183ddea850ade1d182", + "payload_exit": { + "phase": "payload-exited-observer-alive", + "ownership_token": "rea-efficiency-36aa218a8d5c49e8964f53b3fd14ad74.scope", + "payload_exit_code": 0, + "observer_pid": 3757012, + "remaining_processes": [3757012], + "payload_cleanup_verified": true, + "cpu_stat": { + "usage_usec": 10848469, + "user_usec": 9007805, + "system_usec": 1840663, + "nr_periods": 115, + "nr_throttled": 1, + "throttled_usec": 2365, + "nr_bursts": 0, + "burst_usec": 0 + }, + "memory_peak_bytes": 474152960, + "control_group": "user.slice/user-1004.slice/user@1004.service/app.slice/rea-efficiency-36aa218a8d5c49e8964f53b3fd14ad74.scope", + "resource_events": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + } + }, + "post_scope_state": "ActiveState=inactive\nSubState=dead" + }, + "input_asar_sha256_rechecked_after_trials": "e64b9650c0b8241ca9a7d33b56386a385bceee63270cec701d3a467347b697e6", + "limits": [ + "Only the initial four d02cc934c trials are a complete ABBA observation set. Three later attempts were refused before target launch; no performance measurements exist for them.", + "Final source has one real-app recovery/insertion validation, not a new completed-task performance pair. Overall provider cleanup remains unverified; Xvfb teardown also fails in this final pipeline.", + "Serialized Result summaries retain public cleanup diagnostics; they do not reconstruct non-enumerable native Error cause chains.", + "Original manifests, measurements and failed attempts remain unchanged." + ] + }, + "callable_followup": { + "source_pins": { + "candidate_revision": "514b73b78e76f7dc4eeeaca3c708e0667e9a052c", + "baseline_revision": "180ba03bfddd5916251e81e97a500b1ede7a9eea", + "source_sha256": { + "scripts/electron-active-hook.cjs": "66734c9cc3e9b759dec82fb8d5a09c3bc1fe033a5d43b207995942a45f21db8a", + "tests/boundary/browser/electronActiveHook.test.ts": "0bc589ff29120aa3cfe9a5047e42bef78fd5c3314c22d06d59f084a95e180158", + "experiments/analysis-efficiency/drawio-electron-lifecycle.mjs": "d484b1550704b3be1bf8b4fb1980ab50b81924a917ffae8659c84dea036fd96a" + } + }, + "root_cause": "The IPC handle wrapper reads the application callback .call property; ordinary invocation does not. Captured Reflect.apply preserves the actual receiver/argument list without reading callable method properties. EventEmitter callbacks retain native invocation behavior.", + "initial_three_case_attempt": { + "log_name": "rea-4390-callable-red.log", + "log_sha256": "8a8b2043dd785e86740be1c4a57a43c566a5e924a620c01e57f23b21d647af35", + "resource": { + "resource_scope": "rea-efficiency-92e8cb18931e47b99a6d5968dea058c2.scope", + "peak_cgroup_bytes_sampled": 302551040, + "elapsed_seconds": 7.411, + "abort": null, + "hard_memory_bytes": 8589934592, + "early_stop_bytes": 6442450944, + "cpu_quota_percent": 100, + "cpu_affinity": [10], + "task_ceiling": 192, + "peak_tasks_sampled": 56, + "resource_events_last_observed": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + }, + "resource_events_final": null, + "resource_counters_verified": false, + "cpu_stat_last_observed": { + "usage_usec": 7012573, + "user_usec": 4234041, + "system_usec": 2778531, + "nr_periods": 73, + "nr_throttled": 5, + "throttled_usec": 401, + "nr_bursts": 0, + "burst_usec": 0 + }, + "payload_resource_observation": { + "phase": "payload-exited-observer-alive", + "ownership_token": "rea-efficiency-92e8cb18931e47b99a6d5968dea058c2.scope", + "payload_exit_code": 1, + "observer_pid": 3781963, + "remaining_processes": [3781963], + "payload_cleanup_verified": true, + "cpu_stat": { + "usage_usec": 7010225, + "user_usec": 4233433, + "system_usec": 2776792, + "nr_periods": 72, + "nr_throttled": 5, + "throttled_usec": 401, + "nr_bursts": 0, + "burst_usec": 0 + }, + "memory_peak_bytes": 317837312, + "control_group": "user.slice/user-1004.slice/user@1004.service/app.slice/rea-efficiency-92e8cb18931e47b99a6d5968dea058c2.scope", + "resource_events": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + } + }, + "payload_resource_observation_verified": true, + "payload_observation_scope": "Counters at payload exit while observer remains alive; not final whole-scope counters.", + "payload_observer_sources_sha256": { + "resource-run.py": "7728862171415e7dee2c11af2ead2b9a54265fccaf82a7113956d36b29065e33", + "resource_payload.py": "6f089e645459eba80c5d73e39c21606c78173a44daf49fabc3b88c4631ce51c6" + } + }, + "status": "3 failed / 23 passed; only the handle case has a valid successful unhooked oracle. on/once controls fail themselves and are not evidence of a hook regression." + }, + "rejected_event_invocation_change": { + "log_name": "rea-4390-callable-green.log", + "log_sha256": "af5ff795a9247c058c4452ed2406c9f056e2e84a251c80eb0feeb605bef051c8", + "resource": { + "resource_scope": "rea-efficiency-bd12b8c433344feea79aa6f4c989c66d.scope", + "peak_cgroup_bytes_sampled": 310726656, + "elapsed_seconds": 4.533, + "abort": null, + "hard_memory_bytes": 8589934592, + "early_stop_bytes": 6442450944, + "cpu_quota_percent": 100, + "cpu_affinity": [10], + "task_ceiling": 192, + "peak_tasks_sampled": 56, + "resource_events_last_observed": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + }, + "resource_events_final": null, + "resource_counters_verified": false, + "cpu_stat_last_observed": { + "usage_usec": 4210250, + "user_usec": 2646166, + "system_usec": 1564084, + "nr_periods": 44, + "nr_throttled": 0, + "throttled_usec": 0, + "nr_bursts": 0, + "burst_usec": 0 + }, + "payload_resource_observation": { + "phase": "payload-exited-observer-alive", + "ownership_token": "rea-efficiency-bd12b8c433344feea79aa6f4c989c66d.scope", + "payload_exit_code": 1, + "observer_pid": 3788886, + "remaining_processes": [3788886], + "payload_cleanup_verified": true, + "cpu_stat": { + "usage_usec": 4209223, + "user_usec": 2645521, + "system_usec": 1563702, + "nr_periods": 44, + "nr_throttled": 0, + "throttled_usec": 0, + "nr_bursts": 0, + "burst_usec": 0 + }, + "memory_peak_bytes": 326103040, + "control_group": "user.slice/user-1004.slice/user@1004.service/app.slice/rea-efficiency-bd12b8c433344feea79aa6f4c989c66d.scope", + "resource_events": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + } + }, + "payload_resource_observation_verified": true, + "payload_observation_scope": "Counters at payload exit while observer remains alive; not final whole-scope counters.", + "payload_observer_sources_sha256": { + "resource-run.py": "7728862171415e7dee2c11af2ead2b9a54265fccaf82a7113956d36b29065e33", + "resource_payload.py": "6f089e645459eba80c5d73e39c21606c78173a44daf49fabc3b88c4631ce51c6" + } + }, + "status": "2 failed / 24 passed; on/once expected-oracle assertions fail. Intrinsic event invocation change reverted, invalid cases removed." + }, + "final_focused": { + "log_name": "rea-4390-handle-callable-green.log", + "log_sha256": "65cb8446eafc682c6add0096c42b9c35b3f76f94599d8eef7024546092c4d2a7", + "resource": { + "resource_scope": "rea-efficiency-4387af52466849d88fc028de64ac1c80.scope", + "peak_cgroup_bytes_sampled": 298799104, + "elapsed_seconds": 3.661, + "abort": null, + "hard_memory_bytes": 8589934592, + "early_stop_bytes": 6442450944, + "cpu_quota_percent": 100, + "cpu_affinity": [10], + "task_ceiling": 192, + "peak_tasks_sampled": 57, + "resource_events_last_observed": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + }, + "resource_events_final": null, + "resource_counters_verified": false, + "cpu_stat_last_observed": { + "usage_usec": 3405348, + "user_usec": 2423291, + "system_usec": 982056, + "nr_periods": 36, + "nr_throttled": 1, + "throttled_usec": 1016, + "nr_bursts": 0, + "burst_usec": 0 + }, + "payload_resource_observation": { + "phase": "payload-exited-observer-alive", + "ownership_token": "rea-efficiency-4387af52466849d88fc028de64ac1c80.scope", + "payload_exit_code": 0, + "observer_pid": 3791618, + "remaining_processes": [3791618], + "payload_cleanup_verified": true, + "cpu_stat": { + "usage_usec": 3404000, + "user_usec": 2422332, + "system_usec": 981668, + "nr_periods": 35, + "nr_throttled": 1, + "throttled_usec": 1016, + "nr_bursts": 0, + "burst_usec": 0 + }, + "memory_peak_bytes": 318291968, + "control_group": "user.slice/user-1004.slice/user@1004.service/app.slice/rea-efficiency-4387af52466849d88fc028de64ac1c80.scope", + "resource_events": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + } + }, + "payload_resource_observation_verified": true, + "payload_observation_scope": "Counters at payload exit while observer remains alive; not final whole-scope counters.", + "payload_observer_sources_sha256": { + "resource-run.py": "7728862171415e7dee2c11af2ead2b9a54265fccaf82a7113956d36b29065e33", + "resource_payload.py": "6f089e645459eba80c5d73e39c21606c78173a44daf49fabc3b88c4631ce51c6" + } + }, + "status": "24 passed / 24; one added direct-call handle oracle, other 23 existing regressions retained." + }, + "real_app": { + "log_name": "rea-4390-drawio-callable-final.log", + "log_sha256": "f963c64489dc5906e2dd2ac1eafd5c919816184dd901f2255829ce587555a819", + "resource": { + "resource_scope": "rea-efficiency-633ec4a438494d319c34a86df57cf709.scope", + "peak_cgroup_bytes_sampled": 632668160, + "elapsed_seconds": 12.61, + "abort": null, + "hard_memory_bytes": 8589934592, + "early_stop_bytes": 6442450944, + "cpu_quota_percent": 100, + "cpu_affinity": [10], + "task_ceiling": 192, + "peak_tasks_sampled": 75, + "resource_events_last_observed": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + }, + "resource_events_final": null, + "resource_counters_verified": false, + "cpu_stat_last_observed": { + "usage_usec": 7091220, + "user_usec": 5064636, + "system_usec": 2026583, + "nr_periods": 125, + "nr_throttled": 2, + "throttled_usec": 2015, + "nr_bursts": 0, + "burst_usec": 0 + }, + "payload_resource_observation": { + "phase": "payload-exited-observer-alive", + "ownership_token": "rea-efficiency-633ec4a438494d319c34a86df57cf709.scope", + "payload_exit_code": 1, + "observer_pid": 3796320, + "remaining_processes": [3796320, 3796340], + "payload_cleanup_verified": false, + "cpu_stat": { + "usage_usec": 7062834, + "user_usec": 5044360, + "system_usec": 2018473, + "nr_periods": 124, + "nr_throttled": 2, + "throttled_usec": 2015, + "nr_bursts": 0, + "burst_usec": 0 + }, + "memory_peak_bytes": 634187776, + "control_group": "user.slice/user-1004.slice/user@1004.service/app.slice/rea-efficiency-633ec4a438494d319c34a86df57cf709.scope", + "resource_events": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + } + }, + "payload_resource_observation_verified": false, + "payload_observation_scope": "Counters at payload exit while observer remains alive; not final whole-scope counters.", + "payload_observer_sources_sha256": { + "resource-run.py": "7728862171415e7dee2c11af2ead2b9a54265fccaf82a7113956d36b29065e33", + "resource_payload.py": "6f089e645459eba80c5d73e39c21606c78173a44daf49fabc3b88c4631ce51c6" + } + }, + "selected_result": { + "elapsed_ms": 11276.654391, + "deadline_triggered": false, + "selected_shape": ".geSidebar a.geItem:visible >> nth=0", + "before": { + "title": "Untitled Diagram - draw.io", + "invalid_file_dialog": true, + "unsaved_changes": false, + "diagram_svg_elements": 0, + "cells": 0 + }, + "after": { + "title": "Untitled Diagram - draw.io", + "invalid_file_dialog": false, + "unsaved_changes": true, + "diagram_svg_elements": 2, + "cells": 0 + }, + "actions_completed": true, + "shape_inserted": true, + "provider_ok": false, + "cleanup_verified": false, + "reason": "cleanup_failed", + "actions": [ + { + "step_id": "dismiss-invalid-file", + "kind": "click", + "window_index": 0, + "target": "window:0", + "elapsed_ms": 84, + "status": "completed", + "error": null + }, + { + "step_id": "insert-shape", + "kind": "click", + "window_index": 0, + "target": "window:0", + "elapsed_ms": 116, + "status": "completed", + "error": null + }, + { + "step_id": "settle-renderer", + "kind": "wait", + "window_index": 0, + "target": "window:0", + "elapsed_ms": 501, + "status": "completed", + "error": null + } + ], + "workflow_status": "partial-or-failed" + }, + "ipc_observed": 7, + "coverage": { + "status": "partial_attach", + "observed_event_families": [ + "app-lifecycle", + "ipc", + "navigation", + "preload-configuration", + "web-contents-lifecycle", + "window-lifecycle" + ], + "unavailable_event_families": [ + "download", + "error", + "native-addon", + "permission", + "popup-attempt", + "preload", + "process-lifecycle", + "protocol", + "renderer-ipc", + "shell-attempt", + "updater" + ], + "observed_roles": ["browser", "main", "web_contents", "window"], + "pre_capture_activity": "unavailable" + }, + "retention": { + "budget_bytes": 16777216, + "estimated_retained_bytes": 202114, + "event_serialized_byte_upper_bound": 48214, + "retained": 109, + "dropped": 0, + "dropped_event_families": [], + "dropped_event_roles": [], + "observed_ipc": 7, + "dropped_ipc": 0, + "observed_runtime": 102, + "dropped_runtime": 0 + } + }, + "remaining_raw_mutation": { + "log_name": "rea-4390-raw-mutation-probe.log", + "log_sha256": "30371d915b05ca91b1f15b026a70ed81b255a4c20b4d2e3aae068e87cac85d15", + "resource": { + "resource_scope": "rea-efficiency-d075b8f5c15c46079a0c78deffc7f3fc.scope", + "peak_cgroup_bytes_sampled": 6070272, + "elapsed_seconds": 0.265, + "abort": null, + "hard_memory_bytes": 8589934592, + "early_stop_bytes": 6442450944, + "cpu_quota_percent": 100, + "cpu_affinity": [10], + "task_ceiling": 192, + "peak_tasks_sampled": 1, + "resource_events_last_observed": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + }, + "resource_events_final": null, + "resource_counters_verified": false, + "cpu_stat_last_observed": { + "usage_usec": 75525, + "user_usec": 55632, + "system_usec": 19893, + "nr_periods": 2, + "nr_throttled": 0, + "throttled_usec": 0, + "nr_bursts": 0, + "burst_usec": 0 + }, + "payload_resource_observation": { + "phase": "payload-exited-observer-alive", + "ownership_token": "rea-efficiency-d075b8f5c15c46079a0c78deffc7f3fc.scope", + "payload_exit_code": 0, + "observer_pid": 3797331, + "remaining_processes": [3797331], + "payload_cleanup_verified": true, + "cpu_stat": { + "usage_usec": 74664, + "user_usec": 54998, + "system_usec": 19666, + "nr_periods": 1, + "nr_throttled": 0, + "throttled_usec": 0, + "nr_bursts": 0, + "burst_usec": 0 + }, + "memory_peak_bytes": 20475904, + "control_group": "user.slice/user-1004.slice/user@1004.service/app.slice/rea-efficiency-d075b8f5c15c46079a0c78deffc7f3fc.scope", + "resource_events": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + } + }, + "payload_resource_observation_verified": true, + "payload_observation_scope": "Counters at payload exit while observer remains alive; not final whole-scope counters.", + "payload_observer_sources_sha256": { + "resource-run.py": "7728862171415e7dee2c11af2ead2b9a54265fccaf82a7113956d36b29065e33", + "resource_payload.py": "6f089e645459eba80c5d73e39c21606c78173a44daf49fabc3b88c4631ce51c6" + } + }, + "issue": 4513, + "review_thread": "PRRT_kwDOSB6hVc6tQdpI", + "observed": { + "plain": { + "seen": [true], + "remaining": 0, + "failure": null + }, + "hooked": { + "seen": [false], + "remaining": 1, + "failure": "unexpected callback" + } + }, + "reproduction": "See issue #4513 for exact production-hook / native Node probe. No real application with this pattern identified.", + "scope_decision": "Retain and explicitly document this unsupported raw recording-wrapper mutation rather than temporarily rewriting app metadata or adding a copied native removal algorithm. Original-function removal, ordinary decorators, and once fixes do not establish full metaprogramming transparency." + }, + "scope_poststates": { + "rea-efficiency-92e8cb18931e47b99a6d5968dea058c2.scope": { + "properties": "ActiveState=inactive\nSubState=dead", + "exit": 0 + }, + "rea-efficiency-bd12b8c433344feea79aa6f4c989c66d.scope": { + "properties": "ActiveState=inactive\nSubState=dead", + "exit": 0 + }, + "rea-efficiency-4387af52466849d88fc028de64ac1c80.scope": { + "properties": "ActiveState=inactive\nSubState=dead", + "exit": 0 + }, + "rea-efficiency-633ec4a438494d319c34a86df57cf709.scope": { + "properties": "ActiveState=inactive\nSubState=dead", + "exit": 0 + }, + "rea-efficiency-d075b8f5c15c46079a0c78deffc7f3fc.scope": { + "properties": "ActiveState=inactive\nSubState=dead", + "exit": 0 + } + }, + "limits": [ + "No completed-task speedup or token/context savings measured.", + "Real-app operation phase succeeds but overall provider cleanup remains failed; owned-scope teardown does not establish provider cleanup.", + "No packaged-vendor-runtime or actual MCP-wire success claim.", + "Payload-exit counters while observer alive are not final whole-scope counters; memory includes cache." + ], + "static": { + "command": "npm run check:fast", + "status": "passed; two uncached tasks", + "log_sha256": "e1be38c634884e4f6e9127d8f37dbeaf7b188d585c93b2390bcf0fe6125ceffc", + "resource": { + "resource_scope": "rea-efficiency-7bd6cb7b41624ffe863453ab2dc07c44.scope", + "peak_cgroup_bytes_sampled": 4337991680, + "elapsed_seconds": 92.912, + "abort": null, + "hard_memory_bytes": 8589934592, + "early_stop_bytes": 6442450944, + "cpu_quota_percent": 100, + "cpu_affinity": [10], + "task_ceiling": 192, + "peak_tasks_sampled": 67, + "resource_events_last_observed": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + }, + "resource_events_final": null, + "resource_counters_verified": false, + "cpu_stat_last_observed": { + "usage_usec": 92032513, + "user_usec": 87989998, + "system_usec": 4042515, + "nr_periods": 928, + "nr_throttled": 47, + "throttled_usec": 31119, + "nr_bursts": 0, + "burst_usec": 0 + }, + "payload_resource_observation": { + "phase": "payload-exited-observer-alive", + "ownership_token": "rea-efficiency-7bd6cb7b41624ffe863453ab2dc07c44.scope", + "payload_exit_code": 0, + "observer_pid": 3797574, + "remaining_processes": [3797574], + "payload_cleanup_verified": true, + "cpu_stat": { + "usage_usec": 92031383, + "user_usec": 87988917, + "system_usec": 4042466, + "nr_periods": 927, + "nr_throttled": 47, + "throttled_usec": 31119, + "nr_bursts": 0, + "burst_usec": 0 + }, + "memory_peak_bytes": 4340375552, + "control_group": "user.slice/user-1004.slice/user@1004.service/app.slice/rea-efficiency-7bd6cb7b41624ffe863453ab2dc07c44.scope", + "resource_events": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + } + }, + "payload_resource_observation_verified": true, + "payload_observation_scope": "Counters at payload exit while observer remains alive; not final whole-scope counters.", + "payload_observer_sources_sha256": { + "resource-run.py": "7728862171415e7dee2c11af2ead2b9a54265fccaf82a7113956d36b29065e33", + "resource_payload.py": "6f089e645459eba80c5d73e39c21606c78173a44daf49fabc3b88c4631ce51c6" + } + }, + "scope_poststate": "ActiveState=inactive\nSubState=dead" + }, + "docs_check": { + "command": "npm run docs:check", + "status": "passed; six tasks and five README consistency cases", + "log_sha256": "df2b7983d89102cd6035ea80b27f4702a207601cbb5f7417d3cf010ecaa51ca7", + "resource": { + "resource_scope": "rea-efficiency-ce2db4df0beb429283c73b0ce0dcf6d9.scope", + "peak_cgroup_bytes_sampled": 458072064, + "elapsed_seconds": 9.56, + "abort": null, + "hard_memory_bytes": 8589934592, + "early_stop_bytes": 6442450944, + "cpu_quota_percent": 100, + "cpu_affinity": [10], + "task_ceiling": 192, + "peak_tasks_sampled": 65, + "resource_events_last_observed": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + }, + "resource_events_final": null, + "resource_counters_verified": false, + "cpu_stat_last_observed": { + "usage_usec": 9197924, + "user_usec": 7599544, + "system_usec": 1598380, + "nr_periods": 95, + "nr_throttled": 2, + "throttled_usec": 845, + "nr_bursts": 0, + "burst_usec": 0 + }, + "payload_resource_observation": { + "phase": "payload-exited-observer-alive", + "ownership_token": "rea-efficiency-ce2db4df0beb429283c73b0ce0dcf6d9.scope", + "payload_exit_code": 0, + "observer_pid": 3805877, + "remaining_processes": [3805877], + "payload_cleanup_verified": true, + "cpu_stat": { + "usage_usec": 9197275, + "user_usec": 7599008, + "system_usec": 1598267, + "nr_periods": 94, + "nr_throttled": 2, + "throttled_usec": 845, + "nr_bursts": 0, + "burst_usec": 0 + }, + "memory_peak_bytes": 470388736, + "control_group": "user.slice/user-1004.slice/user@1004.service/app.slice/rea-efficiency-ce2db4df0beb429283c73b0ce0dcf6d9.scope", + "resource_events": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + } + }, + "payload_resource_observation_verified": true, + "payload_observation_scope": "Counters at payload exit while observer remains alive; not final whole-scope counters.", + "payload_observer_sources_sha256": { + "resource-run.py": "7728862171415e7dee2c11af2ead2b9a54265fccaf82a7113956d36b29065e33", + "resource_payload.py": "6f089e645459eba80c5d73e39c21606c78173a44daf49fabc3b88c4631ce51c6" + } + }, + "scope_poststate": "ActiveState=inactive\nSubState=dead" + } + }, + "native_dispatch_followup": { + "reviewed_head": "6fc2d2769ec46cb262cce26fda2a02d8ca10d0c7", + "review_thread": "PRRT_kwDOSB6hVc6tRC43", + "source_pins": { + "candidate_revision": "e8d7e8f204fa3262ba2f6b87d6fa781852757300", + "baseline_revision": "180ba03bfddd5916251e81e97a500b1ede7a9eea", + "source_sha256": { + "scripts/electron-active-hook.cjs": "6fe6da2de1a5d34af1d22665fd770cda18e7ba1e0dbbfa7851a0399ffbc6e5d4", + "tests/boundary/browser/electronActiveHook.test.ts": "5bb21abb31f9d827840b1a721965336fff702bfe6dffd117cfb5e9059d3f5e54", + "experiments/analysis-efficiency/drawio-electron-lifecycle.mjs": "d484b1550704b3be1bf8b4fb1980ab50b81924a917ffae8659c84dea036fd96a" + } + }, + "native_node_version": "24.18.0", + "root_cause": "The earlier controls covered a single listener. Native EventEmitter single dispatch reads .apply, but array dispatch invokes each function intrinsically. once uses .call for zero arguments and .apply otherwise. Unconditional callback .apply changes array dispatch and zero-argument once behavior.", + "structural_fix": "Keep one observed invocation boundary. The on recording wrapper delegates direct function calls intrinsically and owns an .apply forwarding method for native method-based dispatch. Node selects which route executes; the hook neither reads nor copies listener storage. once selects native call/apply semantics by its actual argument count. handle remains intrinsic.", + "red": { + "log_name": "rea-4390-native-dispatch-red.log", + "log_sha256": "ced90d3b485894c9ed864ac0e1da473894ed0ffdcb9a511fc5ea1fa311c74e27", + "resource": { + "resource_scope": "rea-efficiency-f8de6051a7854c479e2a5602f4ebe04d.scope", + "peak_cgroup_bytes_sampled": 246337536, + "elapsed_seconds": 5.549, + "abort": null, + "hard_memory_bytes": 8589934592, + "early_stop_bytes": 6442450944, + "cpu_quota_percent": 100, + "cpu_affinity": [10], + "task_ceiling": 192, + "peak_tasks_sampled": 55, + "resource_events_last_observed": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + }, + "resource_events_final": null, + "resource_counters_verified": false, + "cpu_stat_last_observed": { + "usage_usec": 5210925, + "user_usec": 2657164, + "system_usec": 2553760, + "nr_periods": 55, + "nr_throttled": 5, + "throttled_usec": 1571, + "nr_bursts": 0, + "burst_usec": 0 + }, + "payload_resource_observation": { + "phase": "payload-exited-observer-alive", + "ownership_token": "rea-efficiency-f8de6051a7854c479e2a5602f4ebe04d.scope", + "payload_exit_code": 1, + "observer_pid": 3826550, + "remaining_processes": [3826550], + "payload_cleanup_verified": true, + "cpu_stat": { + "usage_usec": 5209925, + "user_usec": 2656654, + "system_usec": 2553270, + "nr_periods": 54, + "nr_throttled": 5, + "throttled_usec": 1571, + "nr_bursts": 0, + "burst_usec": 0 + }, + "memory_peak_bytes": 271044608, + "control_group": "user.slice/user-1004.slice/user@1004.service/app.slice/rea-efficiency-f8de6051a7854c479e2a5602f4ebe04d.scope", + "resource_events": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + } + }, + "payload_resource_observation_verified": true, + "payload_observation_scope": "Counters at payload exit while observer remains alive; not final whole-scope counters.", + "payload_observer_sources_sha256": { + "resource-run.py": "7728862171415e7dee2c11af2ead2b9a54265fccaf82a7113956d36b29065e33", + "resource_payload.py": "6f089e645459eba80c5d73e39c21606c78173a44daf49fabc3b88c4631ce51c6" + } + }, + "scope_poststate": "ActiveState=inactive\nSubState=dead", + "status": "26 passed / 2 failed; multiple-on callback execution and empty-once metadata reads differ from successful native controls." + }, + "green": { + "log_name": "rea-4390-native-dispatch-green.log", + "log_sha256": "11926067de5159f5d949669ed568c55b34ff1746ed0170a2a111fd95df2d9239", + "resource": { + "resource_scope": "rea-efficiency-21d304c2fc64460daa07729bfe6a2ea2.scope", + "peak_cgroup_bytes_sampled": 239734784, + "elapsed_seconds": 5.742, + "abort": null, + "hard_memory_bytes": 8589934592, + "early_stop_bytes": 6442450944, + "cpu_quota_percent": 100, + "cpu_affinity": [10], + "task_ceiling": 192, + "peak_tasks_sampled": 56, + "resource_events_last_observed": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + }, + "resource_events_final": null, + "resource_counters_verified": false, + "cpu_stat_last_observed": { + "usage_usec": 5437741, + "user_usec": 2550363, + "system_usec": 2887377, + "nr_periods": 56, + "nr_throttled": 1, + "throttled_usec": 1258, + "nr_bursts": 0, + "burst_usec": 0 + }, + "payload_resource_observation": { + "phase": "payload-exited-observer-alive", + "ownership_token": "rea-efficiency-21d304c2fc64460daa07729bfe6a2ea2.scope", + "payload_exit_code": 0, + "observer_pid": 3827208, + "remaining_processes": [3827208], + "payload_cleanup_verified": true, + "cpu_stat": { + "usage_usec": 5436427, + "user_usec": 2549050, + "system_usec": 2887377, + "nr_periods": 55, + "nr_throttled": 1, + "throttled_usec": 1258, + "nr_bursts": 0, + "burst_usec": 0 + }, + "memory_peak_bytes": 250449920, + "control_group": "user.slice/user-1004.slice/user@1004.service/app.slice/rea-efficiency-21d304c2fc64460daa07729bfe6a2ea2.scope", + "resource_events": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + } + }, + "payload_resource_observation_verified": true, + "payload_observation_scope": "Counters at payload exit while observer remains alive; not final whole-scope counters.", + "payload_observer_sources_sha256": { + "resource-run.py": "7728862171415e7dee2c11af2ead2b9a54265fccaf82a7113956d36b29065e33", + "resource_payload.py": "6f089e645459eba80c5d73e39c21606c78173a44daf49fabc3b88c4631ce51c6" + } + }, + "scope_poststate": "ActiveState=inactive\nSubState=dead", + "status": "28 passed / 28; single-on, multiple-on, empty-once and event-once each match native control; prior24 remain covered." + }, + "real_app": { + "log_name": "rea-4390-drawio-native-dispatch-final.log", + "log_sha256": "523ae674f24eab0378844f0ca5907389f067d18d4780654ab8c1a999fd2fa4c7", + "resource": { + "resource_scope": "rea-efficiency-faa3330353fc41a8b326af556917c1a3.scope", + "peak_cgroup_bytes_sampled": 547475456, + "elapsed_seconds": 12.29, + "abort": null, + "hard_memory_bytes": 8589934592, + "early_stop_bytes": 6442450944, + "cpu_quota_percent": 100, + "cpu_affinity": [10], + "task_ceiling": 192, + "peak_tasks_sampled": 77, + "resource_events_last_observed": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + }, + "resource_events_final": null, + "resource_counters_verified": false, + "cpu_stat_last_observed": { + "usage_usec": 7146502, + "user_usec": 5178993, + "system_usec": 1967509, + "nr_periods": 122, + "nr_throttled": 5, + "throttled_usec": 5312, + "nr_bursts": 0, + "burst_usec": 0 + }, + "payload_resource_observation": { + "phase": "payload-exited-observer-alive", + "ownership_token": "rea-efficiency-faa3330353fc41a8b326af556917c1a3.scope", + "payload_exit_code": 1, + "observer_pid": 3828459, + "remaining_processes": [3828459, 3828471, 3828701, 3828702], + "payload_cleanup_verified": false, + "cpu_stat": { + "usage_usec": 7118320, + "user_usec": 5158862, + "system_usec": 1959457, + "nr_periods": 121, + "nr_throttled": 5, + "throttled_usec": 5312, + "nr_bursts": 0, + "burst_usec": 0 + }, + "memory_peak_bytes": 548757504, + "control_group": "user.slice/user-1004.slice/user@1004.service/app.slice/rea-efficiency-faa3330353fc41a8b326af556917c1a3.scope", + "resource_events": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + } + }, + "payload_resource_observation_verified": false, + "payload_observation_scope": "Counters at payload exit while observer remains alive; not final whole-scope counters.", + "payload_observer_sources_sha256": { + "resource-run.py": "7728862171415e7dee2c11af2ead2b9a54265fccaf82a7113956d36b29065e33", + "resource_payload.py": "6f089e645459eba80c5d73e39c21606c78173a44daf49fabc3b88c4631ce51c6" + } + }, + "scope_poststate": "ActiveState=inactive\nSubState=dead", + "selected_result": { + "elapsed_ms": 11063.155085, + "deadline_triggered": false, + "selected_shape": ".geSidebar a.geItem:visible >> nth=0", + "before": { + "title": "Untitled Diagram - draw.io", + "invalid_file_dialog": true, + "unsaved_changes": false, + "diagram_svg_elements": 0, + "cells": 0 + }, + "after": { + "title": "Untitled Diagram - draw.io", + "invalid_file_dialog": false, + "unsaved_changes": true, + "diagram_svg_elements": 2, + "cells": 0 + }, + "actions_completed": true, + "shape_inserted": true, + "provider_ok": false, + "cleanup_verified": false, + "reason": "cleanup_failed", + "actions": [ + { + "step_id": "dismiss-invalid-file", + "kind": "click", + "window_index": 0, + "target": "window:0", + "elapsed_ms": 77, + "status": "completed", + "error": null + }, + { + "step_id": "insert-shape", + "kind": "click", + "window_index": 0, + "target": "window:0", + "elapsed_ms": 135, + "status": "completed", + "error": null + }, + { + "step_id": "settle-renderer", + "kind": "wait", + "window_index": 0, + "target": "window:0", + "elapsed_ms": 501, + "status": "completed", + "error": null + } + ], + "workflow_status": "partial-or-failed" + }, + "ipc_observed": 7, + "coverage": { + "status": "partial_attach", + "observed_event_families": [ + "app-lifecycle", + "ipc", + "navigation", + "preload-configuration", + "web-contents-lifecycle", + "window-lifecycle" + ], + "unavailable_event_families": [ + "download", + "error", + "native-addon", + "permission", + "popup-attempt", + "preload", + "process-lifecycle", + "protocol", + "renderer-ipc", + "shell-attempt", + "updater" + ], + "observed_roles": ["browser", "main", "web_contents", "window"], + "pre_capture_activity": "unavailable" + }, + "retention": { + "budget_bytes": 16777216, + "estimated_retained_bytes": 202114, + "event_serialized_byte_upper_bound": 48214, + "retained": 109, + "dropped": 0, + "dropped_event_families": [], + "dropped_event_roles": [], + "observed_ipc": 7, + "dropped_ipc": 0, + "observed_runtime": 102, + "dropped_runtime": 0 + } + }, + "limitations": [ + "The prior failed one-listener experiments remain historical. They did not establish array-dispatch behavior.", + "Raw wrapper .listener mutation remains explicitly unsupported and tracked in4513.", + "No full JavaScript introspection transparency or packaged runtime compatibility claim.", + "Real draw.io operations pass; overall provider cleanup remains failed. No completed-task performance or token/context claim.", + "Native routes verified on Node24.18.0; payload-exit counters are not final whole-scope counters." + ], + "static": { + "command": "npm run check:fast", + "status": "passed; two uncached tasks", + "log_sha256": "ec36dde4920f159355a266f09523c7a2bc5883f298b316e18d551db1278803aa", + "resource": { + "resource_scope": "rea-efficiency-9956bb7437dc4fd39bc8af4dc5728c22.scope", + "peak_cgroup_bytes_sampled": 4090605568, + "elapsed_seconds": 171.43, + "abort": null, + "hard_memory_bytes": 8589934592, + "early_stop_bytes": 6442450944, + "cpu_quota_percent": 100, + "cpu_affinity": [10], + "task_ceiling": 192, + "peak_tasks_sampled": 69, + "resource_events_last_observed": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + }, + "resource_events_final": null, + "resource_counters_verified": false, + "cpu_stat_last_observed": { + "usage_usec": 169140237, + "user_usec": 135932477, + "system_usec": 33207759, + "nr_periods": 1714, + "nr_throttled": 101, + "throttled_usec": 72017, + "nr_bursts": 0, + "burst_usec": 0 + }, + "payload_resource_observation": { + "phase": "payload-exited-observer-alive", + "ownership_token": "rea-efficiency-9956bb7437dc4fd39bc8af4dc5728c22.scope", + "payload_exit_code": 0, + "observer_pid": 3828820, + "remaining_processes": [3828820], + "payload_cleanup_verified": true, + "cpu_stat": { + "usage_usec": 169139239, + "user_usec": 135932477, + "system_usec": 33206761, + "nr_periods": 1713, + "nr_throttled": 101, + "throttled_usec": 72017, + "nr_bursts": 0, + "burst_usec": 0 + }, + "memory_peak_bytes": 4091392000, + "control_group": "user.slice/user-1004.slice/user@1004.service/app.slice/rea-efficiency-9956bb7437dc4fd39bc8af4dc5728c22.scope", + "resource_events": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + } + }, + "payload_resource_observation_verified": true, + "payload_observation_scope": "Counters at payload exit while observer remains alive; not final whole-scope counters.", + "payload_observer_sources_sha256": { + "resource-run.py": "7728862171415e7dee2c11af2ead2b9a54265fccaf82a7113956d36b29065e33", + "resource_payload.py": "6f089e645459eba80c5d73e39c21606c78173a44daf49fabc3b88c4631ce51c6" + } + }, + "scope_poststate": "ActiveState=inactive\nSubState=dead" + }, + "docs_check": { + "command": "npm run docs:check", + "status": "passed; six tasks and five README consistency cases", + "log_sha256": "09d4de4e097b20efd1eb644940260cf62666063250ca7e8713c6863431b773a9", + "resource": { + "resource_scope": "rea-efficiency-36035cd8603d46b5bab5cb9356c1eb54.scope", + "peak_cgroup_bytes_sampled": 467345408, + "elapsed_seconds": 10.452, + "abort": null, + "hard_memory_bytes": 8589934592, + "early_stop_bytes": 6442450944, + "cpu_quota_percent": 100, + "cpu_affinity": [10], + "task_ceiling": 192, + "peak_tasks_sampled": 64, + "resource_events_last_observed": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + }, + "resource_events_final": null, + "resource_counters_verified": false, + "cpu_stat_last_observed": { + "usage_usec": 9824697, + "user_usec": 7901191, + "system_usec": 1923505, + "nr_periods": 103, + "nr_throttled": 3, + "throttled_usec": 841, + "nr_bursts": 0, + "burst_usec": 0 + }, + "payload_resource_observation": { + "phase": "payload-exited-observer-alive", + "ownership_token": "rea-efficiency-36035cd8603d46b5bab5cb9356c1eb54.scope", + "payload_exit_code": 0, + "observer_pid": 3832051, + "remaining_processes": [3832051], + "payload_cleanup_verified": true, + "cpu_stat": { + "usage_usec": 9823704, + "user_usec": 7900393, + "system_usec": 1923311, + "nr_periods": 103, + "nr_throttled": 3, + "throttled_usec": 841, + "nr_bursts": 0, + "burst_usec": 0 + }, + "memory_peak_bytes": 477044736, + "control_group": "user.slice/user-1004.slice/user@1004.service/app.slice/rea-efficiency-36035cd8603d46b5bab5cb9356c1eb54.scope", + "resource_events": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + } + }, + "payload_resource_observation_verified": true, + "payload_observation_scope": "Counters at payload exit while observer remains alive; not final whole-scope counters.", + "payload_observer_sources_sha256": { + "resource-run.py": "7728862171415e7dee2c11af2ead2b9a54265fccaf82a7113956d36b29065e33", + "resource_payload.py": "6f089e645459eba80c5d73e39c21606c78173a44daf49fabc3b88c4631ce51c6" + } + }, + "scope_poststate": "ActiveState=inactive\nSubState=dead" + } + }, + "argument_list_followup": { + "reviewed_head": "23700fd2c3a3240573a9e64a54d272216d739c68", + "review_threads": ["PRRT_kwDOSB6hVc6tRXUw", "PRRT_kwDOSB6hVc6tRaEM"], + "source_pins": { + "candidate_revision": "a82d935ba572dd83c4d6dfcafc93495608822601", + "baseline_revision": "180ba03bfddd5916251e81e97a500b1ede7a9eea", + "source_sha256": { + "scripts/electron-active-hook.cjs": "808e1de949b8e069ed83301ea43fe2cfee27ca57592d19f387bc820a3628dfdd", + "tests/boundary/browser/electronActiveHook.test.ts": "a9da0e1454e12794a38c74b6c2cf7fcda2212e589aed86c5f08fa17d127aeaa1", + "experiments/analysis-efficiency/drawio-electron-lifecycle.mjs": "d484b1550704b3be1bf8b4fb1980ab50b81924a917ffae8659c84dea036fd96a" + } + }, + "root_cause": "The native apply forwarding path assumed an Array although Function.apply accepts other array-like inputs and null/undefined; once also passed a rest Array instead of its native Arguments object. Invocation inputs and recording projections shared an incorrect representation.", + "structural_fix": "Bind the captured native Function.apply to the forwarding function, delegating array-like conversion, missing/null lists and invalid primitive rejection to JavaScript itself before recording. once keeps its actual Arguments object for callback invocation; only recording takes a slice through a captured, uncurried native helper. No arbitrary array-like converter or exception repair is introduced.", + "red": { + "log_name": "rea-4390-argument-list-red.log", + "log_sha256": "94c4dc9bc0f868ea7cc6a4c5ee5186751655a23aae8db60a978996c1567baa11", + "resource": { + "resource_scope": "rea-efficiency-532b9d94428a494981d788ffaaede85f.scope", + "peak_cgroup_bytes_sampled": 325742592, + "elapsed_seconds": 4.516, + "abort": null, + "hard_memory_bytes": 8589934592, + "early_stop_bytes": 6442450944, + "cpu_quota_percent": 100, + "cpu_affinity": [10], + "task_ceiling": 192, + "peak_tasks_sampled": 56, + "resource_events_last_observed": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + }, + "resource_events_final": null, + "resource_counters_verified": false, + "cpu_stat_last_observed": { + "usage_usec": 3998278, + "user_usec": 2724130, + "system_usec": 1274147, + "nr_periods": 44, + "nr_throttled": 0, + "throttled_usec": 0, + "nr_bursts": 0, + "burst_usec": 0 + }, + "payload_resource_observation": { + "phase": "payload-exited-observer-alive", + "ownership_token": "rea-efficiency-532b9d94428a494981d788ffaaede85f.scope", + "payload_exit_code": 1, + "observer_pid": 3844622, + "remaining_processes": [3844622], + "payload_cleanup_verified": true, + "cpu_stat": { + "usage_usec": 3996797, + "user_usec": 2723122, + "system_usec": 1273675, + "nr_periods": 44, + "nr_throttled": 0, + "throttled_usec": 0, + "nr_bursts": 0, + "burst_usec": 0 + }, + "memory_peak_bytes": 344989696, + "control_group": "user.slice/user-1004.slice/user@1004.service/app.slice/rea-efficiency-532b9d94428a494981d788ffaaede85f.scope", + "resource_events": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + } + }, + "payload_resource_observation_verified": true, + "payload_observation_scope": "Counters at payload exit while observer remains alive; not final whole-scope counters.", + "payload_observer_sources_sha256": { + "resource-run.py": "7728862171415e7dee2c11af2ead2b9a54265fccaf82a7113956d36b29065e33", + "resource_payload.py": "6f089e645459eba80c5d73e39c21606c78173a44daf49fabc3b88c4631ce51c6" + } + }, + "scope_poststate": "ActiveState=inactive\nSubState=dead", + "status": "29 passed /6 failed of35: five valid raw apply argument-list forms and the once custom-apply Arguments identity case fail against native controls." + }, + "intermediate_green": { + "log_name": "rea-4390-argument-list-green.log", + "log_sha256": "79dfcb0bedd5ff4dd28df6320be903fb8d260f25876beecec7366bd36e006cd1", + "resource": { + "resource_scope": "rea-efficiency-6f48cc640409442baeb6f59cf0d658d7.scope", + "peak_cgroup_bytes_sampled": 273240064, + "elapsed_seconds": 4.28, + "abort": null, + "hard_memory_bytes": 8589934592, + "early_stop_bytes": 6442450944, + "cpu_quota_percent": 100, + "cpu_affinity": [10], + "task_ceiling": 192, + "peak_tasks_sampled": 56, + "resource_events_last_observed": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + }, + "resource_events_final": null, + "resource_counters_verified": false, + "cpu_stat_last_observed": { + "usage_usec": 4058152, + "user_usec": 2680387, + "system_usec": 1377764, + "nr_periods": 42, + "nr_throttled": 1, + "throttled_usec": 1831, + "nr_bursts": 0, + "burst_usec": 0 + }, + "payload_resource_observation": { + "phase": "payload-exited-observer-alive", + "ownership_token": "rea-efficiency-6f48cc640409442baeb6f59cf0d658d7.scope", + "payload_exit_code": 0, + "observer_pid": 3845753, + "remaining_processes": [3845753], + "payload_cleanup_verified": true, + "cpu_stat": { + "usage_usec": 4057201, + "user_usec": 2679759, + "system_usec": 1377441, + "nr_periods": 42, + "nr_throttled": 1, + "throttled_usec": 1831, + "nr_bursts": 0, + "burst_usec": 0 + }, + "memory_peak_bytes": 294686720, + "control_group": "user.slice/user-1004.slice/user@1004.service/app.slice/rea-efficiency-6f48cc640409442baeb6f59cf0d658d7.scope", + "resource_events": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + } + }, + "payload_resource_observation_verified": true, + "payload_observation_scope": "Counters at payload exit while observer remains alive; not final whole-scope counters.", + "payload_observer_sources_sha256": { + "resource-run.py": "7728862171415e7dee2c11af2ead2b9a54265fccaf82a7113956d36b29065e33", + "resource_payload.py": "6f089e645459eba80c5d73e39c21606c78173a44daf49fabc3b88c4631ce51c6" + } + }, + "scope_poststate": "ActiveState=inactive\nSubState=dead", + "status": "35 passed; before the final captured slice helper change. No final-source hash is assigned retrospectively to this intermediate run." + }, + "final_focused": { + "log_name": "rea-4390-argument-list-final-green.log", + "log_sha256": "2383cd2ce9a25beaf0cf1ea77cbb57d1beba652809545f1649279a49e28d22e7", + "resource": { + "resource_scope": "rea-efficiency-b8f02509e9f546d983b451aabe52ac0d.scope", + "peak_cgroup_bytes_sampled": 247308288, + "elapsed_seconds": 5.437, + "abort": null, + "hard_memory_bytes": 8589934592, + "early_stop_bytes": 6442450944, + "cpu_quota_percent": 100, + "cpu_affinity": [10], + "task_ceiling": 192, + "peak_tasks_sampled": 56, + "resource_events_last_observed": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + }, + "resource_events_final": null, + "resource_counters_verified": false, + "cpu_stat_last_observed": { + "usage_usec": 5092942, + "user_usec": 2798465, + "system_usec": 2294476, + "nr_periods": 54, + "nr_throttled": 4, + "throttled_usec": 2643, + "nr_bursts": 0, + "burst_usec": 0 + }, + "payload_resource_observation": { + "phase": "payload-exited-observer-alive", + "ownership_token": "rea-efficiency-b8f02509e9f546d983b451aabe52ac0d.scope", + "payload_exit_code": 0, + "observer_pid": 3848239, + "remaining_processes": [3848239], + "payload_cleanup_verified": true, + "cpu_stat": { + "usage_usec": 5091715, + "user_usec": 2797791, + "system_usec": 2293923, + "nr_periods": 53, + "nr_throttled": 4, + "throttled_usec": 2643, + "nr_bursts": 0, + "burst_usec": 0 + }, + "memory_peak_bytes": 258252800, + "control_group": "user.slice/user-1004.slice/user@1004.service/app.slice/rea-efficiency-b8f02509e9f546d983b451aabe52ac0d.scope", + "resource_events": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + } + }, + "payload_resource_observation_verified": true, + "payload_observation_scope": "Counters at payload exit while observer remains alive; not final whole-scope counters.", + "payload_observer_sources_sha256": { + "resource-run.py": "7728862171415e7dee2c11af2ead2b9a54265fccaf82a7113956d36b29065e33", + "resource_payload.py": "6f089e645459eba80c5d73e39c21606c78173a44daf49fabc3b88c4631ce51c6" + } + }, + "scope_poststate": "ActiveState=inactive\nSubState=dead", + "status": "35 passed /35, including array-like getter read exactly once, null/undefined/missing lists, primitive rejection and native once Arguments identity." + }, + "real_app": { + "log_name": "rea-4390-drawio-argument-list-final.log", + "log_sha256": "0a5ab8fd34c41dfb6bd40f189f18273d3df25efe95829c18167511241fd70d3a", + "resource": { + "resource_scope": "rea-efficiency-b3e7bbcff7bb40c790c5f61b71726fcc.scope", + "peak_cgroup_bytes_sampled": 675737600, + "elapsed_seconds": 12.45, + "abort": null, + "hard_memory_bytes": 8589934592, + "early_stop_bytes": 6442450944, + "cpu_quota_percent": 100, + "cpu_affinity": [10], + "task_ceiling": 192, + "peak_tasks_sampled": 75, + "resource_events_last_observed": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + }, + "resource_events_final": null, + "resource_counters_verified": false, + "cpu_stat_last_observed": { + "usage_usec": 6902299, + "user_usec": 4998863, + "system_usec": 1903435, + "nr_periods": 123, + "nr_throttled": 1, + "throttled_usec": 28, + "nr_bursts": 0, + "burst_usec": 0 + }, + "payload_resource_observation": { + "phase": "payload-exited-observer-alive", + "ownership_token": "rea-efficiency-b3e7bbcff7bb40c790c5f61b71726fcc.scope", + "payload_exit_code": 1, + "observer_pid": 3850682, + "remaining_processes": [3850682, 3850694], + "payload_cleanup_verified": false, + "cpu_stat": { + "usage_usec": 6877718, + "user_usec": 4982451, + "system_usec": 1895266, + "nr_periods": 123, + "nr_throttled": 1, + "throttled_usec": 28, + "nr_bursts": 0, + "burst_usec": 0 + }, + "memory_peak_bytes": 675999744, + "control_group": "user.slice/user-1004.slice/user@1004.service/app.slice/rea-efficiency-b3e7bbcff7bb40c790c5f61b71726fcc.scope", + "resource_events": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + } + }, + "payload_resource_observation_verified": false, + "payload_observation_scope": "Counters at payload exit while observer remains alive; not final whole-scope counters.", + "payload_observer_sources_sha256": { + "resource-run.py": "7728862171415e7dee2c11af2ead2b9a54265fccaf82a7113956d36b29065e33", + "resource_payload.py": "6f089e645459eba80c5d73e39c21606c78173a44daf49fabc3b88c4631ce51c6" + } + }, + "scope_poststate": "ActiveState=inactive\nSubState=dead", + "selected_result": { + "elapsed_ms": 11090.256545, + "deadline_triggered": false, + "selected_shape": ".geSidebar a.geItem:visible >> nth=0", + "before": { + "title": "Untitled Diagram - draw.io", + "invalid_file_dialog": true, + "unsaved_changes": false, + "diagram_svg_elements": 0, + "cells": 0 + }, + "after": { + "title": "Untitled Diagram - draw.io", + "invalid_file_dialog": false, + "unsaved_changes": true, + "diagram_svg_elements": 2, + "cells": 0 + }, + "actions_completed": true, + "shape_inserted": true, + "provider_ok": false, + "cleanup_verified": false, + "reason": "cleanup_failed", + "actions": [ + { + "step_id": "dismiss-invalid-file", + "kind": "click", + "window_index": 0, + "target": "window:0", + "elapsed_ms": 79, + "status": "completed", + "error": null + }, + { + "step_id": "insert-shape", + "kind": "click", + "window_index": 0, + "target": "window:0", + "elapsed_ms": 167, + "status": "completed", + "error": null + }, + { + "step_id": "settle-renderer", + "kind": "wait", + "window_index": 0, + "target": "window:0", + "elapsed_ms": 501, + "status": "completed", + "error": null + } + ], + "workflow_status": "partial-or-failed" + }, + "ipc_observed": 7, + "coverage": { + "status": "partial_attach", + "observed_event_families": [ + "app-lifecycle", + "ipc", + "navigation", + "preload-configuration", + "web-contents-lifecycle", + "window-lifecycle" + ], + "unavailable_event_families": [ + "download", + "error", + "native-addon", + "permission", + "popup-attempt", + "preload", + "process-lifecycle", + "protocol", + "renderer-ipc", + "shell-attempt", + "updater" + ], + "observed_roles": ["browser", "main", "web_contents", "window"], + "pre_capture_activity": "unavailable" + }, + "retention": { + "budget_bytes": 16777216, + "estimated_retained_bytes": 202114, + "event_serialized_byte_upper_bound": 48214, + "retained": 109, + "dropped": 0, + "dropped_event_families": [], + "dropped_event_roles": [], + "observed_ipc": 7, + "dropped_ipc": 0, + "observed_runtime": 102, + "dropped_runtime": 0 + } + }, + "limits": [ + "Same unchanged real draw.io operation phase passes with7 IPC events; provider cleanup remains failed.", + "No paired completed-task performance, token/context savings, vendor runtime or MCP-wire claim.", + "Raw wrapper metadata mutation remains documented and unfixed in4513.", + "Resource counters at payload exit while observer alive are not final whole-scope counters." + ], + "static": { + "command": "npm run check:fast", + "status": "passed; two uncached tasks", + "log_sha256": "4c64e683c88df2acee34b0c9072b25b41d677cbb3413c180ccece7b64d214bbd", + "resource": { + "resource_scope": "rea-efficiency-f79dcd50a74340cfb5fc04ec52345d77.scope", + "peak_cgroup_bytes_sampled": 4184866816, + "elapsed_seconds": 122.454, + "abort": null, + "hard_memory_bytes": 8589934592, + "early_stop_bytes": 6442450944, + "cpu_quota_percent": 100, + "cpu_affinity": [10], + "task_ceiling": 192, + "peak_tasks_sampled": 70, + "resource_events_last_observed": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + }, + "resource_events_final": null, + "resource_counters_verified": false, + "cpu_stat_last_observed": { + "usage_usec": 121288012, + "user_usec": 101362484, + "system_usec": 19925528, + "nr_periods": 1224, + "nr_throttled": 52, + "throttled_usec": 17357, + "nr_bursts": 0, + "burst_usec": 0 + }, + "payload_resource_observation": { + "phase": "payload-exited-observer-alive", + "ownership_token": "rea-efficiency-f79dcd50a74340cfb5fc04ec52345d77.scope", + "payload_exit_code": 0, + "observer_pid": 3851133, + "remaining_processes": [3851133], + "payload_cleanup_verified": true, + "cpu_stat": { + "usage_usec": 121287057, + "user_usec": 101361685, + "system_usec": 19925371, + "nr_periods": 1224, + "nr_throttled": 52, + "throttled_usec": 17357, + "nr_bursts": 0, + "burst_usec": 0 + }, + "memory_peak_bytes": 4185915392, + "control_group": "user.slice/user-1004.slice/user@1004.service/app.slice/rea-efficiency-f79dcd50a74340cfb5fc04ec52345d77.scope", + "resource_events": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + } + }, + "payload_resource_observation_verified": true, + "payload_observation_scope": "Counters at payload exit while observer remains alive; not final whole-scope counters.", + "payload_observer_sources_sha256": { + "resource-run.py": "7728862171415e7dee2c11af2ead2b9a54265fccaf82a7113956d36b29065e33", + "resource_payload.py": "6f089e645459eba80c5d73e39c21606c78173a44daf49fabc3b88c4631ce51c6" + } + }, + "scope_poststate": "ActiveState=inactive\nSubState=dead" + }, + "input_asar_sha256_rechecked_after_trial": "e64b9650c0b8241ca9a7d33b56386a385bceee63270cec701d3a467347b697e6", + "docs_check": { + "command": "npm run docs:check", + "status": "passed; six tasks and five README consistency cases", + "log_sha256": "ac93468286df1215d7d4cf830dd5026b68fc2e7f7ee6f7e6cc0fb36bea8e6e5c", + "resource": { + "resource_scope": "rea-efficiency-fd52636d5ac94d008bc1991c597e2fda.scope", + "peak_cgroup_bytes_sampled": 408018944, + "elapsed_seconds": 11.136, + "abort": null, + "hard_memory_bytes": 8589934592, + "early_stop_bytes": 6442450944, + "cpu_quota_percent": 100, + "cpu_affinity": [10], + "task_ceiling": 192, + "peak_tasks_sampled": 63, + "resource_events_last_observed": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + }, + "resource_events_final": null, + "resource_counters_verified": false, + "cpu_stat_last_observed": { + "usage_usec": 10683652, + "user_usec": 7686033, + "system_usec": 2997619, + "nr_periods": 111, + "nr_throttled": 8, + "throttled_usec": 7621, + "nr_bursts": 0, + "burst_usec": 0 + }, + "payload_resource_observation": { + "phase": "payload-exited-observer-alive", + "ownership_token": "rea-efficiency-fd52636d5ac94d008bc1991c597e2fda.scope", + "payload_exit_code": 0, + "observer_pid": 3853280, + "remaining_processes": [3853280], + "payload_cleanup_verified": true, + "cpu_stat": { + "usage_usec": 10682720, + "user_usec": 7685363, + "system_usec": 2997357, + "nr_periods": 110, + "nr_throttled": 8, + "throttled_usec": 7621, + "nr_bursts": 0, + "burst_usec": 0 + }, + "memory_peak_bytes": 417841152, + "control_group": "user.slice/user-1004.slice/user@1004.service/app.slice/rea-efficiency-fd52636d5ac94d008bc1991c597e2fda.scope", + "resource_events": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + } + }, + "payload_resource_observation_verified": true, + "payload_observation_scope": "Counters at payload exit while observer remains alive; not final whole-scope counters.", + "payload_observer_sources_sha256": { + "resource-run.py": "7728862171415e7dee2c11af2ead2b9a54265fccaf82a7113956d36b29065e33", + "resource_payload.py": "6f089e645459eba80c5d73e39c21606c78173a44daf49fabc3b88c4631ce51c6" + } + }, + "scope_poststate": "ActiveState=inactive\nSubState=dead" + } + }, + "owned_metadata_and_verifier_followup": { + "reviewed_head": "41a513d993c50d8db91535047a1bd3fcb580d3b9", + "review_threads": ["PRRT_kwDOSB6hVc6tR5kn", "PRRT_kwDOSB6hVc6tR5kr"], + "source_pins": { + "candidate_revision": "4a9a01f7a4aab420eb645f7e06841d6ec1db28bd", + "baseline_revision": "180ba03bfddd5916251e81e97a500b1ede7a9eea", + "source_sha256": { + "scripts/electron-active-hook.cjs": "e06de9e141212187c01743b5c1029061830d4235daf75f7eef08796591bc8559", + "tests/boundary/browser/electronActiveHook.test.ts": "432d69e92707f373153d903f5a193d3dd674cd2d6dfb5be234a51260549b1961", + "experiments/analysis-efficiency/drawio-electron-lifecycle.mjs": "1423a472f58fc81383061bf695059550276b898c780e91bb47052797b25e0ce6", + "experiments/analysis-efficiency/drawio-electron-lifecycle-oracles.py": "c98dd2abf16703744aa32edc5dd12b0f4eec121d40a4aaa387ef580b046032e2" + } + }, + "root_causes": [ + "Ordinary on registration does not assign .listener metadata, but the recording wrapper used strict assignment, exposing hook-owned identity to inherited application setters or readonly prototype fields.", + "Successful capture returns an Evidence envelope; the verifier read observation fields from the envelope rather than normalized_result. Real cleanup failures only exercised the separate raw-partial path." + ], + "structural_fixes": [ + "5dcf79f29 defines the on-wrapper own writable/configurable/enumerable data property directly. once retains its native assignment convention.", + "4a9a01f7a reads success.value.normalized_result, retaining error.partialObservation.capture on failure. New standalone controlled oracles exercise the actual verifier in an isolated stub runtime." + ], + "native_red": { + "log_name": "rea-4390-inherited-metadata-red.log", + "log_sha256": "afd919f233c239f6393362cf64ce519a93e5d7ac0fb5876e18d21e3a4a187919", + "resource": { + "resource_scope": "rea-efficiency-fa6ca7cff41e4b009c73d38899ce9e6e.scope", + "elapsed_seconds": 4.919, + "abort": null, + "hard_memory_bytes": 8589934592, + "early_stop_bytes": 6442450944, + "cpu_quota_percent": 100, + "cpu_affinity": [10], + "task_ceiling": 192, + "payload_resource_observation": { + "phase": "payload-exited-observer-alive", + "ownership_token": "rea-efficiency-fa6ca7cff41e4b009c73d38899ce9e6e.scope", + "payload_exit_code": 1, + "observer_pid": 3870349, + "remaining_processes": [3870349], + "payload_cleanup_verified": true, + "cpu_stat": { + "usage_usec": 4250529, + "user_usec": 2956701, + "system_usec": 1293828, + "nr_periods": 47, + "nr_throttled": 0, + "throttled_usec": 0, + "nr_bursts": 0, + "burst_usec": 0 + }, + "memory_peak_bytes": 353755136, + "control_group": "user.slice/user-1004.slice/user@1004.service/app.slice/rea-efficiency-fa6ca7cff41e4b009c73d38899ce9e6e.scope", + "resource_events": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + } + }, + "payload_resource_observation_verified": true, + "payload_observation_scope": "Counters at payload exit while observer remains alive; not final whole-scope counters.", + "payload_observer_sources_sha256": { + "resource-run.py": "7728862171415e7dee2c11af2ead2b9a54265fccaf82a7113956d36b29065e33", + "resource_payload.py": "6f089e645459eba80c5d73e39c21606c78173a44daf49fabc3b88c4631ce51c6" + } + }, + "scope_poststate": "ActiveState=inactive\nSubState=dead", + "status": "35 passed /4 failed of39: Function/Object prototype setters and readonly fields." + }, + "native_green": { + "log_name": "rea-4390-inherited-metadata-green.log", + "log_sha256": "614e5d0bae651dce139f510946d5c2fd51047240310fe87e9a58aa4606369375", + "resource": { + "resource_scope": "rea-efficiency-59821aae0a8747358570563c08f02964.scope", + "elapsed_seconds": 6.156, + "abort": null, + "hard_memory_bytes": 8589934592, + "early_stop_bytes": 6442450944, + "cpu_quota_percent": 100, + "cpu_affinity": [10], + "task_ceiling": 192, + "payload_resource_observation": { + "phase": "payload-exited-observer-alive", + "ownership_token": "rea-efficiency-59821aae0a8747358570563c08f02964.scope", + "payload_exit_code": 0, + "observer_pid": 3872909, + "remaining_processes": [3872909], + "payload_cleanup_verified": true, + "cpu_stat": { + "usage_usec": 5438514, + "user_usec": 2844035, + "system_usec": 2594479, + "nr_periods": 60, + "nr_throttled": 0, + "throttled_usec": 0, + "nr_bursts": 0, + "burst_usec": 0 + }, + "memory_peak_bytes": 248344576, + "control_group": "user.slice/user-1004.slice/user@1004.service/app.slice/rea-efficiency-59821aae0a8747358570563c08f02964.scope", + "resource_events": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + } + }, + "payload_resource_observation_verified": true, + "payload_observation_scope": "Counters at payload exit while observer remains alive; not final whole-scope counters.", + "payload_observer_sources_sha256": { + "resource-run.py": "7728862171415e7dee2c11af2ead2b9a54265fccaf82a7113956d36b29065e33", + "resource_payload.py": "6f089e645459eba80c5d73e39c21606c78173a44daf49fabc3b88c4631ce51c6" + } + }, + "scope_poststate": "ActiveState=inactive\nSubState=dead", + "status": "39 passed /39." + }, + "verifier_red": { + "log_name": "rea-4390-verifier-evidence-red.log", + "log_sha256": "aad53929f64adc37adaee7bd557afaa119519bfd74cefb426267526ce730209e", + "resource": { + "resource_scope": "rea-efficiency-ead3f8109c8446f191bd50029f7a6591.scope", + "elapsed_seconds": 0.488, + "abort": null, + "hard_memory_bytes": 8589934592, + "early_stop_bytes": 6442450944, + "cpu_quota_percent": 100, + "cpu_affinity": [10], + "task_ceiling": 192, + "payload_resource_observation": { + "phase": "payload-exited-observer-alive", + "ownership_token": "rea-efficiency-ead3f8109c8446f191bd50029f7a6591.scope", + "payload_exit_code": 1, + "observer_pid": 3872641, + "remaining_processes": [3872641], + "payload_cleanup_verified": true, + "cpu_stat": { + "usage_usec": 336957, + "user_usec": 231427, + "system_usec": 105529, + "nr_periods": 4, + "nr_throttled": 0, + "throttled_usec": 0, + "nr_bursts": 0, + "burst_usec": 0 + }, + "memory_peak_bytes": 25305088, + "control_group": "user.slice/user-1004.slice/user@1004.service/app.slice/rea-efficiency-ead3f8109c8446f191bd50029f7a6591.scope", + "resource_events": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + } + }, + "payload_resource_observation_verified": true, + "payload_observation_scope": "Counters at payload exit while observer remains alive; not final whole-scope counters.", + "payload_observer_sources_sha256": { + "resource-run.py": "7728862171415e7dee2c11af2ead2b9a54265fccaf82a7113956d36b29065e33", + "resource_payload.py": "6f089e645459eba80c5d73e39c21606c78173a44daf49fabc3b88c4631ce51c6" + } + }, + "scope_poststate": "ActiveState=inactive\nSubState=dead", + "oracle_result": { + "oracle_cases": [ + { + "case": "complete", + "exit_code": 1, + "expected_success": true, + "summary_present": false, + "matched_expected": false + }, + { + "case": "partial", + "exit_code": 1, + "expected_success": false, + "summary_present": true, + "matched_expected": true + }, + { + "case": "action-failure", + "exit_code": 1, + "expected_success": false, + "summary_present": false, + "matched_expected": false + }, + { + "case": "insertion-failure", + "exit_code": 1, + "expected_success": false, + "summary_present": false, + "matched_expected": false + }, + { + "case": "no-capture", + "exit_code": 1, + "expected_success": false, + "summary_present": true, + "matched_expected": true + } + ], + "real_provider_verified": false + }, + "status": "Old verifier rejected valid successful Evidence without a summary; success-envelope action/insertion failures also crashed before producing their diagnostic summary. Partial and no-capture controls already matched." + }, + "verifier_green": { + "log_name": "rea-4390-verifier-evidence-green.log", + "log_sha256": "d63ab9d48a0e91019460bf16734839ff6b513a21ec64709b0d5ff5b1dd368a36", + "resource": { + "resource_scope": "rea-efficiency-0a9fd24c169c430cbc86df5d9cfc1e84.scope", + "elapsed_seconds": 0.921, + "abort": null, + "hard_memory_bytes": 8589934592, + "early_stop_bytes": 6442450944, + "cpu_quota_percent": 100, + "cpu_affinity": [10], + "task_ceiling": 192, + "payload_resource_observation": { + "phase": "payload-exited-observer-alive", + "ownership_token": "rea-efficiency-0a9fd24c169c430cbc86df5d9cfc1e84.scope", + "payload_exit_code": 0, + "observer_pid": 3872995, + "remaining_processes": [3872995], + "payload_cleanup_verified": true, + "cpu_stat": { + "usage_usec": 353242, + "user_usec": 239440, + "system_usec": 113801, + "nr_periods": 8, + "nr_throttled": 0, + "throttled_usec": 0, + "nr_bursts": 0, + "burst_usec": 0 + }, + "memory_peak_bytes": 25276416, + "control_group": "user.slice/user-1004.slice/user@1004.service/app.slice/rea-efficiency-0a9fd24c169c430cbc86df5d9cfc1e84.scope", + "resource_events": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + } + }, + "payload_resource_observation_verified": true, + "payload_observation_scope": "Counters at payload exit while observer remains alive; not final whole-scope counters.", + "payload_observer_sources_sha256": { + "resource-run.py": "7728862171415e7dee2c11af2ead2b9a54265fccaf82a7113956d36b29065e33", + "resource_payload.py": "6f089e645459eba80c5d73e39c21606c78173a44daf49fabc3b88c4631ce51c6" + } + }, + "scope_poststate": "ActiveState=inactive\nSubState=dead", + "oracle_result": { + "oracle_cases": [ + { + "case": "complete", + "exit_code": 0, + "expected_success": true, + "summary_present": true, + "matched_expected": true + }, + { + "case": "partial", + "exit_code": 1, + "expected_success": false, + "summary_present": true, + "matched_expected": true + }, + { + "case": "action-failure", + "exit_code": 1, + "expected_success": false, + "summary_present": true, + "matched_expected": true + }, + { + "case": "insertion-failure", + "exit_code": 1, + "expected_success": false, + "summary_present": true, + "matched_expected": true + }, + { + "case": "no-capture", + "exit_code": 1, + "expected_success": false, + "summary_present": true, + "matched_expected": true + } + ], + "real_provider_verified": false + }, + "status": "All5 controlled exit/summary oracles match expected results; NOT real-provider verification." + }, + "real_app": { + "log_name": "rea-4390-drawio-owned-metadata-final.log", + "log_sha256": "016646c990c3442ac3c983114a218e302ff9ca3701666260bb1961794aa1a0f0", + "resource": { + "resource_scope": "rea-efficiency-f6fd5130f25b449da0370394a2595fb9.scope", + "elapsed_seconds": 12.777, + "abort": null, + "hard_memory_bytes": 8589934592, + "early_stop_bytes": 6442450944, + "cpu_quota_percent": 100, + "cpu_affinity": [10], + "task_ceiling": 192, + "payload_resource_observation": { + "phase": "payload-exited-observer-alive", + "ownership_token": "rea-efficiency-f6fd5130f25b449da0370394a2595fb9.scope", + "payload_exit_code": 1, + "observer_pid": 3877417, + "remaining_processes": [3877417, 3877429, 3877689, 3877690], + "payload_cleanup_verified": false, + "cpu_stat": { + "usage_usec": 6968048, + "user_usec": 5135210, + "system_usec": 1832837, + "nr_periods": 126, + "nr_throttled": 1, + "throttled_usec": 1995, + "nr_bursts": 0, + "burst_usec": 0 + }, + "memory_peak_bytes": 698638336, + "control_group": "user.slice/user-1004.slice/user@1004.service/app.slice/rea-efficiency-f6fd5130f25b449da0370394a2595fb9.scope", + "resource_events": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + } + }, + "payload_resource_observation_verified": false, + "payload_observation_scope": "Counters at payload exit while observer remains alive; not final whole-scope counters.", + "payload_observer_sources_sha256": { + "resource-run.py": "7728862171415e7dee2c11af2ead2b9a54265fccaf82a7113956d36b29065e33", + "resource_payload.py": "6f089e645459eba80c5d73e39c21606c78173a44daf49fabc3b88c4631ce51c6" + } + }, + "scope_poststate": "ActiveState=inactive\nSubState=dead", + "selected_result": { + "elapsed_ms": 11278.940224, + "deadline_triggered": false, + "selected_shape": ".geSidebar a.geItem:visible >> nth=0", + "before": { + "title": "Untitled Diagram - draw.io", + "invalid_file_dialog": true, + "unsaved_changes": false, + "diagram_svg_elements": 0, + "cells": 0 + }, + "after": { + "title": "Untitled Diagram - draw.io", + "invalid_file_dialog": false, + "unsaved_changes": true, + "diagram_svg_elements": 2, + "cells": 0 + }, + "actions_completed": true, + "shape_inserted": true, + "provider_ok": false, + "cleanup_verified": false, + "reason": "cleanup_failed", + "actions": [ + { + "step_id": "dismiss-invalid-file", + "kind": "click", + "window_index": 0, + "target": "window:0", + "elapsed_ms": 97, + "status": "completed", + "error": null + }, + { + "step_id": "insert-shape", + "kind": "click", + "window_index": 0, + "target": "window:0", + "elapsed_ms": 135, + "status": "completed", + "error": null + }, + { + "step_id": "settle-renderer", + "kind": "wait", + "window_index": 0, + "target": "window:0", + "elapsed_ms": 501, + "status": "completed", + "error": null + } + ], + "workflow_status": "partial-or-failed" + }, + "ipc_observed": 7, + "coverage": { + "status": "partial_attach", + "observed_event_families": [ + "app-lifecycle", + "ipc", + "navigation", + "preload-configuration", + "web-contents-lifecycle", + "window-lifecycle" + ], + "unavailable_event_families": [ + "download", + "error", + "native-addon", + "permission", + "popup-attempt", + "preload", + "process-lifecycle", + "protocol", + "renderer-ipc", + "shell-attempt", + "updater" + ], + "observed_roles": ["browser", "main", "web_contents", "window"], + "pre_capture_activity": "unavailable" + }, + "retention": { + "budget_bytes": 16777216, + "estimated_retained_bytes": 202114, + "event_serialized_byte_upper_bound": 48214, + "retained": 109, + "dropped": 0, + "dropped_event_families": [], + "dropped_event_roles": [], + "observed_ipc": 7, + "dropped_ipc": 0, + "observed_runtime": 102, + "dropped_runtime": 0 + } + }, + "limits": [ + "Latest real run uses corrected verifier hash; earlier ABBA and follow-ups retain their original verifier identities. Do not retroactively assign the new hash.", + "No real provider cleanup success, packaged runtime compatibility, MCP-wire pass or completed-task/token/context performance claim.", + "Raw metadata mutation4513 remains explicit and unfixed.", + "Compact resource receipts retain actual payload-exit counters, bounds, observer hashes and post-scope states; counters are not final whole-scope counters." + ], + "static": { + "command": "npm run check:fast", + "status": "passed; two uncached tasks", + "log_sha256": "3ac9479dc4abb1205f6dc3c5ea3519cb7bb648c6a51cc142188c147e0c26e868", + "resource": { + "resource_scope": "rea-efficiency-b67d2b674f00447095cf3003ae4d15f0.scope", + "elapsed_seconds": 99.992, + "abort": null, + "hard_memory_bytes": 8589934592, + "early_stop_bytes": 6442450944, + "cpu_quota_percent": 100, + "cpu_affinity": [10], + "task_ceiling": 192, + "payload_resource_observation": { + "phase": "payload-exited-observer-alive", + "ownership_token": "rea-efficiency-b67d2b674f00447095cf3003ae4d15f0.scope", + "payload_exit_code": 0, + "observer_pid": 3878266, + "remaining_processes": [3878266], + "payload_cleanup_verified": true, + "cpu_stat": { + "usage_usec": 98956939, + "user_usec": 90005456, + "system_usec": 8951483, + "nr_periods": 999, + "nr_throttled": 32, + "throttled_usec": 12931, + "nr_bursts": 0, + "burst_usec": 0 + }, + "memory_peak_bytes": 4237795328, + "control_group": "user.slice/user-1004.slice/user@1004.service/app.slice/rea-efficiency-b67d2b674f00447095cf3003ae4d15f0.scope", + "resource_events": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + } + }, + "payload_resource_observation_verified": true, + "payload_observation_scope": "Counters at payload exit while observer remains alive; not final whole-scope counters.", + "payload_observer_sources_sha256": { + "resource-run.py": "7728862171415e7dee2c11af2ead2b9a54265fccaf82a7113956d36b29065e33", + "resource_payload.py": "6f089e645459eba80c5d73e39c21606c78173a44daf49fabc3b88c4631ce51c6" + } + }, + "scope_poststate": "ActiveState=inactive\nSubState=dead" + }, + "docs_check": { + "command": "npm run docs:check", + "status": "passed; six tasks and five README consistency cases", + "log_sha256": "bf720204a1a83d664efbb51b83dda4cc62a65223f2f89c861d4772e8117d53ef", + "resource": { + "resource_scope": "rea-efficiency-9993431bac424a8194845b1ebae5ed80.scope", + "elapsed_seconds": 10.855, + "abort": null, + "hard_memory_bytes": 8589934592, + "early_stop_bytes": 6442450944, + "cpu_quota_percent": 100, + "cpu_affinity": [10], + "task_ceiling": 192, + "payload_resource_observation": { + "phase": "payload-exited-observer-alive", + "ownership_token": "rea-efficiency-9993431bac424a8194845b1ebae5ed80.scope", + "payload_exit_code": 0, + "observer_pid": 3880317, + "remaining_processes": [3880317], + "payload_cleanup_verified": true, + "cpu_stat": { + "usage_usec": 10387008, + "user_usec": 7896610, + "system_usec": 2490397, + "nr_periods": 107, + "nr_throttled": 2, + "throttled_usec": 22, + "nr_bursts": 0, + "burst_usec": 0 + }, + "memory_peak_bytes": 429584384, + "control_group": "user.slice/user-1004.slice/user@1004.service/app.slice/rea-efficiency-9993431bac424a8194845b1ebae5ed80.scope", + "resource_events": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + } + }, + "payload_resource_observation_verified": true, + "payload_observation_scope": "Counters at payload exit while observer remains alive; not final whole-scope counters.", + "payload_observer_sources_sha256": { + "resource-run.py": "7728862171415e7dee2c11af2ead2b9a54265fccaf82a7113956d36b29065e33", + "resource_payload.py": "6f089e645459eba80c5d73e39c21606c78173a44daf49fabc3b88c4631ce51c6" + } + }, + "scope_poststate": "ActiveState=inactive\nSubState=dead" + } + }, + "array_species_followup": { + "reviewed_head": "e2cf2d89bbf99a7509ccb37dc2ac53a7558396a1", + "review_thread": "PRRT_kwDOSB6hVc6tSXUE", + "source_pins": { + "candidate_revision": "3141ac3f4b53578a3461a2237e2564083f5183ed", + "baseline_revision": "180ba03bfddd5916251e81e97a500b1ede7a9eea", + "source_sha256": { + "scripts/electron-active-hook.cjs": "3296c01118d9f40af3aaa50ab06e8873e750811d260fc2d18977649df907a8fe", + "tests/boundary/browser/electronActiveHook.test.ts": "ded015d5e160185e2a86028b02a018ec971909190d2feee9b1e41630e5580bb3", + "experiments/analysis-efficiency/drawio-electron-lifecycle.mjs": "1423a472f58fc81383061bf695059550276b898c780e91bb47052797b25e0ce6", + "experiments/analysis-efficiency/drawio-electron-lifecycle-oracles.py": "c98dd2abf16703744aa32edc5dd12b0f4eec121d40a4aaa387ef580b046032e2" + }, + "verifier_and_controlled_oracle_unchanged_from_4a9a01f7a": true + }, + "root_cause": "Slicing a rest Array invokes target Array species before dispatch; snapshots and grouped recording also used species-aware map/filter operations at the same evidence owner.", + "structural_fix": "3141ac3f4 records from real invocation Arguments frames while preserving native callback Array/Arguments input types. Group validation happens during existing loops without allocating a filtered Array. Snapshot mapping uses captured Array.from, including direct Map iteration without intermediate spread Arrays. No application species constructor is consulted by these owner paths.", + "dispatch_red": { + "log_name": "rea-4390-array-species-red.log", + "log_sha256": "0016d3a6c20e2622ffd836077ab70d78f935011b20f9b147e220f69f3fdfd53c", + "resource": { + "resource_scope": "rea-efficiency-066e45c2da3248169298bc3ddd15db79.scope", + "elapsed_seconds": 4.595, + "abort": null, + "hard_memory_bytes": 8589934592, + "early_stop_bytes": 6442450944, + "cpu_quota_percent": 100, + "cpu_affinity": [10], + "task_ceiling": 192, + "payload_resource_observation": { + "phase": "payload-exited-observer-alive", + "ownership_token": "rea-efficiency-066e45c2da3248169298bc3ddd15db79.scope", + "payload_exit_code": 1, + "observer_pid": 3899293, + "remaining_processes": [3899293], + "payload_cleanup_verified": true, + "cpu_stat": { + "usage_usec": 4327113, + "user_usec": 2895380, + "system_usec": 1431733, + "nr_periods": 45, + "nr_throttled": 2, + "throttled_usec": 1912, + "nr_bursts": 0, + "burst_usec": 0 + }, + "memory_peak_bytes": 322740224, + "control_group": "user.slice/user-1004.slice/user@1004.service/app.slice/rea-efficiency-066e45c2da3248169298bc3ddd15db79.scope", + "resource_events": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + } + }, + "payload_resource_observation_verified": true, + "payload_observation_scope": "Counters at payload exit while observer remains alive; not final whole-scope counters.", + "payload_observer_sources_sha256": { + "resource-run.py": "7728862171415e7dee2c11af2ead2b9a54265fccaf82a7113956d36b29065e33", + "resource_payload.py": "6f089e645459eba80c5d73e39c21606c78173a44daf49fabc3b88c4631ce51c6" + } + }, + "scope_poststate": "ActiveState=inactive\nSubState=dead", + "status": "40 passed /1 failed of41: on dispatch fails; once dispatch itself already succeeds." + }, + "dispatch_and_snapshot_red": { + "log_name": "rea-4390-array-species-snapshot-red.log", + "log_sha256": "ffd19d94ba2a3837d27a897aae0f18b30622776e9558dfb606dacc774a6e4aaf", + "resource": { + "resource_scope": "rea-efficiency-c9461402f1b849ef9e63260dfdee7b2e.scope", + "elapsed_seconds": 4.888, + "abort": null, + "hard_memory_bytes": 8589934592, + "early_stop_bytes": 6442450944, + "cpu_quota_percent": 100, + "cpu_affinity": [10], + "task_ceiling": 192, + "payload_resource_observation": { + "phase": "payload-exited-observer-alive", + "ownership_token": "rea-efficiency-c9461402f1b849ef9e63260dfdee7b2e.scope", + "payload_exit_code": 1, + "observer_pid": 3901352, + "remaining_processes": [3901352], + "payload_cleanup_verified": true, + "cpu_stat": { + "usage_usec": 4422352, + "user_usec": 2924277, + "system_usec": 1498075, + "nr_periods": 47, + "nr_throttled": 3, + "throttled_usec": 1729, + "nr_bursts": 0, + "burst_usec": 0 + }, + "memory_peak_bytes": 334934016, + "control_group": "user.slice/user-1004.slice/user@1004.service/app.slice/rea-efficiency-c9461402f1b849ef9e63260dfdee7b2e.scope", + "resource_events": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + } + }, + "payload_resource_observation_verified": true, + "payload_observation_scope": "Counters at payload exit while observer remains alive; not final whole-scope counters.", + "payload_observer_sources_sha256": { + "resource-run.py": "7728862171415e7dee2c11af2ead2b9a54265fccaf82a7113956d36b29065e33", + "resource_payload.py": "6f089e645459eba80c5d73e39c21606c78173a44daf49fabc3b88c4631ce51c6" + } + }, + "scope_poststate": "ActiveState=inactive\nSubState=dead", + "status": "39 passed /2 failed of41: on dispatch and once live snapshot expose the two owner stages." + }, + "green": { + "log_name": "rea-4390-array-species-green.log", + "log_sha256": "11103ab3054b0d3c1182d8bd58a562c0688386fa66c4b73c9080151df3d96466", + "resource": { + "resource_scope": "rea-efficiency-4f4deba4282e4f1a83f9a4789ff55f76.scope", + "elapsed_seconds": 4.324, + "abort": null, + "hard_memory_bytes": 8589934592, + "early_stop_bytes": 6442450944, + "cpu_quota_percent": 100, + "cpu_affinity": [10], + "task_ceiling": 192, + "payload_resource_observation": { + "phase": "payload-exited-observer-alive", + "ownership_token": "rea-efficiency-4f4deba4282e4f1a83f9a4789ff55f76.scope", + "payload_exit_code": 0, + "observer_pid": 3902047, + "remaining_processes": [3902047], + "payload_cleanup_verified": true, + "cpu_stat": { + "usage_usec": 4129103, + "user_usec": 2833788, + "system_usec": 1295314, + "nr_periods": 42, + "nr_throttled": 5, + "throttled_usec": 4018, + "nr_bursts": 0, + "burst_usec": 0 + }, + "memory_peak_bytes": 310116352, + "control_group": "user.slice/user-1004.slice/user@1004.service/app.slice/rea-efficiency-4f4deba4282e4f1a83f9a4789ff55f76.scope", + "resource_events": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + } + }, + "payload_resource_observation_verified": true, + "payload_observation_scope": "Counters at payload exit while observer remains alive; not final whole-scope counters.", + "payload_observer_sources_sha256": { + "resource-run.py": "7728862171415e7dee2c11af2ead2b9a54265fccaf82a7113956d36b29065e33", + "resource_payload.py": "6f089e645459eba80c5d73e39c21606c78173a44daf49fabc3b88c4631ce51c6" + } + }, + "scope_poststate": "ActiveState=inactive\nSubState=dead", + "status": "All41 pass. Both on/once callbacks execute and their evidence is readable while the target throwing species getter remains installed, with zero getter reads." + }, + "real_app": { + "log_name": "rea-4390-drawio-array-species-final.log", + "log_sha256": "42f64550c5b2a3c4c6fdb4f949a12db65b71fd1100324359dfbd5ef37d2df571", + "resource": { + "resource_scope": "rea-efficiency-a833c8522488469c8a4e92019ef9f44f.scope", + "elapsed_seconds": 12.779, + "abort": null, + "hard_memory_bytes": 8589934592, + "early_stop_bytes": 6442450944, + "cpu_quota_percent": 100, + "cpu_affinity": [10], + "task_ceiling": 192, + "payload_resource_observation": { + "phase": "payload-exited-observer-alive", + "ownership_token": "rea-efficiency-a833c8522488469c8a4e92019ef9f44f.scope", + "payload_exit_code": 1, + "observer_pid": 3904044, + "remaining_processes": [3904044, 3904056, 3904230, 3904232], + "payload_cleanup_verified": false, + "cpu_stat": { + "usage_usec": 7099377, + "user_usec": 5201849, + "system_usec": 1897528, + "nr_periods": 126, + "nr_throttled": 5, + "throttled_usec": 4104, + "nr_bursts": 0, + "burst_usec": 0 + }, + "memory_peak_bytes": 671703040, + "control_group": "user.slice/user-1004.slice/user@1004.service/app.slice/rea-efficiency-a833c8522488469c8a4e92019ef9f44f.scope", + "resource_events": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + } + }, + "payload_resource_observation_verified": false, + "payload_observation_scope": "Counters at payload exit while observer remains alive; not final whole-scope counters.", + "payload_observer_sources_sha256": { + "resource-run.py": "7728862171415e7dee2c11af2ead2b9a54265fccaf82a7113956d36b29065e33", + "resource_payload.py": "6f089e645459eba80c5d73e39c21606c78173a44daf49fabc3b88c4631ce51c6" + } + }, + "scope_poststate": "ActiveState=inactive\nSubState=dead", + "selected_result": { + "elapsed_ms": 11251.109197, + "deadline_triggered": false, + "selected_shape": ".geSidebar a.geItem:visible >> nth=0", + "before": { + "title": "Untitled Diagram - draw.io", + "invalid_file_dialog": true, + "unsaved_changes": false, + "diagram_svg_elements": 0, + "cells": 0 + }, + "after": { + "title": "Untitled Diagram - draw.io", + "invalid_file_dialog": false, + "unsaved_changes": true, + "diagram_svg_elements": 2, + "cells": 0 + }, + "actions_completed": true, + "shape_inserted": true, + "provider_ok": false, + "cleanup_verified": false, + "reason": "cleanup_failed", + "actions": [ + { + "step_id": "dismiss-invalid-file", + "kind": "click", + "window_index": 0, + "target": "window:0", + "elapsed_ms": 90, + "status": "completed", + "error": null + }, + { + "step_id": "insert-shape", + "kind": "click", + "window_index": 0, + "target": "window:0", + "elapsed_ms": 129, + "status": "completed", + "error": null + }, + { + "step_id": "settle-renderer", + "kind": "wait", + "window_index": 0, + "target": "window:0", + "elapsed_ms": 501, + "status": "completed", + "error": null + } + ], + "workflow_status": "partial-or-failed" + }, + "ipc_observed": 7, + "coverage": { + "status": "partial_attach", + "observed_event_families": [ + "app-lifecycle", + "ipc", + "navigation", + "preload-configuration", + "web-contents-lifecycle", + "window-lifecycle" + ], + "unavailable_event_families": [ + "download", + "error", + "native-addon", + "permission", + "popup-attempt", + "preload", + "process-lifecycle", + "protocol", + "renderer-ipc", + "shell-attempt", + "updater" + ], + "observed_roles": ["browser", "main", "web_contents", "window"], + "pre_capture_activity": "unavailable" + }, + "retention": { + "budget_bytes": 16777216, + "estimated_retained_bytes": 202114, + "event_serialized_byte_upper_bound": 48214, + "retained": 109, + "dropped": 0, + "dropped_event_families": [], + "dropped_event_roles": [], + "observed_ipc": 7, + "dropped_ipc": 0, + "observed_runtime": 102, + "dropped_runtime": 0 + } + }, + "unchanged_verifier_oracles": "Previously passing five controlled cases reused; MJS/PY helper hashes are identical to4a9a01f7a. They were not rerun for this CJS-only change.", + "limits": [ + "The native species controls do not establish that draw.io customizes Array species. Real draw.io tests ordinary application compatibility and operation retention.", + "Real cleanup remains failed; no completed-task speedup/token/context/vendor-runtime/MCP-wire claim.", + "Raw wrapper metadata mutation4513 remains explicit and unfixed.", + "Payload-exit counters are not final whole-scope counters; post-scope inactivity is not provider cleanup." + ], + "all_native_dispatch_paths": { + "status": "42 passed /42: on single, on multiple and once all execute and expose retained evidence under the throwing species getter. Only the test case expanded; production/verifier hashes are unchanged.", + "validation_pins": { + "validation_revision": "a8ac5821e3c0fe787479b7d00ffb0e2ea0d8c1cb", + "production_revision": "3141ac3f4b53578a3461a2237e2564083f5183ed", + "source_sha256": { + "scripts/electron-active-hook.cjs": "3296c01118d9f40af3aaa50ab06e8873e750811d260fc2d18977649df907a8fe", + "tests/boundary/browser/electronActiveHook.test.ts": "217b9d95404dee6d2eff3404cd47789f8f18b0b77bd94ee1ad415c00bae6df9c", + "experiments/analysis-efficiency/drawio-electron-lifecycle.mjs": "1423a472f58fc81383061bf695059550276b898c780e91bb47052797b25e0ce6", + "experiments/analysis-efficiency/drawio-electron-lifecycle-oracles.py": "c98dd2abf16703744aa32edc5dd12b0f4eec121d40a4aaa387ef580b046032e2" + }, + "production_and_verifier_sources_unchanged": true + }, + "log_sha256": "4c8e0e2663deda24bd3999b78770b81bb61da45539cb16f1320d59c076c28641", + "resource": { + "resource_scope": "rea-efficiency-266423bf25464f9ca6df7df66406cf6c.scope", + "elapsed_seconds": 8.043, + "abort": null, + "hard_memory_bytes": 8589934592, + "early_stop_bytes": 6442450944, + "cpu_quota_percent": 100, + "cpu_affinity": [10], + "task_ceiling": 192, + "payload_resource_observation": { + "phase": "payload-exited-observer-alive", + "ownership_token": "rea-efficiency-266423bf25464f9ca6df7df66406cf6c.scope", + "payload_exit_code": 0, + "observer_pid": 3910989, + "remaining_processes": [3910989], + "payload_cleanup_verified": true, + "cpu_stat": { + "usage_usec": 7744066, + "user_usec": 4702352, + "system_usec": 3041713, + "nr_periods": 79, + "nr_throttled": 0, + "throttled_usec": 0, + "nr_bursts": 0, + "burst_usec": 0 + }, + "memory_peak_bytes": 319320064, + "control_group": "user.slice/user-1004.slice/user@1004.service/app.slice/rea-efficiency-266423bf25464f9ca6df7df66406cf6c.scope", + "resource_events": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + } + }, + "payload_resource_observation_verified": true, + "payload_observation_scope": "Counters at payload exit while observer remains alive; not final whole-scope counters.", + "payload_observer_sources_sha256": { + "resource-run.py": "7728862171415e7dee2c11af2ead2b9a54265fccaf82a7113956d36b29065e33", + "resource_payload.py": "6f089e645459eba80c5d73e39c21606c78173a44daf49fabc3b88c4631ce51c6" + } + }, + "scope_poststate": "ActiveState=inactive\nSubState=dead" + }, + "real_idle_negative_control": { + "command_difference": "Omit --selector; keep --dismiss-error. Same production/verifier hashes, artifact/runtime, fresh profile and 500ms declared wait.", + "validation_pins": { + "validation_revision": "a8ac5821e3c0fe787479b7d00ffb0e2ea0d8c1cb", + "production_revision": "3141ac3f4b53578a3461a2237e2564083f5183ed", + "source_sha256": { + "scripts/electron-active-hook.cjs": "3296c01118d9f40af3aaa50ab06e8873e750811d260fc2d18977649df907a8fe", + "tests/boundary/browser/electronActiveHook.test.ts": "217b9d95404dee6d2eff3404cd47789f8f18b0b77bd94ee1ad415c00bae6df9c", + "experiments/analysis-efficiency/drawio-electron-lifecycle.mjs": "1423a472f58fc81383061bf695059550276b898c780e91bb47052797b25e0ce6", + "experiments/analysis-efficiency/drawio-electron-lifecycle-oracles.py": "c98dd2abf16703744aa32edc5dd12b0f4eec121d40a4aaa387ef580b046032e2" + }, + "production_and_verifier_sources_unchanged": true + }, + "selected_result": { + "elapsed_ms": 6230.357785, + "deadline_triggered": false, + "selected_shape": null, + "before": { + "title": "Untitled Diagram - draw.io", + "invalid_file_dialog": true, + "unsaved_changes": false, + "diagram_svg_elements": 0, + "cells": 0 + }, + "after": { + "title": "Untitled Diagram - draw.io", + "invalid_file_dialog": false, + "unsaved_changes": false, + "diagram_svg_elements": 0, + "cells": 0 + }, + "actions_completed": true, + "shape_inserted": null, + "provider_ok": false, + "cleanup_verified": false, + "reason": "cleanup_failed", + "actions": [ + { + "step_id": "dismiss-invalid-file", + "kind": "click", + "window_index": 0, + "target": "window:0", + "elapsed_ms": 125, + "status": "completed", + "error": null + }, + { + "step_id": "settle-renderer", + "kind": "wait", + "window_index": 0, + "target": "window:0", + "elapsed_ms": 502, + "status": "completed", + "error": null + } + ], + "workflow_status": "partial-or-failed" + }, + "ipc_observed": 7, + "functional_negative_oracle_passed": true, + "limits": "Operation-phase contrast only; task differs and both provider cleanups fail. Not a paired performance trial or proof of the mxGraph model cell count.", + "log_sha256": "3c8bf2dc9c0ab357d02b5f21b4bc9d220c52b636f96e8e529f1849342250ffc0", + "resource": { + "resource_scope": "rea-efficiency-72608ec90eab4733813396a0ce8bb6ec.scope", + "elapsed_seconds": 7.597, + "abort": null, + "hard_memory_bytes": 8589934592, + "early_stop_bytes": 6442450944, + "cpu_quota_percent": 100, + "cpu_affinity": [10], + "task_ceiling": 192, + "payload_resource_observation": { + "phase": "payload-exited-observer-alive", + "ownership_token": "rea-efficiency-72608ec90eab4733813396a0ce8bb6ec.scope", + "payload_exit_code": 1, + "observer_pid": 3912639, + "remaining_processes": [3912639, 3912661], + "payload_cleanup_verified": false, + "cpu_stat": { + "usage_usec": 6291201, + "user_usec": 4510028, + "system_usec": 1781173, + "nr_periods": 74, + "nr_throttled": 0, + "throttled_usec": 0, + "nr_bursts": 0, + "burst_usec": 0 + }, + "memory_peak_bytes": 605532160, + "control_group": "user.slice/user-1004.slice/user@1004.service/app.slice/rea-efficiency-72608ec90eab4733813396a0ce8bb6ec.scope", + "resource_events": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + } + }, + "payload_resource_observation_verified": false, + "payload_observation_scope": "Counters at payload exit while observer remains alive; not final whole-scope counters.", + "payload_observer_sources_sha256": { + "resource-run.py": "7728862171415e7dee2c11af2ead2b9a54265fccaf82a7113956d36b29065e33", + "resource_payload.py": "6f089e645459eba80c5d73e39c21606c78173a44daf49fabc3b88c4631ce51c6" + } + }, + "scope_poststate": "ActiveState=inactive\nSubState=dead" + }, + "final_static": { + "command": "npm run check:fast", + "status": "passed; two uncached tasks after all42 native cases", + "log_sha256": "4d054110e38de7456fb654e9e2cd21b252d8cb01e345dade264d2609d1538c43", + "resource": { + "resource_scope": "rea-efficiency-7723ecfbda554e388c116b46c3573c67.scope", + "elapsed_seconds": 94.234, + "abort": null, + "hard_memory_bytes": 8589934592, + "early_stop_bytes": 6442450944, + "cpu_quota_percent": 100, + "cpu_affinity": [10], + "task_ceiling": 192, + "payload_resource_observation": { + "phase": "payload-exited-observer-alive", + "ownership_token": "rea-efficiency-7723ecfbda554e388c116b46c3573c67.scope", + "payload_exit_code": 0, + "observer_pid": 3913373, + "remaining_processes": [3913373], + "payload_cleanup_verified": true, + "cpu_stat": { + "usage_usec": 93308800, + "user_usec": 88792871, + "system_usec": 4515928, + "nr_periods": 940, + "nr_throttled": 33, + "throttled_usec": 11683, + "nr_bursts": 0, + "burst_usec": 0 + }, + "memory_peak_bytes": 4406538240, + "control_group": "user.slice/user-1004.slice/user@1004.service/app.slice/rea-efficiency-7723ecfbda554e388c116b46c3573c67.scope", + "resource_events": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + } + }, + "payload_resource_observation_verified": true, + "payload_observation_scope": "Counters at payload exit while observer remains alive; not final whole-scope counters.", + "payload_observer_sources_sha256": { + "resource-run.py": "7728862171415e7dee2c11af2ead2b9a54265fccaf82a7113956d36b29065e33", + "resource_payload.py": "6f089e645459eba80c5d73e39c21606c78173a44daf49fabc3b88c4631ce51c6" + } + }, + "scope_poststate": "ActiveState=inactive\nSubState=dead" + }, + "docs_check": { + "command": "npm run docs:check", + "status": "passed; six tasks and five README consistency cases", + "log_sha256": "b8800391f1b2cddfcc20d0333f076b0af16689cf5dcdbf073ca8a4582f56b0d9", + "resource": { + "resource_scope": "rea-efficiency-e8c3f9d9768744abbecf9c2f362e9c35.scope", + "elapsed_seconds": 9.671, + "abort": null, + "hard_memory_bytes": 8589934592, + "early_stop_bytes": 6442450944, + "cpu_quota_percent": 100, + "cpu_affinity": [10], + "task_ceiling": 192, + "payload_resource_observation": { + "phase": "payload-exited-observer-alive", + "ownership_token": "rea-efficiency-e8c3f9d9768744abbecf9c2f362e9c35.scope", + "payload_exit_code": 0, + "observer_pid": 3916338, + "remaining_processes": [3916338], + "payload_cleanup_verified": true, + "cpu_stat": { + "usage_usec": 9243276, + "user_usec": 7570971, + "system_usec": 1672304, + "nr_periods": 95, + "nr_throttled": 6, + "throttled_usec": 725, + "nr_bursts": 0, + "burst_usec": 0 + }, + "memory_peak_bytes": 475615232, + "control_group": "user.slice/user-1004.slice/user@1004.service/app.slice/rea-efficiency-e8c3f9d9768744abbecf9c2f362e9c35.scope", + "resource_events": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + } + }, + "payload_resource_observation_verified": true, + "payload_observation_scope": "Counters at payload exit while observer remains alive; not final whole-scope counters.", + "payload_observer_sources_sha256": { + "resource-run.py": "7728862171415e7dee2c11af2ead2b9a54265fccaf82a7113956d36b29065e33", + "resource_payload.py": "6f089e645459eba80c5d73e39c21606c78173a44daf49fabc3b88c4631ce51c6" + } + }, + "scope_poststate": "ActiveState=inactive\nSubState=dead" + } + }, + "opaque_apply_and_iterator_followup": { + "source_pins": { + "source_revision": "d608123f192e5bb50e67b21cc9647e1191b56154", + "recorded_utc": "2026-10-16T17:54:58.174648+00:00", + "source_sha256": { + "scripts/electron-active-hook.cjs": "48e1eeadf8eac681f1e893cb9d6a85f70ea5e7e02b1ab5539b89e6ad4416c151", + "src/domain/javascript/electronActiveObservation.ts": "868d126480785898008a6d7de75cbcbb273eca1185d27c0decb72bdbd5a3630c", + "src/browser/PlaywrightElectronActiveProjection.ts": "89024969646f14db41317ca6e2c0d783e9d0e3cc875ffb8ffa3e50546691c61b", + "src/browser/PlaywrightElectronActiveProjection.test.ts": "75140495a9eef03f4b53291cfdd942db700308f9c7d96b66c097276168ebf51c", + "tests/boundary/browser/electronActiveHook.test.ts": "0fdecfbd5f48a2d67cdee92e90d7a559e497aa5208fb1f07f0192d80bda78bea", + "tests/boundary/mcp/electronMcp.test.ts": "0f180f3d060506228335421f21d5023d8ae54546de6856c507ba1f737ff58cfd", + "experiments/analysis-efficiency/drawio-electron-lifecycle.mjs": "1423a472f58fc81383061bf695059550276b898c780e91bb47052797b25e0ce6", + "experiments/analysis-efficiency/drawio-electron-lifecycle-oracles.py": "c98dd2abf16703744aa32edc5dd12b0f4eec121d40a4aaa387ef580b046032e2" + }, + "verifier_and_five_oracle_sources_unchanged": true, + "compiled_runtime_root": "/home/yann/yann/rea-electron-ipc-listener-4390-20261016", + "artifact_sha256": { + "app.asar": "e64b9650c0b8241ca9a7d33b56386a385bceee63270cec701d3a467347b697e6" + }, + "scope": "Frozen before current-source real trial; prior measurements keep their original pins.", + "compiled_sha256": { + "dist/browser/PlaywrightElectronActiveProjection.js": "93184515d326d51b080cd52d616f79472727341ea5ad441a0c2eeaa94ac42ffc", + "dist/domain/javascript/electronActiveObservation.js": "21d8401adde0eacfd018c834b8e0bcc0ce7cb907b288a02ff7712a4b0e94d13d" + }, + "selected_electron_runtime": { + "executable": "/tmp/rea-4390-realapps-20261016/electron-44.7.0/electron", + "executable_sha256": "10a14d05c6ff4f94075cfb3eeb6ed6571be33ebcc08cbd675b5ce9ff84706564", + "version": "44.7.0", + "archive_sha256": "3ae7d5bdad61c664486c6ab61361dd084d064c8c08af8d13a687096e18ce555a" + }, + "runtime_pin_recorded_before_trial_utc": "2026-10-16T17:57:17.849958+00:00" + }, + "scope": "Actual fresh review findings: opaque on-listener custom apply and retained snapshot iterators. Typed gaps preserve unknown callback execution and arguments; no fabricated captured IPC. No completed-task acceleration/token claim.", + "negative_control": { + "production_revision": "84d9cf5db1a18a01670f4cdc6ea87039163f1ff1", + "result": "42 passed /6 failed; four exact custom-apply inputs and two snapshot iterators", + "controlled_native_regressions_not_real_app_proof": true, + "log_sha256": "28a496681c9c4372cf2a50810d3e9e67229ca07d291ad3f194d2b06b4e9f9fc7", + "resource": { + "resource_scope": "rea-efficiency-1b2b7f60a41b4fc5829c2ca272bfb81d.scope", + "elapsed_seconds": 9.404, + "abort": null, + "hard_memory_bytes": 8589934592, + "early_stop_bytes": 6442450944, + "cpu_quota_percent": 100, + "cpu_affinity": [10], + "task_ceiling": 192, + "payload_resource_observation": { + "phase": "payload-exited-observer-alive", + "ownership_token": "rea-efficiency-1b2b7f60a41b4fc5829c2ca272bfb81d.scope", + "payload_exit_code": 1, + "observer_pid": 3951696, + "remaining_processes": [3951696], + "payload_cleanup_verified": true, + "cpu_stat": { + "usage_usec": 8723337, + "user_usec": 5506777, + "system_usec": 3216559, + "nr_periods": 92, + "nr_throttled": 5, + "throttled_usec": 3941, + "nr_bursts": 0, + "burst_usec": 0 + }, + "memory_peak_bytes": 355504128, + "control_group": "user.slice/user-1004.slice/user@1004.service/app.slice/rea-efficiency-1b2b7f60a41b4fc5829c2ca272bfb81d.scope", + "resource_events": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + } + }, + "payload_resource_observation_verified": true, + "payload_observation_scope": "Counters at payload exit while observer remains alive; not final whole-scope counters.", + "payload_observer_sources_sha256": { + "resource-run.py": "7728862171415e7dee2c11af2ead2b9a54265fccaf82a7113956d36b29065e33", + "resource_payload.py": "6f089e645459eba80c5d73e39c21606c78173a44daf49fabc3b88c4631ce51c6" + } + }, + "scope_poststate": "ActiveState=inactive\nSubState=dead" + }, + "gates": { + "hook_projection_actions": { + "log_sha256": "1579f86122acc6661a369f138950f3ac256e2231eaae531b27522536d463afa5", + "resource": { + "resource_scope": "rea-efficiency-03988332ea504d72a8298258610fc385.scope", + "elapsed_seconds": 44.198, + "abort": null, + "hard_memory_bytes": 8589934592, + "early_stop_bytes": 6442450944, + "cpu_quota_percent": 100, + "cpu_affinity": [10], + "task_ceiling": 192, + "payload_resource_observation": { + "phase": "payload-exited-observer-alive", + "ownership_token": "rea-efficiency-03988332ea504d72a8298258610fc385.scope", + "payload_exit_code": 0, + "observer_pid": 3958710, + "remaining_processes": [3958710], + "payload_cleanup_verified": true, + "cpu_stat": { + "usage_usec": 42969090, + "user_usec": 38494634, + "system_usec": 4474456, + "nr_periods": 441, + "nr_throttled": 13, + "throttled_usec": 8359, + "nr_bursts": 0, + "burst_usec": 0 + }, + "memory_peak_bytes": 3355848704, + "control_group": "user.slice/user-1004.slice/user@1004.service/app.slice/rea-efficiency-03988332ea504d72a8298258610fc385.scope", + "resource_events": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + } + }, + "payload_resource_observation_verified": true, + "payload_observation_scope": "Counters at payload exit while observer remains alive; not final whole-scope counters.", + "payload_observer_sources_sha256": { + "resource-run.py": "7728862171415e7dee2c11af2ead2b9a54265fccaf82a7113956d36b29065e33", + "resource_payload.py": "6f089e645459eba80c5d73e39c21606c78173a44daf49fabc3b88c4631ce51c6" + } + }, + "scope_poststate": "ActiveState=inactive\nSubState=dead", + "result": "68 passed:48 boundary,4 projection,16 actions" + }, + "mcp_and_converted_catalog_schemas": { + "log_sha256": "e267a532413380ff7aee3a0985aef624c4016bdb0ee673a62f95e59485008bc0", + "resource": { + "resource_scope": "rea-efficiency-1c4e9d148c464818b1370a421a5a3b9f.scope", + "elapsed_seconds": 57.074, + "abort": null, + "hard_memory_bytes": 8589934592, + "early_stop_bytes": 6442450944, + "cpu_quota_percent": 100, + "cpu_affinity": [10], + "task_ceiling": 192, + "payload_resource_observation": { + "phase": "payload-exited-observer-alive", + "ownership_token": "rea-efficiency-1c4e9d148c464818b1370a421a5a3b9f.scope", + "payload_exit_code": 0, + "observer_pid": 3967182, + "remaining_processes": [3967182], + "payload_cleanup_verified": true, + "cpu_stat": { + "usage_usec": 56116219, + "user_usec": 50525946, + "system_usec": 5590272, + "nr_periods": 568, + "nr_throttled": 32, + "throttled_usec": 12410, + "nr_bursts": 0, + "burst_usec": 0 + }, + "memory_peak_bytes": 2009497600, + "control_group": "user.slice/user-1004.slice/user@1004.service/app.slice/rea-efficiency-1c4e9d148c464818b1370a421a5a3b9f.scope", + "resource_events": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + } + }, + "payload_resource_observation_verified": true, + "payload_observation_scope": "Counters at payload exit while observer remains alive; not final whole-scope counters.", + "payload_observer_sources_sha256": { + "resource-run.py": "7728862171415e7dee2c11af2ead2b9a54265fccaf82a7113956d36b29065e33", + "resource_payload.py": "6f089e645459eba80c5d73e39c21606c78173a44daf49fabc3b88c4631ce51c6" + } + }, + "scope_poststate": "ActiveState=inactive\nSubState=dead", + "result": "23 passed; actual SDK converted output validated by Ajv2020, simulated provider" + }, + "intermediate_static_failure": { + "log_sha256": "8d1bd59b6581b266bb3a989a9f11af1a14c62020016a869dd96d3cc1df745fab", + "resource": { + "resource_scope": "rea-efficiency-fdd2d21857bf4ee48a1e3f47f92c3b77.scope", + "elapsed_seconds": 94.57, + "abort": null, + "hard_memory_bytes": 8589934592, + "early_stop_bytes": 6442450944, + "cpu_quota_percent": 100, + "cpu_affinity": [10], + "task_ceiling": 192, + "payload_resource_observation": { + "phase": "payload-exited-observer-alive", + "ownership_token": "rea-efficiency-fdd2d21857bf4ee48a1e3f47f92c3b77.scope", + "payload_exit_code": 1, + "observer_pid": 3968050, + "remaining_processes": [3968050], + "payload_cleanup_verified": true, + "cpu_stat": { + "usage_usec": 93730783, + "user_usec": 89229194, + "system_usec": 4501588, + "nr_periods": 944, + "nr_throttled": 73, + "throttled_usec": 43222, + "nr_bursts": 0, + "burst_usec": 0 + }, + "memory_peak_bytes": 4284182528, + "control_group": "user.slice/user-1004.slice/user@1004.service/app.slice/rea-efficiency-fdd2d21857bf4ee48a1e3f47f92c3b77.scope", + "resource_events": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + } + }, + "payload_resource_observation_verified": true, + "payload_observation_scope": "Counters at payload exit while observer remains alive; not final whole-scope counters.", + "payload_observer_sources_sha256": { + "resource-run.py": "7728862171415e7dee2c11af2ead2b9a54265fccaf82a7113956d36b29065e33", + "resource_payload.py": "6f089e645459eba80c5d73e39c21606c78173a44daf49fabc3b88c4631ce51c6" + } + }, + "scope_poststate": "ActiveState=inactive\nSubState=dead", + "result": "failed: SDK schema type incompatible with Ajv in new test; corrected through parsed JSON object boundary" + }, + "final_client_projection": { + "log_sha256": "42545b84a77c5f8795e4bc71d0c87bf2c2288e771e29de51caa1e38c65caee0a", + "resource": { + "resource_scope": "rea-efficiency-eebee3db38534f458b53bc9fb0535ac5.scope", + "elapsed_seconds": 23.984, + "abort": null, + "hard_memory_bytes": 8589934592, + "early_stop_bytes": 6442450944, + "cpu_quota_percent": 100, + "cpu_affinity": [10], + "task_ceiling": 192, + "payload_resource_observation": { + "phase": "payload-exited-observer-alive", + "ownership_token": "rea-efficiency-eebee3db38534f458b53bc9fb0535ac5.scope", + "payload_exit_code": 0, + "observer_pid": 3969628, + "remaining_processes": [3969628], + "payload_cleanup_verified": true, + "cpu_stat": { + "usage_usec": 23539018, + "user_usec": 20469774, + "system_usec": 3069244, + "nr_periods": 238, + "nr_throttled": 6, + "throttled_usec": 2012, + "nr_bursts": 0, + "burst_usec": 0 + }, + "memory_peak_bytes": 1281675264, + "control_group": "user.slice/user-1004.slice/user@1004.service/app.slice/rea-efficiency-eebee3db38534f458b53bc9fb0535ac5.scope", + "resource_events": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + } + }, + "payload_resource_observation_verified": true, + "payload_observation_scope": "Counters at payload exit while observer remains alive; not final whole-scope counters.", + "payload_observer_sources_sha256": { + "resource-run.py": "7728862171415e7dee2c11af2ead2b9a54265fccaf82a7113956d36b29065e33", + "resource_payload.py": "6f089e645459eba80c5d73e39c21606c78173a44daf49fabc3b88c4631ce51c6" + } + }, + "scope_poststate": "ActiveState=inactive\nSubState=dead", + "result": "12 passed:8 MCP,4 projection" + }, + "final_static": { + "log_sha256": "e3c132d97aaa2804c4d4fb161cc427688e017805b97662cdab8538c93b2d5766", + "resource": { + "resource_scope": "rea-efficiency-1413bb27eec44d0bbe35a75ebed50a1e.scope", + "elapsed_seconds": 94.646, + "abort": null, + "hard_memory_bytes": 8589934592, + "early_stop_bytes": 6442450944, + "cpu_quota_percent": 100, + "cpu_affinity": [10], + "task_ceiling": 192, + "payload_resource_observation": { + "phase": "payload-exited-observer-alive", + "ownership_token": "rea-efficiency-1413bb27eec44d0bbe35a75ebed50a1e.scope", + "payload_exit_code": 0, + "observer_pid": 3970405, + "remaining_processes": [3970405], + "payload_cleanup_verified": true, + "cpu_stat": { + "usage_usec": 93878644, + "user_usec": 90652873, + "system_usec": 3225770, + "nr_periods": 945, + "nr_throttled": 46, + "throttled_usec": 15255, + "nr_bursts": 0, + "burst_usec": 0 + }, + "memory_peak_bytes": 4300570624, + "control_group": "user.slice/user-1004.slice/user@1004.service/app.slice/rea-efficiency-1413bb27eec44d0bbe35a75ebed50a1e.scope", + "resource_events": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + } + }, + "payload_resource_observation_verified": true, + "payload_observation_scope": "Counters at payload exit while observer remains alive; not final whole-scope counters.", + "payload_observer_sources_sha256": { + "resource-run.py": "7728862171415e7dee2c11af2ead2b9a54265fccaf82a7113956d36b29065e33", + "resource_payload.py": "6f089e645459eba80c5d73e39c21606c78173a44daf49fabc3b88c4631ce51c6" + } + }, + "scope_poststate": "ActiveState=inactive\nSubState=dead", + "result": "2 uncached tasks passed" + }, + "docs_check": { + "result": "six tasks and five README consistency cases passed", + "log_sha256": "097314d7bba99e2d2ff14934ebe25603759d4e4e1ae012d91b2bae6dae91e09a", + "resource": { + "resource_scope": "rea-efficiency-da6dcbda85694ac9b19ce66aa3f3d4ad.scope", + "elapsed_seconds": 13.145, + "abort": null, + "hard_memory_bytes": 8589934592, + "early_stop_bytes": 6442450944, + "cpu_quota_percent": 100, + "cpu_affinity": [10], + "task_ceiling": 192, + "payload_resource_observation": { + "phase": "payload-exited-observer-alive", + "ownership_token": "rea-efficiency-da6dcbda85694ac9b19ce66aa3f3d4ad.scope", + "payload_exit_code": 0, + "observer_pid": 3972085, + "remaining_processes": [3972085], + "payload_cleanup_verified": true, + "cpu_stat": { + "usage_usec": 12856165, + "user_usec": 10596770, + "system_usec": 2259395, + "nr_periods": 130, + "nr_throttled": 6, + "throttled_usec": 3038, + "nr_bursts": 0, + "burst_usec": 0 + }, + "memory_peak_bytes": 473522176, + "control_group": "user.slice/user-1004.slice/user@1004.service/app.slice/rea-efficiency-da6dcbda85694ac9b19ce66aa3f3d4ad.scope", + "resource_events": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + } + }, + "payload_resource_observation_verified": true, + "payload_observation_scope": "Counters at payload exit while observer remains alive; not final whole-scope counters.", + "payload_observer_sources_sha256": { + "resource-run.py": "7728862171415e7dee2c11af2ead2b9a54265fccaf82a7113956d36b29065e33", + "resource_payload.py": "6f089e645459eba80c5d73e39c21606c78173a44daf49fabc3b88c4631ce51c6" + } + }, + "scope_poststate": "ActiveState=inactive\nSubState=dead" + } + }, + "residuals": [ + "#4513 raw wrapper identity mutation remains unfixed", + "#4282 real provider cleanup remains unverified", + "#2974 packaged startup not established", + "#4494 initial invalid-file dialog remains observed" + ], + "missing_runtime_attempt": { + "log_sha256": "d5138b00f0ec24f4b20bcf56c5d7c67725bd0c7e2fa74c639c0b231e514bd63c", + "resource": { + "resource_scope": "rea-efficiency-48bf71dfcf5c4b1a8e983526196ae435.scope", + "elapsed_seconds": 1.275, + "abort": null, + "hard_memory_bytes": 8589934592, + "early_stop_bytes": 6442450944, + "cpu_quota_percent": 100, + "cpu_affinity": [10], + "task_ceiling": 192, + "payload_resource_observation": { + "phase": "payload-exited-observer-alive", + "ownership_token": "rea-efficiency-48bf71dfcf5c4b1a8e983526196ae435.scope", + "payload_exit_code": 1, + "observer_pid": 3970342, + "remaining_processes": [3970342, 3970354], + "payload_cleanup_verified": false, + "cpu_stat": { + "usage_usec": 852874, + "user_usec": 650254, + "system_usec": 202620, + "nr_periods": 11, + "nr_throttled": 0, + "throttled_usec": 0, + "nr_bursts": 0, + "burst_usec": 0 + }, + "memory_peak_bytes": 126021632, + "control_group": "user.slice/user-1004.slice/user@1004.service/app.slice/rea-efficiency-48bf71dfcf5c4b1a8e983526196ae435.scope", + "resource_events": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + } + }, + "payload_resource_observation_verified": false, + "payload_observation_scope": "Counters at payload exit while observer remains alive; not final whole-scope counters.", + "payload_observer_sources_sha256": { + "resource-run.py": "7728862171415e7dee2c11af2ead2b9a54265fccaf82a7113956d36b29065e33", + "resource_payload.py": "6f089e645459eba80c5d73e39c21606c78173a44daf49fabc3b88c4631ce51c6" + } + }, + "scope_poststate": "ActiveState=inactive\nSubState=dead", + "result": "failed before application launch: selected runtime ENOENT; not a provider performance observation", + "summary": { + "profile": "/tmp/rea-4390-realapps-20261016/profiles/drawio-ui-TbNbZp", + "runtime": "/home/yann/yann/rea-electron-ipc-listener-4390-20261016", + "elapsed_ms": 2.696429999999964, + "deadline_triggered": false, + "selected_shape": ".geSidebar a.geItem:visible >> nth=0", + "actions_completed": false, + "shape_inserted": false, + "provider_ok": false, + "cleanup_verified": false, + "workflow_status": "partial-or-failed" + }, + "error_tag": "AnalysisInputError" + }, + "runtime_recovery": { + "log_sha256": "3e21b14313f6e890bc170a8bd9d71b1173d44267dd996dbbb5ab62acbac45c9d", + "resource": { + "resource_scope": "rea-efficiency-bf98e60db03642c79868f78a5a015bbb.scope", + "elapsed_seconds": 2.283, + "abort": null, + "hard_memory_bytes": 8589934592, + "early_stop_bytes": 6442450944, + "cpu_quota_percent": 100, + "cpu_affinity": [10], + "task_ceiling": 192, + "payload_resource_observation": { + "phase": "payload-exited-observer-alive", + "ownership_token": "rea-efficiency-bf98e60db03642c79868f78a5a015bbb.scope", + "payload_exit_code": 0, + "observer_pid": 3971556, + "remaining_processes": [3971556], + "payload_cleanup_verified": true, + "cpu_stat": { + "usage_usec": 2008741, + "user_usec": 1363093, + "system_usec": 645648, + "nr_periods": 21, + "nr_throttled": 0, + "throttled_usec": 0, + "nr_bursts": 0, + "burst_usec": 0 + }, + "memory_peak_bytes": 319066112, + "control_group": "user.slice/user-1004.slice/user@1004.service/app.slice/rea-efficiency-bf98e60db03642c79868f78a5a015bbb.scope", + "resource_events": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + } + }, + "payload_resource_observation_verified": true, + "payload_observation_scope": "Counters at payload exit while observer remains alive; not final whole-scope counters.", + "payload_observer_sources_sha256": { + "resource-run.py": "7728862171415e7dee2c11af2ead2b9a54265fccaf82a7113956d36b29065e33", + "resource_payload.py": "6f089e645459eba80c5d73e39c21606c78173a44daf49fabc3b88c4631ce51c6" + } + }, + "scope_poststate": "ActiveState=inactive\nSubState=dead", + "archive_verification": { + "source": "https://github.com/electron/electron/releases/tag/v44.7.0", + "archive_sha256": "3ae7d5bdad61c664486c6ab61361dd084d064c8c08af8d13a687096e18ce555a", + "version": "44.7.0", + "expected_source": "official SHASUMS256.txt", + "status": "archive verified against official checksum; extracted into new owned task directory" + }, + "runtime": { + "executable": "/tmp/rea-4390-realapps-20261016/electron-44.7.0/electron", + "executable_sha256": "10a14d05c6ff4f94075cfb3eeb6ed6571be33ebcc08cbd675b5ce9ff84706564", + "version": "44.7.0", + "archive_sha256": "3ae7d5bdad61c664486c6ab61361dd084d064c8c08af8d13a687096e18ce555a" + } + }, + "real_app": { + "log_sha256": "d7290cc6eeb4ac6e3c2f77e0d7e4fe6400a712611e9200ff571d3737467c4316", + "resource": { + "resource_scope": "rea-efficiency-c38b019b38a2442b92350f5f8759b445.scope", + "elapsed_seconds": 11.574, + "abort": null, + "hard_memory_bytes": 8589934592, + "early_stop_bytes": 6442450944, + "cpu_quota_percent": 100, + "cpu_affinity": [10], + "task_ceiling": 192, + "payload_resource_observation": { + "phase": "payload-exited-observer-alive", + "ownership_token": "rea-efficiency-c38b019b38a2442b92350f5f8759b445.scope", + "payload_exit_code": 1, + "observer_pid": 3971656, + "remaining_processes": [3971656, 3971669], + "payload_cleanup_verified": false, + "cpu_stat": { + "usage_usec": 6442239, + "user_usec": 4809581, + "system_usec": 1632658, + "nr_periods": 115, + "nr_throttled": 2, + "throttled_usec": 16020, + "nr_bursts": 0, + "burst_usec": 0 + }, + "memory_peak_bytes": 574693376, + "control_group": "user.slice/user-1004.slice/user@1004.service/app.slice/rea-efficiency-c38b019b38a2442b92350f5f8759b445.scope", + "resource_events": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + } + }, + "payload_resource_observation_verified": false, + "payload_observation_scope": "Counters at payload exit while observer remains alive; not final whole-scope counters.", + "payload_observer_sources_sha256": { + "resource-run.py": "7728862171415e7dee2c11af2ead2b9a54265fccaf82a7113956d36b29065e33", + "resource_payload.py": "6f089e645459eba80c5d73e39c21606c78173a44daf49fabc3b88c4631ce51c6" + } + }, + "scope_poststate": "ActiveState=inactive\nSubState=dead", + "summary": { + "elapsed_ms": 10447.227074, + "deadline_triggered": false, + "selected_shape": ".geSidebar a.geItem:visible >> nth=0", + "before": { + "title": "Untitled Diagram - draw.io", + "invalid_file_dialog": true, + "unsaved_changes": false, + "diagram_svg_elements": 0, + "cells": 0 + }, + "after": { + "title": "Untitled Diagram - draw.io", + "invalid_file_dialog": false, + "unsaved_changes": true, + "diagram_svg_elements": 2, + "cells": 0 + }, + "actions_completed": true, + "shape_inserted": true, + "provider_ok": false, + "cleanup_verified": false, + "reason": "cleanup_failed", + "coverage": { + "status": "partial_attach", + "observed_event_families": [ + "app-lifecycle", + "ipc", + "navigation", + "preload-configuration", + "web-contents-lifecycle", + "window-lifecycle" + ], + "unavailable_event_families": [ + "download", + "error", + "native-addon", + "permission", + "popup-attempt", + "preload", + "process-lifecycle", + "protocol", + "renderer-ipc", + "shell-attempt", + "updater" + ], + "observed_roles": ["browser", "main", "web_contents", "window"], + "pre_capture_activity": "unavailable" + }, + "retention": { + "budget_bytes": 16777216, + "estimated_retained_bytes": 202114, + "event_serialized_byte_upper_bound": 48214, + "retained": 109, + "dropped": 0, + "dropped_event_families": [], + "dropped_event_roles": [], + "observed_ipc": 7, + "dropped_ipc": 0, + "observed_runtime": 102, + "dropped_runtime": 0 + }, + "actions": [ + { + "step_id": "dismiss-invalid-file", + "kind": "click", + "window_index": 0, + "target": "window:0", + "elapsed_ms": 117, + "status": "completed", + "error": null + }, + { + "step_id": "insert-shape", + "kind": "click", + "window_index": 0, + "target": "window:0", + "elapsed_ms": 106, + "status": "completed", + "error": null + }, + { + "step_id": "settle-renderer", + "kind": "wait", + "window_index": 0, + "target": "window:0", + "elapsed_ms": 502, + "status": "completed", + "error": null + } + ], + "workflow_status": "partial-or-failed" + }, + "ipc_channels": { + "app-load-finished": 2, + "args-obj": 1, + "rendererReq": 4 + }, + "result": "real actions complete; overall provider cleanup failed, nonzero payload" + }, + "unrelated_ci_baseline": { + "ci_run": 38935598884, + "ci_merge": "eedae92d406015895a6d0eced5035da1b9ead07a", + "ci_head": "84d9cf5db1a18a01670f4cdc6ea87039163f1ff1", + "ci_base": "9d940c2f284cc6f75b27900336e52ceeba4aa2af", + "baseline_revision": "9d940c2f284cc6f75b27900336e52ceeba4aa2af", + "baseline_clean": true, + "command": "npm run test:focused -- tests/boundary/firmware/firmwareIntegration.test.ts", + "result": "same two named failures: 2 failed /33 passed", + "failure_names": [ + "retains a setup-failed publication tree for provider cleanup retry", + "retains the complete firmware result when published output cleanup stays uncertain" + ], + "scope": "CI test reproduction only, fake extractor fixtures; not real-provider behavior. No firmware source/test modification.", + "resource": { + "resource_scope": "rea-efficiency-3aca1cd2a4d444e6863ccc0a801161f0.scope", + "peak_cgroup_bytes_sampled": 3338067968, + "elapsed_seconds": 82.054, + "abort": null, + "hard_memory_bytes": 8589934592, + "early_stop_bytes": 6442450944, + "cpu_quota_percent": 100, + "cpu_affinity": [10], + "task_ceiling": 192, + "peak_tasks_sampled": 77, + "resource_events_last_observed": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + }, + "resource_events_final": null, + "resource_counters_verified": false, + "cpu_stat_last_observed": { + "usage_usec": 80772218, + "user_usec": 66273687, + "system_usec": 14498531, + "nr_periods": 819, + "nr_throttled": 49, + "throttled_usec": 23206, + "nr_bursts": 0, + "burst_usec": 0 + }, + "payload_resource_observation": { + "phase": "payload-exited-observer-alive", + "ownership_token": "rea-efficiency-3aca1cd2a4d444e6863ccc0a801161f0.scope", + "payload_exit_code": 1, + "observer_pid": 3934376, + "remaining_processes": [3934376], + "payload_cleanup_verified": true, + "cpu_stat": { + "usage_usec": 80771051, + "user_usec": 66272729, + "system_usec": 14498321, + "nr_periods": 818, + "nr_throttled": 49, + "throttled_usec": 23206, + "nr_bursts": 0, + "burst_usec": 0 + }, + "memory_peak_bytes": 3340832768, + "control_group": "user.slice/user-1004.slice/user@1004.service/app.slice/rea-efficiency-3aca1cd2a4d444e6863ccc0a801161f0.scope", + "resource_events": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + } + }, + "payload_resource_observation_verified": true, + "payload_observation_scope": "Counters at payload exit while observer remains alive; not final whole-scope counters.", + "payload_observer_sources_sha256": { + "resource-run.py": "7728862171415e7dee2c11af2ead2b9a54265fccaf82a7113956d36b29065e33", + "resource_payload.py": "6f089e645459eba80c5d73e39c21606c78173a44daf49fabc3b88c4631ce51c6" + } + }, + "scope_poststate": "ActiveState=inactive\nSubState=dead", + "log_sha256": "5ccf6be057aec66fea220a4a2f998daff3778dcd8e43fd9f28ca54485efda06f" + } + }, + "borrowed_apply_followup": { + "source_pins": { + "source_revision": "da04465fe7cc8edac331a25935b099e84ac2875f", + "recorded_utc": "2026-10-16T18:15:20.401571+00:00", + "source_sha256": { + "scripts/electron-active-hook.cjs": "45bc04d7baae3ed677008aa66443afc3d4a6dcaf841adea81f773b5d2ada9905", + "src/domain/javascript/electronActiveObservation.ts": "868d126480785898008a6d7de75cbcbb273eca1185d27c0decb72bdbd5a3630c", + "src/browser/PlaywrightElectronActiveProjection.ts": "89024969646f14db41317ca6e2c0d783e9d0e3cc875ffb8ffa3e50546691c61b", + "src/browser/PlaywrightElectronActiveProjection.test.ts": "75140495a9eef03f4b53291cfdd942db700308f9c7d96b66c097276168ebf51c", + "tests/boundary/browser/electronActiveHook.test.ts": "aba38126127f3873e60b126e6efc822b711526290028c26e6533d84b05b1decc", + "tests/boundary/mcp/electronMcp.test.ts": "0f180f3d060506228335421f21d5023d8ae54546de6856c507ba1f737ff58cfd", + "experiments/analysis-efficiency/drawio-electron-lifecycle.mjs": "1423a472f58fc81383061bf695059550276b898c780e91bb47052797b25e0ce6", + "experiments/analysis-efficiency/drawio-electron-lifecycle-oracles.py": "c98dd2abf16703744aa32edc5dd12b0f4eec121d40a4aaa387ef580b046032e2" + }, + "verifier_and_five_oracle_sources_unchanged": true, + "compiled_runtime_root": "/home/yann/yann/rea-electron-ipc-listener-4390-20261016", + "artifact_sha256": { + "app.asar": "e64b9650c0b8241ca9a7d33b56386a385bceee63270cec701d3a467347b697e6" + }, + "scope": "Frozen before borrowed-receiver follow-up real trial. Native/custom borrowed methods preserve dynamic receiver; preceding source measurements remain unchanged.", + "compiled_sha256": { + "dist/browser/PlaywrightElectronActiveProjection.js": "93184515d326d51b080cd52d616f79472727341ea5ad441a0c2eeaa94ac42ffc", + "dist/domain/javascript/electronActiveObservation.js": "21d8401adde0eacfd018c834b8e0bcc0ce7cb907b288a02ff7712a4b0e94d13d" + }, + "selected_electron_runtime": { + "executable": "/tmp/rea-4390-realapps-20261016/electron-44.7.0/electron", + "executable_sha256": "10a14d05c6ff4f94075cfb3eeb6ed6571be33ebcc08cbd675b5ce9ff84706564", + "version": "44.7.0", + "archive_sha256": "3ae7d5bdad61c664486c6ab61361dd084d064c8c08af8d13a687096e18ce555a" + }, + "runtime_pin_recorded_before_trial_utc": "2026-10-16T17:57:17.849958+00:00", + "unchanged_sources_reusing_passing_schema_client_and_oracle_checks": [ + "src/domain/javascript/electronActiveObservation.ts", + "src/browser/PlaywrightElectronActiveProjection.ts", + "src/browser/PlaywrightElectronActiveProjection.test.ts", + "tests/boundary/mcp/electronMcp.test.ts", + "experiments/analysis-efficiency/drawio-electron-lifecycle.mjs", + "experiments/analysis-efficiency/drawio-electron-lifecycle-oracles.py" + ] + }, + "scope": "Preserve dynamic method receivers for borrowed native and custom apply; no new state/schema/tool. Controlled native cases are not real-app metadata proof.", + "negative_control": { + "log_sha256": "ca61973b85cf83aa9828c7df4077ed01c579305b3927bb2456c50af7ccee923f", + "resource": { + "resource_scope": "rea-efficiency-086094f79a214a7c9fb00839e159e818.scope", + "elapsed_seconds": 9.803, + "abort": null, + "hard_memory_bytes": 8589934592, + "early_stop_bytes": 6442450944, + "cpu_quota_percent": 100, + "cpu_affinity": [10], + "task_ceiling": 192, + "payload_resource_observation": { + "phase": "payload-exited-observer-alive", + "ownership_token": "rea-efficiency-086094f79a214a7c9fb00839e159e818.scope", + "payload_exit_code": 1, + "observer_pid": 3982924, + "remaining_processes": [3982924], + "payload_cleanup_verified": true, + "cpu_stat": { + "usage_usec": 9435897, + "user_usec": 5855570, + "system_usec": 3580326, + "nr_periods": 97, + "nr_throttled": 1, + "throttled_usec": 7, + "nr_bursts": 0, + "burst_usec": 0 + }, + "memory_peak_bytes": 321433600, + "control_group": "user.slice/user-1004.slice/user@1004.service/app.slice/rea-efficiency-086094f79a214a7c9fb00839e159e818.scope", + "resource_events": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + } + }, + "payload_resource_observation_verified": true, + "payload_observation_scope": "Counters at payload exit while observer remains alive; not final whole-scope counters.", + "payload_observer_sources_sha256": { + "resource-run.py": "7728862171415e7dee2c11af2ead2b9a54265fccaf82a7113956d36b29065e33", + "resource_payload.py": "6f089e645459eba80c5d73e39c21606c78173a44daf49fabc3b88c4631ce51c6" + } + }, + "scope_poststate": "ActiveState=inactive\nSubState=dead", + "result": "48 passed,2 failed on7541; both native and custom borrowed apply execute the wrong callback" + }, + "hook_projection_actions": { + "log_sha256": "42aae2b11faf2484c2eded2443a9b1f83c4ff6182f1a7edfe50e95f75c8d6b8d", + "resource": { + "resource_scope": "rea-efficiency-3bef698e14bf437d984d0b79ed52d859.scope", + "elapsed_seconds": 10.321, + "abort": null, + "hard_memory_bytes": 8589934592, + "early_stop_bytes": 6442450944, + "cpu_quota_percent": 100, + "cpu_affinity": [10], + "task_ceiling": 192, + "payload_resource_observation": { + "phase": "payload-exited-observer-alive", + "ownership_token": "rea-efficiency-3bef698e14bf437d984d0b79ed52d859.scope", + "payload_exit_code": 0, + "observer_pid": 3983650, + "remaining_processes": [3983650], + "payload_cleanup_verified": true, + "cpu_stat": { + "usage_usec": 10042986, + "user_usec": 6417750, + "system_usec": 3625235, + "nr_periods": 102, + "nr_throttled": 6, + "throttled_usec": 3019, + "nr_bursts": 0, + "burst_usec": 0 + }, + "memory_peak_bytes": 324165632, + "control_group": "user.slice/user-1004.slice/user@1004.service/app.slice/rea-efficiency-3bef698e14bf437d984d0b79ed52d859.scope", + "resource_events": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + } + }, + "payload_resource_observation_verified": true, + "payload_observation_scope": "Counters at payload exit while observer remains alive; not final whole-scope counters.", + "payload_observer_sources_sha256": { + "resource-run.py": "7728862171415e7dee2c11af2ead2b9a54265fccaf82a7113956d36b29065e33", + "resource_payload.py": "6f089e645459eba80c5d73e39c21606c78173a44daf49fabc3b88c4631ce51c6" + } + }, + "scope_poststate": "ActiveState=inactive\nSubState=dead", + "result": "70 passed:50 hook boundary,4 projection,16 actions" + }, + "static_check": { + "log_sha256": "75f890b386500fa2a7807548e8602bdf19c54af8d98c0a634b86726767797bb6", + "resource": { + "resource_scope": "rea-efficiency-5cd81cf0c9dc49be88bcbe1b5fb80b1d.scope", + "elapsed_seconds": 108.067, + "abort": null, + "hard_memory_bytes": 8589934592, + "early_stop_bytes": 6442450944, + "cpu_quota_percent": 100, + "cpu_affinity": [10], + "task_ceiling": 192, + "payload_resource_observation": { + "phase": "payload-exited-observer-alive", + "ownership_token": "rea-efficiency-5cd81cf0c9dc49be88bcbe1b5fb80b1d.scope", + "payload_exit_code": 0, + "observer_pid": 3984534, + "remaining_processes": [3984534], + "payload_cleanup_verified": true, + "cpu_stat": { + "usage_usec": 106996233, + "user_usec": 95601026, + "system_usec": 11395207, + "nr_periods": 1079, + "nr_throttled": 60, + "throttled_usec": 24654, + "nr_bursts": 0, + "burst_usec": 0 + }, + "memory_peak_bytes": 4430647296, + "control_group": "user.slice/user-1004.slice/user@1004.service/app.slice/rea-efficiency-5cd81cf0c9dc49be88bcbe1b5fb80b1d.scope", + "resource_events": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + } + }, + "payload_resource_observation_verified": true, + "payload_observation_scope": "Counters at payload exit while observer remains alive; not final whole-scope counters.", + "payload_observer_sources_sha256": { + "resource-run.py": "7728862171415e7dee2c11af2ead2b9a54265fccaf82a7113956d36b29065e33", + "resource_payload.py": "6f089e645459eba80c5d73e39c21606c78173a44daf49fabc3b88c4631ce51c6" + } + }, + "scope_poststate": "ActiveState=inactive\nSubState=dead", + "result": "two uncached tasks passed" + }, + "real_app": { + "log_sha256": "25feea536a89dd755140eaa42b0ff49e26e6e024282bc9eb83761d580774f425", + "resource": { + "resource_scope": "rea-efficiency-77924cbdee4646aca1c163597e0ce14d.scope", + "elapsed_seconds": 12.038, + "abort": null, + "hard_memory_bytes": 8589934592, + "early_stop_bytes": 6442450944, + "cpu_quota_percent": 100, + "cpu_affinity": [10], + "task_ceiling": 192, + "payload_resource_observation": { + "phase": "payload-exited-observer-alive", + "ownership_token": "rea-efficiency-77924cbdee4646aca1c163597e0ce14d.scope", + "payload_exit_code": 1, + "observer_pid": 3985599, + "remaining_processes": [3985599, 3985611, 3985731, 3985733], + "payload_cleanup_verified": false, + "cpu_stat": { + "usage_usec": 6662295, + "user_usec": 4983676, + "system_usec": 1678618, + "nr_periods": 119, + "nr_throttled": 1, + "throttled_usec": 1681, + "nr_bursts": 0, + "burst_usec": 0 + }, + "memory_peak_bytes": 598835200, + "control_group": "user.slice/user-1004.slice/user@1004.service/app.slice/rea-efficiency-77924cbdee4646aca1c163597e0ce14d.scope", + "resource_events": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + } + }, + "payload_resource_observation_verified": false, + "payload_observation_scope": "Counters at payload exit while observer remains alive; not final whole-scope counters.", + "payload_observer_sources_sha256": { + "resource-run.py": "7728862171415e7dee2c11af2ead2b9a54265fccaf82a7113956d36b29065e33", + "resource_payload.py": "6f089e645459eba80c5d73e39c21606c78173a44daf49fabc3b88c4631ce51c6" + } + }, + "scope_poststate": "ActiveState=inactive\nSubState=dead", + "summary": { + "elapsed_ms": 10537.506792, + "deadline_triggered": false, + "selected_shape": ".geSidebar a.geItem:visible >> nth=0", + "before": { + "title": "Untitled Diagram - draw.io", + "invalid_file_dialog": true, + "unsaved_changes": false, + "diagram_svg_elements": 0, + "cells": 0 + }, + "after": { + "title": "Untitled Diagram - draw.io", + "invalid_file_dialog": false, + "unsaved_changes": true, + "diagram_svg_elements": 2, + "cells": 0 + }, + "actions_completed": true, + "shape_inserted": true, + "provider_ok": false, + "cleanup_verified": false, + "reason": "cleanup_failed", + "coverage": { + "status": "partial_attach", + "observed_event_families": [ + "app-lifecycle", + "ipc", + "navigation", + "preload-configuration", + "web-contents-lifecycle", + "window-lifecycle" + ], + "unavailable_event_families": [ + "download", + "error", + "native-addon", + "permission", + "popup-attempt", + "preload", + "process-lifecycle", + "protocol", + "renderer-ipc", + "shell-attempt", + "updater" + ], + "observed_roles": ["browser", "main", "web_contents", "window"], + "pre_capture_activity": "unavailable" + }, + "retention": { + "budget_bytes": 16777216, + "estimated_retained_bytes": 202114, + "event_serialized_byte_upper_bound": 48214, + "retained": 109, + "dropped": 0, + "dropped_event_families": [], + "dropped_event_roles": [], + "observed_ipc": 7, + "dropped_ipc": 0, + "observed_runtime": 102, + "dropped_runtime": 0 + }, + "actions": [ + { + "step_id": "dismiss-invalid-file", + "kind": "click", + "window_index": 0, + "target": "window:0", + "elapsed_ms": 94, + "status": "completed", + "error": null + }, + { + "step_id": "insert-shape", + "kind": "click", + "window_index": 0, + "target": "window:0", + "elapsed_ms": 118, + "status": "completed", + "error": null + }, + { + "step_id": "settle-renderer", + "kind": "wait", + "window_index": 0, + "target": "window:0", + "elapsed_ms": 501, + "status": "completed", + "error": null + } + ], + "workflow_status": "partial-or-failed" + }, + "ipc_observed": 7, + "result": "Real actions completed; overall provider cleanup failed, payload nonzero" + }, + "preceding_ci": { + "identity": { + "run": 38943919946, + "head": "7541a900c8dac3cde6174398bd5fcac6b1a04215", + "actual_checkout": "98c14dfc01fe4698a50eedc8b1cd1ac9cd03267d", + "actual_base": "4838f444939677107902549c59b73439e14d3ae9", + "checkout_evidence": "Build package job117118583439 log and Git commit parents", + "checkout_log_sha256": "048817f058cd2836206c4c5f7158b0808042c3a7cadd27f72caf9b1dbb51e4cd", + "rest_pr_base_sha_stale": "2c7583599b9ac1c847bd77b1940930fd8d4594c7", + "authored_files_relative_current_main": [ + "docs/electron-observation.md", + "docs/verification/electron-ipc-listeners-2026-10-16.json", + "docs/verification/electron-ipc-listeners-2026-10-16.md", + "experiments/analysis-efficiency/drawio-electron-lifecycle-oracles.py", + "experiments/analysis-efficiency/drawio-electron-lifecycle.mjs", + "scripts/electron-active-hook.cjs", + "src/browser/PlaywrightElectronActiveProjection.test.ts", + "src/browser/PlaywrightElectronActiveProjection.ts", + "src/domain/javascript/electronActiveObservation.ts", + "tests/boundary/browser/electronActiveHook.test.ts", + "tests/boundary/mcp/electronMcp.test.ts" + ], + "firmware_paths_unchanged_between_prior_and_current_base": true, + "current_ci_result": "pending; do not infer runtime result from source equality" + }, + "terminal_jobs": { + "cancelled": [ + { + "id": 117119626186, + "name": "Test shard (2/5)" + }, + { + "id": 117119626348, + "name": "Test shard (1/5)" + } + ], + "counts": [ + { + "conclusion": "cancelled", + "count": 2 + }, + { + "conclusion": "failure", + "count": 3 + }, + { + "conclusion": "success", + "count": 31 + } + ], + "failed": [ + { + "html_url": "https://github.com/morluto/rea/actions/runs/38943919946/job/117119626359", + "id": 117119626359, + "name": "Test shard (5/5)" + }, + { + "html_url": "https://github.com/morluto/rea/actions/runs/38943919946/job/117123527867", + "id": 117123527867, + "name": "Portable tests" + }, + { + "html_url": "https://github.com/morluto/rea/actions/runs/38943919946/job/117124523233", + "id": 117124523233, + "name": "CI required" + } + ], + "loaded": 36, + "terminal": true, + "total_count": 36 + }, + "fifth_shard_log_sha256": "afc4c06270399d4ed8a26cf159fc098ce42aea58157c6da8478e2d572320c8b5", + "failures": "Same two firmware cleanup tests as unmodified9d940c2 control; unrelated to Electron, no firmware source/test change." + }, + "docs_check": { + "result": "six tasks and five README consistency cases passed", + "log_sha256": "750e9828efe43a73ce6788200551fde868ebaf79b1816d6948bff8f9efcc905e", + "resource": { + "resource_scope": "rea-efficiency-b2c223529ddd4d5eac5d335199e9e01e.scope", + "elapsed_seconds": 10.339, + "abort": null, + "hard_memory_bytes": 8589934592, + "early_stop_bytes": 6442450944, + "cpu_quota_percent": 100, + "cpu_affinity": [10], + "task_ceiling": 192, + "payload_resource_observation": { + "phase": "payload-exited-observer-alive", + "ownership_token": "rea-efficiency-b2c223529ddd4d5eac5d335199e9e01e.scope", + "payload_exit_code": 0, + "observer_pid": 3985902, + "remaining_processes": [3985902], + "payload_cleanup_verified": true, + "cpu_stat": { + "usage_usec": 9798204, + "user_usec": 8022081, + "system_usec": 1776123, + "nr_periods": 102, + "nr_throttled": 3, + "throttled_usec": 1262, + "nr_bursts": 0, + "burst_usec": 0 + }, + "memory_peak_bytes": 490909696, + "control_group": "user.slice/user-1004.slice/user@1004.service/app.slice/rea-efficiency-b2c223529ddd4d5eac5d335199e9e01e.scope", + "resource_events": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + } + }, + "payload_resource_observation_verified": true, + "payload_observation_scope": "Counters at payload exit while observer remains alive; not final whole-scope counters.", + "payload_observer_sources_sha256": { + "resource-run.py": "7728862171415e7dee2c11af2ead2b9a54265fccaf82a7113956d36b29065e33", + "resource_payload.py": "6f089e645459eba80c5d73e39c21606c78173a44daf49fabc3b88c4631ce51c6" + } + }, + "scope_poststate": "ActiveState=inactive\nSubState=dead" + } + }, + "listener_intrinsics_followup": { + "source_pins": { + "source_revision": "a9c5611d0807d5b2ff1df13e2291acac2b376d71", + "recorded_utc": "2026-10-16T18:35:25.548691+00:00", + "source_sha256": { + "scripts/electron-active-hook.cjs": "185c69f5ecbe65c060cc8b83147c72ea6df5e37543bd1eca949f57facc4cf87f", + "src/domain/javascript/electronActiveObservation.ts": "868d126480785898008a6d7de75cbcbb273eca1185d27c0decb72bdbd5a3630c", + "src/browser/PlaywrightElectronActiveProjection.ts": "89024969646f14db41317ca6e2c0d783e9d0e3cc875ffb8ffa3e50546691c61b", + "src/browser/PlaywrightElectronActiveProjection.test.ts": "75140495a9eef03f4b53291cfdd942db700308f9c7d96b66c097276168ebf51c", + "tests/boundary/browser/electronActiveHook.test.ts": "47dcfbf600737e40aea635a39799d9249510f957d82d4fde0b0630d55fc2b585", + "tests/boundary/mcp/electronMcp.test.ts": "0f180f3d060506228335421f21d5023d8ae54546de6856c507ba1f737ff58cfd", + "experiments/analysis-efficiency/drawio-electron-lifecycle.mjs": "1423a472f58fc81383061bf695059550276b898c780e91bb47052797b25e0ce6", + "experiments/analysis-efficiency/drawio-electron-lifecycle-oracles.py": "c98dd2abf16703744aa32edc5dd12b0f4eec121d40a4aaa387ef580b046032e2" + }, + "verifier_and_five_oracle_sources_unchanged": true, + "compiled_runtime_root": "/home/yann/yann/rea-electron-ipc-listener-4390-20261016", + "artifact_sha256": { + "app.asar": "e64b9650c0b8241ca9a7d33b56386a385bceee63270cec701d3a467347b697e6" + }, + "scope": "Frozen before real trial of listener-owner intrinsic capture. Related descriptor/traversal operations and constructors captured at startup; private receiver uses null-prototype literal. Not a promise of arbitrary JavaScript transparency.", + "compiled_sha256": { + "dist/browser/PlaywrightElectronActiveProjection.js": "93184515d326d51b080cd52d616f79472727341ea5ad441a0c2eeaa94ac42ffc", + "dist/domain/javascript/electronActiveObservation.js": "21d8401adde0eacfd018c834b8e0bcc0ce7cb907b288a02ff7712a4b0e94d13d" + }, + "selected_electron_runtime": { + "executable": "/tmp/rea-4390-realapps-20261016/electron-44.7.0/electron", + "executable_sha256": "10a14d05c6ff4f94075cfb3eeb6ed6571be33ebcc08cbd675b5ce9ff84706564", + "version": "44.7.0", + "archive_sha256": "3ae7d5bdad61c664486c6ab61361dd084d064c8c08af8d13a687096e18ce555a" + }, + "runtime_pin_recorded_before_trial_utc": "2026-10-16T17:57:17.849958+00:00", + "unchanged_sources_reusing_passing_schema_client_and_oracle_checks": [ + "src/domain/javascript/electronActiveObservation.ts", + "src/browser/PlaywrightElectronActiveProjection.ts", + "src/browser/PlaywrightElectronActiveProjection.test.ts", + "tests/boundary/mcp/electronMcp.test.ts", + "experiments/analysis-efficiency/drawio-electron-lifecycle.mjs", + "experiments/analysis-efficiency/drawio-electron-lifecycle-oracles.py" + ] + }, + "scope": "Capture only the newly added listener owner property operations and private constructors; remove late Object.create with a null-prototype literal. Controlled hardening cases are not real-app behavior proof; arbitrary introspection and #4513 remain unsupported.", + "negative_controls": { + "log_sha256": "5aa0d0a6243f5e62b80a73fb91eca6da05dd3a2ab03a5e76faba5c81cc29c946", + "resource": { + "resource_scope": "rea-efficiency-769365f48a5243549f37e5069bb2c3e6.scope", + "elapsed_seconds": 5.454, + "abort": null, + "hard_memory_bytes": 8589934592, + "early_stop_bytes": 6442450944, + "cpu_quota_percent": 100, + "cpu_affinity": [10], + "task_ceiling": 192, + "payload_resource_observation": { + "phase": "payload-exited-observer-alive", + "ownership_token": "rea-efficiency-769365f48a5243549f37e5069bb2c3e6.scope", + "payload_exit_code": 1, + "observer_pid": 3992264, + "remaining_processes": [3992264], + "payload_cleanup_verified": true, + "cpu_stat": { + "usage_usec": 5174101, + "user_usec": 3492059, + "system_usec": 1682042, + "nr_periods": 53, + "nr_throttled": 0, + "throttled_usec": 0, + "nr_bursts": 0, + "burst_usec": 0 + }, + "memory_peak_bytes": 330100736, + "control_group": "user.slice/user-1004.slice/user@1004.service/app.slice/rea-efficiency-769365f48a5243549f37e5069bb2c3e6.scope", + "resource_events": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + } + }, + "payload_resource_observation_verified": true, + "payload_observation_scope": "Counters at payload exit while observer remains alive; not final whole-scope counters.", + "payload_observer_sources_sha256": { + "resource-run.py": "7728862171415e7dee2c11af2ead2b9a54265fccaf82a7113956d36b29065e33", + "resource_payload.py": "6f089e645459eba80c5d73e39c21606c78173a44daf49fabc3b88c4631ce51c6" + } + }, + "scope_poststate": "ActiveState=inactive\nSubState=dead", + "result": "50 passed /7 failed on f5b4; each targeted primitive replacement changes registration/removal relative native control" + }, + "hook_projection_actions": { + "log_sha256": "ff864f561aaadd709f382eb9a6376455868c693ea55013c6b8d8578331ed39a0", + "resource": { + "resource_scope": "rea-efficiency-40264373233c4800a490dada8b16441a.scope", + "elapsed_seconds": 5.937, + "abort": null, + "hard_memory_bytes": 8589934592, + "early_stop_bytes": 6442450944, + "cpu_quota_percent": 100, + "cpu_affinity": [10], + "task_ceiling": 192, + "payload_resource_observation": { + "phase": "payload-exited-observer-alive", + "ownership_token": "rea-efficiency-40264373233c4800a490dada8b16441a.scope", + "payload_exit_code": 0, + "observer_pid": 3992877, + "remaining_processes": [3992877], + "payload_cleanup_verified": true, + "cpu_stat": { + "usage_usec": 5735407, + "user_usec": 3932357, + "system_usec": 1803049, + "nr_periods": 58, + "nr_throttled": 0, + "throttled_usec": 0, + "nr_bursts": 0, + "burst_usec": 0 + }, + "memory_peak_bytes": 319033344, + "control_group": "user.slice/user-1004.slice/user@1004.service/app.slice/rea-efficiency-40264373233c4800a490dada8b16441a.scope", + "resource_events": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + } + }, + "payload_resource_observation_verified": true, + "payload_observation_scope": "Counters at payload exit while observer remains alive; not final whole-scope counters.", + "payload_observer_sources_sha256": { + "resource-run.py": "7728862171415e7dee2c11af2ead2b9a54265fccaf82a7113956d36b29065e33", + "resource_payload.py": "6f089e645459eba80c5d73e39c21606c78173a44daf49fabc3b88c4631ce51c6" + } + }, + "scope_poststate": "ActiveState=inactive\nSubState=dead", + "result": "77 passed:57 hook boundary +4 projection +16 actions" + }, + "static_check": { + "log_sha256": "c2561658f19975edcdb574111712a4f8f5700f21261aa3c3e21090a9c85276d7", + "resource": { + "resource_scope": "rea-efficiency-e612145b95224b31bb80942c95ba6831.scope", + "elapsed_seconds": 90.98, + "abort": null, + "hard_memory_bytes": 8589934592, + "early_stop_bytes": 6442450944, + "cpu_quota_percent": 100, + "cpu_affinity": [10], + "task_ceiling": 192, + "payload_resource_observation": { + "phase": "payload-exited-observer-alive", + "ownership_token": "rea-efficiency-e612145b95224b31bb80942c95ba6831.scope", + "payload_exit_code": 0, + "observer_pid": 3994088, + "remaining_processes": [3994088], + "payload_cleanup_verified": true, + "cpu_stat": { + "usage_usec": 90119351, + "user_usec": 85280801, + "system_usec": 4838549, + "nr_periods": 907, + "nr_throttled": 45, + "throttled_usec": 67151, + "nr_bursts": 0, + "burst_usec": 0 + }, + "memory_peak_bytes": 4338749440, + "control_group": "user.slice/user-1004.slice/user@1004.service/app.slice/rea-efficiency-e612145b95224b31bb80942c95ba6831.scope", + "resource_events": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + } + }, + "payload_resource_observation_verified": true, + "payload_observation_scope": "Counters at payload exit while observer remains alive; not final whole-scope counters.", + "payload_observer_sources_sha256": { + "resource-run.py": "7728862171415e7dee2c11af2ead2b9a54265fccaf82a7113956d36b29065e33", + "resource_payload.py": "6f089e645459eba80c5d73e39c21606c78173a44daf49fabc3b88c4631ce51c6" + } + }, + "scope_poststate": "ActiveState=inactive\nSubState=dead", + "result": "two uncached tasks passed" + }, + "real_app": { + "log_sha256": "ee92174216436fb75fdd19a53ff500413b65d61bc93d2c73d31f790b28b22cc2", + "resource": { + "resource_scope": "rea-efficiency-0a56278ea20d4f8fb0c53c699ee884ea.scope", + "elapsed_seconds": 11.429, + "abort": null, + "hard_memory_bytes": 8589934592, + "early_stop_bytes": 6442450944, + "cpu_quota_percent": 100, + "cpu_affinity": [10], + "task_ceiling": 192, + "payload_resource_observation": { + "phase": "payload-exited-observer-alive", + "ownership_token": "rea-efficiency-0a56278ea20d4f8fb0c53c699ee884ea.scope", + "payload_exit_code": 1, + "observer_pid": 3994642, + "remaining_processes": [3994642, 3994654], + "payload_cleanup_verified": false, + "cpu_stat": { + "usage_usec": 6441029, + "user_usec": 4692422, + "system_usec": 1748606, + "nr_periods": 113, + "nr_throttled": 3, + "throttled_usec": 2219, + "nr_bursts": 0, + "burst_usec": 0 + }, + "memory_peak_bytes": 550363136, + "control_group": "user.slice/user-1004.slice/user@1004.service/app.slice/rea-efficiency-0a56278ea20d4f8fb0c53c699ee884ea.scope", + "resource_events": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + } + }, + "payload_resource_observation_verified": false, + "payload_observation_scope": "Counters at payload exit while observer remains alive; not final whole-scope counters.", + "payload_observer_sources_sha256": { + "resource-run.py": "7728862171415e7dee2c11af2ead2b9a54265fccaf82a7113956d36b29065e33", + "resource_payload.py": "6f089e645459eba80c5d73e39c21606c78173a44daf49fabc3b88c4631ce51c6" + } + }, + "scope_poststate": "ActiveState=inactive\nSubState=dead", + "summary": { + "elapsed_ms": 10333.639054000001, + "deadline_triggered": false, + "selected_shape": ".geSidebar a.geItem:visible >> nth=0", + "before": { + "title": "Untitled Diagram - draw.io", + "invalid_file_dialog": true, + "unsaved_changes": false, + "diagram_svg_elements": 0, + "cells": 0 + }, + "after": { + "title": "Untitled Diagram - draw.io", + "invalid_file_dialog": false, + "unsaved_changes": true, + "diagram_svg_elements": 2, + "cells": 0 + }, + "actions_completed": true, + "shape_inserted": true, + "provider_ok": false, + "cleanup_verified": false, + "reason": "cleanup_failed", + "coverage": { + "status": "partial_attach", + "observed_event_families": [ + "app-lifecycle", + "ipc", + "navigation", + "preload-configuration", + "web-contents-lifecycle", + "window-lifecycle" + ], + "unavailable_event_families": [ + "download", + "error", + "native-addon", + "permission", + "popup-attempt", + "preload", + "process-lifecycle", + "protocol", + "renderer-ipc", + "shell-attempt", + "updater" + ], + "observed_roles": ["browser", "main", "web_contents", "window"], + "pre_capture_activity": "unavailable" + }, + "retention": { + "budget_bytes": 16777216, + "estimated_retained_bytes": 202114, + "event_serialized_byte_upper_bound": 48214, + "retained": 109, + "dropped": 0, + "dropped_event_families": [], + "dropped_event_roles": [], + "observed_ipc": 7, + "dropped_ipc": 0, + "observed_runtime": 102, + "dropped_runtime": 0 + }, + "actions": [ + { + "step_id": "dismiss-invalid-file", + "kind": "click", + "window_index": 0, + "target": "window:0", + "elapsed_ms": 79, + "status": "completed", + "error": null + }, + { + "step_id": "insert-shape", + "kind": "click", + "window_index": 0, + "target": "window:0", + "elapsed_ms": 126, + "status": "completed", + "error": null + }, + { + "step_id": "settle-renderer", + "kind": "wait", + "window_index": 0, + "target": "window:0", + "elapsed_ms": 501, + "status": "completed", + "error": null + } + ], + "workflow_status": "partial-or-failed" + }, + "ipc_observed": 7, + "result": "Real actions complete, overall provider cleanup fails; payload nonzero. Resource observer sees an additional scope descendant at payload exit, so complete family resource observation is unverified. Later external scope inactive/dead is not provider cleanup proof." + }, + "preceding_ci": { + "identity": { + "run": 38946503183, + "actual_checkout": "fcd7a9d92a2e64d6eb96d4fabfed7fc7f44b0d45", + "actual_head": "f5b489c1b410b85e4f711ceadec823c1a65e771c", + "actual_base": "4838f444939677107902549c59b73439e14d3ae9", + "build_log_sha256": "6803072869c36e26f33c0f714d722a7773fba821791a91dc6bff2e9cb3ef97fe", + "result": "pending; actual checkout only, not CI success" + }, + "terminal_jobs": { + "cancelled": [ + { + "id": 117127335216, + "name": "Test shard (4/5)" + }, + { + "id": 117127335226, + "name": "Test shard (1/5)" + }, + { + "id": 117127335257, + "name": "Test shard (3/5)" + }, + { + "id": 117127335262, + "name": "Test shard (2/5)" + } + ], + "counts": [ + { + "conclusion": "cancelled", + "count": 4 + }, + { + "conclusion": "failure", + "count": 3 + }, + { + "conclusion": "success", + "count": 29 + } + ], + "failed": [ + { + "html_url": "https://github.com/morluto/rea/actions/runs/38946503183/job/117127335056", + "id": 117127335056, + "name": "Test shard (5/5)" + }, + { + "html_url": "https://github.com/morluto/rea/actions/runs/38946503183/job/117131309826", + "id": 117131309826, + "name": "Portable tests" + }, + { + "html_url": "https://github.com/morluto/rea/actions/runs/38946503183/job/117132060598", + "id": 117132060598, + "name": "CI required" + } + ], + "loaded": 36, + "terminal": true, + "total_count": 36 + }, + "fifth_shard_log_sha256": "7171ffabc48333dff550895b4f5ec0e07e91f3e1fdefcc1950cf7cc7d1e59c1c", + "failures": "Same two firmware failures as earlier confirmed unmodified main control; no firmware diff/test relaxation." + }, + "docs_check": { + "result": "six tasks and five README consistency cases passed", + "log_sha256": "c1a3d522c6381d476e8eea5d9dcf9a14bc5b20764f25223a4fa2c850d0029523", + "resource": { + "resource_scope": "rea-efficiency-76746f0388384633864a60251d2534da.scope", + "elapsed_seconds": 9.316, + "abort": null, + "hard_memory_bytes": 8589934592, + "early_stop_bytes": 6442450944, + "cpu_quota_percent": 100, + "cpu_affinity": [10], + "task_ceiling": 192, + "payload_resource_observation": { + "phase": "payload-exited-observer-alive", + "ownership_token": "rea-efficiency-76746f0388384633864a60251d2534da.scope", + "payload_exit_code": 0, + "observer_pid": 3995121, + "remaining_processes": [3995121], + "payload_cleanup_verified": true, + "cpu_stat": { + "usage_usec": 9025695, + "user_usec": 7347664, + "system_usec": 1678031, + "nr_periods": 92, + "nr_throttled": 6, + "throttled_usec": 2299, + "nr_bursts": 0, + "burst_usec": 0 + }, + "memory_peak_bytes": 467570688, + "control_group": "user.slice/user-1004.slice/user@1004.service/app.slice/rea-efficiency-76746f0388384633864a60251d2534da.scope", + "resource_events": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + } + }, + "payload_resource_observation_verified": true, + "payload_observation_scope": "Counters at payload exit while observer remains alive; not final whole-scope counters.", + "payload_observer_sources_sha256": { + "resource-run.py": "7728862171415e7dee2c11af2ead2b9a54265fccaf82a7113956d36b29065e33", + "resource_payload.py": "6f089e645459eba80c5d73e39c21606c78173a44daf49fabc3b88c4631ce51c6" + } + }, + "scope_poststate": "ActiveState=inactive\nSubState=dead" + } + }, + "private_collection_methods_followup": { + "source_pins": { + "source_revision": "774ed6aa640f8929b01d36d3000ac1391ef0c1fe", + "recorded_utc": "2026-10-16T18:54:23.992009+00:00", + "source_sha256": { + "scripts/electron-active-hook.cjs": "84b05c910644121d879a6277c8c9a974ff96934e020956028d4a470930cf416c", + "src/domain/javascript/electronActiveObservation.ts": "868d126480785898008a6d7de75cbcbb273eca1185d27c0decb72bdbd5a3630c", + "src/browser/PlaywrightElectronActiveProjection.ts": "89024969646f14db41317ca6e2c0d783e9d0e3cc875ffb8ffa3e50546691c61b", + "src/browser/PlaywrightElectronActiveProjection.test.ts": "75140495a9eef03f4b53291cfdd942db700308f9c7d96b66c097276168ebf51c", + "tests/boundary/browser/electronActiveHook.test.ts": "5c33e6a76512d482e456cdbc939ffba97104988f83ec96bbf285c8a58636bfae", + "tests/boundary/mcp/electronMcp.test.ts": "0f180f3d060506228335421f21d5023d8ae54546de6856c507ba1f737ff58cfd", + "experiments/analysis-efficiency/drawio-electron-lifecycle.mjs": "1423a472f58fc81383061bf695059550276b898c780e91bb47052797b25e0ce6", + "experiments/analysis-efficiency/drawio-electron-lifecycle-oracles.py": "c98dd2abf16703744aa32edc5dd12b0f4eec121d40a4aaa387ef580b046032e2" + }, + "verifier_and_five_oracle_sources_unchanged": true, + "compiled_runtime_root": "/home/yann/yann/rea-electron-ipc-listener-4390-20261016", + "artifact_sha256": { + "app.asar": "e64b9650c0b8241ca9a7d33b56386a385bceee63270cec701d3a467347b697e6" + }, + "scope": "Frozen before private WeakMap/Set method lifetime follow-up real trial. Initialization-only method binding helper removes repeated startup binding construction; no per-invocation argument-array wrapper.", + "compiled_sha256": { + "dist/browser/PlaywrightElectronActiveProjection.js": "93184515d326d51b080cd52d616f79472727341ea5ad441a0c2eeaa94ac42ffc", + "dist/domain/javascript/electronActiveObservation.js": "21d8401adde0eacfd018c834b8e0bcc0ce7cb907b288a02ff7712a4b0e94d13d" + }, + "selected_electron_runtime": { + "executable": "/tmp/rea-4390-realapps-20261016/electron-44.7.0/electron", + "executable_sha256": "10a14d05c6ff4f94075cfb3eeb6ed6571be33ebcc08cbd675b5ce9ff84706564", + "version": "44.7.0", + "archive_sha256": "3ae7d5bdad61c664486c6ab61361dd084d064c8c08af8d13a687096e18ce555a" + }, + "runtime_pin_recorded_before_trial_utc": "2026-10-16T17:57:17.849958+00:00", + "unchanged_sources_reusing_passing_schema_client_and_oracle_checks": [ + "src/domain/javascript/electronActiveObservation.ts", + "src/browser/PlaywrightElectronActiveProjection.ts", + "src/browser/PlaywrightElectronActiveProjection.test.ts", + "tests/boundary/mcp/electronMcp.test.ts", + "experiments/analysis-efficiency/drawio-electron-lifecycle.mjs", + "experiments/analysis-efficiency/drawio-electron-lifecycle-oracles.py" + ] + }, + "scope": "Bind new listener-owner WeakMap/Set methods before startup; share initialization-only binder with prior Map traversal/argument slicing. No per-call argument-array helper, schema/tool/state/capability or arbitrary JS transparency claim.", + "negative_controls": { + "log_sha256": "9849d3cdabe94b35172e7bc0292bf260c67831f9af607faed7363621ed1520a0", + "resource": { + "resource_scope": "rea-efficiency-413708baf99d46b5ad282f727848d1fc.scope", + "elapsed_seconds": 5.729, + "abort": null, + "hard_memory_bytes": 8589934592, + "early_stop_bytes": 6442450944, + "cpu_quota_percent": 100, + "cpu_affinity": [10], + "task_ceiling": 192, + "payload_resource_observation": { + "phase": "payload-exited-observer-alive", + "ownership_token": "rea-efficiency-413708baf99d46b5ad282f727848d1fc.scope", + "payload_exit_code": 1, + "observer_pid": 3999900, + "remaining_processes": [3999900], + "payload_cleanup_verified": true, + "cpu_stat": { + "usage_usec": 5412435, + "user_usec": 3595048, + "system_usec": 1817387, + "nr_periods": 55, + "nr_throttled": 1, + "throttled_usec": 2047, + "nr_bursts": 0, + "burst_usec": 0 + }, + "memory_peak_bytes": 322379776, + "control_group": "user.slice/user-1004.slice/user@1004.service/app.slice/rea-efficiency-413708baf99d46b5ad282f727848d1fc.scope", + "resource_events": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + } + }, + "payload_resource_observation_verified": true, + "payload_observation_scope": "Counters at payload exit while observer remains alive; not final whole-scope counters.", + "payload_observer_sources_sha256": { + "resource-run.py": "7728862171415e7dee2c11af2ead2b9a54265fccaf82a7113956d36b29065e33", + "resource_payload.py": "6f089e645459eba80c5d73e39c21606c78173a44daf49fabc3b88c4631ce51c6" + } + }, + "scope_poststate": "ActiveState=inactive\nSubState=dead", + "result": "57 passed /5 failed on e6c; private WeakMap has/get/set and Set has/add replacements change registration/removal relative plain Node" + }, + "hook_projection_actions": { + "log_sha256": "9cbf78d4178c0b36033fe31a6bc288a6b590689aa72b5b8ead5660f975bd579b", + "resource": { + "resource_scope": "rea-efficiency-de52195527f445a7865d27372c55826e.scope", + "elapsed_seconds": 6.218, + "abort": null, + "hard_memory_bytes": 8589934592, + "early_stop_bytes": 6442450944, + "cpu_quota_percent": 100, + "cpu_affinity": [10], + "task_ceiling": 192, + "payload_resource_observation": { + "phase": "payload-exited-observer-alive", + "ownership_token": "rea-efficiency-de52195527f445a7865d27372c55826e.scope", + "payload_exit_code": 0, + "observer_pid": 4000668, + "remaining_processes": [4000668], + "payload_cleanup_verified": true, + "cpu_stat": { + "usage_usec": 6029725, + "user_usec": 4144604, + "system_usec": 1885121, + "nr_periods": 61, + "nr_throttled": 0, + "throttled_usec": 0, + "nr_bursts": 0, + "burst_usec": 0 + }, + "memory_peak_bytes": 315863040, + "control_group": "user.slice/user-1004.slice/user@1004.service/app.slice/rea-efficiency-de52195527f445a7865d27372c55826e.scope", + "resource_events": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + } + }, + "payload_resource_observation_verified": true, + "payload_observation_scope": "Counters at payload exit while observer remains alive; not final whole-scope counters.", + "payload_observer_sources_sha256": { + "resource-run.py": "7728862171415e7dee2c11af2ead2b9a54265fccaf82a7113956d36b29065e33", + "resource_payload.py": "6f089e645459eba80c5d73e39c21606c78173a44daf49fabc3b88c4631ce51c6" + } + }, + "scope_poststate": "ActiveState=inactive\nSubState=dead", + "result": "82 passed:62 hook +4 projection +16 actions" + }, + "static_check": { + "log_sha256": "a8ba34177614fefd012924f38b8be201275b5fc47f66240f7bfa8951c83f5cbc", + "resource": { + "resource_scope": "rea-efficiency-c5c997e501044f45a2d624a8aa0c6aa8.scope", + "elapsed_seconds": 92.999, + "abort": null, + "hard_memory_bytes": 8589934592, + "early_stop_bytes": 6442450944, + "cpu_quota_percent": 100, + "cpu_affinity": [10], + "task_ceiling": 192, + "payload_resource_observation": { + "phase": "payload-exited-observer-alive", + "ownership_token": "rea-efficiency-c5c997e501044f45a2d624a8aa0c6aa8.scope", + "payload_exit_code": 0, + "observer_pid": 4001991, + "remaining_processes": [4001991], + "payload_cleanup_verified": true, + "cpu_stat": { + "usage_usec": 92227652, + "user_usec": 87187787, + "system_usec": 5039865, + "nr_periods": 928, + "nr_throttled": 7, + "throttled_usec": 1830, + "nr_bursts": 0, + "burst_usec": 0 + }, + "memory_peak_bytes": 4371951616, + "control_group": "user.slice/user-1004.slice/user@1004.service/app.slice/rea-efficiency-c5c997e501044f45a2d624a8aa0c6aa8.scope", + "resource_events": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + } + }, + "payload_resource_observation_verified": true, + "payload_observation_scope": "Counters at payload exit while observer remains alive; not final whole-scope counters.", + "payload_observer_sources_sha256": { + "resource-run.py": "7728862171415e7dee2c11af2ead2b9a54265fccaf82a7113956d36b29065e33", + "resource_payload.py": "6f089e645459eba80c5d73e39c21606c78173a44daf49fabc3b88c4631ce51c6" + } + }, + "scope_poststate": "ActiveState=inactive\nSubState=dead", + "result": "two uncached tasks passed" + }, + "real_app": { + "log_sha256": "6272fdaf8806a3608af035480a55a3f2ef7e6cd596e0275a00bf5d19833d332b", + "resource": { + "resource_scope": "rea-efficiency-7a55ecbb06694e0e95b1a84ac387f44f.scope", + "elapsed_seconds": 11.238, + "abort": null, + "hard_memory_bytes": 8589934592, + "early_stop_bytes": 6442450944, + "cpu_quota_percent": 100, + "cpu_affinity": [10], + "task_ceiling": 192, + "payload_resource_observation": { + "phase": "payload-exited-observer-alive", + "ownership_token": "rea-efficiency-7a55ecbb06694e0e95b1a84ac387f44f.scope", + "payload_exit_code": 1, + "observer_pid": 4002524, + "remaining_processes": [4002524, 4002536], + "payload_cleanup_verified": false, + "cpu_stat": { + "usage_usec": 6285321, + "user_usec": 4746647, + "system_usec": 1538674, + "nr_periods": 111, + "nr_throttled": 1, + "throttled_usec": 228, + "nr_bursts": 0, + "burst_usec": 0 + }, + "memory_peak_bytes": 556285952, + "control_group": "user.slice/user-1004.slice/user@1004.service/app.slice/rea-efficiency-7a55ecbb06694e0e95b1a84ac387f44f.scope", + "resource_events": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + } + }, + "payload_resource_observation_verified": false, + "payload_observation_scope": "Counters at payload exit while observer remains alive; not final whole-scope counters.", + "payload_observer_sources_sha256": { + "resource-run.py": "7728862171415e7dee2c11af2ead2b9a54265fccaf82a7113956d36b29065e33", + "resource_payload.py": "6f089e645459eba80c5d73e39c21606c78173a44daf49fabc3b88c4631ce51c6" + } + }, + "scope_poststate": "ActiveState=inactive\nSubState=dead", + "summary": { + "elapsed_ms": 10159.257436, + "deadline_triggered": false, + "selected_shape": ".geSidebar a.geItem:visible >> nth=0", + "before": { + "title": "Untitled Diagram - draw.io", + "invalid_file_dialog": true, + "unsaved_changes": false, + "diagram_svg_elements": 0, + "cells": 0 + }, + "after": { + "title": "Untitled Diagram - draw.io", + "invalid_file_dialog": false, + "unsaved_changes": true, + "diagram_svg_elements": 2, + "cells": 0 + }, + "actions_completed": true, + "shape_inserted": true, + "provider_ok": false, + "cleanup_verified": false, + "reason": "cleanup_failed", + "coverage": { + "status": "partial_attach", + "observed_event_families": [ + "app-lifecycle", + "ipc", + "navigation", + "preload-configuration", + "web-contents-lifecycle", + "window-lifecycle" + ], + "unavailable_event_families": [ + "download", + "error", + "native-addon", + "permission", + "popup-attempt", + "preload", + "process-lifecycle", + "protocol", + "renderer-ipc", + "shell-attempt", + "updater" + ], + "observed_roles": ["browser", "main", "web_contents", "window"], + "pre_capture_activity": "unavailable" + }, + "retention": { + "budget_bytes": 16777216, + "estimated_retained_bytes": 202114, + "event_serialized_byte_upper_bound": 48214, + "retained": 109, + "dropped": 0, + "dropped_event_families": [], + "dropped_event_roles": [], + "observed_ipc": 7, + "dropped_ipc": 0, + "observed_runtime": 102, + "dropped_runtime": 0 + }, + "actions": [ + { + "step_id": "dismiss-invalid-file", + "kind": "click", + "window_index": 0, + "target": "window:0", + "elapsed_ms": 102, + "status": "completed", + "error": null + }, + { + "step_id": "insert-shape", + "kind": "click", + "window_index": 0, + "target": "window:0", + "elapsed_ms": 123, + "status": "completed", + "error": null + }, + { + "step_id": "settle-renderer", + "kind": "wait", + "window_index": 0, + "target": "window:0", + "elapsed_ms": 501, + "status": "completed", + "error": null + } + ], + "workflow_status": "partial-or-failed" + }, + "ipc_observed": 7, + "result": "Real UI actions complete; overall provider cleanup failed. Payload-exit resource observation unverified because another scope descendant remains; not complete-family measurement." + }, + "preceding_ci": { + "identity": { + "run": 38949129899, + "actual_checkout": "23fbf05eb64cd6d838061581ae3916c57482f00e", + "actual_head": "e6c373d0169e688db244a2583b707d56fcb246f3", + "actual_base": "4838f444939677107902549c59b73439e14d3ae9", + "build_log_sha256": "83d4014ac9f714287ac7374223d21165e66c1b1596d1983e5d1c05d1775d867c", + "result": "pending; checkout identity only" + }, + "terminal_jobs": { + "cancelled": [ + { + "id": 117136988304, + "name": "Test shard (4/5)" + }, + { + "id": 117136988308, + "name": "Test shard (2/5)" + } + ], + "counts": [ + { + "conclusion": "cancelled", + "count": 2 + }, + { + "conclusion": "failure", + "count": 3 + }, + { + "conclusion": "success", + "count": 31 + } + ], + "failed": [ + { + "html_url": "https://github.com/morluto/rea/actions/runs/38949129899/job/117136988800", + "id": 117136988800, + "name": "Test shard (5/5)" + }, + { + "html_url": "https://github.com/morluto/rea/actions/runs/38949129899/job/117141889043", + "id": 117141889043, + "name": "Portable tests" + }, + { + "html_url": "https://github.com/morluto/rea/actions/runs/38949129899/job/117142144521", + "id": 117142144521, + "name": "CI required" + } + ], + "loaded": 36, + "terminal": true, + "total_count": 36 + }, + "fifth_shard_log_sha256": "b967c534f05677c3e6315fd1ef0396fed76690129d918c43bf5b978b40f59036", + "failures": "Same two firmware failures as unmodified main control; no firmware diff or check bypass." + }, + "docs_check": { + "result": "six tasks and five README consistency cases passed", + "log_sha256": "5fced3abcff52abba960449ccfa015606399e0bcff8e9e17afda3c35ad8d53cc", + "resource": { + "resource_scope": "rea-efficiency-27ed682e360f43249002323ccbac16c0.scope", + "elapsed_seconds": 9.33, + "abort": null, + "hard_memory_bytes": 8589934592, + "early_stop_bytes": 6442450944, + "cpu_quota_percent": 100, + "cpu_affinity": [10], + "task_ceiling": 192, + "payload_resource_observation": { + "phase": "payload-exited-observer-alive", + "ownership_token": "rea-efficiency-27ed682e360f43249002323ccbac16c0.scope", + "payload_exit_code": 0, + "observer_pid": 4002935, + "remaining_processes": [4002935], + "payload_cleanup_verified": true, + "cpu_stat": { + "usage_usec": 9010657, + "user_usec": 7252402, + "system_usec": 1758255, + "nr_periods": 91, + "nr_throttled": 4, + "throttled_usec": 69, + "nr_bursts": 0, + "burst_usec": 0 + }, + "memory_peak_bytes": 466321408, + "control_group": "user.slice/user-1004.slice/user@1004.service/app.slice/rea-efficiency-27ed682e360f43249002323ccbac16c0.scope", + "resource_events": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + } + }, + "payload_resource_observation_verified": true, + "payload_observation_scope": "Counters at payload exit while observer remains alive; not final whole-scope counters.", + "payload_observer_sources_sha256": { + "resource-run.py": "7728862171415e7dee2c11af2ead2b9a54265fccaf82a7113956d36b29065e33", + "resource_payload.py": "6f089e645459eba80c5d73e39c21606c78173a44daf49fabc3b88c4631ce51c6" + } + }, + "scope_poststate": "ActiveState=inactive\nSubState=dead" + } + }, + "chain_ownership_followup": { + "source_pins": { + "source_revision": "ef46b8f0b5e53c3ae2bc5bdaf37f7f8cb9d78995", + "recorded_utc": "2026-10-16T19:22:21.281077+00:00", + "source_sha256": { + "scripts/electron-active-hook.cjs": "e02189d2db242655721b41544438291e796a568de48b0bdd61c990ced34178d4", + "src/domain/javascript/electronActiveObservation.ts": "868d126480785898008a6d7de75cbcbb273eca1185d27c0decb72bdbd5a3630c", + "src/browser/PlaywrightElectronActiveProjection.ts": "319633d684977523c5a2c1a2c4863f3e73c156a34fa06b250fbb1722b3899ffa", + "src/browser/PlaywrightElectronActiveProjection.test.ts": "8c2531e4a5480895c55b1daf7284c157938b1017850e3da2ed65d073c2122c69", + "tests/boundary/browser/electronActiveHook.test.ts": "85f7b951f9ed3521c693dd212d6e0969633afa07183713c84341e44f62170ecc", + "tests/boundary/mcp/electronMcp.test.ts": "4af403fc089c189bcedc034713198cc2014eb278085f545a1e328c39de35d9b5", + "experiments/analysis-efficiency/drawio-electron-lifecycle.mjs": "1423a472f58fc81383061bf695059550276b898c780e91bb47052797b25e0ce6", + "experiments/analysis-efficiency/drawio-electron-lifecycle-oracles.py": "c98dd2abf16703744aa32edc5dd12b0f4eec121d40a4aaa387ef580b046032e2" + }, + "verifier_and_five_oracle_sources_unchanged": true, + "compiled_runtime_root": "/home/yann/yann/rea-electron-ipc-listener-4390-20261016", + "artifact_sha256": { + "app.asar": "e64b9650c0b8241ca9a7d33b56386a385bceee63270cec701d3a467347b697e6" + }, + "scope": "Frozen after compiled runtime rebuilt and final13 client/projection cases pass, before actual real-app trial. Ownership unknown is preserved as bounded registration gaps.", + "compiled_sha256": { + "dist/browser/PlaywrightElectronActiveProjection.js": "9732f6422fff971b75a614225d4da69ca401552ce5a650308224ed897ce6f26c", + "dist/domain/javascript/electronActiveObservation.js": "21d8401adde0eacfd018c834b8e0bcc0ce7cb907b288a02ff7712a4b0e94d13d" + }, + "selected_electron_runtime": { + "executable": "/tmp/rea-4390-realapps-20261016/electron-44.7.0/electron", + "executable_sha256": "10a14d05c6ff4f94075cfb3eeb6ed6571be33ebcc08cbd675b5ce9ff84706564", + "version": "44.7.0", + "archive_sha256": "3ae7d5bdad61c664486c6ab61361dd084d064c8c08af8d13a687096e18ce555a" + }, + "runtime_pin_recorded_before_trial_utc": "2026-10-16T17:57:17.849958+00:00", + "unchanged_sources_reusing_passing_schema_client_and_oracle_checks": [ + "src/domain/javascript/electronActiveObservation.ts", + "experiments/analysis-efficiency/drawio-electron-lifecycle.mjs", + "experiments/analysis-efficiency/drawio-electron-lifecycle-oracles.py" + ], + "reuse_scope": "Canonical schema and verifier/five controlled oracle sources unchanged. Projection and MCP client changed and were freshly tested, not reused." + }, + "scope": "Owned/unowned/unknown metadata classification. Successful unknown registrations retain listener-chain gaps, not proof of IPC delivery; native registration remains unwrapped. Shared descriptor parsing and projection/MCP semantics; no new schema kind, tool, cache or authority.", + "negative_controls": { + "log_sha256": "81a57a6ad1dd3bca183b5623523587dfc13a3f7048e8b3eb6d766535bfde1399", + "resource": { + "resource_scope": "rea-efficiency-02ff5047e3ad47a78101fc3ebfba8599.scope", + "elapsed_seconds": 6.026, + "abort": null, + "hard_memory_bytes": 8589934592, + "early_stop_bytes": 6442450944, + "cpu_quota_percent": 100, + "cpu_affinity": [10], + "task_ceiling": 192, + "payload_resource_observation": { + "phase": "payload-exited-observer-alive", + "ownership_token": "rea-efficiency-02ff5047e3ad47a78101fc3ebfba8599.scope", + "payload_exit_code": 1, + "observer_pid": 4008711, + "remaining_processes": [4008711], + "payload_cleanup_verified": true, + "cpu_stat": { + "usage_usec": 5721068, + "user_usec": 3744984, + "system_usec": 1976084, + "nr_periods": 59, + "nr_throttled": 2, + "throttled_usec": 1081, + "nr_bursts": 0, + "burst_usec": 0 + }, + "memory_peak_bytes": 327770112, + "control_group": "user.slice/user-1004.slice/user@1004.service/app.slice/rea-efficiency-02ff5047e3ad47a78101fc3ebfba8599.scope", + "resource_events": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + } + }, + "payload_resource_observation_verified": true, + "payload_observation_scope": "Counters at payload exit while observer remains alive; not final whole-scope counters.", + "payload_observer_sources_sha256": { + "resource-run.py": "7728862171415e7dee2c11af2ead2b9a54265fccaf82a7113956d36b29065e33", + "resource_payload.py": "6f089e645459eba80c5d73e39c21606c78173a44daf49fabc3b88c4631ce51c6" + } + }, + "scope_poststate": "ActiveState=inactive\nSubState=dead", + "result": "63 passed /4 failed; depth16 already passes, depth17 once/on and accessor/Proxy unknown boundaries fail on0b4f" + }, + "hook_projection_actions_mcp": { + "log_sha256": "30c75aacbd584659ff01ad7c48cfeba5793e32e82e723e5d07e7a719ddb0258a", + "resource": { + "resource_scope": "rea-efficiency-e5981c433c1d4002800886305f86cf1e.scope", + "elapsed_seconds": 66.803, + "abort": null, + "hard_memory_bytes": 8589934592, + "early_stop_bytes": 6442450944, + "cpu_quota_percent": 100, + "cpu_affinity": [10], + "task_ceiling": 192, + "payload_resource_observation": { + "phase": "payload-exited-observer-alive", + "ownership_token": "rea-efficiency-e5981c433c1d4002800886305f86cf1e.scope", + "payload_exit_code": 0, + "observer_pid": 4009751, + "remaining_processes": [4009751], + "payload_cleanup_verified": true, + "cpu_stat": { + "usage_usec": 65703220, + "user_usec": 56408602, + "system_usec": 9294618, + "nr_periods": 667, + "nr_throttled": 39, + "throttled_usec": 24396, + "nr_bursts": 0, + "burst_usec": 0 + }, + "memory_peak_bytes": 3159363584, + "control_group": "user.slice/user-1004.slice/user@1004.service/app.slice/rea-efficiency-e5981c433c1d4002800886305f86cf1e.scope", + "resource_events": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + } + }, + "payload_resource_observation_verified": true, + "payload_observation_scope": "Counters at payload exit while observer remains alive; not final whole-scope counters.", + "payload_observer_sources_sha256": { + "resource-run.py": "7728862171415e7dee2c11af2ead2b9a54265fccaf82a7113956d36b29065e33", + "resource_payload.py": "6f089e645459eba80c5d73e39c21606c78173a44daf49fabc3b88c4631ce51c6" + } + }, + "scope_poststate": "ActiveState=inactive\nSubState=dead", + "result": "96 passed:67 hook+5 projection+16 actions+8 MCP; runtime rebuilt" + }, + "fixture_lint_failure": { + "log_sha256": "184672514f56f6472d7e68d8c99235b5596764f6046ed177de90e7e875229346", + "resource": { + "resource_scope": "rea-efficiency-a3caf3f16365400faadf8ad78474bf1d.scope", + "elapsed_seconds": 1.879, + "abort": null, + "hard_memory_bytes": 8589934592, + "early_stop_bytes": 6442450944, + "cpu_quota_percent": 100, + "cpu_affinity": [10], + "task_ceiling": 192, + "payload_resource_observation": { + "phase": "payload-exited-observer-alive", + "ownership_token": "rea-efficiency-a3caf3f16365400faadf8ad78474bf1d.scope", + "payload_exit_code": 1, + "observer_pid": 4010947, + "remaining_processes": [4010947], + "payload_cleanup_verified": true, + "cpu_stat": { + "usage_usec": 1503286, + "user_usec": 968715, + "system_usec": 534570, + "nr_periods": 17, + "nr_throttled": 0, + "throttled_usec": 0, + "nr_bursts": 0, + "burst_usec": 0 + }, + "memory_peak_bytes": 138440704, + "control_group": "user.slice/user-1004.slice/user@1004.service/app.slice/rea-efficiency-a3caf3f16365400faadf8ad78474bf1d.scope", + "resource_events": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + } + }, + "payload_resource_observation_verified": true, + "payload_observation_scope": "Counters at payload exit while observer remains alive; not final whole-scope counters.", + "payload_observer_sources_sha256": { + "resource-run.py": "7728862171415e7dee2c11af2ead2b9a54265fccaf82a7113956d36b29065e33", + "resource_payload.py": "6f089e645459eba80c5d73e39c21606c78173a44daf49fabc3b88c4631ce51c6" + } + }, + "scope_poststate": "ActiveState=inactive\nSubState=dead", + "result": "precommit fails new MCP fixture function length; no commit created" + }, + "intermediate_static_failure": { + "log_sha256": "4443af47fc462803c7c689da44e284dcf11bacf759d7b82ea4fd640672815ba8", + "resource": { + "resource_scope": "rea-efficiency-976f6d2b3b334c18800e3cc36439959d.scope", + "elapsed_seconds": 7.027, + "abort": null, + "hard_memory_bytes": 8589934592, + "early_stop_bytes": 6442450944, + "cpu_quota_percent": 100, + "cpu_affinity": [10], + "task_ceiling": 192, + "payload_resource_observation": { + "phase": "payload-exited-observer-alive", + "ownership_token": "rea-efficiency-976f6d2b3b334c18800e3cc36439959d.scope", + "payload_exit_code": 1, + "observer_pid": 4011442, + "remaining_processes": [4011442], + "payload_cleanup_verified": true, + "cpu_stat": { + "usage_usec": 6672578, + "user_usec": 5501960, + "system_usec": 1170618, + "nr_periods": 68, + "nr_throttled": 3, + "throttled_usec": 651, + "nr_bursts": 0, + "burst_usec": 0 + }, + "memory_peak_bytes": 1758814208, + "control_group": "user.slice/user-1004.slice/user@1004.service/app.slice/rea-efficiency-976f6d2b3b334c18800e3cc36439959d.scope", + "resource_events": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + } + }, + "payload_resource_observation_verified": true, + "payload_observation_scope": "Counters at payload exit while observer remains alive; not final whole-scope counters.", + "payload_observer_sources_sha256": { + "resource-run.py": "7728862171415e7dee2c11af2ead2b9a54265fccaf82a7113956d36b29065e33", + "resource_payload.py": "6f089e645459eba80c5d73e39c21606c78173a44daf49fabc3b88c4631ce51c6" + } + }, + "scope_poststate": "ActiveState=inactive\nSubState=dead", + "result": "same fixture lint failure; rule retained, rows refactored into table" + }, + "final_client_projection": { + "log_sha256": "0c9dcee7f136cf987f680fddccd6efad11b818c05b587587dfbe2eba2cf76db2", + "resource": { + "resource_scope": "rea-efficiency-81736705b08042b891c80d2e91ec5473.scope", + "elapsed_seconds": 24.75, + "abort": null, + "hard_memory_bytes": 8589934592, + "early_stop_bytes": 6442450944, + "cpu_quota_percent": 100, + "cpu_affinity": [10], + "task_ceiling": 192, + "payload_resource_observation": { + "phase": "payload-exited-observer-alive", + "ownership_token": "rea-efficiency-81736705b08042b891c80d2e91ec5473.scope", + "payload_exit_code": 0, + "observer_pid": 4012739, + "remaining_processes": [4012739], + "payload_cleanup_verified": true, + "cpu_stat": { + "usage_usec": 24288450, + "user_usec": 20895738, + "system_usec": 3392711, + "nr_periods": 246, + "nr_throttled": 6, + "throttled_usec": 6058, + "nr_bursts": 0, + "burst_usec": 0 + }, + "memory_peak_bytes": 1299320832, + "control_group": "user.slice/user-1004.slice/user@1004.service/app.slice/rea-efficiency-81736705b08042b891c80d2e91ec5473.scope", + "resource_events": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + } + }, + "payload_resource_observation_verified": true, + "payload_observation_scope": "Counters at payload exit while observer remains alive; not final whole-scope counters.", + "payload_observer_sources_sha256": { + "resource-run.py": "7728862171415e7dee2c11af2ead2b9a54265fccaf82a7113956d36b29065e33", + "resource_payload.py": "6f089e645459eba80c5d73e39c21606c78173a44daf49fabc3b88c4631ce51c6" + } + }, + "scope_poststate": "ActiveState=inactive\nSubState=dead", + "result": "13 passed:8 MCP+5 projection after fixture refactor" + }, + "final_static": { + "log_sha256": "d54dd95627805366a3e83e3b29422f965760cf566e4799249fa64392df0e4c6b", + "resource": { + "resource_scope": "rea-efficiency-63d2c4e4e435412ba9be4fe8136cdf73.scope", + "elapsed_seconds": 88.383, + "abort": null, + "hard_memory_bytes": 8589934592, + "early_stop_bytes": 6442450944, + "cpu_quota_percent": 100, + "cpu_affinity": [10], + "task_ceiling": 192, + "payload_resource_observation": { + "phase": "payload-exited-observer-alive", + "ownership_token": "rea-efficiency-63d2c4e4e435412ba9be4fe8136cdf73.scope", + "payload_exit_code": 0, + "observer_pid": 4013148, + "remaining_processes": [4013148], + "payload_cleanup_verified": true, + "cpu_stat": { + "usage_usec": 87726306, + "user_usec": 84126991, + "system_usec": 3599314, + "nr_periods": 883, + "nr_throttled": 63, + "throttled_usec": 22045, + "nr_bursts": 0, + "burst_usec": 0 + }, + "memory_peak_bytes": 4399632384, + "control_group": "user.slice/user-1004.slice/user@1004.service/app.slice/rea-efficiency-63d2c4e4e435412ba9be4fe8136cdf73.scope", + "resource_events": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + } + }, + "payload_resource_observation_verified": true, + "payload_observation_scope": "Counters at payload exit while observer remains alive; not final whole-scope counters.", + "payload_observer_sources_sha256": { + "resource-run.py": "7728862171415e7dee2c11af2ead2b9a54265fccaf82a7113956d36b29065e33", + "resource_payload.py": "6f089e645459eba80c5d73e39c21606c78173a44daf49fabc3b88c4631ce51c6" + } + }, + "scope_poststate": "ActiveState=inactive\nSubState=dead", + "result": "two uncached tasks passed" + }, + "real_app": { + "log_sha256": "b3282e35514ef0658947cd004a991106145a7da1d1902c3a11b041bb719ed330", + "resource": { + "resource_scope": "rea-efficiency-03ecd4d051de4f8f86c936c653801662.scope", + "elapsed_seconds": 11.163, + "abort": null, + "hard_memory_bytes": 8589934592, + "early_stop_bytes": 6442450944, + "cpu_quota_percent": 100, + "cpu_affinity": [10], + "task_ceiling": 192, + "payload_resource_observation": { + "phase": "payload-exited-observer-alive", + "ownership_token": "rea-efficiency-03ecd4d051de4f8f86c936c653801662.scope", + "payload_exit_code": 1, + "observer_pid": 4013918, + "remaining_processes": [4013918, 4013930, 4014047, 4014049], + "payload_cleanup_verified": false, + "cpu_stat": { + "usage_usec": 6203177, + "user_usec": 4588107, + "system_usec": 1615069, + "nr_periods": 110, + "nr_throttled": 1, + "throttled_usec": 3971, + "nr_bursts": 0, + "burst_usec": 0 + }, + "memory_peak_bytes": 547418112, + "control_group": "user.slice/user-1004.slice/user@1004.service/app.slice/rea-efficiency-03ecd4d051de4f8f86c936c653801662.scope", + "resource_events": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + } + }, + "payload_resource_observation_verified": false, + "payload_observation_scope": "Counters at payload exit while observer remains alive; not final whole-scope counters.", + "payload_observer_sources_sha256": { + "resource-run.py": "7728862171415e7dee2c11af2ead2b9a54265fccaf82a7113956d36b29065e33", + "resource_payload.py": "6f089e645459eba80c5d73e39c21606c78173a44daf49fabc3b88c4631ce51c6" + } + }, + "scope_poststate": "ActiveState=inactive\nSubState=dead", + "summary": { + "elapsed_ms": 10072.396793, + "deadline_triggered": false, + "selected_shape": ".geSidebar a.geItem:visible >> nth=0", + "before": { + "title": "Untitled Diagram - draw.io", + "invalid_file_dialog": true, + "unsaved_changes": false, + "diagram_svg_elements": 0, + "cells": 0 + }, + "after": { + "title": "Untitled Diagram - draw.io", + "invalid_file_dialog": false, + "unsaved_changes": true, + "diagram_svg_elements": 2, + "cells": 0 + }, + "actions_completed": true, + "shape_inserted": true, + "provider_ok": false, + "cleanup_verified": false, + "reason": "cleanup_failed", + "coverage": { + "status": "partial_attach", + "observed_event_families": [ + "app-lifecycle", + "ipc", + "navigation", + "preload-configuration", + "web-contents-lifecycle", + "window-lifecycle" + ], + "unavailable_event_families": [ + "download", + "error", + "native-addon", + "permission", + "popup-attempt", + "preload", + "process-lifecycle", + "protocol", + "renderer-ipc", + "shell-attempt", + "updater" + ], + "observed_roles": ["browser", "main", "web_contents", "window"], + "pre_capture_activity": "unavailable" + }, + "retention": { + "budget_bytes": 16777216, + "estimated_retained_bytes": 202114, + "event_serialized_byte_upper_bound": 48214, + "retained": 109, + "dropped": 0, + "dropped_event_families": [], + "dropped_event_roles": [], + "observed_ipc": 7, + "dropped_ipc": 0, + "observed_runtime": 102, + "dropped_runtime": 0 + }, + "actions": [ + { + "step_id": "dismiss-invalid-file", + "kind": "click", + "window_index": 0, + "target": "window:0", + "elapsed_ms": 86, + "status": "completed", + "error": null + }, + { + "step_id": "insert-shape", + "kind": "click", + "window_index": 0, + "target": "window:0", + "elapsed_ms": 115, + "status": "completed", + "error": null + }, + { + "step_id": "settle-renderer", + "kind": "wait", + "window_index": 0, + "target": "window:0", + "elapsed_ms": 501, + "status": "completed", + "error": null + } + ], + "workflow_status": "partial-or-failed" + }, + "ipc_observed": 7, + "result": "Actual unchanged app UI actions complete, provider cleanup fails. Extra scope descendant leaves complete-family resource observation unverified; later external inactive/dead scope is not provider cleanup proof." + }, + "preceding_ci": { + "identity": { + "run": 38951509761, + "actual_checkout": "fa090a6bde3a5dadeb71066824477292520fb43b", + "actual_head": "0b4f2e8104529ded9f8411ec2219d038feb4b9b6", + "actual_base": "f31b231a92a61be6bd1800b16be0b3210ae82944", + "build_log_sha256": "b99ff61f3d70ed6682f0e80279db5522b26cfb49d982e0930818a376cf6b1857", + "result": "pending; checkout identity only" + }, + "terminal_jobs": { + "cancelled": [], + "counts": [ + { + "conclusion": "success", + "count": 36 + } + ], + "failed": [], + "loaded": 36, + "terminal": true, + "total_count": 36 + }, + "result": "All36 jobs succeed on0b4f + mainf31. Earlier firmware failures do not recur in this combination; no cause inferred or firmware fix claimed." + }, + "docs_check": { + "result": "six tasks and five README consistency cases passed", + "log_sha256": "080713205f0ebd296726efab159c0a312921c16481bc271c73064d6ff7f63f01", + "resource": { + "resource_scope": "rea-efficiency-3bec9678bbbc45acad439822712ead0e.scope", + "elapsed_seconds": 27.017, + "abort": null, + "hard_memory_bytes": 8589934592, + "early_stop_bytes": 6442450944, + "cpu_quota_percent": 100, + "cpu_affinity": [10], + "task_ceiling": 192, + "payload_resource_observation": { + "phase": "payload-exited-observer-alive", + "ownership_token": "rea-efficiency-3bec9678bbbc45acad439822712ead0e.scope", + "payload_exit_code": 0, + "observer_pid": 4014191, + "remaining_processes": [4014191], + "payload_cleanup_verified": true, + "cpu_stat": { + "usage_usec": 26459125, + "user_usec": 19764497, + "system_usec": 6694627, + "nr_periods": 268, + "nr_throttled": 27, + "throttled_usec": 20968, + "nr_bursts": 0, + "burst_usec": 0 + }, + "memory_peak_bytes": 465231872, + "control_group": "user.slice/user-1004.slice/user@1004.service/app.slice/rea-efficiency-3bec9678bbbc45acad439822712ead0e.scope", + "resource_events": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + } + }, + "payload_resource_observation_verified": true, + "payload_observation_scope": "Counters at payload exit while observer remains alive; not final whole-scope counters.", + "payload_observer_sources_sha256": { + "resource-run.py": "7728862171415e7dee2c11af2ead2b9a54265fccaf82a7113956d36b29065e33", + "resource_payload.py": "6f089e645459eba80c5d73e39c21606c78173a44daf49fabc3b88c4631ce51c6" + } + }, + "scope_poststate": "ActiveState=inactive\nSubState=dead" + } + }, + "inherited_listener_links_followup": { + "source_pins": { + "source_revision": "e22ce9aaf74e867347c522c938fb050141afe2ad", + "recorded_utc": "2026-10-16T19:49:23.759697+00:00", + "source_sha256": { + "scripts/electron-active-hook.cjs": "6e39dfe414813b28faca734ceeca20b23f46b271e5f1b8ede943d462aa8ba01c", + "src/domain/javascript/electronActiveObservation.ts": "868d126480785898008a6d7de75cbcbb273eca1185d27c0decb72bdbd5a3630c", + "src/browser/PlaywrightElectronActiveProjection.ts": "319633d684977523c5a2c1a2c4863f3e73c156a34fa06b250fbb1722b3899ffa", + "src/browser/PlaywrightElectronActiveProjection.test.ts": "8c2531e4a5480895c55b1daf7284c157938b1017850e3da2ed65d073c2122c69", + "tests/boundary/browser/electronActiveHook.test.ts": "c894800b1a1301a4e5f0a34b612d94f7d0ab343f8449190e8e82b206768ac896", + "tests/boundary/mcp/electronMcp.test.ts": "4af403fc089c189bcedc034713198cc2014eb278085f545a1e328c39de35d9b5", + "experiments/analysis-efficiency/drawio-electron-lifecycle.mjs": "1423a472f58fc81383061bf695059550276b898c780e91bb47052797b25e0ce6", + "experiments/analysis-efficiency/drawio-electron-lifecycle-oracles.py": "c98dd2abf16703744aa32edc5dd12b0f4eec121d40a4aaa387ef580b046032e2" + }, + "verifier_and_five_oracle_sources_unchanged": true, + "compiled_runtime_root": "/home/yann/yann/rea-electron-ipc-listener-4390-20261016", + "artifact_sha256": { + "app.asar": "e64b9650c0b8241ca9a7d33b56386a385bceee63270cec701d3a467347b697e6" + }, + "scope": "Frozen before inherited-listener metadata follow-up real trial. Both function-link and prototype walks are bounded and distinguish unknown from complete absence.", + "compiled_sha256": { + "dist/browser/PlaywrightElectronActiveProjection.js": "9732f6422fff971b75a614225d4da69ca401552ce5a650308224ed897ce6f26c", + "dist/domain/javascript/electronActiveObservation.js": "21d8401adde0eacfd018c834b8e0bcc0ce7cb907b288a02ff7712a4b0e94d13d" + }, + "selected_electron_runtime": { + "executable": "/tmp/rea-4390-realapps-20261016/electron-44.7.0/electron", + "executable_sha256": "10a14d05c6ff4f94075cfb3eeb6ed6571be33ebcc08cbd675b5ce9ff84706564", + "version": "44.7.0", + "archive_sha256": "3ae7d5bdad61c664486c6ab61361dd084d064c8c08af8d13a687096e18ce555a" + }, + "runtime_pin_recorded_before_trial_utc": "2026-10-16T17:57:17.849958+00:00", + "reuse_scope": "Canonical schema and verifier/five controlled oracles reuse earlier unchanged hashes. Projection/actions/MCP are included in freshly passing102 cases despite unchanged sources.", + "unchanged_source_paths": [ + "src/domain/javascript/electronActiveObservation.ts", + "src/browser/PlaywrightElectronActiveProjection.ts", + "src/browser/PlaywrightElectronActiveProjection.test.ts", + "tests/boundary/mcp/electronMcp.test.ts", + "experiments/analysis-efficiency/drawio-electron-lifecycle.mjs", + "experiments/analysis-efficiency/drawio-electron-lifecycle-oracles.py" + ] + }, + "scope": "Bounded native prototype lookup before proving metadata absence; getter/Proxy/bound unknown, setter-only known undefined shadow. Same parser owns recognition/removal. No increased bounds/new schema/tool/cache/authority.", + "negative_controls": { + "log_sha256": "c62e95da0d12211bd025142f8c60cc953a72b8f50e666aea1c87cc27175c49ed", + "resource": { + "resource_scope": "rea-efficiency-dbe0855338f44073874ba60211284433.scope", + "elapsed_seconds": 5.903, + "abort": null, + "hard_memory_bytes": 8589934592, + "early_stop_bytes": 6442450944, + "cpu_quota_percent": 100, + "cpu_affinity": [10], + "task_ceiling": 192, + "payload_resource_observation": { + "phase": "payload-exited-observer-alive", + "ownership_token": "rea-efficiency-dbe0855338f44073874ba60211284433.scope", + "payload_exit_code": 1, + "observer_pid": 4020537, + "remaining_processes": [4020537], + "payload_cleanup_verified": true, + "cpu_stat": { + "usage_usec": 5691586, + "user_usec": 3825045, + "system_usec": 1866541, + "nr_periods": 58, + "nr_throttled": 2, + "throttled_usec": 919, + "nr_bursts": 0, + "burst_usec": 0 + }, + "memory_peak_bytes": 317288448, + "control_group": "user.slice/user-1004.slice/user@1004.service/app.slice/rea-efficiency-dbe0855338f44073874ba60211284433.scope", + "resource_events": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + } + }, + "payload_resource_observation_verified": true, + "payload_observation_scope": "Counters at payload exit while observer remains alive; not final whole-scope counters.", + "payload_observer_sources_sha256": { + "resource-run.py": "7728862171415e7dee2c11af2ead2b9a54265fccaf82a7113956d36b29065e33", + "resource_payload.py": "6f089e645459eba80c5d73e39c21606c78173a44daf49fabc3b88c4631ce51c6" + } + }, + "scope_poststate": "ActiveState=inactive\nSubState=dead", + "result": "68 passed /5 failed on ae2c; inherited data/getter/Proxy and prototype16/17 fail; late getPrototypeOf positive control already passes" + }, + "hook_projection_actions_mcp": { + "log_sha256": "49c26f54b8b9b7d9461cb717e8466fbdf5a0d703d40110c86bd62f4afcf2702f", + "resource": { + "resource_scope": "rea-efficiency-fef54e71542341fab51db7205d154887.scope", + "elapsed_seconds": 28.972, + "abort": null, + "hard_memory_bytes": 8589934592, + "early_stop_bytes": 6442450944, + "cpu_quota_percent": 100, + "cpu_affinity": [10], + "task_ceiling": 192, + "payload_resource_observation": { + "phase": "payload-exited-observer-alive", + "ownership_token": "rea-efficiency-fef54e71542341fab51db7205d154887.scope", + "payload_exit_code": 0, + "observer_pid": 4021328, + "remaining_processes": [4021328], + "payload_cleanup_verified": true, + "cpu_stat": { + "usage_usec": 28448290, + "user_usec": 23516737, + "system_usec": 4931552, + "nr_periods": 288, + "nr_throttled": 23, + "throttled_usec": 27920, + "nr_bursts": 0, + "burst_usec": 0 + }, + "memory_peak_bytes": 1281515520, + "control_group": "user.slice/user-1004.slice/user@1004.service/app.slice/rea-efficiency-fef54e71542341fab51db7205d154887.scope", + "resource_events": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + } + }, + "payload_resource_observation_verified": true, + "payload_observation_scope": "Counters at payload exit while observer remains alive; not final whole-scope counters.", + "payload_observer_sources_sha256": { + "resource-run.py": "7728862171415e7dee2c11af2ead2b9a54265fccaf82a7113956d36b29065e33", + "resource_payload.py": "6f089e645459eba80c5d73e39c21606c78173a44daf49fabc3b88c4631ce51c6" + } + }, + "scope_poststate": "ActiveState=inactive\nSubState=dead", + "result": "102 passed:73 hook +5 projection +16 actions +8 MCP" + }, + "static_check": { + "log_sha256": "d8a713e443b0a55c4353fa3b6fbfe17102b1d12b04d492d44ea0072b5055dcd9", + "resource": { + "resource_scope": "rea-efficiency-674970f09cce463cafef1e8971994d10.scope", + "elapsed_seconds": 90.797, + "abort": null, + "hard_memory_bytes": 8589934592, + "early_stop_bytes": 6442450944, + "cpu_quota_percent": 100, + "cpu_affinity": [10], + "task_ceiling": 192, + "payload_resource_observation": { + "phase": "payload-exited-observer-alive", + "ownership_token": "rea-efficiency-674970f09cce463cafef1e8971994d10.scope", + "payload_exit_code": 0, + "observer_pid": 4023158, + "remaining_processes": [4023158], + "payload_cleanup_verified": true, + "cpu_stat": { + "usage_usec": 90096799, + "user_usec": 85737104, + "system_usec": 4359694, + "nr_periods": 906, + "nr_throttled": 38, + "throttled_usec": 14575, + "nr_bursts": 0, + "burst_usec": 0 + }, + "memory_peak_bytes": 4335267840, + "control_group": "user.slice/user-1004.slice/user@1004.service/app.slice/rea-efficiency-674970f09cce463cafef1e8971994d10.scope", + "resource_events": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + } + }, + "payload_resource_observation_verified": true, + "payload_observation_scope": "Counters at payload exit while observer remains alive; not final whole-scope counters.", + "payload_observer_sources_sha256": { + "resource-run.py": "7728862171415e7dee2c11af2ead2b9a54265fccaf82a7113956d36b29065e33", + "resource_payload.py": "6f089e645459eba80c5d73e39c21606c78173a44daf49fabc3b88c4631ce51c6" + } + }, + "scope_poststate": "ActiveState=inactive\nSubState=dead", + "result": "two uncached tasks passed" + }, + "real_app": { + "log_sha256": "f0ce484413739e70111ccb4f853caf8fd0535287675827ac743c43f4af2e8d19", + "resource": { + "resource_scope": "rea-efficiency-b4372d0355fe425ca3c9046656d99506.scope", + "elapsed_seconds": 11.264, + "abort": null, + "hard_memory_bytes": 8589934592, + "early_stop_bytes": 6442450944, + "cpu_quota_percent": 100, + "cpu_affinity": [10], + "task_ceiling": 192, + "payload_resource_observation": { + "phase": "payload-exited-observer-alive", + "ownership_token": "rea-efficiency-b4372d0355fe425ca3c9046656d99506.scope", + "payload_exit_code": 1, + "observer_pid": 4023705, + "remaining_processes": [4023705], + "payload_cleanup_verified": true, + "cpu_stat": { + "usage_usec": 6376574, + "user_usec": 4777324, + "system_usec": 1599250, + "nr_periods": 111, + "nr_throttled": 3, + "throttled_usec": 10186, + "nr_bursts": 0, + "burst_usec": 0 + }, + "memory_peak_bytes": 557068288, + "control_group": "user.slice/user-1004.slice/user@1004.service/app.slice/rea-efficiency-b4372d0355fe425ca3c9046656d99506.scope", + "resource_events": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + } + }, + "payload_resource_observation_verified": true, + "payload_observation_scope": "Counters at payload exit while observer remains alive; not final whole-scope counters.", + "payload_observer_sources_sha256": { + "resource-run.py": "7728862171415e7dee2c11af2ead2b9a54265fccaf82a7113956d36b29065e33", + "resource_payload.py": "6f089e645459eba80c5d73e39c21606c78173a44daf49fabc3b88c4631ce51c6" + } + }, + "scope_poststate": "ActiveState=inactive\nSubState=dead", + "summary": { + "elapsed_ms": 10112.679721999999, + "deadline_triggered": false, + "selected_shape": ".geSidebar a.geItem:visible >> nth=0", + "before": { + "title": "Untitled Diagram - draw.io", + "invalid_file_dialog": true, + "unsaved_changes": false, + "diagram_svg_elements": 0, + "cells": 0 + }, + "after": { + "title": "Untitled Diagram - draw.io", + "invalid_file_dialog": false, + "unsaved_changes": true, + "diagram_svg_elements": 2, + "cells": 0 + }, + "actions_completed": true, + "shape_inserted": true, + "provider_ok": false, + "cleanup_verified": false, + "reason": "cleanup_failed", + "coverage": { + "status": "partial_attach", + "observed_event_families": [ + "app-lifecycle", + "ipc", + "navigation", + "preload-configuration", + "web-contents-lifecycle", + "window-lifecycle" + ], + "unavailable_event_families": [ + "download", + "error", + "native-addon", + "permission", + "popup-attempt", + "preload", + "process-lifecycle", + "protocol", + "renderer-ipc", + "shell-attempt", + "updater" + ], + "observed_roles": ["browser", "main", "web_contents", "window"], + "pre_capture_activity": "unavailable" + }, + "retention": { + "budget_bytes": 16777216, + "estimated_retained_bytes": 202114, + "event_serialized_byte_upper_bound": 48214, + "retained": 109, + "dropped": 0, + "dropped_event_families": [], + "dropped_event_roles": [], + "observed_ipc": 7, + "dropped_ipc": 0, + "observed_runtime": 102, + "dropped_runtime": 0 + }, + "actions": [ + { + "step_id": "dismiss-invalid-file", + "kind": "click", + "window_index": 0, + "target": "window:0", + "elapsed_ms": 119, + "status": "completed", + "error": null + }, + { + "step_id": "insert-shape", + "kind": "click", + "window_index": 0, + "target": "window:0", + "elapsed_ms": 125, + "status": "completed", + "error": null + }, + { + "step_id": "settle-renderer", + "kind": "wait", + "window_index": 0, + "target": "window:0", + "elapsed_ms": 501, + "status": "completed", + "error": null + } + ], + "workflow_status": "partial-or-failed" + }, + "ipc_observed": 7, + "result": "Actual unchanged app UI actions complete; overall provider cleanup failed. Payload-exit resource observation verified at that phase only, not final whole-scope/provider cleanup proof." + }, + "preceding_ci": { + "identity": { + "run": 38955237255, + "actual_checkout": "9c0ceb152c902c1339806608f8b4a010c3d22fc6", + "actual_head": "ae2cd8d41b12ee36e94cca9fdca6b71889465a06", + "actual_base": "8116dc06512b4609d8db63cebc9f2ff8cb964151", + "build_log_sha256": "a00f7b3c9c308396eb6e07d351f307e83720e8c9e7aa86019e13aa08a6807204", + "result": "pending; actual checkout identity only" + }, + "terminal_jobs": { + "cancelled": [], + "counts": [ + { + "conclusion": "success", + "count": 36 + } + ], + "failed": [], + "loaded": 36, + "terminal": true, + "total_count": 36 + }, + "result": "All36 jobs pass on ae2c + main8116." + }, + "docs_check": { + "result": "six tasks and five README consistency cases passed", + "log_sha256": "664dded0a383d163e598091a64e5a1c5b21e0beb3ca18b1b02944a981762df0c", + "resource": { + "resource_scope": "rea-efficiency-80ab85b3e7f3490a8b33d0c1d20533c0.scope", + "elapsed_seconds": 9.416, + "abort": null, + "hard_memory_bytes": 8589934592, + "early_stop_bytes": 6442450944, + "cpu_quota_percent": 100, + "cpu_affinity": [10], + "task_ceiling": 192, + "payload_resource_observation": { + "phase": "payload-exited-observer-alive", + "ownership_token": "rea-efficiency-80ab85b3e7f3490a8b33d0c1d20533c0.scope", + "payload_exit_code": 0, + "observer_pid": 4024170, + "remaining_processes": [4024170], + "payload_cleanup_verified": true, + "cpu_stat": { + "usage_usec": 9185306, + "user_usec": 7388075, + "system_usec": 1797230, + "nr_periods": 92, + "nr_throttled": 2, + "throttled_usec": 1510, + "nr_bursts": 0, + "burst_usec": 0 + }, + "memory_peak_bytes": 468070400, + "control_group": "user.slice/user-1004.slice/user@1004.service/app.slice/rea-efficiency-80ab85b3e7f3490a8b33d0c1d20533c0.scope", + "resource_events": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + } + }, + "payload_resource_observation_verified": true, + "payload_observation_scope": "Counters at payload exit while observer remains alive; not final whole-scope counters.", + "payload_observer_sources_sha256": { + "resource-run.py": "7728862171415e7dee2c11af2ead2b9a54265fccaf82a7113956d36b29065e33", + "resource_payload.py": "6f089e645459eba80c5d73e39c21606c78173a44daf49fabc3b88c4631ce51c6" + } + }, + "scope_poststate": "ActiveState=inactive\nSubState=dead" + } + } +} diff --git a/docs/verification/electron-ipc-listeners-2026-10-16.md b/docs/verification/electron-ipc-listeners-2026-10-16.md new file mode 100644 index 0000000000..7c1a86c2ce --- /dev/null +++ b/docs/verification/electron-ipc-listeners-2026-10-16.md @@ -0,0 +1,592 @@ +# Electron IPC listener verification, 2026-10-16 + +[#4390](https://github.com/morluto/rea/issues/4390), existing +[#4389](https://github.com/morluto/rea/pull/4389), umbrella +[#4027](https://github.com/morluto/rea/issues/4027). + +All 73 IPC boundary regressions pass, alongside shared projection/actions and +converted MCP schema checks. Real draw.io operation phases succeed. +Every real capture still **fails overall provider cleanup**. These are distinct +results; neither completed-task acceleration nor token savings is established. + +## Producing boundary and regressions + +The existing PR `efd452948` was integrated with main `180ba03bf` in `5c3b02468`. +It fixes removal through the caller's original function and duplicate recording +of `once` invocations. Independent review found two further producing-boundary +problems: wrapper recognition evaluated caller `.listener` getters, and a copied +`removeAllListeners` snapshot loop differed from native live-array traversal when +removal observers prepended a listener. + +Both new differential cases failed on that implementation (14 passed, two +failed). Fix `3c330f259` delegates traversal to Node, translating only owned `on` +wrappers at `removeListener`, and preserves the `off` alias. Descriptor reads +avoid evaluating getters. A further Proxy case exposed that descriptor reflection +can itself run a trap (16 passed, one failed); `2fb7f5523` uses Node's Proxy +identity first and removes the superseded catch. Those initial **17 cases** pass; +the later decorator finding and final 23-case validation are recorded below. +The native EventEmitter controls are semantic regressions, not real-app proof. + +The change uses weak wrapper keys, removes the copied traversal and its snapshot +allocation, and adds no tool, cache, schema or normal reply field. Related +compiled provider/service modules and catalog metadata were byte-identical to +baseline main `180ba03bf` at the initial source freeze. This does not prove +arbitrary function introspection equivalence: custom +`.listener` properties and raw wrapper re-registration retain the limitations +documented in [Electron observation](../electron-observation.md). + +## Unchanged real application and observed scenario + +Official [draw.io Desktop 32.4.1](https://github.com/jgraph/drawio-desktop/releases/tag/v32.4.1) +AppImage SHA-256 is +`c687a9f1ddcaa146b78547452d264c03c2afcf125639cba5deeb65f3e9554c9e`; +both the release asset digest and published checksum file match. Its unchanged +62,941,673-byte ASAR is +`e64b9650c0b8241ca9a7d33b56386a385bceee63270cec701d3a467347b697e6`. +Complete accompanying resources were extracted; no application source was edited. + +Public fuse inspection reports the official executable's Inspector fuse disabled. +No vendor Inspector attempt was made. The following trials explicitly select +generic Electron **44.7.0**, Xvfb and fresh profiles; they establish no packaged +runtime compatibility. + +The real renderer initially displays an **Invalid file data** dialog. A public +CLI startup smoke and independent DOM/argv probes preserve this failure, tracked +separately in [#4494](https://github.com/morluto/rea/issues/4494). Its exact cause +is not proven. Two attempts to click a palette item without handling the dialog +timed out and were cancelled; they remain failed diagnostics. An initial helper +also incorrectly queried `application.process()` after transport closure; the +corrected helper retains the original ChildProcess handle instead. + +The final scenario uses declared REA clicks to dismiss that actual error and +insert a visible palette item, then waits for rendering. Independent DOM reads +confirm SVG elements **0 → 2**, disappearance of the error and the real unsaved +changes indicator. The verifier exercises the real app callbacks and channels; +the production hook wraps their registration as described above. +The source also registers `app-load-finished` through both `once` and `on`; +recording its once callback twice adds a spurious event. + +## Initial fixed-source ABBA observations + +Verifier/source revision is `d02cc934c`; its hashes were recorded before all four +trials. Baseline is main `180ba03bf`. The same script, runtime, artifact and +declared actions run in the order baseline, candidate, candidate, baseline. + +| Variant/run | IPC events | `app-load-finished` | SVG change | Pipeline wall s | Family CPU s | Peak cgroup bytes | +| ----------- | ---------- | ------------------- | ---------- | --------------- | ------------ | ----------------- | +| Baseline 1 | 8 | 3 | 0 → 2 | 11.084 | 6.270053 | 560,615,424 | +| Candidate 1 | 7 | 2 | 0 → 2 | 11.135 | 6.395461 | 550,383,616 | +| Candidate 2 | 7 | 2 | 0 → 2 | 11.898 | 6.907123 | 525,631,488 | +| Baseline 2 | 8 | 3 | 0 → 2 | 11.469 | 6.526674 | 558,456,832 | + +Every declared action completed, every insertion oracle passed, and no verifier +deadline fired. Every provider returned `cleanup_failed`; the captures remain +partial/failed, including possible dirty-editor teardown and the known Linux +ownership gap [#4282](https://github.com/morluto/rea/issues/4282). Baseline 2 also +reports Xvfb temporary-directory cleanup failure (payload exit 5). Its directory +identity was not captured; no broad path-based deletion was attempted. + +Runs are serial on CPU 10 with one-CPU quota, 8 GiB hard memory, 6 GiB early +stop, no swap and 192 tasks. Counters cover the cgroup family at payload exit +while the observer remains alive; they are **not final whole-scope counters**. +Memory includes file cache, not RSS. Some scopes still contain Xvfb at that +point; all recorded scopes were later independently confirmed inactive/dead. +External recovery does not establish provider cleanup. + +OS/build caches are uncontrolled; these are first/repeat fresh-process runs, +not verified cold/warm system pairs. Two samples per variant and a cleanup-bound +failed workflow cannot establish a speedup. Token billing, peak agent context, +complete schema/call costs and actual MCP-wire parity are unmeasured. Fewer +events alone do not establish lower token cost. + +Full static checks passed (two tasks, no cache hits): 89.441 s wall, +88.630288 family CPU s and 4,337,172,480 peak cgroup bytes, with no OOM. +Documentation checks also passed (6 tasks plus five README consistency cases). +The [selected JSON record](electron-ipc-listeners-2026-10-16.json) retains exact +source/input identities, failed attempts, coverage, IPC and resource receipts. +Historical diagnostic helper hashes were not captured and remain explicitly +unknown; the frozen hash is not assigned to them retrospectively. + +## Reproduction + +Build both pinned checkouts. Under the existing resource observer and a private +Xvfb instance, use the committed helper for each runtime root in ABBA order: + +```sh +node experiments/analysis-efficiency/drawio-electron-lifecycle.mjs \ + --runtime-root /absolute/pinned-checkout \ + --executable /absolute/electron-44.7.0/electron \ + --application /absolute/drawio/resources/app.asar \ + --profile-root /absolute/temporary-profiles --dismiss-error \ + --selector '.geSidebar a.geItem:visible >> nth=0' +``` + +The helper returns failure on incomplete provider cleanup. This shared-service +probe does not establish successful public CLI/MCP workflows. App images, +extractions/profiles and full Result files are regenerable and excluded from Git. + +## Review follow-up: normalization before app decorators + +Fresh review of `f3bb9b9ed` found that translation inside a patched +`removeListener` happened after an application's outer decorator had already +seen the recording wrapper. Two native controls reproduced this with stable and +replaced decorators (17 passed, two failed). + +`c8c494b5c` delegates the original Node traversal through a private forwarding +receiver. It shares the real emitter's live state and translates owned `on` +entries **before** invoking the current application removal method. App methods +and getters receive the real emitter; read-only method properties remain valid. +Native once entries pass unchanged. The public `removeListener`/`off` patches +are removed. Five decorator variants pass: stable, replaced, read-only, getter +and Proxy. + +An additional mutation case proved that reading a changed public `.listener` +getter before delegation also changed deletion timing (22 passed, one failed). +Final source `5a094a0df` retains canonical removal targets in the weak owner +registry. Only unchanged data links are translated; changed getters run when +Node performs removal, preserving the exception and state transition. All +**23 cases** pass, including the original retention/fatal-error coverage. + +The same frozen draw.io verifier was reused. The follow-up ABBA attempt ran +its first baseline, then the resource launcher refused the other three requests +because 8 GiB plus its 4 GiB host reserve was unavailable. Those are **not +executed**, with no performance measurements. The final source was separately +admitted and completed dialog recovery and insertion with seven IPC events; +overall cleanup still failed, and Xvfb teardown also returned an error. No new +completed-task speedup or cold/warm pair is claimed. + +Final static checks passed: two uncached tasks, 135.173 s wall, 133.786166 family +CPU s and 4,274,634,752 peak cgroup bytes, no OOM. The JSON record appends exact +source pins, denial logs, final real-app/public cleanup diagnostics and checks +under `review_followup`; the initial records were not overwritten. + +## Callback invocation and remaining raw-metadata limitation + +Source `514b73b78` invokes `handle` callbacks through captured `Reflect.apply`, +preserving the receiver and actual arguments without reading an app-defined +`.call` property. The added direct-call control and hook case pass; all **24 +IPC regressions** pass (3.661 s wall, 3.404 family CPU s, 318,291,968 peak cgroup +bytes, no OOM). This is a Node boundary check, not a real Electron handle claim. + +An initial three-case experiment incorrectly assumed native EventEmitter avoided +callable method-property reads. Its `on` and `once` controls themselves failed; +they do not demonstrate hook regressions. The attempted intrinsic event-call +change was reverted and those invalid cases removed. Both failed runs remain +in `callable_followup`; no successful result is assigned to them. + +The same unchanged draw.io verifier at this source completes actual dialog +recovery and shape insertion, with seven IPC events, SVG elements 0 → 2 and an +unsaved-changes indicator. Overall provider cleanup still fails. Pipeline wall +time is 12.610 s, family CPU 7.062834 s and peak cgroup memory 634,187,776 bytes; +there is no paired performance or token claim. All recorded scopes were later +independently inactive/dead, which does not establish provider cleanup. + +Fresh review also found a distinct remaining metadata case: changing a raw +recording wrapper's writable `.listener` property can make an app decorator +reject removal. A production-hook/native-Node probe reproduces this (plain: +removed; hook: decorator throws and one listener remains). Exact reproduction +and the ownership conflict are tracked in [#4513](https://github.com/morluto/rea/issues/4513). +No affected real app has been identified. Removing the descriptor guard alone +does not make native removal match the mutated wrapper; temporarily rewriting +public app metadata would add further visible mutations. This limitation is +explicit in the observation guide and is not represented as fixed by this PR. + +Current-source static checks also pass: two uncached tasks, 92.912 s wall, +92.031383 family CPU s and 4,340,375,552 peak cgroup bytes, no OOM. + +## Native single/array dispatch follow-up + +Fresh review of `6fc2d2769` identified that the earlier native controls covered +only one listener. Node 24.18.0 reads `.apply` during single-listener dispatch, +but invokes each callback intrinsically during array dispatch. Native `once` +uses `.call` for zero arguments and `.apply` otherwise. Unconditional `.apply` +in the observation helper changed two of those paths: a multi-listener callback +could throw before later callbacks ran, and zero-argument once read the wrong +property. Four explicit native controls reproduce two failures (26 passed, +two failed), rather than assuming all callable-property reads should disappear. + +Source `e8d7e8f20` keeps one observation boundary. An `on` recording wrapper +delegates direct calls intrinsically and forwards native `.apply` calls through +its own nonenumerable method. Node selects the route; the hook does not inspect +or copy listener storage. `once` preserves its native zero/nonzero-argument +invocation. All **28 cases** pass, including the prior callback identity, +retention, reentrant-removal and fatal-error coverage. `handle` remains intrinsic. + +The unchanged real draw.io verifier again completes dialog recovery and shape +insertion, with seven IPC events, SVG elements 0 → 2 and the unsaved indicator. +Overall provider cleanup still fails; no deadline fires. Its 12.290 s pipeline +run is a validation observation, not a paired performance comparison. Exact +source hashes, failures, resource measurements and post-scope states are appended +under `native_dispatch_followup`; all previous records remain unchanged. +The separate raw metadata limitation [#4513](https://github.com/morluto/rea/issues/4513) +remains open and is not represented as fixed. + +Final-source static checks pass: two uncached tasks, 171.430 s wall, +169.139239 family CPU s and 4,091,392,000 peak cgroup bytes, no OOM. + +## Argument-list ownership follow-up + +Review of `23700fd2c` found two related representation errors. A raw callback's +`.apply` accepts Arguments objects, other array-like objects, null or omitted +argument lists; the recording helper assumed an Array. Native once also passes +its actual Arguments object to a callback-defined `.apply`, whereas the hook +passed a rest Array. Seven explicit native controls reproduce six failures +(29 passed, six failed of 35). + +Source `a82d935ba` binds captured native Function apply to the method-forwarding +function. JavaScript itself converts argument lists, treats null/undefined as +empty and rejects invalid primitives before recording reads values. The +array-like getter control is read exactly once. Once retains its actual +Arguments object for invocation; recording alone takes an array slice. The +captured slice helper is bound once, avoiding an extra helper-argument array +on each invocation. No custom array-like parser or exception repair is added. + +All **35 cases** pass. The same frozen real draw.io verifier again completes +dialog recovery and shape insertion with seven IPC events; no deadline fires, +and overall provider cleanup still fails. Exact source identities, both failed +boundaries, intermediate/final tests, resource costs and post-scope states are +appended under `argument_list_followup`; previous records remain unchanged. + +Current-source static checks pass: two uncached tasks, 122.454 s wall, +121.287057 family CPU s and 4,185,915,392 peak cgroup bytes, no OOM. +The unchanged ASAR digest was checked again after the real-app trial. + +## Owned metadata and successful-Evidence verifier follow-up + +Fresh review of `41a513d99` found that `on` wrapper metadata used strict +assignment. Native on does not write such metadata, so inherited Function or +Object prototype setters/read-only fields could prevent registration only under +capture. Four native controls fail before `5dcf79f29` (35 passed, four failed). +The recording wrapper now defines its own data property directly; native once +assignment behavior is retained. All **39 cases** pass. + +The verifier also incorrectly treated a successful Evidence envelope as a raw +capture. Real cleanup failures had exercised only the separate partial path. +`4a9a01f7a` reads successful `value.normalized_result` and preserves failed +`error.partialObservation.capture`. The new +`experiments/analysis-efficiency/drawio-electron-lifecycle-oracles.py` exercises +the actual verifier with controlled success, partial cleanup, action failure, +insertion failure and absent capture. The old verifier rejects valid successful +Evidence before producing a summary; all five corrected controls pass. +**These are controlled reply/exit checks, not real-provider evidence.** + +The corrected verifier was separately frozen before a real unchanged draw.io +trial. Actual dialog recovery and shape insertion again complete with seven IPC +events, no deadline and failed overall provider cleanup (pipeline 12.777 s). +Success with real provider cleanup remains unverified. Earlier trials retain +their earlier verifier hashes; the corrected hash is not assigned to them. +Exact source pins, failed controls, resource costs and post-scope states are +appended under `owned_metadata_and_verifier_followup` without replacing history. + +Final-source static checks pass: two uncached tasks, 99.992 s wall, +98.956939 family CPU s and 4,237,795,328 peak cgroup bytes, no OOM. + +## Species-free recording and snapshot ownership + +Fresh review of `e2cf2d89b` found that slicing a rest Array invokes application +`Array[Symbol.species]` before dispatch. The first control reproduced failed on +dispatch (40 passed, one failed); once dispatch already used Arguments. +Extending the check to retrieve the retained snapshot while the throwing getter +remains installed exposed the same representation problem in snapshot maps +(39 passed, two failed). + +Source `3141ac3f4` records from actual invocation frames while preserving native +callback argument-list types: the method-based on path still passes an Array, +and once passes Arguments. Group validation uses the existing recording loops +without a filtered intermediate Array. Captured `Array.from` produces snapshots +without species lookup, reading dropped-counter Maps directly rather than +spreading them into another Array first. The initial **41 cases** pass, including both +live snapshots under the throwing getter with zero getter reads. + +The frozen-source real unchanged draw.io operation again completes recovery and +insertion with seven IPC events, no deadline and failed overall cleanup +(pipeline 12.779 s). This does not establish that draw.io customizes species. +The corrected verifier and its five previously passing controlled oracles are +byte-identical to `4a9a01f7a`, so those unchanged oracle sources were reused. +Exact hashes, failures, resource receipts and scope states are appended under +`array_species_followup`; earlier records remain unchanged. There is no new +completed-task performance or token/context claim. + +The native multiple-listener route was then included in the same live-species +control: all **42 cases** pass. Only test coverage expanded; production hook +and verifier bytes remain identical to `3141ac3f4`. Exact validation identities +and the additional result are recorded under `all_native_dispatch_paths`. + +A fresh-profile real negative control omits `--selector` while retaining dialog +recovery and the same 500 ms wait. SVG elements remain **0 → 0** and unsaved +changes remain false; insertion gives **0 → 2** and unsaved true. This supports +the operation oracle rather than attributing startup rendering to the click. +The control also returns seven IPC events and failed provider cleanup. These +are different tasks, not a performance pair or a model-cell-count proof. + +Final 42-case source validation includes two uncached static tasks passing in +94.234 s wall, 93.308800 family CPU s and 4,406,538,240 peak cgroup bytes, +with no OOM. These are verification costs, not an application speedup. + +## Opaque apply inputs and iterator-free snapshots + +Fresh review of `84d9cf5db` found two producing-boundary regressions: native +array-like conversion replaced the argument-list object before a custom +listener `.apply` received it, and `Array.from` snapshots consulted application +iterators on retained arrays/Maps. Four exact-input native controls and two +snapshot controls fail before correction (42 passed, six failed of 48). + +Source `e442ca27f` forwards custom `.apply` the original receiver and method +arguments without interpreting its opaque list. Custom apply may transform +arguments or skip callback execution, so the hook emits a bounded +`ipc-observation-gap` timeline record with the known registration channel. It +does not fabricate a main-event invocation, argument shapes or callback result. +The shared result projection retains explicit uncertainty and counts dropped +gaps through existing retention. The canonical timeline enum adds that kind; +the captured-IPC subset does not. Native apply still performs its own conversion +before an actual callback frame is observed. + +Snapshots now copy arrays through private null-prototype indexed views and +counters through captured native Map traversal. Neither retained buffers nor +counter Maps are exposed to application iterators; returned records and shape +arrays remain independent copies. No cache, tool or normal-dispatch metadata +is added. The raw-wrapper metadata limitation in #4513 remains unfixed. + +The focused hook/projection/actions gate passes **68 cases** (48 + 4 + 16). +The separate MCP/schema gate passes **23 cases**: an actual SDK client receives +the gap alongside ordinary IPC Evidence, and Ajv2020 validates the advertised +converted output; all canonical tool schemas also pass their existing checks. +These tests use controlled providers and establish no real-provider success. +The verifier and its five controlled exit/summary oracles are hash-identical to +the earlier passing source and are reused without another redundant run. + +## Unrelated CI baseline failure + +CI run `38935598884` tested synthetic merge `eedae92d406015895a6d0eced5035da1b9ead07a` +(head `84d9cf5db`, base `9d940c2f2`). Two firmware cleanup tests failed; another +shard was cancelled by fail-fast. A fresh, unmodified detached checkout of that +exact base reproduces the same failures: **33 passed, two failed** in +`tests/boundary/firmware/firmwareIntegration.test.ts`. Evidence is posted on +[the PR](https://github.com/morluto/rea/pull/4389#issuecomment-6221054273) and +[existing #4475](https://github.com/morluto/rea/issues/4475#issuecomment-6221054775). +This establishes that these failures predate the Electron changes; it does not +resolve whether the firmware producer or its expectations need correction. No +firmware code or assertions are changed here, and required CI is not bypassed. + +The first final-source real request fails before application launch because the +previous `/tmp` runtime is absent. It is retained as an input-resolution failure, +not a provider measurement. The cached Electron 44.7.0 archive is verified against +the official release SHASUMS256 and extracted into a new owned task directory; +its executable/archive digests are pinned before the next request. No target +source, vendor fuse or scenario success requirement is changed. + +The recovered-runtime real operation completes all three declared actions: +SVG elements **0 → 2**, unsaved changes true, seven IPC events, no deadline. +Overall provider cleanup still fails. Current-source static validation passes +two uncached tasks in **94.646 s wall, 93.878644 family CPU s and 4,300,570,624 +peak cgroup bytes**, no OOM. The initial static attempt's test-only SDK/Ajv type +error is retained; parsing that boundary fixes it, and 12 affected client and +projection cases pass again. `opaque_apply_and_iterator_followup` preserves all +source pins, failures, resource receipts and independently checked scope states +without replacing earlier measurements. Draw.io does not establish that real +applications exercise the synthetic custom-apply/iterator cases. + +The final documentation gate also passes six tasks and five README consistency +cases. Source and unchanged ASAR hashes are checked again after validation. + +## Borrowed apply receivers + +Fresh review of `7541a900c` supplied the borrowed custom-method case +`raw.apply.call(g, emitter, list)`: the plain emitter runs `g`, while the hook +hard-coded its registered callback as the method receiver. The same hard-coded +receiver also affected borrowed native apply. Both native differential controls +fail before correction (48 passed, two failed of 50). + +Source `da04465fe` maps only the hook wrapper itself to the original listener +when forwarding custom apply; other method receivers pass unchanged. Native +apply uses its actual receiver, preserving both ordinary wrapper recording and +borrowed-call dispatch. The hook does not fabricate a registered callback +invocation when another callable is selected. The method-forwarding boundary is +corrected directly; no new helper, state, tool or schema is added. All **70** +hook/projection/actions cases pass (50 + 4 + 16). Shared schema/client, projection +and verifier/oracle sources are byte-identical to the prior passing versions, +so their checks are reused by verified hash. + +CI run `38943919946` is confirmed by the successful build checkout log and Git +parents to use merge `98c14dfc01fe4698a50eedc8b1cd1ac9cd03267d` +(head `7541a900c`, main `4838f4449`). Its fifth shard fails the same two firmware +cleanup tests as the unmodified `9d940c2f2` base control. The relevant firmware +source/test and package paths have no changes between these main revisions. +The REST PR `base.sha` remains stale at `2c7583599`; it is not used as the CI +identity. No firmware code/assertion or required-check bypass is included. + +The frozen-source unchanged draw.io operation completes recovery/insertion +again with seven IPC events, SVG **0 → 2**, unsaved changes true and failed +overall provider cleanup. Pipeline: **12.038 s wall, 6.662295 family CPU s, +598,835,200 peak cgroup bytes**. This remains a partial workflow, not a speedup +claim. Final static checks pass two uncached tasks in **108.067 s wall, +106.996233 family CPU s and 4,430,647,296 peak cgroup bytes**, no OOM. +All prior measurements remain unchanged under `borrowed_apply_followup`. + +The final documentation gate also passes six tasks and five README consistency +cases. Current source digests are checked again after validation. + +## Listener-owner intrinsic lifetime + +Fresh review of `f5b489c1b` found that late application replacement of +`Object.defineProperty` prevents the hook's new wrapper properties from being +created, while a plain native emitter still registers/invokes its callbacks. +Inspection of this same new registration/removal owner found its other late +property/constructor lookups. Seven independent native controls replace +`Object.defineProperty`, `Object.getOwnPropertyDescriptor`, `Object.create`, +`Reflect.get`, `Reflect.set`, global `Set` or global `Proxy`; each fails before +correction (50 passed, seven failed of 57). The controls include both on/once +execution and complete removal, with globals restored after the scenario. + +Source `a9c5611d0` captures the owner's descriptor/property operations and private +constructors before application startup. The private receiver uses a direct +null-prototype object literal instead of looking up Object.create. All **77** +hook/projection/actions cases pass (57 + 4 + 16). Application-owned callback +methods and actual receivers retain their existing semantics. No tool, schema, +state, target authority or general-purpose hardening layer is introduced; this +is not complete JavaScript introspection equivalence. Unchanged schema/client, +projection and verifier/oracle checks are reused by verified source hashes. + +The preceding CI run `38946503183` tests confirmed merge +`fcd7a9d92a2e64d6eb96d4fabfed7fc7f44b0d45` (head `f5b489c1b`, main `4838f4449`). +Its 36 jobs finish with 29 successes, the same two firmware failures causing +three failed jobs including summaries, and four fail-fast cancellations. The +fifth-shard log again names the two cases reproduced on untouched main. No +firmware code/assertions or required checks are altered. + +Frozen-source real unchanged draw.io actions complete again with seven IPC +events, SVG **0 → 2**, unsaved changes true, no deadline and failed overall +provider cleanup. Pipeline wall is **11.429 s**. Observed payload-exit cgroup +counters are 6.441029 CPU s and 550,363,136 peak bytes, but the observer still +sees one additional scope descendant: **family resource observation is +unverified**, not a complete-family performance measurement. The scope is later +inactive/dead after external recovery; this does not verify provider cleanup. +No identity or cleanup cause is inferred from the remaining PID. + +Final static checks pass two uncached tasks in **90.980 s wall, 90.119351 family +CPU s and 4,338,749,440 peak cgroup bytes**, no OOM. Source identities, all seven +failures and resource limitations are appended under `listener_intrinsics_followup` +without replacing any previous measurements. + +The final docs gate passes six tasks and five README consistency cases; source +digests are checked again after validation. + +## Private collection method lifetime + +Fresh review of `e6c373d0` found that replacing `WeakMap.prototype.has` after +hook initialization still intercepts its private wrapper registry. Inspecting +the same owner found analogous get/set and visited-Set has/add lookups. All five +independent native controls fail before correction (57 passed, five failed of +62); plain EventEmitter registration, on/once calls and complete removal succeed. + +Source `774ed6aa6` binds these private WeakMap/Set methods before application +startup and reuses captured Set membership for event-family classification. +One initialization-only binding helper replaces duplicated binding construction +for existing Map traversal/argument slicing too. It adds no helper argument +arrays per invocation, cache, schema, tool or target authority. Application-owned +callback methods/receivers remain dynamic. All **82** hook/projection/actions +cases pass (62 + 4 + 16). The previous seven global-binding controls also remain +green, and unchanged schema/client/projection/verifier/oracle checks are reused +by verified source hashes. Arbitrary introspection and #4513 remain unsupported. + +Frozen-source unchanged draw.io actions complete again with seven IPC events, +SVG **0 → 2**, unsaved changes true and failed overall provider cleanup. Pipeline +wall **11.238 s**; observed payload-exit cgroup CPU **6.285321 s**, peak +**556,285,952 bytes**. Another scope descendant remains at payload exit, so complete-family resource observation is unverified. Later external scope teardown does not prove provider cleanup. + +Final static checks pass two uncached tasks in **92.999 s wall, 92.227652 family +CPU s and 4,371,951,616 peak cgroup bytes**, no OOM. Preceding CI38949129899 checks +confirmed e6c3 + main4838, repeats the same two firmware failures and finishes +31 successful /3 failed jobs including summaries /2 fail-fast cancellations. +Sources, failures and resource limitations are appended under +`private_collection_methods_followup`; prior fields remain unchanged. + +The final docs gate passes six tasks and five README consistency cases; source +digests are verified again afterward. + +## Unknown listener-chain ownership + +Fresh review of `0b4f2e81` found that the bounded Boolean chain test treats a +still-unexamined function as fresh when 17 app decorators hide an owned once +wrapper. Two emits then produce three invocation records for one actual once +callback. The same unknown-as-fresh error affects accessor/Proxy links. Four +native controls fail before correction (63 passed, four failed of 67); the +16-decorator boundary already passes. A deep ordinary on control confirms +missing ownership cannot justify fabricated captured IPC either. + +Source `ef46b8f0b` classifies metadata as owned, unowned or unknown. Bounded-walk +exhaustion, accessor links and Proxies preserve native registration without +adding another recording wrapper. Successful unknown registrations retain a +bounded `listener-chain` gap; this is not proof of IPC delivery. An existing +inner wrapper may still report its actual once invocation. Shared native +data-descriptor parsing preserves accessor uncertainty at recognition/removal +and avoids inherited descriptor-value interpretation. The superseded Boolean +classification is removed. + +The shared projection explains both gap boundaries and retention/coverage, and +an actual MCP SDK client receives custom-apply and listener-chain gaps alongside +ordinary IPC Evidence, validated by its converted advertised schema. All **96** +hook/projection/actions/MCP cases pass (67 + 5 + 16 + 8). A test-only lint length +failure is corrected by generating repeated fixture rows from one table without +relaxing rules; all **13** affected client/projection cases pass again. The +canonical schema and unchanged verifier/five controlled oracle sources retain +their earlier passing checks by verified hashes; the changed projection/client +are freshly tested rather than claimed unchanged. + +Prior CI run `38951509761` uses confirmed merge +`fa090a6bde3a5dadeb71066824477292520fb43b` (0b4f + mainf31b231a). All **36 jobs +succeed**. The earlier unrelated firmware failures do not recur in this +combination; their cause is not inferred or claimed fixed here. Current-source +CI/review remain required. No firmware code/assertions were modified. + +Frozen-source unchanged draw.io recovery/insertion again completes with seven +IPC events, SVG **0 → 2**, unsaved true, no deadline and failed overall provider +cleanup. Pipeline wall **11.163 s**; observed payload-exit cgroup CPU **6.203177 s**, +peak **547,418,112 bytes**. An extra scope descendant leaves complete-family +resource observation unverified; later inactive/dead scope after external +recovery does not establish provider cleanup. + +Final static checks pass two uncached tasks in **88.383 s wall, 87.726306 family +CPU s and 4,399,632,384 peak cgroup bytes**, no OOM. All source identities, failed +controls, corrected fixture-only lint failure and resource caveats are appended +under `chain_ownership_followup`; historical fields are unchanged. + +Final docs validation passes six tasks and five README consistency cases; +current source digests are verified again afterward. + +## Inherited listener-link coverage + +Fresh review of `ae2cd8d4` found that a missing own descriptor is not absence: +an app decorator can inherit its data link to an owned once wrapper. Native +removal follows that link and removes the decorator; the hook hid it under +another recorder, left a listener installed and produced three invocation +records for one callback. Five native inherited-link cases fail before correction +(68 passed, five failed of 73): data/getter/Proxy prototypes and prototype +depths16/17. A late Object.getPrototypeOf replacement control already passes. + +Source `e22ce9aaf` completes bounded native prototype lookup at the descriptor +owner, capturing getPrototypeOf before startup. Data links retain their actual +source value. A setter-only property has a known undefined read that shadows +ancestors; a getter, Proxy or incomplete walk remains unknown. Missing metadata becomes absent only after the prototype chain ends; found +properties retain native read/shadowing semantics. Registration and removal consume +the same parsed descriptor, and redundant outer Proxy interpretation is removed. +Neither existing resource bound is raised. All **102** hook/projection/actions/ +MCP cases pass (73 + 5 + 16 + 8). Projection/client are freshly tested unchanged +sources; canonical schema and verifier/oracles retain prior checks by hash. + +Prior CI run38955237255 tests confirmed merge9c0ceb152c902c1339806608f8b4a010c3d22fc6 +(ae2c + main8116dc06). All36 jobs succeed; current-head CI/review remain required. + +Frozen-source unchanged draw.io recovery/insertion again completes with seven +IPC events, SVG **0 → 2**, unsaved true and failed overall provider cleanup. +Pipeline: **11.264 s wall /6.376574 family CPU s /557,068,288 peak cgroup bytes**. +Payload-exit observation is verified at that phase while the observer lives; +these are not final whole-scope counters or provider cleanup proof. + +Final static checks pass two uncached tasks in **90.797 s wall, 90.096799 family +CPU s and 4,335,267,840 peak cgroup bytes**, no OOM. All source identities, native +failures and current results are appended under `inherited_listener_links_followup`; +previous fields and earlier resource caveats remain unchanged. + +Final docs validation passes six tasks and five README consistency cases; +current source digests are verified again afterward. diff --git a/experiments/analysis-efficiency/drawio-electron-lifecycle-oracles.py b/experiments/analysis-efficiency/drawio-electron-lifecycle-oracles.py new file mode 100644 index 0000000000..7914929c21 --- /dev/null +++ b/experiments/analysis-efficiency/drawio-electron-lifecycle-oracles.py @@ -0,0 +1,115 @@ +#!/usr/bin/env python3 +"""Check verifier result/exit semantics with controlled replies, not real apps.""" + +import argparse +import json +import os +from pathlib import Path +import shutil +import subprocess +import tempfile +import textwrap + + +def write(root, name, source): + path = root / name + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(textwrap.dedent(source)) + + +def check(arguments, root, case): + shutil.copyfile(arguments.verifier, root / "probe.mjs") + write(root, "package.json", '{"type":"module"}') + write(root, "node_modules/playwright-core/package.json", + '{"type":"module","exports":"./index.js"}') + write(root, "node_modules/playwright-core/index.js", """ + let reads = 0; + const page = { + locator: () => ({ waitFor: async () => {} }), + getByText: () => ({ waitFor: async () => {} }), + async evaluate() { + const after = reads++ > 0; + return { title: 'controlled oracle', invalid_file_dialog: !after, + unsaved_changes: after, + diagram_svg_elements: after && process.env.REA_DRAWIO_ORACLE_CASE !== 'insertion-failure' ? 2 : 0, + cells: 0 }; + }, + }; + export const _electron = { async launch() { + return { process: () => ({exitCode: 0, signalCode: null}), + firstWindow: async () => page, close: async () => {} }; + }}; + """) + write(root, "dist/browser/PlaywrightElectronActiveProvider.js", """ + export class PlaywrightElectronActiveProvider { + constructor(environment, launch) { this.launch = launch; } + } + """) + write(root, "dist/browser/PlaywrightElectronActiveActions.js", """ + export const runWithExecutionLimits = operation => operation; + """) + write(root, "dist/application/javascript/ElectronActiveObservationService.js", """ + export async function captureElectronScenario(provider, input) { + const app = await provider.launch({}); + await app.close(); + const name = process.env.REA_DRAWIO_ORACLE_CASE; + const capture = { actions: input.actions.map(action => ({...action, + status: name === 'action-failure' ? 'failed' : 'completed'})), + coverage: {}, retention: {}, ipc: {events: [], observed: 0, dropped: 0} }; + if (name === 'no-capture') return {ok: false, error: {reason: 'startup_failed'}}; + if (name === 'partial') return {ok: false, error: {reason: 'cleanup_failed', + partialObservation: {capture}}}; + return {ok: true, value: {evidence_id: 'ev_controlled', + normalized_result: capture}}; + } + """) + result = subprocess.run( + [arguments.node, str(root / "probe.mjs"), "--runtime-root", str(root), + "--executable", arguments.node, "--application", str(root), + "--profile-root", str(root), "--dismiss-error", "--selector", "controlled"], + env={**os.environ, "REA_DRAWIO_ORACLE_CASE": case}, + capture_output=True, text=True, timeout=15, + ) + replies = [] + for line in result.stdout.splitlines(): + try: + replies.append(json.loads(line)) + except ValueError: + pass + receipt = next((value for value in replies if "workflow_status" in value), None) + expected_success = case == "complete" + expected_provider = case not in ("partial", "no-capture") + expected_actions = case not in ("action-failure", "no-capture") + expected_inserted = case != "insertion-failure" + matched = ( + (result.returncode == 0) is expected_success + and receipt is not None + and receipt["provider_ok"] is expected_provider + and receipt["cleanup_verified"] is expected_provider + and receipt["actions_completed"] is expected_actions + and receipt["shape_inserted"] is expected_inserted + and (receipt["workflow_status"] == "verified") is expected_success + ) + return {"case": case, "exit_code": result.returncode, + "expected_success": expected_success, "summary_present": receipt is not None, + "matched_expected": matched} + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--node", required=True) + parser.add_argument("--verifier", type=Path, + default=Path(__file__).with_name("drawio-electron-lifecycle.mjs")) + arguments = parser.parse_args() + receipts = [] + with tempfile.TemporaryDirectory(prefix="rea-drawio-oracles-") as directory: + for case in ("complete", "partial", "action-failure", "insertion-failure", "no-capture"): + root = Path(directory) / case + root.mkdir() + receipts.append(check(arguments, root, case)) + print(json.dumps({"oracle_cases": receipts, "real_provider_verified": False})) + assert all(receipt["matched_expected"] for receipt in receipts), receipts + + +if __name__ == "__main__": + main() diff --git a/experiments/analysis-efficiency/drawio-electron-lifecycle.mjs b/experiments/analysis-efficiency/drawio-electron-lifecycle.mjs new file mode 100644 index 0000000000..aef07cc64d --- /dev/null +++ b/experiments/analysis-efficiency/drawio-electron-lifecycle.mjs @@ -0,0 +1,191 @@ +#!/usr/bin/env node +// Real draw.io ASAR, renderer and declared actions; DOM reads are independent +// verification probes. No fabricated IPC channels or replacement app callbacks. +import assert from "node:assert/strict"; +import { mkdtemp, writeFile } from "node:fs/promises"; +import { join, resolve } from "node:path"; +import { pathToFileURL } from "node:url"; +import { parseArgs } from "node:util"; +import { _electron } from "playwright-core"; + +const { values } = parseArgs({ + options: { + "runtime-root": { type: "string" }, + executable: { type: "string" }, + application: { type: "string" }, + "profile-root": { type: "string" }, + selector: { type: "string" }, + "dismiss-error": { type: "boolean", default: false }, + }, +}); +for (const key of ["runtime-root", "executable", "application", "profile-root"]) + assert.ok(values[key], `Missing --${key}`); +const runtime = resolve(values["runtime-root"]); +const { PlaywrightElectronActiveProvider } = await import( + pathToFileURL( + join(runtime, "dist/browser/PlaywrightElectronActiveProvider.js"), + ) +); +const { captureElectronScenario } = await import( + pathToFileURL( + join( + runtime, + "dist/application/javascript/ElectronActiveObservationService.js", + ), + ) +); +const { runWithExecutionLimits } = await import( + pathToFileURL( + join(runtime, "dist/browser/PlaywrightElectronActiveActions.js"), + ) +); +const profile = await mkdtemp( + join(resolve(values["profile-root"]), "drawio-ui-"), +); +const controller = new AbortController(); +const timer = setTimeout( + () => controller.abort(new Error("Verifier deadline")), + 40_000, +); +let application; +let child; +let page; +let before; +let after; +let checkedFinalState = false; +const inspect = () => + page.evaluate(() => { + const diagram = document.querySelector(".geDiagramContainer svg"); + return { + title: document.title, + invalid_file_dialog: + document.body.innerText.includes("Invalid file data"), + unsaved_changes: document.body.innerText.includes("Unsaved changes"), + diagram_svg_elements: + diagram?.querySelectorAll("rect,path,ellipse,text").length ?? null, + cells: document.querySelectorAll(".geDiagramContainer [data-cell-id]") + .length, + }; + }); +const started = performance.now(); +try { + const actions = values["dismiss-error"] + ? [ + { + step_id: "dismiss-invalid-file", + kind: "click", + window_index: 0, + selector: 'button:has-text("OK"):visible', + }, + ] + : []; + if (values.selector !== undefined) + actions.push({ + step_id: "insert-shape", + kind: "click", + window_index: 0, + selector: values.selector, + }); + actions.push({ + step_id: "settle-renderer", + kind: "wait", + window_index: 0, + duration_ms: 500, + }); + const result = await captureElectronScenario( + new PlaywrightElectronActiveProvider(process.env, async (options) => { + application = await _electron.launch(options); + child = application.process(); + page = await application.firstWindow({ timeout: 15_000 }); + await page + .locator(".geDiagramContainer") + .waitFor({ state: "visible", timeout: 15_000 }); + if (values["dismiss-error"]) + await page + .getByText("Invalid file data", { exact: true }) + .waitFor({ state: "visible", timeout: 15_000 }); + before = await inspect(); + return new Proxy(application, { + get(target, key) { + if (key === "close") + return async () => { + if (!checkedFinalState) { + checkedFinalState = true; + after = await inspect(); + } + return target.close(); + }; + const value = Reflect.get(target, key, target); + return typeof value === "function" ? value.bind(target) : value; + }, + }); + }), + { + executable_path: resolve(values.executable), + application_path: resolve(values.application), + args: ["--no-sandbox", "--disable-gpu", `--user-data-dir=${profile}`], + actions, + }, + { signal: controller.signal }, + ); + const capture = result.ok + ? result.value.normalized_result + : result.error.partialObservation?.capture; + await writeFile(join(profile, "result.json"), JSON.stringify(result)); + const actionsCompleted = + capture?.actions.every((action) => action.status === "completed") === true; + const inserted = + values.selector === undefined + ? null + : after?.diagram_svg_elements > before?.diagram_svg_elements; + console.log( + JSON.stringify({ + profile, + runtime, + elapsed_ms: performance.now() - started, + deadline_triggered: controller.signal.aborted, + selected_shape: values.selector ?? null, + before, + after, + actions_completed: actionsCompleted, + shape_inserted: inserted, + provider_ok: result.ok, + cleanup_verified: result.ok, + reason: result.ok ? null : result.error.reason, + coverage: capture?.coverage, + retention: capture?.retention, + ipc: capture?.ipc, + actions: capture?.actions, + workflow_status: + result.ok && actionsCompleted && inserted !== false + ? "verified" + : "partial-or-failed", + }), + ); + assert.ok(actionsCompleted, "Declared real-app actions did not complete"); + assert.ok(before && after, "Real renderer state was not verified"); + assert.notEqual(inserted, false, "Click did not insert a diagram shape"); + if (values["dismiss-error"]) assert.equal(after.invalid_file_dialog, false); + assert.equal( + controller.signal.aborted, + false, + "Verifier deadline was reached", + ); + assert.ok(result.ok, "Provider cleanup remains unverified"); +} finally { + clearTimeout(timer); + if ( + application !== undefined && + child?.exitCode === null && + child?.signalCode === null + ) + await runWithExecutionLimits( + application.close(), + undefined, + performance.now() + 5_000, + ).catch(() => { + process.exitCode = 1; + }); + // The resource scope owns any unresolved descendants; no PID/path-only kill. + if (child?.exitCode === null && child?.signalCode === null) process.exit(1); +} diff --git a/scripts/electron-active-hook.cjs b/scripts/electron-active-hook.cjs index 133aa2ebbb..c8892594b0 100644 --- a/scripts/electron-active-hook.cjs +++ b/scripts/electron-active-hook.cjs @@ -1,10 +1,34 @@ "use strict"; +const applyFunction = Reflect.apply; +const applyArguments = Function.prototype.apply; +const bindFunction = Function.prototype.bind; +const callFunction = Function.prototype.call; +const arrayFrom = Array.from; +const defineProperty = Object.defineProperty; +const ownPropertyDescriptor = Object.getOwnPropertyDescriptor; +const getPrototypeOf = Object.getPrototypeOf; +const getProperty = Reflect.get; +const setProperty = Reflect.set; +const SetConstructor = Set; +const ProxyConstructor = Proxy; +const bindBuiltinMethod = (method) => + applyFunction(bindFunction, callFunction, [method]); +const forEachMapEntry = bindBuiltinMethod(Map.prototype.forEach); +const sliceArguments = bindBuiltinMethod(Array.prototype.slice); +const weakMapHas = bindBuiltinMethod(WeakMap.prototype.has); +const weakMapGet = bindBuiltinMethod(WeakMap.prototype.get); +const weakMapSet = bindBuiltinMethod(WeakMap.prototype.set); +const setHas = bindBuiltinMethod(Set.prototype.has); +const setAdd = bindBuiltinMethod(Set.prototype.add); +const hasOwnProperty = bindBuiltinMethod(Object.prototype.hasOwnProperty); + +const { isProxy } = require("node:util").types; const { createElectronActiveBoundaryPatches, } = require("./electron-active-hook-boundaries.cjs"); -const ipcEventKinds = new Set([ +const ipcEventKinds = new SetConstructor([ "main-handler-invocation", "main-event-invocation", "utility-process-fork", @@ -98,7 +122,7 @@ const eventFamily = (kind, processTypeValue) => { return "preload-configuration"; if (kind.startsWith("ipc-renderer")) return processTypeValue === "renderer" ? "renderer-ipc" : "ipc"; - return ipcEventKinds.has(kind) ? "ipc" : kind; + return setHas(ipcEventKinds, kind) ? "ipc" : kind; }; const noteDropped = (kind, processTypeValue, isIpc) => { @@ -163,7 +187,7 @@ const frameIdentity = (event) => { const record = (event) => { observed += 1; - const isIpc = ipcEventKinds.has(event.kind); + const isIpc = setHas(ipcEventKinds, event.kind); if (isIpc) observedIpc += 1; else observedRuntime += 1; const raw = { @@ -191,7 +215,7 @@ const record = (event) => { ...event, }; const shapeGroups = Array.isArray(raw.argument_shape_value_groups) - ? raw.argument_shape_value_groups.filter(Array.isArray) + ? raw.argument_shape_value_groups : Array.isArray(raw.argument_shape_values) ? [raw.argument_shape_values] : []; @@ -242,11 +266,13 @@ const record = (event) => { eventBytes + estimatedRetainedBytes + snapshotEnvelopeReserveBytes > retentionBudgetBytes; }; - for (const group of shapeGroups) + for (const group of shapeGroups) { + if (!Array.isArray(group)) continue; for (const argument of group) { countShape(argument); if (shapeOverflow) break; } + } if (shapeOverflow) { noteDropped(raw.kind, retainedEvent.process_type, isIpc); return null; @@ -290,9 +316,11 @@ const record = (event) => { noteDropped(raw.kind, retainedEvent.process_type, isIpc); return null; } - for (const group of shapeGroups) + for (const group of shapeGroups) { + if (!Array.isArray(group)) continue; for (const argument of group) retainedEvent.argument_shapes.push(shape(argument)); + } for (const valueShape of existingShapes) retainedEvent.argument_shapes.push( typeof valueShape === "string" ? valueShape : "unknown", @@ -317,6 +345,14 @@ const recordIpc = (kind, channel, args, details = {}) => }); })(); +const recordIpcObservationGap = (channel, event, error = false) => + recordRuntime("ipc-observation-gap", event, error ? "failed" : "observed", { + channel, + target: "ipcMain.on", + process_type: "main", + error, + }); + const patchEmitter = (emitter, observeEvent) => { if (emitter === null || emitter === undefined) return; const original = emitter.emit; @@ -343,43 +379,248 @@ const recordInvocation = (kind, channel, event, args) => error: false, }); +const invokeObservedEventListener = ( + thisArg, + channel, + listener, + args, + nativeMethod, +) => { + // Recording slices an actual invocation frame, avoiding Array species; + // callback dispatch keeps its native Array/Arguments representation. + const recorded = recordInvocation( + "main-event-invocation", + channel, + args[0], + sliceArguments(args, 1), + ); + try { + if (nativeMethod === "call") return listener.call(thisArg); + if (nativeMethod === "apply") return listener.apply(thisArg, args); + return applyFunction(listener, thisArg, args); + } catch (cause) { + if (recorded !== null) recorded.error = true; + throw cause; + } +}; + +// Recording wrappers follow EventEmitter's own wrapper convention: `.listener` +// names the caller's function so native removal can match it. Node retains +// ownership of listener traversal and meta-events; hook-created "on" entries +// are translated before calling the application's removal method/decorators. +// Only hook-created wrappers are tracked, by the registration they stand in +// for: they already record, so patched on() registers them as is. An "on" +// wrapper stands in for the caller's raw function and a "once" wrapper for +// EventEmitter's once wrapper, which removeAllListeners reports accordingly. +// The value is the native removal argument: the app function for an "on" +// wrapper, or the wrapper itself for "once". Public .listener is mutable; +// it is not the authority for the original function's identity. +const hookListenerRemovalTargets = new WeakMap(); +const MAX_LISTENER_CHAIN_HOPS = 16; + +// Follow native property lookup without invoking application getters or Proxy +// traps. Undefined means the readable link is absent; null means unknown. +const listenerDataDescriptor = (listener) => { + let candidate = listener; + for ( + let hops = 0; + hops <= MAX_LISTENER_CHAIN_HOPS && candidate !== null; + hops += 1 + ) { + if (isProxy(candidate)) return null; + const descriptor = ownPropertyDescriptor(candidate, "listener"); + if (descriptor !== undefined) { + if (hasOwnProperty(descriptor, "value")) return descriptor; + // A setter-only property shadows ancestors with a known undefined read. + return descriptor.get === undefined ? undefined : null; + } + candidate = getPrototypeOf(candidate); + } + return candidate === null ? undefined : null; +}; + +// A decorator that wraps once's hook wrapper and names it through `.listener` +// already records through that wrapper. Registering the decorator as is keeps +// native self-removal working: the once wrapper removes itself by identity, and +// removeListener matches the decorator through its `.listener`. +const classifyHookListenerWrapper = (listener) => { + const visited = new SetConstructor(); + let candidate = listener; + for ( + let hops = 0; + hops <= MAX_LISTENER_CHAIN_HOPS && typeof candidate === "function"; + hops += 1 + ) { + if (weakMapHas(hookListenerRemovalTargets, candidate)) return "owned"; + if (setHas(visited, candidate)) return "unowned"; + setAdd(visited, candidate); + const descriptor = listenerDataDescriptor(candidate); + if (descriptor === null) return "unknown"; + candidate = descriptor?.value; + } + return typeof candidate === "function" ? "unknown" : "unowned"; +}; + const patchIpcMain = (ipcMain) => { if (ipcMain === null || ipcMain === undefined) return; - for (const [method, kind] of [ - ["handle", "main-handler-invocation"], - ["on", "main-event-invocation"], - ["once", "main-event-invocation"], - ]) { - const original = ipcMain[method]; - if (typeof original !== "function") continue; - ipcMain[method] = function patchedIpcMain(channel, listener) { + const { handle, on, once, removeAllListeners } = ipcMain; + if (typeof handle === "function") + ipcMain.handle = function patchedIpcMainHandle(channel, listener) { if (typeof listener !== "function") - return original.call(this, channel, listener); - const wrapped = - kind === "main-handler-invocation" - ? async function wrappedHandler(event, ...args) { - const recorded = recordInvocation(kind, channel, event, args); - try { - const result = await listener.call(this, event, ...args); - if (recorded !== null) recorded.result_shape = shape(result); - return result; - } catch (cause) { - if (recorded !== null) recorded.error = true; - throw cause; - } - } - : function wrappedEventListener(event, ...args) { - const recorded = recordInvocation(kind, channel, event, args); - try { - return listener.call(this, event, ...args); - } catch (cause) { - if (recorded !== null) recorded.error = true; - throw cause; - } - }; - return original.call(this, channel, wrapped); + return handle.call(this, channel, listener); + return handle.call( + this, + channel, + async function wrappedHandler(event, ...args) { + const recorded = recordInvocation( + "main-handler-invocation", + channel, + event, + args, + ); + try { + const result = await applyFunction(listener, this, arguments); + if (recorded !== null) recorded.result_shape = shape(result); + return result; + } catch (cause) { + if (recorded !== null) recorded.error = true; + throw cause; + } + }, + ); }; - } + if (typeof on !== "function") return; + ipcMain.on = function patchedIpcMainOn(channel, listener) { + if (typeof listener !== "function") return on.call(this, channel, listener); + const wrapperStatus = classifyHookListenerWrapper(listener); + if (wrapperStatus !== "unowned") { + const result = on.call(this, channel, listener); + if (wrapperStatus === "unknown") + recordIpcObservationGap(channel, "listener-chain"); + return result; + } + const wrapped = function wrappedEventListener() { + return invokeObservedEventListener(this, channel, listener, arguments); + }; + // Native single-listener dispatch reads .apply; array dispatch invokes the + // function directly. Let Node select that path without inspecting or + // copying the emitter's listener storage, including during reentrant emits. + defineProperty(wrapped, "apply", { + configurable: true, + writable: true, + value: function nativeApply(...methodArgs) { + let applicationApply; + try { + applicationApply = listener.apply; + } catch (cause) { + recordIpcObservationGap(channel, "custom-apply", true); + throw cause; + } + if (applicationApply !== applyArguments) { + // Custom apply owns the original list's interpretation. Forward it + // unchanged rather than executing getters to invent callback facts. + const gap = recordIpcObservationGap(channel, "custom-apply"); + try { + return applyFunction( + applicationApply, + this === wrapped ? listener : this, + methodArgs, + ); + } catch (cause) { + if (gap !== null) { + gap.error = true; + gap.phase = "failed"; + } + throw cause; + } + } + // Native Function.apply performs its own list conversion before the + // recording wrapper observes an actual invocation frame. + // Borrowing the exposed method keeps its dynamic receiver. + return applyFunction(applyArguments, this, methodArgs); + }, + }); + defineProperty(wrapped, "listener", { + configurable: true, + enumerable: true, + writable: true, + value: listener, + }); + weakMapSet(hookListenerRemovalTargets, wrapped, listener); + return on.call(this, channel, wrapped); + }; + // Mirror EventEmitter#once and _onceWrap: resolve this.on at registration + // and target.removeListener at firing, so later decorators observe both. + if (typeof once === "function") + ipcMain.once = function patchedIpcMainOnce(channel, listener) { + if (typeof listener !== "function") + return once.call(this, channel, listener); + const target = this; + let fired = false; + const wrapped = function wrappedOnceEventListener() { + if (fired) return undefined; + target.removeListener(channel, wrapped); + fired = true; + return invokeObservedEventListener( + target, + channel, + listener, + arguments, + arguments.length === 0 ? "call" : "apply", + ); + }; + wrapped.listener = listener; + weakMapSet(hookListenerRemovalTargets, wrapped, wrapped); + this.on(channel, wrapped); + return this; + }; + if (typeof removeAllListeners !== "function") return; + // Node owns its live-array traversal. A forwarding receiver translates only + // our "on" entries before calling the current application removal method, + // including decorators installed/replaced during traversal. Application + // getters and methods always receive the real emitter; its public methods + // are never temporarily replaced. Native once entries remain unchanged. + ipcMain.removeAllListeners = function patchedIpcMainRemoveAllListeners( + ...args + ) { + const emitter = this; + // A separate empty target also permits forwarding a non-writable app + // method; a Proxy of the emitter itself would violate get invariants. + const receiver = new ProxyConstructor( + { __proto__: null }, + { + get(_target, key) { + const value = getProperty(emitter, key, emitter); + if ( + (key !== "removeListener" && key !== "removeAllListeners") || + typeof value !== "function" + ) + return value; + return (...methodArgs) => { + if (key === "removeListener") { + const entry = methodArgs[1]; + const removalTarget = weakMapGet( + hookListenerRemovalTargets, + entry, + ); + // Translate only a still-valid data link. If app code changed it + // to an accessor, native removal owns when that getter executes. + if ( + removalTarget !== undefined && + removalTarget !== entry && + listenerDataDescriptor(entry)?.value === removalTarget + ) + methodArgs[1] = removalTarget; + } + return applyFunction(value, emitter, methodArgs); + }; + }, + set: (_target, key, value) => setProperty(emitter, key, value, emitter), + }, + ); + const result = applyFunction(removeAllListeners, receiver, args); + return result === receiver ? emitter : result; + }; }; const patchNavigationMethods = (webContents, contentsId) => { @@ -649,10 +890,24 @@ try { ); } +// Never expose retained buffers to application iterators or array constructors. +const snapshotArray = (values, project) => + arrayFrom({ __proto__: null, length: values.length }, (_unused, index) => + project === undefined ? values[index] : project(values[index]), + ); + +const snapshotCounts = (counts, field) => { + const values = { __proto__: null, length: 0 }; + forEachMapEntry(counts, (count, value) => { + values[values.length++] = { [field]: value, count }; + }); + return arrayFrom(values); +}; + globalThis.__reaElectronActiveSnapshot = () => ({ - events: events.map((event) => ({ + events: snapshotArray(events, (event) => ({ ...event, - argument_shapes: [...event.argument_shapes], + argument_shapes: snapshotArray(event.argument_shapes), })), retention_budget_bytes: retentionBudgetBytes, estimated_retained_bytes: estimatedRetainedBytes, @@ -661,14 +916,8 @@ globalThis.__reaElectronActiveSnapshot = () => ({ dropped, dropped_ipc: droppedIpc, dropped_runtime: droppedRuntime, - dropped_event_families: [...droppedFamilies].map(([family, count]) => ({ - family, - count, - })), - dropped_event_roles: [...droppedRoles].map(([role, count]) => ({ - role, - count, - })), + dropped_event_families: snapshotCounts(droppedFamilies, "family"), + dropped_event_roles: snapshotCounts(droppedRoles, "role"), observed, observed_ipc: observedIpc, observed_runtime: observedRuntime, diff --git a/src/adb/AdbCommand.test.ts b/src/adb/AdbCommand.test.ts new file mode 100644 index 0000000000..63bb5ab0aa --- /dev/null +++ b/src/adb/AdbCommand.test.ts @@ -0,0 +1,112 @@ +import { expect, it, vi } from "vitest"; +import { execFileBytes } from "../process/ExecFileOutput.js"; +import { probeAdbPathExistence } from "./AdbCommand.js"; + +vi.mock("../process/ExecFileOutput.js", async (importOriginal) => ({ + ...(await importOriginal()), + execFileBytes: vi.fn(), +})); + +const probe = (signal?: AbortSignal) => + probeAdbPathExistence( + "push_adb_file", + "device", + "/device/path", + "/host/adb", + { + timeoutMs: 100, + maxOutputBytes: 1024, + }, + signal, + ); + +it("recognizes only the completed empty false result as absence", async () => { + vi.mocked(execFileBytes).mockRejectedValueOnce( + Object.assign(new Error("test false"), { + code: 1, + stdout: "", + stderr: "", + signal: null, + killed: false, + }), + ); + await expect(probe()).resolves.toEqual({ ok: true, value: false }); +}); + +it.each([ + { code: 2 }, + { code: "ENOENT" }, + { signal: "SIGTERM" }, + { collectionFailure: true }, + { stdout: "unexpected" }, + { stderr: " " }, +])("keeps nonstandard false completion unknown: %j", async (extra) => { + vi.mocked(execFileBytes).mockRejectedValueOnce( + Object.assign(new Error("probe failed"), { + code: 1, + stdout: "", + stderr: "", + signal: null, + killed: false, + ...extra, + }), + ); + const result = await probe(); + expect(result).toMatchObject({ + ok: false, + error: { + _tag: "AnalysisProtocolError", + diagnostics: { destination_existence: "unknown" }, + }, + }); +}); + +it.each([ + [{ killed: true }, "AnalysisTimeoutError"], + [{ outputTruncated: true }, "AnalysisResourceConstraintError"], + [{ code: "ABORT_ERR" }, "AnalysisCancelledError"], +] as const)( + "preserves a stopped or incomplete probe: %j", + async (extra, tag) => { + vi.mocked(execFileBytes).mockRejectedValueOnce( + Object.assign(new Error("probe failed"), { + code: 1, + stdout: "", + stderr: "", + signal: null, + killed: false, + ...extra, + }), + ); + await expect(probe()).resolves.toMatchObject({ + ok: false, + error: { _tag: tag }, + }); + }, +); + +it("recognizes an empty successful probe as existence", async () => { + vi.mocked(execFileBytes).mockResolvedValueOnce({ + stdout: Buffer.alloc(0), + stderr: Buffer.alloc(0), + }); + await expect(probe()).resolves.toEqual({ ok: true, value: true }); +}); + +it.each(["stdout", "stderr"] as const)( + "retains unexpected successful %s as unknown", + async (stream) => { + vi.mocked(execFileBytes).mockResolvedValueOnce({ + stdout: Buffer.alloc(0), + stderr: Buffer.alloc(0), + [stream]: Buffer.from("unexpected"), + }); + await expect(probe()).resolves.toMatchObject({ + ok: false, + error: { + _tag: "AnalysisProtocolError", + capturedOutput: { [stream]: "unexpected", exit_code: 0 }, + }, + }); + }, +); diff --git a/src/adb/AdbCommand.ts b/src/adb/AdbCommand.ts new file mode 100644 index 0000000000..5ab13e4b57 --- /dev/null +++ b/src/adb/AdbCommand.ts @@ -0,0 +1,244 @@ +import { + AnalysisCancelledError, + AnalysisProtocolError, + AnalysisResourceConstraintError, + AnalysisTimeoutError, + AnalysisUnsupportedTargetError, +} from "../domain/analysisErrorCore.js"; +import type { AnalysisError } from "../domain/analysisErrorBase.js"; +import { err, ok, type Result } from "../domain/result.js"; +import { + execFileBytes, + execFileOutputFailure, + type ExecFileOutputFailure, +} from "../process/ExecFileOutput.js"; +import { packageChangeFailureReason } from "./AdbShellOutput.js"; + +const STDERR_EXCERPT_BYTES = 512; + +/** Parsed command failure owned by this adapter, separate from its public tag. */ +class AdbProtocolError extends AnalysisProtocolError { + constructor( + message: string, + readonly commandFailure: ExecFileOutputFailure, + cause: unknown, + ) { + super(message, { cause }); + } +} + +/** adb joins shell operands into a remote POSIX command; quote at that boundary. */ +const quoteShellOperand = (value: string): string => + /^[A-Za-z0-9_./:=@+,-]+$/u.test(value) + ? value + : "'" + value.replaceAll("'", "'\\''") + "'"; + +export const adbArguments = ( + serial: string | undefined, + arguments_: readonly string[], +): readonly string[] => { + const operands = + arguments_[0] === "shell" + ? ["shell", ...arguments_.slice(1).map(quoteShellOperand)] + : [...arguments_]; + return serial === undefined ? operands : ["-s", serial, ...operands]; +}; + +export interface AdbRunLimits { + readonly timeoutMs: number; + readonly maxOutputBytes: number; +} + +/** Run one bounded adb invocation and keep its exact failure shape. */ +export const runAdbCapture = async ( + operation: string, + targetPath: string, + binary: string, + arguments_: readonly string[], + limits: AdbRunLimits, + signal: AbortSignal | undefined, +): Promise> => { + if (signal?.aborted === true) + return err(new AnalysisCancelledError(operation, { cause: signal.reason })); + try { + return ok( + await execFileBytes(binary, arguments_, { + timeout: limits.timeoutMs, + maxBuffer: limits.maxOutputBytes, + stopSignal: "SIGTERM", + ...(signal === undefined ? {} : { signal }), + }), + ); + } catch (cause) { + const failure = execFileOutputFailure(cause); + if (signal?.aborted || failure?.code === "ABORT_ERR") + return err(new AnalysisCancelledError(operation, { cause })); + if (failure === undefined) + return err( + new AnalysisProtocolError(`adb failed for ${operation}`, { cause }), + ); + if (failure.outputTruncated) + return err( + new AnalysisResourceConstraintError( + operation, + "memory", + `adb output exceeded the ${String(limits.maxOutputBytes)}-byte capture budget`, + { max_output_bytes: limits.maxOutputBytes }, + { cause }, + ), + ); + const excerpt = failure.stderr.trim().slice(0, STDERR_EXCERPT_BYTES); + if (/unauthorized/iu.test(excerpt)) + return err( + new AnalysisUnsupportedTargetError(operation, targetPath, excerpt, { + cause, + }), + ); + if ( + /device[^\n]*not found|device offline|still authorizing|more than one device/iu.test( + excerpt, + ) + ) + return err( + new AnalysisUnsupportedTargetError(operation, targetPath, excerpt, { + cause, + }), + ); + if ( + /No such file or directory|Permission denied|read: Permission denied/iu.test( + excerpt, + ) + ) + return err( + new AnalysisUnsupportedTargetError(operation, targetPath, excerpt, { + cause, + }), + ); + if (failure.killed) + return err( + new AnalysisTimeoutError(operation, limits.timeoutMs, { cause }), + ); + return err( + new AdbProtocolError( + excerpt === "" + ? `adb exited with code ${String(failure.code)} for ${operation}` + : excerpt, + failure, + cause, + ), + ); + } +}; + +/** Decode textual adb protocols after the shared byte capture and error boundary. */ +export const runAdb = async ( + ...arguments_: Parameters +): Promise< + Result<{ stdout: string; stderr: string; stdoutBytes: number }, AnalysisError> +> => { + const captured = await runAdbCapture(...arguments_); + if (!captured.ok) return captured; + return ok({ + stdout: captured.value.stdout.toString("utf8"), + stderr: captured.value.stderr.toString("utf8"), + // Byte count of the captured output before UTF-8 decoding, so malformed + // sequences replaced by U+FFFD still report their true captured size. + stdoutBytes: captured.value.stdout.byteLength, + }); +}; + +/** + * adb exits nonzero when the device refuses a package change, printing the + * `Failure [...]` verdict to stderr (install) or stdout (uninstall). Read the + * refusal from that failed run so it is not reported as unreadable output. + */ +export const packageChangeRefusal = ( + operation: string, + serial: string, + error: AnalysisError, +): Result | undefined => { + if (!(error instanceof AdbProtocolError)) return undefined; + const failure = error.commandFailure; + const reason = packageChangeFailureReason( + `${failure.stdout}\n${failure.stderr}`, + ); + return reason === null + ? undefined + : err( + new AnalysisUnsupportedTargetError(operation, serial, reason, { + cause: error, + }), + ); +}; + +/** Only the clean false result of test -e establishes absence. */ +export const probeAdbPathExistence = async ( + operation: string, + serial: string, + path: string, + binary: string, + limits: AdbRunLimits, + signal: AbortSignal | undefined, +): Promise> => { + const probed = await runAdbCapture( + operation, + path, + binary, + adbArguments(serial, ["shell", "test", "-e", path]), + limits, + signal, + ); + if (probed.ok) { + if (probed.value.stdout.length === 0 && probed.value.stderr.length === 0) + return ok(true); + return err( + new AnalysisProtocolError( + "The ADB path existence probe returned unexpected output; destination existence is unknown", + { + diagnostics: { destination_existence: "unknown" }, + capturedOutput: { + stdout: probed.value.stdout.toString("utf8"), + stderr: probed.value.stderr.toString("utf8"), + stdout_bytes: probed.value.stdout.length, + stderr_bytes: probed.value.stderr.length, + exit_code: 0, + signal: null, + truncated: false, + }, + }, + ), + ); + } + const error = probed.error; + if (!(error instanceof AdbProtocolError)) return probed; + const failure = error.commandFailure; + if ( + failure.code === 1 && + failure.signal === null && + !failure.killed && + !failure.outputTruncated && + failure.collectionFailure !== true && + failure.stdout === "" && + failure.stderr === "" + ) + return ok(false); + return err( + new AnalysisProtocolError(error.message, { + cause: error.cause, + diagnostics: { destination_existence: "unknown" }, + capturedOutput: { + stdout: failure.stdout, + stderr: failure.stderr, + ...(failure.stdoutBytes === undefined + ? {} + : { stdout_bytes: failure.stdoutBytes }), + ...(failure.stderrBytes === undefined + ? {} + : { stderr_bytes: failure.stderrBytes }), + exit_code: typeof failure.code === "number" ? failure.code : null, + signal: failure.signal, + truncated: failure.outputTruncated, + }, + }), + ); +}; diff --git a/src/adb/AdbProvider.ts b/src/adb/AdbProvider.ts index 0def72ccae..60d7b3672e 100644 --- a/src/adb/AdbProvider.ts +++ b/src/adb/AdbProvider.ts @@ -70,9 +70,13 @@ import { import { ProviderCleanupError } from "../domain/providerCleanupError.js"; import { PrivateRuntimeRootUnavailableError } from "../process/PrivateRuntimeRootUnavailableError.js"; import { - execFileBytes, - execFileOutputFailure, -} from "../process/ExecFileOutput.js"; + adbArguments, + runAdb, + runAdbCapture, + packageChangeRefusal, + probeAdbPathExistence, + type AdbRunLimits, +} from "./AdbCommand.js"; import { AdbConfigurationFailure, adbConfigurationAvailability, @@ -102,7 +106,6 @@ import { parseBugreportCopiedPath, parseDumpsysPackageOutput, parseFeatureListOutput, - packageChangeFailureReason, parsePackageChangeOutput, parseProcessListOutput, parsePngDimensions, @@ -144,7 +147,6 @@ const SCREEN_TIMEOUT_MS = 60_000; const SCREEN_MAX_OUTPUT_BYTES = 32 * 1024 * 1024; const BUGREPORT_TIMEOUT_MS = 600_000; const BUGREPORT_MAX_OUTPUT_BYTES = 1024 * 1024; -const STDERR_EXCERPT_BYTES = 512; const UNPARSED_LINES_REPORTED = 10; /** Carry the exact digest owner through caller-specific partial-result mapping. */ @@ -170,7 +172,7 @@ const hashAdbFile = async ( return await hashStableFile(path, { ...(signal === undefined ? {} : { signal }), retainFailedClose: (handle) => { - closeLease = new AnalysisCleanupLease(() => handle.close()); + closeLease = new AnalysisCleanupLease(() => handle.close(), [path]); resources.retain({ kind: "lease", resource: `${path} (ADB digest handle)`, @@ -326,7 +328,10 @@ const syncStagedAdbFile = async ( } const resource = `${path} (ADB staged-output handle)`; const stagedOwner = owner; - const closeLease = new AnalysisCleanupLease(() => stagedOwner.close()); + const closeLease = new AnalysisCleanupLease( + () => stagedOwner.close(), + [resource], + ); const released = await resources.release({ kind: "lease", resource, @@ -361,150 +366,6 @@ const syncStagedAdbFile = async ( if (primary !== undefined) throw primary; }; -/** adb joins shell operands into a remote POSIX command; quote at that boundary. */ -const quoteShellOperand = (value: string): string => - /^[A-Za-z0-9_./:=@+,-]+$/u.test(value) - ? value - : "'" + value.replaceAll("'", "'\\''") + "'"; - -const adbArguments = ( - serial: string | undefined, - arguments_: readonly string[], -): readonly string[] => { - const operands = - arguments_[0] === "shell" - ? ["shell", ...arguments_.slice(1).map(quoteShellOperand)] - : [...arguments_]; - return serial === undefined ? operands : ["-s", serial, ...operands]; -}; - -interface AdbRunLimits { - readonly timeoutMs: number; - readonly maxOutputBytes: number; -} - -/** Run one bounded adb invocation and keep its exact failure shape. */ -const runAdbCapture = async ( - operation: string, - targetPath: string, - binary: string, - arguments_: readonly string[], - limits: AdbRunLimits, - signal: AbortSignal | undefined, -): Promise> => { - if (signal?.aborted === true) - return err(new AnalysisCancelledError(operation, { cause: signal.reason })); - try { - return ok( - await execFileBytes(binary, arguments_, { - timeout: limits.timeoutMs, - maxBuffer: limits.maxOutputBytes, - stopSignal: "SIGTERM", - ...(signal === undefined ? {} : { signal }), - }), - ); - } catch (cause) { - if (signal?.aborted || execFileOutputFailure(cause)?.code === "ABORT_ERR") - return err(new AnalysisCancelledError(operation, { cause })); - const failure = execFileOutputFailure(cause); - if (failure === undefined) - return err( - new AnalysisProtocolError(`adb failed for ${operation}`, { cause }), - ); - if (failure.outputTruncated) - return err( - new AnalysisResourceConstraintError( - operation, - "memory", - `adb output exceeded the ${String(limits.maxOutputBytes)}-byte capture budget`, - { max_output_bytes: limits.maxOutputBytes }, - { cause }, - ), - ); - const excerpt = failure.stderr.trim().slice(0, STDERR_EXCERPT_BYTES); - if (/unauthorized/iu.test(excerpt)) - return err( - new AnalysisUnsupportedTargetError(operation, targetPath, excerpt, { - cause, - }), - ); - if ( - /device[^\n]*not found|device offline|still authorizing|more than one device/iu.test( - excerpt, - ) - ) - return err( - new AnalysisUnsupportedTargetError(operation, targetPath, excerpt, { - cause, - }), - ); - if ( - /No such file or directory|Permission denied|read: Permission denied/iu.test( - excerpt, - ) - ) - return err( - new AnalysisUnsupportedTargetError(operation, targetPath, excerpt, { - cause, - }), - ); - if (failure.killed) - return err( - new AnalysisTimeoutError(operation, limits.timeoutMs, { cause }), - ); - return err( - new AnalysisProtocolError( - excerpt === "" - ? `adb exited with code ${String(failure.code)} for ${operation}` - : excerpt, - { cause }, - ), - ); - } -}; - -/** Decode textual adb protocols after the shared byte capture and error boundary. */ -const runAdb = async ( - ...arguments_: Parameters -): Promise< - Result<{ stdout: string; stderr: string; stdoutBytes: number }, AnalysisError> -> => { - const captured = await runAdbCapture(...arguments_); - if (!captured.ok) return captured; - return ok({ - stdout: captured.value.stdout.toString("utf8"), - stderr: captured.value.stderr.toString("utf8"), - // Byte count of the captured output before UTF-8 decoding, so malformed - // sequences replaced by U+FFFD still report their true captured size. - stdoutBytes: captured.value.stdout.byteLength, - }); -}; - -/** - * adb exits nonzero when the device refuses a package change, printing the - * `Failure [...]` verdict to stderr (install) or stdout (uninstall). Read the - * refusal from that failed run so it is not reported as unreadable output. - */ -const packageChangeRefusal = ( - operation: string, - serial: string, - error: AnalysisError, -): Result | undefined => { - if (!(error instanceof AnalysisProtocolError)) return undefined; - const failure = execFileOutputFailure(error.cause); - if (failure === undefined) return undefined; - const reason = packageChangeFailureReason( - `${failure.stdout}\n${failure.stderr}`, - ); - return reason === null - ? undefined - : err( - new AnalysisUnsupportedTargetError(operation, serial, reason, { - cause: error, - }), - ); -}; - const clientIdentityResult = ( selection: AdbBinarySelection, identity: AdbClientIdentity, @@ -605,7 +466,10 @@ const publishScreenExclusively = async ( // the visible file despite later cancellation so its receipt remains known. const ownedOutput = new OwnedFileHandle(output); const handleResource = `${outputPath} (ADB publication handle)`; - const closeOnly = new AnalysisCleanupLease(() => ownedOutput.close()); + const closeOnly = new AnalysisCleanupLease( + () => ownedOutput.close(), + [handleResource], + ); const removeOutput = async ( expected: Awaited>, ): Promise => { @@ -2529,28 +2393,16 @@ export class AdbProvider { operation, signal, ); - // `test -e` exits nonzero with no adb diagnosis when the path is absent. - // Any other probe failure leaves existence unknown, so it is reported as - // such rather than treated as absence or followed by a push. - const existsProbe = await runAdb( + const existsProbe = await probeAdbPathExistence( operation, + input.serial, input.device_path, selection.binary, - adbArguments(input.serial, [ - "shell", - "test", - "-e", - input.device_path, - ]), { timeoutMs: SETTINGS_TIMEOUT_MS, maxOutputBytes: 1024 }, signal, ); - if ( - !existsProbe.ok && - !(existsProbe.error instanceof AnalysisProtocolError) - ) - return existsProbe; - const deviceExists = existsProbe.ok; + if (!existsProbe.ok) return existsProbe; + const deviceExists = existsProbe.value; if (deviceExists && !input.overwrite) return err( new AnalysisOutputError( diff --git a/src/application/AnalysisProvider.ts b/src/application/AnalysisProvider.ts index 668b5ca5fa..c921d766cf 100644 --- a/src/application/AnalysisProvider.ts +++ b/src/application/AnalysisProvider.ts @@ -290,6 +290,12 @@ export interface AnalysisProviderReadinessSnapshot { /** Deep provider candidate discoverable before one target-bound route is chosen. */ export interface AnalysisProviderCandidate extends AnalysisProvider { + /** + * Own inspection through final reads and cleanup. Observe the supplied owner + * signal cooperatively; resolve only after finalization settles, retaining any + * failed cleanup capability. Registry cancellation and shutdown join this + * completion rather than abandoning the promise. + */ resolveAnalysisProfile( target: BinaryTarget, options?: AnalysisProfileResolutionOptions, diff --git a/src/application/binary/AnalysisProviderEvaluation.ts b/src/application/binary/AnalysisProviderEvaluation.ts index 559e18b084..b47b7caf62 100644 --- a/src/application/binary/AnalysisProviderEvaluation.ts +++ b/src/application/binary/AnalysisProviderEvaluation.ts @@ -7,12 +7,12 @@ import { AnalysisCancelledError, AnalysisUnsupportedTargetError, } from "../../domain/analysisErrorCore.js"; -import type { AnalysisError } from "../../domain/analysisErrorBase.js"; +import { AnalysisError } from "../../domain/analysisErrorBase.js"; import { jsonObjectSchema, type JsonValue } from "../../domain/jsonValue.js"; import { err, ok, type Result } from "../../domain/result.js"; import type { AnalysisProviderCandidate } from "../AnalysisProvider.js"; import type { AnalysisProviderCandidateStatus } from "./AnalysisProviderRegistry.js"; -import { ABORTED, waitForAbortable } from "../../process/AbortablePromise.js"; +import { ProviderAdapterError } from "../../domain/providerAdapterError.js"; import type { FileCloseResourceOwner } from "../AnalysisProvider.js"; /** Profile-resolution truth for one discovered provider candidate. */ @@ -34,34 +34,29 @@ export const evaluateAnalysisProviderCandidate = async ( Result > => { if (signalIsAborted(signal)) - return err(new AnalysisCancelledError("open_binary")); + return err( + new AnalysisCancelledError("open_binary", { cause: signal?.reason }), + ); let resolved: Awaited>; try { - const resolution = await waitForAbortable( - candidate.resolveAnalysisProfile( - target, - signal === undefined && fileCloseResources === undefined - ? undefined - : { - ...(signal === undefined ? {} : { signal }), - ...(fileCloseResources === undefined - ? {} - : { fileCloseResources }), - }, - ), - signal, - ); - if (resolution === ABORTED) - return err(new AnalysisCancelledError("open_binary")); - resolved = resolution; + resolved = await candidate.resolveAnalysisProfile(target, { + ...(signal === undefined ? {} : { signal }), + ...(fileCloseResources === undefined ? {} : { fileCloseResources }), + }); } catch (cause: unknown) { - void cause; - return ok( - rejectedProfile(candidate, status, "Provider profile resolution threw"), + resolved = err( + cause instanceof AnalysisError + ? cause + : new ProviderAdapterError( + status.provider.id, + "resolve_analysis_profile", + { + cause, + diagnostics: { reason: "Provider profile resolution threw" }, + }, + ), ); } - if (signalIsAborted(signal)) - return err(new AnalysisCancelledError("open_binary")); if (!resolved.ok && resolved.error instanceof AnalysisCancelledError) return err(resolved.error); if (!resolved.ok) { @@ -85,6 +80,10 @@ export const evaluateAnalysisProviderCandidate = async ( }); return ok({ ...rejection, profileError: error }); } + if (signalIsAborted(signal)) + return err( + new AnalysisCancelledError("open_binary", { cause: signal?.reason }), + ); try { if (resolved.value.profile === null) return ok( diff --git a/src/application/binary/AnalysisProviderRegistry.test.ts b/src/application/binary/AnalysisProviderRegistry.test.ts index 38b5403221..13986a4684 100644 --- a/src/application/binary/AnalysisProviderRegistry.test.ts +++ b/src/application/binary/AnalysisProviderRegistry.test.ts @@ -16,6 +16,7 @@ import { ProviderAdapterError } from "../../domain/providerAdapterError.js"; import { ProviderCleanupError } from "../../domain/providerCleanupError.js"; import { ProviderSelectionError } from "../../domain/providerSelectionError.js"; import { projectAnalysisError } from "../../domain/analysisErrorProjection.js"; +import { AnalysisCancelledError } from "../../domain/analysisErrorCore.js"; import { err, ok } from "../../domain/result.js"; const DATABASE_TARGET: BinaryTarget = { @@ -395,29 +396,63 @@ describe("analysis provider registry: profile and cleanup failures", () => { expect(alternate.created).toEqual([]); }); - it("cancels a pending profile probe even when the adapter ignores its signal", async () => { + it("owns a signal-ignoring profile probe until final completion and joins shutdown", async () => { const alpha = candidate("alpha"); let observedSignal: AbortSignal | undefined; + let finish: (() => void) | undefined; + let started: (() => void) | undefined; + const pending = new Promise((resolve) => { + started = resolve; + }); + const gate = new Promise((resolve) => { + finish = resolve; + }); const ignoresCancellation: AnalysisProviderCandidate = { ...alpha.provider, - resolveAnalysisProfile: (_target, options) => { + resolveAnalysisProfile: async (target, options) => { observedSignal = options?.signal; - return new Promise(() => undefined); + started?.(); + await gate; + return alpha.provider.resolveAnalysisProfile(target); }, }; + const registry = new AnalysisProviderRegistry([ignoresCancellation]); const controller = new AbortController(); - const selecting = new AnalysisProviderRegistry([ - ignoresCancellation, - ]).select(DATABASE_TARGET, "alpha", { signal: controller.signal }); - expect(observedSignal).toBe(controller.signal); - - controller.abort(); - - await expect(selecting).resolves.toMatchObject({ - ok: false, - error: { _tag: "AnalysisCancelledError", operation: "open_binary" }, + const reason = { caller: "profile finalization" }; + let selected = false; + const selecting = registry + .select(DATABASE_TARGET, "alpha", { signal: controller.signal }) + .then((result) => { + selected = true; + return result; + }); + await pending; + controller.abort(reason); + let closed = false; + const closing = registry.closeOwnedResources().then((result) => { + closed = true; + return result; }); - expect(alpha.created).toEqual([]); + try { + await new Promise((resolve) => setImmediate(resolve)); + expect(selected).toBe(false); + expect(closed).toBe(false); + expect(registry.hasPendingOwnedResources()).toBe(true); + expect(observedSignal?.aborted).toBe(true); + expect(observedSignal?.reason).toBe(reason); + finish?.(); + const result = await selecting; + expect(result).toMatchObject({ + ok: false, + error: { _tag: "AnalysisCancelledError", cause: reason }, + }); + expect(await closing).toEqual(ok(null)); + expect(registry.hasPendingOwnedResources()).toBe(false); + expect(alpha.created).toEqual([]); + } finally { + finish?.(); + await Promise.all([selecting, closing]); + } }); }); @@ -524,3 +559,207 @@ const capability = (provider: ProviderIdentity): CapabilityDescriptor => ({ }, limitations: [], }); + +describe("profile resolution terminal facts", () => { + it.each([false, true])( + "preserves a physical profile failure across late caller abort (throws: %s)", + async (throws) => { + const alpha = candidate("alpha"); + const controller = new AbortController(); + const primary = new ProviderAdapterError( + "alpha", + "resolve_analysis_profile", + { cause: new Error("physical profile failure") }, + ); + const registry = new AnalysisProviderRegistry([ + { + ...alpha.provider, + resolveAnalysisProfile: async () => { + controller.abort({ caller: "after physical failure" }); + if (throws) throw primary; + return err(primary); + }, + }, + ]); + const result = await registry.select(DATABASE_TARGET, "alpha", { + signal: controller.signal, + }); + expect(result).toEqual(err(primary)); + expect(registry.hasPendingOwnedResources()).toBe(false); + expect(await registry.closeOwnedResources()).toEqual(ok(null)); + }, + ); + + it("preserves exact cancellation with cleanup and blocks admission until its owner confirms release", async () => { + const alpha = candidate("alpha"); + const controller = new AbortController(); + const reason = { caller: "final cleanup" }; + const primary = new AnalysisCancelledError("resolve_analysis_profile", { + cause: reason, + cleanup: { reason: "file close rejected", resources: ["profile-file"] }, + }); + let attempts = 0; + let allowClose = false; + const registry = new AnalysisProviderRegistry([ + { + ...alpha.provider, + resolveAnalysisProfile: async () => { + attempts++; + controller.abort(reason); + return err(primary); + }, + closeOwnedResources: async () => { + if (!allowClose) return err(primary); + primary.resolveCleanup(); + return ok(null); + }, + }, + ]); + const result = await registry.select(DATABASE_TARGET, "alpha", { + signal: controller.signal, + }); + expect(result).toEqual(err(primary)); + expect(primary.cause).toBe(reason); + expect(registry.hasPendingOwnedResources()).toBe(true); + expect(await registry.select(DATABASE_TARGET, "alpha")).toEqual( + err(primary), + ); + expect(attempts).toBe(1); + allowClose = true; + expect(await registry.closeOwnedResources()).toEqual(ok(null)); + expect(primary.cleanupIncomplete).toBe(false); + expect(registry.hasPendingOwnedResources()).toBe(false); + }); +}); + +describe("automatic profile cleanup ownership", () => { + it.each(["alpha", "beta"])( + "keeps automatic mixed-child cleanup conjunctive when %s releases first", + async (first) => { + const alpha = candidate("alpha"); + const beta = candidate("beta"); + const controller = new AbortController(); + const reason = { caller: "mixed profile finalization" }; + const failures = { + alpha: new AnalysisCancelledError("resolve_analysis_profile", { + cause: reason, + cleanup: { reason: "alpha descriptor", resources: ["alpha-file"] }, + }), + beta: new ProviderCleanupError("beta", ["beta-file"], { + reason: "beta descriptor", + }), + }; + let entered = 0; + let admitChildren: (() => void) | undefined; + const admitted = new Promise((resolve) => { + admitChildren = resolve; + }); + const released = new Set(); + const registry = new AnalysisProviderRegistry( + [alpha, beta].map((fixture) => { + const id = fixture.provider.identity().id as "alpha" | "beta"; + return { + ...fixture.provider, + resolveAnalysisProfile: async () => { + entered++; + if (entered === 2) { + controller.abort(reason); + admitChildren?.(); + } + await admitted; + return err(failures[id]); + }, + closeOwnedResources: async () => { + if (!released.has(id)) return err(failures[id]); + failures[id].resolveCleanup(); + return ok(null); + }, + }; + }), + ); + const result = await registry.select(DATABASE_TARGET, "auto", { + signal: controller.signal, + }); + if (result.ok) throw new Error("Mixed failed profiles admitted"); + expect(result.error.cleanupIncomplete).toBe(true); + expect(result.error.cleanupResources).toEqual([ + "alpha-file", + "beta-file", + ]); + expect(result.error.diagnostics?.profile_errors).toEqual( + expect.arrayContaining([ + expect.objectContaining({ provider_id: "alpha" }), + expect.objectContaining({ provider_id: "beta" }), + ]), + ); + const close1 = await registry.closeOwnedResources(); + expect(close1.ok).toBe(false); + released.add(first); + const close2 = await registry.closeOwnedResources(); + expect(close2.ok).toBe(false); + expect(result.error.cleanupIncomplete).toBe(true); + expect(result.error.cleanupResources).toEqual([ + first === "alpha" ? "beta-file" : "alpha-file", + ]); + expect(close1.ok ? [] : close1.error.cleanupResources).toEqual([ + first === "alpha" ? "beta-file" : "alpha-file", + ]); + released.add(first === "alpha" ? "beta" : "alpha"); + expect(await registry.closeOwnedResources()).toEqual(ok(null)); + expect(result.error.cleanupIncomplete).toBe(false); + if (close1.ok || close2.ok) + throw new Error("Cleanup unexpectedly succeeded"); + expect(close1.error.cleanupIncomplete).toBe(false); + expect(close2.error.cleanupIncomplete).toBe(false); + expect(registry.hasPendingOwnedResources()).toBe(false); + }, + ); +}); + +it("registers before production and gates fresh selections until shutdown has joined finalization", async () => { + const alpha = candidate("alpha"); + let calls = 0; + let enter: (() => void) | undefined; + let finish: (() => void) | undefined; + const entered = new Promise((resolve) => { + enter = resolve; + }); + const gate = new Promise((resolve) => { + finish = resolve; + }); + const registry = new AnalysisProviderRegistry([ + { + ...alpha.provider, + resolveAnalysisProfile: async (target) => { + calls++; + if (calls === 1) { + enter?.(); + await gate; + } + return alpha.provider.resolveAnalysisProfile(target); + }, + }, + ]); + const selecting = registry.select(DATABASE_TARGET, "alpha"); + await entered; + const closing = registry.closeOwnedResources(); + const next = registry.select(DATABASE_TARGET, "alpha"); + try { + await new Promise((resolve) => setImmediate(resolve)); + expect(calls).toBe(1); + expect(registry.hasPendingOwnedResources()).toBe(true); + finish?.(); + expect(await selecting).toMatchObject({ + ok: false, + error: { _tag: "AnalysisCancelledError" }, + }); + expect(await closing).toEqual(ok(null)); + expect((await next).ok).toBe(true); + expect(calls).toBe(2); + expect(registry.hasPendingOwnedResources()).toBe(false); + } finally { + finish?.(); + await Promise.all([selecting, closing, next]); + await registry.closeOwnedResources(); + } +}); diff --git a/src/application/binary/AnalysisProviderRegistry.ts b/src/application/binary/AnalysisProviderRegistry.ts index f2a45bd29d..1a08e2efcb 100644 --- a/src/application/binary/AnalysisProviderRegistry.ts +++ b/src/application/binary/AnalysisProviderRegistry.ts @@ -1,3 +1,10 @@ +import { AnalysisCleanupLease } from "../../artifacts/AnalysisCleanupLease.js"; +import { ArtifactResourceScope } from "../../artifacts/ArtifactResourceScope.js"; +import { ArtifactReaderFailure } from "../../artifacts/ArtifactReader.js"; +import { bindArtifactFileCleanup } from "../../artifacts/ArtifactFileOperation.js"; +import { SharedStartupAttempt } from "../../process/SharedStartupAttempt.js"; +import { ProviderAdapterError } from "../../domain/providerAdapterError.js"; +import { projectAnalysisError } from "../../domain/analysisErrorProjection.js"; import { ProviderCleanupError } from "../../domain/providerCleanupError.js"; import { analysisProviderIdSchema, @@ -67,9 +74,10 @@ interface TargetSelectionContext { readonly target: BinaryTarget; readonly baseline: readonly AnalysisProviderCandidateStatus[]; readonly signal: AbortSignal | undefined; - readonly fileCloseResources?: FileCloseResourceOwner; } +type SelectionResult = Result; + /** * Discover and select overlapping deep providers without starting their clients. * Registration order never affects selection or caller-visible output. @@ -80,6 +88,20 @@ export class AnalysisProviderRegistry { readonly #declaredOperations: readonly string[]; readonly #defaultSelector: AnalysisProviderSelector; + readonly #selections = new Set>(); + readonly #selectionCleanup = new Set(); + readonly #profileFiles = new ArtifactResourceScope(); + readonly #profileFileOwner: FileCloseResourceOwner = { + retain: (owner, resource) => + this.#profileFiles.retain({ + kind: "lease", + resource, + lease: new AnalysisCleanupLease(() => owner.close(), [resource]), + }), + run: (operation) => this.#profileFiles.run(operation), + }; + #closing: Promise> | undefined; + constructor( providers: readonly AnalysisProviderCandidate[], defaultSelector: AnalysisProviderSelector = "auto", @@ -143,29 +165,67 @@ export class AnalysisProviderRegistry { /** Report retained candidate resources without retrying cleanup. */ hasPendingOwnedResources(): boolean { - return this.#providers.some( - (provider) => provider.hasPendingOwnedResources?.() === true, + return ( + this.#selections.size > 0 || + this.#profileFiles.hasPending || + [...this.#selectionCleanup].some((error) => error.cleanupIncomplete) || + this.#providers.some( + (provider) => provider.hasPendingOwnedResources?.() === true, + ) ); } /** Close candidate-owned resources even when no target client was opened. */ - async closeOwnedResources(): Promise> { + closeOwnedResources(): Promise> { + this.#closing ??= Promise.resolve() + .then(() => this.#closeOwnedResources()) + .finally(() => { + this.#closing = undefined; + }); + return this.#closing; + } + + async #closeOwnedResources(): Promise> { + const selections = [...this.#selections]; + for (const selection of selections) selection.close(); + await Promise.allSettled(selections.map((selection) => selection.promise)); const failures: AnalysisError[] = []; + try { + await this.#profileFiles.retryCleanup(); + } catch (cause: unknown) { + const cleanup = ArtifactReaderFailure.cleanupObservation( + cause, + "provider profile files", + ); + const failure = new ProviderCleanupError( + "rea", + cleanup.resources, + { reason: cleanup.reason }, + { cause }, + ); + bindArtifactFileCleanup(cause, failure); + failures.push(failure); + } for (const provider of this.#providers) { if (provider.closeOwnedResources === undefined) continue; const result = await provider.closeOwnedResources(); if (!result.ok) failures.push(result.error); } - if (failures.length === 0) return ok(null); - if (failures.length === 1) return err(failures[0]!); + for (const failure of this.#selectionCleanup) { + if (failure.cleanupIncomplete) failures.push(failure); + else this.#selectionCleanup.delete(failure); + } + const distinct = [...new Set(failures)]; + if (distinct.length === 0) return ok(null); + if (distinct.length === 1) return err(distinct[0]!); return err( new ProviderCleanupError( "rea", - failures.flatMap((failure) => failure.cleanupResources), - { - reason: failures.map((failure) => failure.message).join("; "), - }, - { cause: new AggregateError(failures) }, + [...new Set(distinct.flatMap((failure) => failure.cleanupResources))], + { reason: distinct.map((failure) => failure.message).join("; ") }, + { cause: new AggregateError(distinct) }, + ).bindCleanupDependencies( + distinct.filter((failure) => failure.cleanupIncomplete), ), ); } @@ -186,13 +246,84 @@ export class AnalysisProviderRegistry { async select( target: BinaryTarget, requestedSelector?: AnalysisProviderSelector, - options: { - readonly signal?: AbortSignal; - readonly fileCloseResources?: FileCloseResourceOwner; - } = {}, - ): Promise> { + options: { readonly signal?: AbortSignal } = {}, + ): Promise { if (signalIsAborted(options.signal)) - return err(new AnalysisCancelledError("open_binary")); + return err( + new AnalysisCancelledError("open_binary", { + cause: options.signal?.reason, + }), + ); + if (this.#closing !== undefined) { + const closed = await this.#closing; + if (!closed.ok) return closed; + } + for (const failure of this.#selectionCleanup) + if (!failure.cleanupIncomplete) this.#selectionCleanup.delete(failure); + if ( + [...this.#selectionCleanup].some((failure) => failure.cleanupIncomplete) + ) { + // A retry may release retained owners; never stop another live selection + // merely because a new caller wants admission. + if (this.#selections.size > 0) + return err( + [...this.#selectionCleanup].find( + (failure) => failure.cleanupIncomplete, + )!, + ); + const closed = await this.closeOwnedResources(); + if (!closed.ok) return closed; + } + if (signalIsAborted(options.signal)) + return err( + new AnalysisCancelledError("open_binary", { + cause: options.signal?.reason, + }), + ); + const selection = new SharedStartupAttempt( + (signal) => + Promise.resolve().then(() => + this.#selectTarget(target, requestedSelector, signal), + ), + options.signal, + ); + this.#selections.add(selection); + void selection.promise.then( + (result) => { + this.#selections.delete(selection); + if (!result.ok && result.error.cleanupIncomplete) + this.#selectionCleanup.add(result.error); + }, + () => { + this.#selections.delete(selection); + }, + ); + const waited = await selection.wait(options.signal); + if (waited.status !== "cancelled") { + if (!waited.value.ok) return waited.value; + if (!selection.signal.aborted) return waited.value; + return err( + new AnalysisCancelledError("open_binary", { + cause: selection.signal.reason, + }), + ); + } + return err( + new AnalysisCancelledError("open_binary", { + cause: options.signal?.reason, + }), + ); + } + + async #selectTarget( + target: BinaryTarget, + requestedSelector: AnalysisProviderSelector | undefined, + signal: AbortSignal, + ): Promise { + if (signalIsAborted(signal)) + return err( + new AnalysisCancelledError("open_binary", { cause: signal.reason }), + ); const parsedRequest = requestedSelector === undefined ? undefined @@ -210,8 +341,10 @@ export class AnalysisProviderRegistry { const request = parsedRequest?.data; const selector = request ?? this.#defaultSelector; const baseline = this.candidates(target); - if (signalIsAborted(options.signal)) - return err(new AnalysisCancelledError("open_binary")); + if (signalIsAborted(signal)) + return err( + new AnalysisCancelledError("open_binary", { cause: signal.reason }), + ); if (selector === "auto" && this.canInspectWithoutDeepProvider?.(target)) return ok({ requestedProviderId: selector, @@ -236,10 +369,7 @@ export class AnalysisProviderRegistry { { target, baseline, - signal: options.signal, - ...(options.fileCloseResources === undefined - ? {} - : { fileCloseResources: options.fileCloseResources }), + signal: signal, }, selector, request === undefined ? "environment" : "request", @@ -247,10 +377,7 @@ export class AnalysisProviderRegistry { return this.#selectAutomatically({ target, baseline, - signal: options.signal, - ...(options.fileCloseResources === undefined - ? {} - : { fileCloseResources: options.fileCloseResources }), + signal: signal, }); } @@ -299,7 +426,7 @@ export class AnalysisProviderRegistry { initial, target, signal, - context.fileCloseResources, + this.#profileFileOwner, ); if (!evaluation.ok) return evaluation; const evaluated = evaluation.value; @@ -346,7 +473,7 @@ export class AnalysisProviderRegistry { status, target, signal, - context.fileCloseResources, + this.#profileFileOwner, ) : ok({ candidate: provider, @@ -355,17 +482,21 @@ export class AnalysisProviderRegistry { }, ), ); - const cancelled = results.find((result) => !result.ok); - if (cancelled !== undefined && !cancelled.ok) return cancelled; - const evaluations = results.map((result) => { - if (!result.ok) - throw new TypeError("Cancelled candidate evaluation disappeared"); - return result.value; + const evaluations = results.flatMap((result) => + result.ok ? [result.value] : [], + ); + const failures = results.flatMap((result, index) => { + const error = result.ok ? result.value.profileError : result.error; + return error === undefined + ? [] + : [{ providerId: this.#providers[index]!.identity().id, error }]; }); - const cleanupFailure = evaluations.find( - ({ profileError }) => profileError?.cleanupIncomplete === true, - )?.profileError; - if (cleanupFailure !== undefined) return err(cleanupFailure); + if ( + results.some((result) => !result.ok) || + failures.some(({ error }) => error.cleanupIncomplete) || + signalIsAborted(signal) + ) + return err(profileSelectionFailure(failures, signal)); const statuses = evaluations.map(({ status }) => status); const usable = evaluations.filter( ( @@ -507,3 +638,64 @@ const cloneTargetSupport = ( ...support, diagnostics: jsonObjectSchema.parse(support.diagnostics), }); + +/** Every terminal child remains attributed; cleanup is conjunctive across owners. */ +const profileSelectionFailure = ( + failures: readonly { + readonly providerId: string; + readonly error: AnalysisError; + }[], + signal?: AbortSignal, +): AnalysisError => { + const primary = + failures.find(({ error }) => !(error instanceof AnalysisCancelledError)) ?? + failures[0]; + if (primary === undefined) + return new AnalysisCancelledError("open_binary", { cause: signal?.reason }); + if (failures.length === 1) return primary.error; + const cleanupFailures = failures + .filter(({ error }) => error.cleanupIncomplete) + .map(({ error }) => error); + const options = { + diagnostics: { + profile_errors: failures.map(({ providerId, error }) => ({ + provider_id: providerId, + error: projectAnalysisError(error), + })), + }, + ...(primary.error.capturedOutput === undefined + ? {} + : { capturedOutput: primary.error.capturedOutput }), + ...(primary.error.partialObservation === undefined + ? {} + : { partialObservation: primary.error.partialObservation }), + ...(cleanupFailures.length === 0 + ? {} + : { + cleanup: { + reason: cleanupFailures.map((error) => error.message).join("; "), + resources: [ + ...new Set( + cleanupFailures.flatMap((error) => error.cleanupResources), + ), + ], + }, + }), + }; + const failure = + primary.error instanceof AnalysisCancelledError + ? new AnalysisCancelledError("open_binary", { + ...options, + cause: primary.error.cause, + }) + : new ProviderAdapterError("rea", "resolve_analysis_profile", { + ...options, + userMessage: projectAnalysisError(primary.error).message, + cause: new AggregateError( + failures.map(({ error }) => error), + "Provider profile resolutions failed", + { cause: primary.error }, + ), + }); + return failure.bindCleanupDependencies(cleanupFailures); +}; diff --git a/src/application/binary/BinarySession.ts b/src/application/binary/BinarySession.ts index fd02c8c12f..7ac894cc99 100644 --- a/src/application/binary/BinarySession.ts +++ b/src/application/binary/BinarySession.ts @@ -200,12 +200,15 @@ export class BinarySession current: this.#active, target, options, - fileCloseResources: this.fileCloseResourceOwner, stagedSnapshotMatches: (target, profile) => this.matchesSnapshot(target, profile), }).then((resolved) => - isAborted(options.signal) - ? err(new AnalysisCancelledError("open_binary")) + resolved.ok && isAborted(options.signal) + ? err( + new AnalysisCancelledError("open_binary", { + cause: options.signal?.reason, + }), + ) : resolved, ); } @@ -225,7 +228,6 @@ export class BinarySession current: this.#active, path, options: admitted, - fileCloseResources: this.fileCloseResourceOwner, stagedSnapshotMatches: (target, profile) => this.matchesSnapshot(target, profile), }), @@ -503,11 +505,9 @@ export class BinarySession if (clearRecords) this.clearSessionRecords(); this.#clearRuntimeAvailability(); const fileResources = await this.closeWriteResources(); - const hashResources = await this.closeFailedFileCloses(); const candidateResources = await this.#providerRouter.closeOwnedResources(); const resourceFailures = [ ...(fileResources.ok ? [] : [fileResources.error]), - ...(hashResources.ok ? [] : [hashResources.error]), ...(candidateResources.ok ? [] : [candidateResources.error]), ]; let resourceFailure = resourceFailures[0]; diff --git a/src/application/binary/BinarySessionOpen.ts b/src/application/binary/BinarySessionOpen.ts index 263501aa63..bb1631f350 100644 --- a/src/application/binary/BinarySessionOpen.ts +++ b/src/application/binary/BinarySessionOpen.ts @@ -15,7 +15,6 @@ import { AnalysisSnapshotMismatchError, } from "../../domain/evidenceErrors.js"; import type { AnalysisError } from "../../domain/analysisErrorBase.js"; -import type { FileCloseResourceOwner } from "../AnalysisProvider.js"; import { err, type Result } from "../../domain/result.js"; import type { SessionProviderRoute } from "./SessionProviderRouter.js"; import { SessionProviderRouter } from "./SessionProviderRouter.js"; @@ -61,7 +60,6 @@ interface ResolveSessionOpenInput { target: BinaryTarget, profile: AnalysisProfileCommitment | null, ) => boolean; - readonly fileCloseResources?: FileCloseResourceOwner; } interface ResolveSessionTargetInput { @@ -73,7 +71,6 @@ interface ResolveSessionTargetInput { target: BinaryTarget, profile: AnalysisProfileCommitment | null, ) => boolean; - readonly fileCloseResources?: FileCloseResourceOwner; } /** Parse a target, resolve its provider route, and validate snapshot binding. */ @@ -109,12 +106,7 @@ export const resolveSessionTarget = async ( const resolvedRoute = sameTarget && options.providerId === undefined && current !== undefined ? ({ ok: true, value: current.route } as const) - : await router.resolve( - target, - options.providerId, - options.signal, - input.fileCloseResources, - ); + : await router.resolve(target, options.providerId, options.signal); if (!resolvedRoute.ok) return resolvedRoute; return validateResolvedSessionTarget({ ...input, diff --git a/src/application/binary/BinarySessionRecords.ts b/src/application/binary/BinarySessionRecords.ts index f88b27fd74..83f02aa58c 100644 --- a/src/application/binary/BinarySessionRecords.ts +++ b/src/application/binary/BinarySessionRecords.ts @@ -32,8 +32,6 @@ import { AnalysisSnapshotCache } from "./AnalysisSnapshotCache.js"; import { InvestigationRecords } from "../investigation/InvestigationRecords.js"; import { ArtifactResourceScope } from "../../artifacts/ArtifactResourceScope.js"; import { ArtifactReaderFailure } from "../../artifacts/ArtifactReader.js"; -import { ProviderCleanupError } from "../../domain/providerCleanupError.js"; -import type { FileCloseResourceOwner } from "../AnalysisProvider.js"; import { writeAnalysisSnapshot as writeSnapshotFile } from "./AnalysisSnapshotFiles.js"; const SNAPSHOT_MUTATION_RECOVERY = @@ -57,21 +55,9 @@ export interface WorkflowSnapshotRecordInput { export abstract class BinarySessionRecords { readonly #records: InvestigationRecords; readonly #writeResources = new ArtifactResourceScope(); - readonly #fileCloseResources = new ArtifactResourceScope(); - protected readonly fileCloseResourceOwner: FileCloseResourceOwner = { - retain: (owner, resource) => - this.#fileCloseResources.retain({ - kind: "lease", - resource, - lease: { release: () => owner.close() }, - }), - run: (operation) => this.#fileCloseResources.run(operation), - }; protected get hasPendingFileResources(): boolean { - return ( - this.#writeResources.hasPending || this.#fileCloseResources.hasPending - ); + return this.#writeResources.hasPending; } readonly #snapshot = new AnalysisSnapshotCache(); #snapshotInvalidated = false; @@ -150,27 +136,6 @@ export abstract class BinarySessionRecords { } } - /** Retry retained descriptors owned by this composed binary session. */ - async closeFailedFileCloses(): Promise> { - try { - await this.#fileCloseResources.retryCleanup(); - return ok(null); - } catch (cause: unknown) { - const observation = ArtifactReaderFailure.cleanupObservation( - cause, - "provider file descriptor", - ); - return err( - new ProviderCleanupError( - "rea", - observation.resources, - { reason: observation.reason }, - { cause, operation: "close_binary" }, - ), - ); - } - } - importEvidenceBundle( bundle: unknown, ): Result { diff --git a/src/application/binary/SessionProviderRouter.ts b/src/application/binary/SessionProviderRouter.ts index 9101a6b786..e5babdf669 100644 --- a/src/application/binary/SessionProviderRouter.ts +++ b/src/application/binary/SessionProviderRouter.ts @@ -12,7 +12,6 @@ import type { CapabilityDescriptor, ProviderIdentity, } from "../AnalysisProvider.js"; -import type { FileCloseResourceOwner } from "../AnalysisProvider.js"; import { AnalysisProviderRegistry, type AnalysisProviderBinding, @@ -86,15 +85,8 @@ export class SessionProviderRouter { target: BinaryTarget, providerId?: AnalysisProviderSelector, signal?: AbortSignal, - fileCloseResources?: FileCloseResourceOwner, ): Promise> { - const resolutionOptions = - signal === undefined && fileCloseResources === undefined - ? undefined - : { - ...(signal === undefined ? {} : { signal }), - ...(fileCloseResources === undefined ? {} : { fileCloseResources }), - }; + const resolutionOptions = signal === undefined ? undefined : { signal }; const selected = await this.registry.select( target, providerId, diff --git a/src/artifacts/AnalysisCleanupLease.test.ts b/src/artifacts/AnalysisCleanupLease.test.ts index 214237f3e9..a5a0e3bc68 100644 --- a/src/artifacts/AnalysisCleanupLease.test.ts +++ b/src/artifacts/AnalysisCleanupLease.test.ts @@ -51,12 +51,19 @@ it("resolves an aggregate only after every bound owner releases", async () => { ["first resource", "second resource"], {}, ); - const first = new AnalysisCleanupLease(async () => undefined); - const second = new AnalysisCleanupLease(async () => undefined); + const first = new AnalysisCleanupLease( + async () => undefined, + ["first resource"], + ); + const second = new AnalysisCleanupLease( + async () => undefined, + ["second resource"], + ); new AnalysisCleanupLeaseGroup(error, [first, second]); await first.release(); expect(error.cleanupIncomplete).toBe(true); + expect(error.cleanupResources).toEqual(["second resource"]); await second.release(); expect(error.cleanupIncomplete).toBe(false); }); diff --git a/src/artifacts/AnalysisCleanupLease.ts b/src/artifacts/AnalysisCleanupLease.ts index 42ff774255..b823d1d730 100644 --- a/src/artifacts/AnalysisCleanupLease.ts +++ b/src/artifacts/AnalysisCleanupLease.ts @@ -6,7 +6,15 @@ export class AnalysisCleanupLease { #errors: WeakRef[] = []; readonly #groups = new Set(); - constructor(private readonly cleanup: () => Promise) {} + readonly cleanupResources: readonly string[] | undefined; + + constructor( + private readonly cleanup: () => Promise, + resources?: readonly string[], + ) { + this.cleanupResources = + resources === undefined ? undefined : [...resources]; + } async release(): Promise { if (this.#released) return; @@ -55,9 +63,23 @@ export class AnalysisCleanupLeaseGroup { this.#error = new WeakRef(error); this.#pending = new Set(owners); if (this.#pending.size === 0) return; + error.bindCleanupResourceOwner(this); for (const owner of this.#pending) owner.bindCleanupGroup(this); } + /** Unlabelled owners keep attribution unknown; never match resources by text. */ + get cleanupResources(): readonly string[] | undefined { + if ( + [...this.#pending].some((owner) => owner.cleanupResources === undefined) + ) + return undefined; + return [ + ...new Set( + [...this.#pending].flatMap((owner) => owner.cleanupResources ?? []), + ), + ]; + } + get hasLiveError(): boolean { return this.#error.deref() !== undefined; } diff --git a/src/artifacts/ArtifactFileOperation.ts b/src/artifacts/ArtifactFileOperation.ts index ece86f6979..dbed70192d 100644 --- a/src/artifacts/ArtifactFileOperation.ts +++ b/src/artifacts/ArtifactFileOperation.ts @@ -176,7 +176,7 @@ export const withArtifactRegularFile = async ( { symlinks, retainFailedClose: (owner) => { - cleanupLease = new AnalysisCleanupLease(() => owner.close()); + cleanupLease = new AnalysisCleanupLease(() => owner.close(), [path]); resources.retain({ kind: "lease", resource: path, @@ -218,7 +218,7 @@ export const withArtifactFileHandle = async ( operation: (file: FileHandle) => Promise, ): Promise => { const owner = new OwnedFileHandle(file); - const cleanupLease = new AnalysisCleanupLease(() => owner.close()); + const cleanupLease = new AnalysisCleanupLease(() => owner.close(), [path]); try { return await withOwnedFileHandle( path, diff --git a/src/browser/CdpTargetSessionOwner.ts b/src/browser/CdpTargetSessionOwner.ts index 3da2a281a0..a9baa7c1eb 100644 --- a/src/browser/CdpTargetSessionOwner.ts +++ b/src/browser/CdpTargetSessionOwner.ts @@ -144,7 +144,10 @@ export class CdpTargetSessionOwner { connection, operation, resource, - cleanupLease: new AnalysisCleanupLease(() => connection.close()), + cleanupLease: new AnalysisCleanupLease( + () => connection.close(), + [resource], + ), active: true, settled, settle, diff --git a/src/browser/PlaywrightElectronActiveProjection.test.ts b/src/browser/PlaywrightElectronActiveProjection.test.ts index 080e8185ba..5fcc931a09 100644 --- a/src/browser/PlaywrightElectronActiveProjection.test.ts +++ b/src/browser/PlaywrightElectronActiveProjection.test.ts @@ -2,28 +2,87 @@ import { expect, it } from "vitest"; import { projectElectronActiveCapture } from "./PlaywrightElectronActiveProjection.js"; +const event = { + sequence: 1, + correlation_id: "capture:123:1", + kind: "main-handler-invocation" as const, + event: null, + phase: "observed" as const, + channel: "readiness:echo", + direction: "renderer-to-main" as const, + sender: null, + receiver: "main", + frame: null, + target: null, + argument_shapes: [], + result_shape: null, + process_type: "main", + source: "electron-active-hook", + capture_method: "api-wrapper" as const, + artifact_path: null, + artifact_sha256: null, + error: false, +}; + +it.each(["custom-apply", "listener-chain"])( + "keeps %s gaps distinct from captured IPC and retention loss", + (boundary) => { + const result = projectElectronActiveCapture( + { executable: "/opt/electron", application: "/opt/app/main.js" }, + [], + { + windows: [], + metrics: [], + electronVersion: "test-electron", + hookSnapshot: { + events: [ + { + ...event, + kind: "ipc-observation-gap", + event: boundary, + channel: "custom", + direction: null, + receiver: null, + target: "ipcMain.on", + }, + ], + retention_budget_bytes: 16 * 1024 * 1024, + estimated_retained_bytes: 1_024, + event_serialized_byte_upper_bound: 512, + retained: 1, + dropped: 2, + dropped_ipc: 0, + dropped_runtime: 2, + dropped_event_families: [{ family: "ipc-observation-gap", count: 2 }], + dropped_event_roles: [{ role: "main", count: 2 }], + observed: 3, + observed_ipc: 0, + observed_runtime: 3, + hook_error: false, + }, + }, + ); + expect(result.ipc).toEqual({ events: [], observed: 0, dropped: 0 }); + expect(result.timeline.events[0]).toMatchObject({ + kind: "ipc-observation-gap", + channel: "custom", + }); + expect(result.coverage.status).toBe("partial_attach"); + expect(result.coverage.observed_event_families).toContain( + "ipc-observation-gap", + ); + expect(result.coverage.unavailable_event_families).toContain("ipc"); + expect(result.retention.dropped).toBe(2); + expect(result.limitations).toContainEqual( + expect.stringContaining("unobserved callback arguments and execution"), + ); + expect(result.limitations).not.toContain( + "The active IPC hook could not be installed.", + ); + }, +); + it("projects hook retention and dropped event families through the result schema", () => { - const event = { - sequence: 1, - correlation_id: "capture:123:1", - kind: "main-handler-invocation" as const, - event: null, - phase: "observed" as const, - channel: "readiness:echo", - direction: "renderer-to-main" as const, - sender: null, - receiver: "main", - frame: null, - target: null, - argument_shapes: [], - result_shape: null, - process_type: "main", - source: "electron-active-hook", - capture_method: "api-wrapper" as const, - artifact_path: null, - artifact_sha256: null, - error: false, - }; const result = projectElectronActiveCapture( { executable: "/opt/electron", diff --git a/src/browser/PlaywrightElectronActiveProjection.ts b/src/browser/PlaywrightElectronActiveProjection.ts index d527d3bba1..facb233f17 100644 --- a/src/browser/PlaywrightElectronActiveProjection.ts +++ b/src/browser/PlaywrightElectronActiveProjection.ts @@ -184,6 +184,11 @@ export const projectElectronActiveCapture = ( coverage: createCoverage(hookSnapshot, observations), limitations: [ "IPC payloads are represented by value shapes; payload values are never retained.", + ...(observations.observedEventFamilies.includes("ipc-observation-gap") + ? [ + "IPC observation gaps have unobserved callback arguments and execution. Available ipc-observation-gap timeline records identify the custom-apply or listener-chain boundary and registered channel; dropped gap records are counted by retention. Listener-chain gaps describe registration uncertainty, not proof of IPC delivery.", + ] + : []), "IPC direction and sender/receiver identifiers are observed only where Electron exposes them at the hooked boundary.", "The runtime timeline records lifecycle, navigation, shell, permission, popup, download, protocol, preload, native-addon, process, and IPC events; activity before hook installation is unavailable.", "The preload and renderer process contexts are not instrumented by the main-process -r hook; preload configuration and contextBridge API-shape events are main-boundary observations, not proof of renderer-side execution.", diff --git a/src/domain/analysisErrorBase.ts b/src/domain/analysisErrorBase.ts index f02df6b604..c62ba74117 100644 --- a/src/domain/analysisErrorBase.ts +++ b/src/domain/analysisErrorBase.ts @@ -60,6 +60,11 @@ export interface AnalysisErrorOptions extends ErrorOptions { readonly partialObservation?: AnalysisPartialObservation | undefined; } +/** Exact cleanup owner that can attribute its currently unresolved resources. */ +export interface AnalysisCleanupResourceOwner { + readonly cleanupResources: readonly string[] | undefined; +} + /** Base class for expected analysis, provider, and session failures. */ export abstract class AnalysisError extends Error { readonly #cleanupDependencies = new Set(); @@ -68,7 +73,8 @@ export abstract class AnalysisError extends Error { readonly capturedOutput: AnalysisCapturedOutput | undefined; readonly cleanup: AnalysisCleanupObservation | undefined; cleanupIncomplete: boolean; - readonly cleanupResources: readonly string[]; + #ownedCleanupResources: readonly string[]; + #cleanupResourceOwner: AnalysisCleanupResourceOwner | undefined; constructor(message: string, options?: AnalysisErrorOptions) { super(message, options); this.diagnostics = @@ -81,7 +87,7 @@ export abstract class AnalysisError extends Error { : { ...options.capturedOutput }; this.cleanup = options?.cleanup; this.cleanupIncomplete = options?.cleanup !== undefined; - this.cleanupResources = options?.cleanup?.resources ?? []; + this.#ownedCleanupResources = [...(options?.cleanup?.resources ?? [])]; this.partialObservation = options?.partialObservation; } abstract readonly _tag: AnalysisErrorTag; @@ -91,6 +97,33 @@ export abstract class AnalysisError extends Error { partialObservation: AnalysisPartialObservation | undefined; readonly cleanupReport: ProcessCaptureCleanupReport | undefined = undefined; + /** Live dependencies own current resource attribution; cleanup keeps history. */ + get cleanupResources(): readonly string[] { + if (this.#cleanupDependencies.size === 0) + return ( + this.#cleanupResourceOwner?.cleanupResources ?? + this.#ownedCleanupResources + ); + return [ + ...new Set( + [...this.#cleanupDependencies] + .filter((dependency) => dependency.cleanupIncomplete) + .flatMap((dependency) => dependency.cleanupResources), + ), + ]; + } + + /** Bind attribution only to the producer that owns the complete cleanup set. */ + bindCleanupResourceOwner(owner: AnalysisCleanupResourceOwner): this { + this.#cleanupResourceOwner = owner; + return this; + } + + /** Initialize resource facts supplied by a more specific error producer. */ + protected setCleanupResources(resources: readonly string[]): void { + this.#ownedCleanupResources = [...resources]; + } + /** Retain a later lifecycle failure without replacing the original typed error. */ retainSecondaryFailure( failure: AnalysisError, diff --git a/src/domain/browserObservationError.ts b/src/domain/browserObservationError.ts index 7bb5da8c1b..a519900b79 100644 --- a/src/domain/browserObservationError.ts +++ b/src/domain/browserObservationError.ts @@ -12,7 +12,6 @@ import { AnalysisCancelledError } from "./analysisErrorCore.js"; export class BrowserObservationError extends AnalysisError { readonly _tag = "BrowserObservationError"; override cleanupIncomplete: boolean; - override readonly cleanupResources: readonly string[]; override readonly userCategory: "cancelled" | undefined; override readonly userMessage: string | undefined; @@ -26,9 +25,10 @@ export class BrowserObservationError extends AnalysisError { options?.detail ?? observationFailureMessage(operation, reason); this.cleanupIncomplete = this.cleanup !== undefined || reason === "cleanup_failed"; - this.cleanupResources = + this.setCleanupResources( this.cleanup?.resources ?? - (reason === "cleanup_failed" ? ["browser_transport"] : []); + (reason === "cleanup_failed" ? ["browser_transport"] : []), + ); this.userCategory = reason === "cancelled" || options?.cause instanceof AnalysisCancelledError || diff --git a/src/domain/javascript/electronActiveObservation.ts b/src/domain/javascript/electronActiveObservation.ts index f46acfe6c9..0740f80f24 100644 --- a/src/domain/javascript/electronActiveObservation.ts +++ b/src/domain/javascript/electronActiveObservation.ts @@ -97,6 +97,7 @@ export const electronActiveTimelineEventSchema = z.strictObject({ kind: z.enum([ "main-handler-invocation", "main-event-invocation", + "ipc-observation-gap", "utility-process-fork", "utility-process-message", "ipc-main-to-renderer", diff --git a/src/domain/providerCleanupError.test.ts b/src/domain/providerCleanupError.test.ts index 6363fddba5..2f208c6c2d 100644 --- a/src/domain/providerCleanupError.test.ts +++ b/src/domain/providerCleanupError.test.ts @@ -59,3 +59,40 @@ it.each([false, true])( ]); }, ); + +it.each(["alpha", "beta"])( + "projects only unresolved resources through nested cleanup groups when %s closes first", + (first) => { + const alpha = new ProviderCleanupError("alpha", ["alpha-file"], {}); + const beta = new ProviderCleanupError("beta", ["beta-file"], {}); + const third = new ProviderCleanupError("third", ["third-file"], {}); + const children = new ProviderCleanupError( + "rea", + ["alpha-file", "beta-file"], + {}, + ).bindCleanupDependencies([alpha, beta]); + const outer = new ProviderCleanupError( + "rea", + ["alpha-file", "beta-file", "third-file"], + {}, + ).bindCleanupDependencies([children, third]); + const released = first === "alpha" ? alpha : beta; + const remaining = first === "alpha" ? beta : alpha; + released.resolveCleanup(); + expect(outer.cleanupIncomplete).toBe(true); + expect(outer.cleanupResources).toEqual([ + ...remaining.cleanupResources, + "third-file", + ]); + expect(projectAnalysisError(outer)).toMatchObject({ + code: "cleanup_incomplete", + details: { resources: [...remaining.cleanupResources, "third-file"] }, + }); + remaining.resolveCleanup(); + expect(outer.cleanupIncomplete).toBe(true); + expect(outer.cleanupResources).toEqual(["third-file"]); + third.resolveCleanup(); + expect(outer.cleanupIncomplete).toBe(false); + expect(projectAnalysisError(outer).code).not.toBe("cleanup_incomplete"); + }, +); diff --git a/src/domain/providerCleanupError.ts b/src/domain/providerCleanupError.ts index a21a6bceaf..7a8299cb74 100644 --- a/src/domain/providerCleanupError.ts +++ b/src/domain/providerCleanupError.ts @@ -5,7 +5,6 @@ import type { AnalysisPartialObservation } from "./analysisErrorBase.js"; /** Provider resources could not be proven closed after bounded cleanup. */ export class ProviderCleanupError extends ProviderAdapterError { override cleanupIncomplete = true; - override readonly cleanupResources: readonly string[]; override readonly userMessage = "Provider cleanup could not be fully confirmed. Review the reported local resources before opening another target."; @@ -30,6 +29,6 @@ export class ProviderCleanupError extends ProviderAdapterError { ...(partialObservation === undefined ? {} : { partialObservation }), diagnostics, }); - this.cleanupResources = [...resources]; + this.setCleanupResources(resources); } } diff --git a/src/domain/webScriptExportError.ts b/src/domain/webScriptExportError.ts index db01d50b21..dc970b9b23 100644 --- a/src/domain/webScriptExportError.ts +++ b/src/domain/webScriptExportError.ts @@ -5,7 +5,6 @@ import type { AnalysisErrorOptions } from "./analysisErrorBase.js"; export class WebScriptExportError extends ArtifactOperationError { override readonly userMessage: string; override cleanupIncomplete: boolean; - override readonly cleanupResources: readonly string[]; constructor( reason: ArtifactOperationError["reason"], @@ -17,12 +16,12 @@ export class WebScriptExportError extends ArtifactOperationError { ) { super("export_web_scripts", reason, undefined, undefined, options); this.userMessage = `Script export failed for ${target}: ${diagnostic}`; - this.cleanupResources = [ + this.setCleanupResources([ ...new Set([ ...(options.cleanup?.resources ?? []), ...(options.residualPaths ?? []), ]), - ]; + ]); this.cleanupIncomplete = options.cleanup !== undefined || (options.residualPaths?.length ?? 0) > 0; } diff --git a/src/ghidra/GhidraAnalysisSeeds.test.ts b/src/ghidra/GhidraAnalysisSeeds.test.ts index 7693573732..226f69ca2c 100644 --- a/src/ghidra/GhidraAnalysisSeeds.test.ts +++ b/src/ghidra/GhidraAnalysisSeeds.test.ts @@ -1,3 +1,4 @@ +import { ArtifactResourceScope } from "../artifacts/ArtifactResourceScope.js"; import { createHash } from "node:crypto"; import { mkdtemp, @@ -27,7 +28,10 @@ import { ghidraProfileInstallationHost as host } from "./GhidraInstallation.fixt import { GhidraProvider } from "./GhidraProvider.js"; const directories: string[] = []; +let resources = new ArtifactResourceScope(); afterEach(async () => { + await resources.close(); + resources = new ArtifactResourceScope(); for (const directory of directories.splice(0)) await rm(directory, { recursive: true, force: true }); }); @@ -92,7 +96,7 @@ describe("Ghidra seed commitment", () => { const directory = await workspace(); const path = join(directory, "seeds.tsv"); await writeFile(path, SEEDS); - const resolved = await resolveGhidraAnalysisSeeds(path); + const resolved = await resolveGhidraAnalysisSeeds(path, resources); if (!resolved.ok) throw resolved.error; const commitment = resolved.value; if (commitment === undefined) throw new Error("Expected a commitment"); @@ -103,12 +107,16 @@ describe("Ghidra seed commitment", () => { entries: { function: 2, code: 1, label: 1 }, }); const runtime = await workspace(); - const snapshot = await snapshotGhidraAnalysisSeeds(commitment, runtime); + const snapshot = await snapshotGhidraAnalysisSeeds( + commitment, + runtime, + resources, + ); expect(await readFile(snapshot, "utf8")).toBe(CANONICAL); }); it("is absent when no seed file is configured", async () => { - const resolved = await resolveGhidraAnalysisSeeds(undefined); + const resolved = await resolveGhidraAnalysisSeeds(undefined, resources); expect(resolved.ok && resolved.value).toBeUndefined(); }); @@ -116,13 +124,20 @@ describe("Ghidra seed commitment", () => { const directory = await workspace(); const path = join(directory, "seeds.tsv"); await writeFile(path, SEEDS); - const resolved = await resolveGhidraAnalysisSeeds(path); + const resolved = await resolveGhidraAnalysisSeeds(path, resources); if (!resolved.ok || resolved.value === undefined) throw new Error("Expected a commitment"); await writeFile(path, `${SEEDS}0x401100\tcode\n`); await expect( - snapshotGhidraAnalysisSeeds(resolved.value, await workspace()), - ).rejects.toThrow(/changed after the analysis profile was committed/u); + snapshotGhidraAnalysisSeeds(resolved.value, await workspace(), resources), + ).rejects.toMatchObject({ + _tag: "ProviderAdapterError", + diagnostics: { + reason: expect.stringMatching( + /changed after the analysis profile was committed/u, + ), + }, + }); }); it("commits the resolved path behind a configured symbolic link", async () => { @@ -131,7 +146,7 @@ describe("Ghidra seed commitment", () => { await writeFile(path, SEEDS); const link = join(directory, "link.tsv"); await symlink(path, link); - const resolved = await resolveGhidraAnalysisSeeds(link); + const resolved = await resolveGhidraAnalysisSeeds(link, resources); if (!resolved.ok || resolved.value === undefined) throw new Error("Expected a commitment"); expect(resolved.value.path).toBe(await realpath(path)); @@ -140,6 +155,7 @@ describe("Ghidra seed commitment", () => { it("reports an unreadable seed file as a provider error", async () => { const resolved = await resolveGhidraAnalysisSeeds( join(await workspace(), "absent.tsv"), + resources, ); if (resolved.ok) throw new Error("Expected an error"); expect(projectAnalysisError(resolved.error)).toMatchObject({ diff --git a/src/ghidra/GhidraAnalysisSeeds.ts b/src/ghidra/GhidraAnalysisSeeds.ts index e6621c459e..5dc6ba2d66 100644 --- a/src/ghidra/GhidraAnalysisSeeds.ts +++ b/src/ghidra/GhidraAnalysisSeeds.ts @@ -1,12 +1,18 @@ import { createHash } from "node:crypto"; -import { constants } from "node:fs"; -import { mkdir, open, realpath, writeFile } from "node:fs/promises"; +import { mkdir, realpath } from "node:fs/promises"; import { isAbsolute, join } from "node:path"; import { z } from "zod"; import type { AnalysisOperation } from "../application/AnalysisProvider.js"; import { ConfigurationError } from "../domain/configurationErrors.js"; import { ProviderAdapterError } from "../domain/providerAdapterError.js"; +import type { AnalysisError } from "../domain/analysisErrorBase.js"; +import type { ArtifactResourceScope } from "../artifacts/ArtifactResourceScope.js"; +import { + readGhidraProfileArtifact, + writeGhidraProfileArtifact, + ghidraProfileArtifactError, +} from "./GhidraProfileArtifacts.js"; import { err, ok, type Result } from "../domain/result.js"; /** Seed file format admitted by this adapter. */ @@ -131,114 +137,106 @@ const normalizedSeeds = (entries: readonly SeedEntry[]): string => ) .join(""); -/** Read a bounded regular file through one handle without following links. */ -const readSeedFile = async (path: string): Promise => { - const file = await open( - path, - constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK, - ); - try { - const stat = await file.stat(); - if (!stat.isFile() || stat.size > MAX_GHIDRA_SEED_BYTES) - throw new Error( - `Ghidra seed file must be a regular file of at most ${MAX_GHIDRA_SEED_BYTES} bytes: ${path}`, - ); - const bytes = Buffer.alloc(stat.size); - let offset = 0; - while (offset < bytes.length) { - const read = await file.read( - bytes, - offset, - bytes.length - offset, - offset, - ); - if (read.bytesRead === 0) - throw new Error(`Incomplete Ghidra seed file read: ${path}`); - offset += read.bytesRead; - } - return bytes; - } finally { - await file.close(); - } -}; - const sha256 = (bytes: Buffer | string): string => createHash("sha256").update(bytes).digest("hex"); -const decodeSeeds = (bytes: Buffer) => { - const text = new TextDecoder("utf-8", { fatal: true }).decode(bytes); - return parseGhidraSeeds(text); -}; +/** Parse seed facts before final close so later cleanup cannot replace invalid input. */ +const readSeeds = ( + path: string, + resources: ArtifactResourceScope, + signal?: AbortSignal, +): Promise => + readGhidraProfileArtifact( + path, + MAX_GHIDRA_SEED_BYTES, + resources, + signal, + (bytes) => { + let text: string; + try { + text = new TextDecoder("utf-8", { fatal: true }).decode(bytes); + } catch { + throw seedLineError(0, "file is not valid UTF-8"); + } + const parsed = parseGhidraSeeds(text); + if (!parsed.ok) throw parsed.error; + return parsed.value; + }, + ); /** Resolve the configured seed file into an immutable profile commitment. */ export const resolveGhidraAnalysisSeeds = async ( configured: string | undefined, -): Promise< - Result< - GhidraSeedCommitment | undefined, - ConfigurationError | ProviderAdapterError - > -> => { - if (configured === undefined) return ok(undefined); - let bytes: Buffer; - let path: string; - try { - path = await realpath(configured); - bytes = await readSeedFile(path); - } catch (cause: unknown) { - return err( - new ProviderAdapterError("ghidra", "resolve_analysis_profile", { - cause, - diagnostics: { + resources: ArtifactResourceScope, + signal?: AbortSignal, +): Promise> => + resources + .run(async () => { + if (configured === undefined) return ok(undefined); + let entries: readonly SeedEntry[]; + let path: string; + try { + path = await realpath(configured); + entries = await readSeeds(path, resources, signal); + } catch (cause: unknown) { + return err( + ghidraProfileArtifactError(cause, "resolve_analysis_profile", { + setting: "REA_GHIDRA_SEED_FILE", + path: configured, + }), + ); + } + const normalized = normalizedSeeds(entries); + const tally = { function: 0, code: 0, label: 0 }; + for (const entry of entries) tally[entry.kind] += 1; + return ok({ + format: GHIDRA_SEED_FORMAT, + path, + configured_path: configured, + sha256: sha256(normalized), + entries: tally, + }); + }) + .catch((cause: unknown) => + err( + ghidraProfileArtifactError(cause, "resolve_analysis_profile", { setting: "REA_GHIDRA_SEED_FILE", - path: configured, - reason: cause instanceof Error ? cause.message : String(cause), - }, - }), + }), + ), ); - } - let parsed: ReturnType; - try { - parsed = decodeSeeds(bytes); - } catch { - return err(seedLineError(0, "file is not valid UTF-8")); - } - if (!parsed.ok) return parsed; - const normalized = normalizedSeeds(parsed.value); - const tally = { function: 0, code: 0, label: 0 }; - for (const entry of parsed.value) tally[entry.kind] += 1; - return ok({ - format: GHIDRA_SEED_FORMAT, - path, - configured_path: configured, - sha256: sha256(normalized), - entries: tally, - }); -}; /** Re-read the committed seeds and write the canonical copy the script applies. */ export const snapshotGhidraAnalysisSeeds = async ( commitment: GhidraSeedCommitment, runtimeRoot: string, -): Promise => { - ghidraSeedCommitmentSchema.parse(commitment); - if ((await realpath(commitment.configured_path)) !== commitment.path) - throw new Error( - "Ghidra seed file resolved path differs from explicit configuration", - ); - const parsed = decodeSeeds(await readSeedFile(commitment.path)); - if (!parsed.ok) throw parsed.error; - const normalized = normalizedSeeds(parsed.value); - if (sha256(normalized) !== commitment.sha256) - throw new Error( - `Ghidra seed file changed after the analysis profile was committed: ${commitment.path}`, - ); - const directory = join(runtimeRoot, "seeds"); - await mkdir(directory, { mode: 0o700 }); - const path = join(directory, "seeds.tsv"); - await writeFile(path, normalized, { flag: "wx", mode: 0o600 }); - return path; -}; + resources: ArtifactResourceScope, + signal?: AbortSignal, +): Promise => + resources + .run(async () => { + ghidraSeedCommitmentSchema.parse(commitment); + if ((await realpath(commitment.configured_path)) !== commitment.path) + throw new Error( + "Ghidra seed file resolved path differs from explicit configuration", + ); + const entries = await readSeeds(commitment.path, resources, signal); + const normalized = normalizedSeeds(entries); + if (sha256(normalized) !== commitment.sha256) + throw new Error( + `Ghidra seed file changed after the analysis profile was committed: ${commitment.path}`, + ); + const directory = join(runtimeRoot, "seeds"); + await mkdir(directory, { mode: 0o700 }); + const path = join(directory, "seeds.tsv"); + await writeGhidraProfileArtifact(path, normalized, resources, signal); + return path; + }) + .catch((cause: unknown) => { + throw ghidraProfileArtifactError(cause, "open_binary", { + setting: "REA_GHIDRA_SEED_FILE", + path: commitment.path, + }); + }); const totalEntries = (commitment: GhidraSeedCommitment): number => commitment.entries.function + diff --git a/src/ghidra/GhidraLauncher.ts b/src/ghidra/GhidraLauncher.ts index 7167f3bd80..186a7e4755 100644 --- a/src/ghidra/GhidraLauncher.ts +++ b/src/ghidra/GhidraLauncher.ts @@ -309,17 +309,30 @@ export class GhidraHeadlessLauncher implements GhidraLauncher { platform, this.options.configurationDirectoryName, ); - const seedPath = - this.options.analysisSeeds === undefined - ? undefined - : await snapshotGhidraAnalysisSeeds( - this.options.analysisSeeds, - session.runtimeRoot, - ); - const extensions = await snapshotGhidraExtensions( - this.options.analysisExtensions ?? [], - session.runtimeRoot, - ); + let seedPath: string | undefined; + let extensions: readonly GhidraExtension[] = []; + if ( + this.options.analysisSeeds !== undefined || + (this.options.analysisExtensions?.length ?? 0) > 0 + ) { + if (options.resourceScope === undefined) + throw new GhidraLaunchError( + "Configured Ghidra artifacts require a caller-owned resource scope.", + ); + if (this.options.analysisSeeds !== undefined) + seedPath = await snapshotGhidraAnalysisSeeds( + this.options.analysisSeeds, + session.runtimeRoot, + options.resourceScope, + options.signal, + ); + extensions = await snapshotGhidraExtensions( + this.options.analysisExtensions ?? [], + session.runtimeRoot, + options.resourceScope, + options.signal, + ); + } await writeGhidraRuntimeFile( paths.descriptorPath, `${JSON.stringify({ diff --git a/src/ghidra/GhidraProfileArtifacts.ts b/src/ghidra/GhidraProfileArtifacts.ts new file mode 100644 index 0000000000..3527992eda --- /dev/null +++ b/src/ghidra/GhidraProfileArtifacts.ts @@ -0,0 +1,120 @@ +import { lstat, open } from "node:fs/promises"; +import type { ArtifactResourceScope } from "../artifacts/ArtifactResourceScope.js"; +import { ArtifactReaderFailure } from "../artifacts/ArtifactReader.js"; +import { + assertArtifactReadActive, + artifactFileCancellationCause, + artifactFilePrimaryFailure, + bindArtifactFileCleanup, + withArtifactFileHandle, + withArtifactRegularFile, +} from "../artifacts/ArtifactFileOperation.js"; +import { sameRegularFileState } from "../filesystem/RegularFile.js"; +import { AnalysisError } from "../domain/analysisErrorBase.js"; +import { AnalysisCancelledError } from "../domain/analysisErrorCore.js"; +import { ProviderAdapterError } from "../domain/providerAdapterError.js"; +import { ProviderCleanupError } from "../domain/providerCleanupError.js"; +import { analysisErrorWithCleanupFailure } from "../domain/analysisErrorCleanup.js"; +import type { JsonValue } from "../domain/jsonValue.js"; + +/** Bounded serial read; the caller owns admission and final descriptor cleanup. */ +export const readGhidraProfileArtifact = ( + path: string, + maximumBytes: number, + resources: ArtifactResourceScope, + signal: AbortSignal | undefined, + interpret: (bytes: Buffer) => Value, +): Promise => + withArtifactRegularFile(path, resources, signal, async (file) => { + const before = await file.stat({ bigint: true }); + if (before.size > BigInt(maximumBytes)) + throw new Error( + `Ghidra profile artifact must be at most ${String(maximumBytes)} bytes: ${path}`, + ); + const bytes = Buffer.alloc(Number(before.size)); + let offset = 0; + while (offset < bytes.length) { + assertArtifactReadActive(signal); + const read = await file.read( + bytes, + offset, + bytes.length - offset, + offset, + ); + if (read.bytesRead === 0) + throw new Error(`Incomplete Ghidra profile artifact read: ${path}`); + offset += read.bytesRead; + } + assertArtifactReadActive(signal); + const extra = Buffer.alloc(1); + if ( + (await file.read(extra, 0, 1, offset)).bytesRead !== 0 || + !sameRegularFileState(before, await file.stat({ bigint: true })) || + !sameRegularFileState(before, await lstat(path, { bigint: true })) + ) + throw new Error(`Ghidra profile artifact changed during read: ${path}`); + assertArtifactReadActive(signal); + return interpret(bytes); + }); + +/** Own the new private snapshot descriptor through write and final close. */ +export const writeGhidraProfileArtifact = async ( + path: string, + bytes: Buffer | string, + resources: ArtifactResourceScope, + signal?: AbortSignal, +): Promise => { + assertArtifactReadActive(signal); + const file = await open(path, "wx", 0o600); + await withArtifactFileHandle(path, file, resources, signal, (handle) => + handle.writeFile(bytes), + ); +}; + +/** Preserve physical failure, exact cancellation, and all retained file leases. */ +export const ghidraProfileArtifactError = ( + cause: unknown, + operation: string, + diagnostics: Readonly> = {}, +): AnalysisError => { + if (cause instanceof AnalysisError) return cause; + const primary = artifactFilePrimaryFailure(cause); + const reason = primary instanceof Error ? primary.message : String(primary); + const cleanup = + cause instanceof ArtifactReaderFailure ? cause.cleanup : undefined; + if (primary instanceof AnalysisError) { + if (cleanup === undefined) return primary; + const failure = new ProviderCleanupError( + "ghidra", + cleanup.resources, + { + reason: cleanup.reason, + }, + { cause }, + ); + bindArtifactFileCleanup(cause, failure); + return analysisErrorWithCleanupFailure( + primary, + failure, + operation, + ).bindCleanupDependencies([ + ...(primary.cleanupIncomplete ? [primary] : []), + failure, + ]); + } + const error = + cause instanceof ArtifactReaderFailure && cause.reason === "cancelled" + ? new AnalysisCancelledError(operation, { + cause: artifactFileCancellationCause(cause), + cleanup, + diagnostics, + }) + : new ProviderAdapterError("ghidra", operation, { + cause, + cleanup, + diagnostics: { ...diagnostics, reason }, + userMessage: reason, + }); + bindArtifactFileCleanup(cause, error); + return error; +}; diff --git a/src/ghidra/GhidraProvider.ts b/src/ghidra/GhidraProvider.ts index f8dd4f9001..4c447b818d 100644 --- a/src/ghidra/GhidraProvider.ts +++ b/src/ghidra/GhidraProvider.ts @@ -6,6 +6,7 @@ import type { AnalysisClient, AnalysisClientContext, AnalysisProfileResolutionOptions, + AnalysisProfileResolution, AnalysisProviderCandidate, AnalysisProviderReadinessSnapshot, CapabilityDescriptor, @@ -27,7 +28,8 @@ import { jsonValueSchema, type JsonValue, } from "../domain/jsonValue.js"; -import { err, ok } from "../domain/result.js"; +import { err, ok, type Result } from "../domain/result.js"; +import type { AnalysisError } from "../domain/analysisErrorBase.js"; import { ProviderAdapterError } from "../domain/providerAdapterError.js"; import { ProviderCleanupError } from "../domain/providerCleanupError.js"; import type { Logger } from "pino"; @@ -82,6 +84,10 @@ const SUPPORTED_ARCHITECTURES = new Set([ /** Ghidra candidate backed by an isolated ephemeral headless import. */ export class GhidraProvider implements AnalysisProviderCandidate { readonly #inspectionResources = new ArtifactResourceScope(); + readonly #profileWork = new Set< + Promise> + >(); + #closing: Promise> | undefined; #installation: GhidraInstallationInspection | undefined; private readonly environment: NodeJS.ProcessEnv; @@ -228,10 +234,22 @@ export class GhidraProvider implements AnalysisProviderCandidate { } hasPendingOwnedResources(): boolean { - return this.#inspectionResources.hasPending; + return this.#profileWork.size > 0 || this.#inspectionResources.hasPending; } - async closeOwnedResources() { + closeOwnedResources() { + this.#closing ??= Promise.resolve() + .then(async () => { + await Promise.allSettled(this.#profileWork); + return this.#closeInspectionResources(); + }) + .finally(() => { + this.#closing = undefined; + }); + return this.#closing; + } + + async #closeInspectionResources() { try { await this.#inspectionResources.retryCleanup(); return ok(null); @@ -252,6 +270,25 @@ export class GhidraProvider implements AnalysisProviderCandidate { async resolveAnalysisProfile( target: BinaryTarget, options?: AnalysisProfileResolutionOptions, + ): Promise> { + if (this.#closing !== undefined) { + const closed = await this.#closing; + if (!closed.ok) return closed; + } + const work = Promise.resolve().then(() => + this.#resolveProfile(target, options), + ); + this.#profileWork.add(work); + try { + return await work; + } finally { + this.#profileWork.delete(work); + } + } + + async #resolveProfile( + target: BinaryTarget, + options?: AnalysisProfileResolutionOptions, ) { const installation = this.#inspectInstallation(); const resolved = await resolveGhidraAnalysisProfile( @@ -307,12 +344,21 @@ export class GhidraProvider implements AnalysisProviderCandidate { this.config, target, installation.platform, + this.#inspectionResources, options?.signal, ); if (!extensions.ok) return extensions; if (options?.signal?.aborted === true) - return err(new AnalysisCancelledError("open_binary")); - const seeds = await resolveGhidraAnalysisSeeds(this.config.ghidraSeedFile); + return err( + new AnalysisCancelledError("open_binary", { + cause: options.signal.reason, + }), + ); + const seeds = await resolveGhidraAnalysisSeeds( + this.config.ghidraSeedFile, + this.#inspectionResources, + options?.signal, + ); if (!seeds.ok) return seeds; if (extensions.value.length === 0 && seeds.value === undefined) return resolved; diff --git a/src/ghidra/GhidraPspExtension.ts b/src/ghidra/GhidraPspExtension.ts index bdd759fda8..b418f3d49a 100644 --- a/src/ghidra/GhidraPspExtension.ts +++ b/src/ghidra/GhidraPspExtension.ts @@ -287,7 +287,7 @@ const readChildren = async ( } catch (cause: unknown) { outcome = { kind: "failed", cause }; } - const lease = new AnalysisCleanupLease(() => owner.close()); + const lease = new AnalysisCleanupLease(() => owner.close(), [path]); const attempt = await resources.release({ kind: "lease", resource: path, diff --git a/src/ghidra/extensions/GhidraExtensions.test.ts b/src/ghidra/extensions/GhidraExtensions.test.ts index 1fbe884cfb..61edea4beb 100644 --- a/src/ghidra/extensions/GhidraExtensions.test.ts +++ b/src/ghidra/extensions/GhidraExtensions.test.ts @@ -1,3 +1,4 @@ +import { ArtifactResourceScope } from "../../artifacts/ArtifactResourceScope.js"; import { execFile } from "node:child_process"; import { constants } from "node:fs"; import { promisify } from "node:util"; @@ -19,7 +20,10 @@ import { } from "./GhidraExtensions.js"; const roots: string[] = []; +let resources = new ArtifactResourceScope(); afterEach(async () => { + await resources.close(); + resources = new ArtifactResourceScope(); await Promise.all( roots.splice(0).map((path) => rm(path, { recursive: true, force: true })), ); @@ -49,7 +53,12 @@ const artifact = async () => { const path = join(directory, "fixture.jar"); const bytes = Buffer.from([0x50, 0x4b, 3, 4, 0, 1, 2, 3]); await writeFile(path, bytes); - const resolved = await resolveGhidraExtensions(config(path), target, "linux"); + const resolved = await resolveGhidraExtensions( + config(path), + target, + "linux", + resources, + ); if (!resolved.ok || resolved.value[0] === undefined) throw new Error("expected fixture artifact"); return { extension: resolved.value[0], bytes, path }; @@ -77,6 +86,7 @@ describe("optional Ghidra extension refusals", () => { config("/absent.jar"), target, "darwin", + resources, ); expect(host.ok).toBe(false); if (!host.ok) @@ -101,6 +111,7 @@ describe("optional Ghidra extension refusals", () => { availableArchitectures: ["arm64"], }, "linux", + resources, ); expect(architecture.ok).toBe(false); if (!architecture.ok) @@ -120,7 +131,9 @@ describe("optional Ghidra extension refusals", () => { describe("optional Ghidra extension boundary", () => { it("keeps ordinary profiles free of unconfigured extensions", async () => { - expect(await resolveGhidraExtensions(config(), target, "linux")).toEqual({ + expect( + await resolveGhidraExtensions(config(), target, "linux", resources), + ).toEqual({ ok: true, value: [], }); @@ -142,21 +155,31 @@ describe("optional Ghidra extension boundary", () => { it("snapshots identical bytes exclusively into the private runtime", async () => { const { extension, bytes } = await artifact(); const runtime = await root(); - const snapshots = await snapshotGhidraExtensions([extension], runtime); + const snapshots = await snapshotGhidraExtensions( + [extension], + runtime, + resources, + ); expect(snapshots[0]?.path).toBe(join(runtime, "extensions/nativeaot.jar")); expect(await readFile(join(runtime, "extensions/nativeaot.jar"))).toEqual( bytes, ); await expect( - snapshotGhidraExtensions([extension], runtime), - ).rejects.toMatchObject({ code: "EEXIST" }); + snapshotGhidraExtensions([extension], runtime, resources), + ).rejects.toMatchObject({ + _tag: "ProviderAdapterError", + cause: { code: "EEXIST" }, + }); }); it("rejects artifact changes after the profile commitment", async () => { const { extension, path } = await artifact(); await writeFile(path, Buffer.from([0x50, 0x4b, 3, 4, 9, 9, 9, 9])); await expect( - snapshotGhidraExtensions([extension], await root()), - ).rejects.toThrow("digest differs"); + snapshotGhidraExtensions([extension], await root(), resources), + ).rejects.toMatchObject({ + _tag: "ProviderAdapterError", + diagnostics: { reason: expect.stringContaining("digest differs") }, + }); }); it("rejects nonexistent, empty and non-JAR artifacts with useful reasons", async () => { const directory = await root(); @@ -167,6 +190,7 @@ describe("optional Ghidra extension boundary", () => { config(path), target, "linux", + resources, ); expect(resolved.ok).toBe(false); if (!resolved.ok) @@ -179,6 +203,7 @@ describe("optional Ghidra extension boundary", () => { config(absentPath), target, "linux", + resources, ); expect(absent.ok).toBe(false); if (!absent.ok) @@ -212,11 +237,12 @@ describe("optional Ghidra extension boundary", () => { config(path), target, "linux", + resources, ); expect(resolved.ok).toBe(false); if (!resolved.ok) expect(resolved.error).toMatchObject({ - diagnostics: { reason: expect.stringContaining("regular JAR") }, + diagnostics: { reason: expect.stringContaining("regular file") }, }); expect(writerRequired).toBe(false); } finally { @@ -232,6 +258,7 @@ describe("optional Ghidra extension boundary", () => { config("/absent.jar"), target, "linux", + resources, controller.signal, ); expect(resolved.ok).toBe(false); @@ -332,6 +359,7 @@ describe("Ghidra extension profile and producer validation", () => { config("/absent.jar"), { ...target, format: "pe", managed: true, executableRole: "application" }, "linux", + resources, ); expect(resolved.ok).toBe(false); if (!resolved.ok) @@ -349,15 +377,26 @@ describe("Ghidra extension profile and producer validation", () => { snapshotGhidraExtensions( [{ ...second.extension, configured_path: first.path }], await root(), + resources, ), - ).rejects.toThrow("resolved path differs"); + ).rejects.toMatchObject({ + _tag: "ProviderAdapterError", + diagnostics: { reason: expect.stringContaining("resolved path differs") }, + }); }); it("rejects unregistered descriptors before snapshot filesystem writes", async () => { const { extension } = await artifact(); const runtime = await root(); await expect( - snapshotGhidraExtensions([{ ...extension, id: "unknown" }], runtime), - ).rejects.toThrow("Unregistered"); + snapshotGhidraExtensions( + [{ ...extension, id: "unknown" }], + runtime, + resources, + ), + ).rejects.toMatchObject({ + _tag: "ProviderAdapterError", + diagnostics: { reason: expect.stringContaining("Unregistered") }, + }); }); }); diff --git a/src/ghidra/extensions/GhidraExtensions.ts b/src/ghidra/extensions/GhidraExtensions.ts index 11740a75fd..8d188ec723 100644 --- a/src/ghidra/extensions/GhidraExtensions.ts +++ b/src/ghidra/extensions/GhidraExtensions.ts @@ -1,6 +1,5 @@ import { createHash } from "node:crypto"; -import { constants } from "node:fs"; -import { open, realpath, writeFile, mkdir } from "node:fs/promises"; +import { realpath, mkdir } from "node:fs/promises"; import { isAbsolute, join } from "node:path"; import { z } from "zod"; import type { AppConfig } from "../../config/types.js"; @@ -10,7 +9,13 @@ import { AnalysisCapabilityUnavailableError, AnalysisUnsupportedTargetError, } from "../../domain/analysisErrorCore.js"; -import { ProviderAdapterError } from "../../domain/providerAdapterError.js"; +import type { ArtifactResourceScope } from "../../artifacts/ArtifactResourceScope.js"; +import { assertArtifactReadActive } from "../../artifacts/ArtifactFileOperation.js"; +import { + readGhidraProfileArtifact, + writeGhidraProfileArtifact, + ghidraProfileArtifactError, +} from "../GhidraProfileArtifacts.js"; import type { AnalysisError } from "../../domain/analysisErrorBase.js"; import { jsonObjectSchema } from "../../domain/jsonValue.js"; import { err, ok, type Result } from "../../domain/result.js"; @@ -68,86 +73,74 @@ export interface GhidraExtensionAdapter { const adapters: readonly GhidraExtensionAdapter[] = [nativeAotAdapter]; const MAX_EXTENSION_BYTES = 8 * 1024 * 1024; -/** Read a bounded regular artifact through its open handle, without ambient state changes. */ -const readJar = async (path: string): Promise => { - const file = await open( +/** JAR framing belongs to the extension adapter; file lifecycle is shared. */ +const readJar = ( + path: string, + resources: ArtifactResourceScope, + signal?: AbortSignal, +): Promise => + readGhidraProfileArtifact( path, - constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK, + MAX_EXTENSION_BYTES, + resources, + signal, + (bytes) => { + if (bytes.length < 4 || bytes.readUInt32LE(0) !== 0x04034b50) + throw new Error(`Ghidra extension is not a JAR: ${path}`); + return bytes; + }, ); - try { - const stat = await file.stat(); - if (!stat.isFile() || stat.size < 4 || stat.size > MAX_EXTENSION_BYTES) - throw new Error( - `Ghidra extension must be a regular JAR of 4..${MAX_EXTENSION_BYTES} bytes: ${path}`, - ); - const bytes = Buffer.alloc(stat.size); - let offset = 0; - while (offset < bytes.length) { - const read = await file.read( - bytes, - offset, - bytes.length - offset, - offset, - ); - if (read.bytesRead === 0) - throw new Error(`Incomplete Ghidra extension read: ${path}`); - offset += read.bytesRead; - } - const extra = Buffer.alloc(1); - if ( - (await file.read(extra, 0, 1, offset)).bytesRead !== 0 || - bytes.readUInt32LE(0) !== 0x04034b50 - ) - throw new Error( - `Ghidra extension changed during read or is not a JAR: ${path}`, - ); - return bytes; - } finally { - await file.close(); - } -}; /** Resolve explicitly configured optional extensions into the immutable analysis profile. */ export const resolveGhidraExtensions = async ( config: AppConfig, target: BinaryTarget, platform: NodeJS.Platform, + resources: ArtifactResourceScope, signal?: AbortSignal, -): Promise> => { - const extensions: GhidraExtension[] = []; - for (const adapter of adapters) { - if (signal?.aborted) return err(new AnalysisCancelledError("open_binary")); - const configured = adapter.configuredPath(config); - if (configured === undefined) continue; - const refusal = adapter.unsupportedReason(target, platform); - if (refusal !== null) return err(extensionRefusalError(refusal, target)); - try { - const path = await realpath(configured); - const bytes = await readJar(path); - extensions.push({ - id: adapter.id, - path, - configured_path: configured, - sha256: createHash("sha256").update(bytes).digest("hex"), - entry_class: adapter.entryClass, - integration_api: 1, - }); - } catch (cause: unknown) { - return err( - new ProviderAdapterError("ghidra", "resolve_analysis_profile", { - cause, - diagnostics: { - extension: adapter.id, - path: configured, - reason: cause instanceof Error ? cause.message : String(cause), - }, - }), - ); - } - } - if (signal?.aborted) return err(new AnalysisCancelledError("open_binary")); - return ok(extensions); -}; +): Promise> => + resources + .run(async () => { + const extensions: GhidraExtension[] = []; + for (const adapter of adapters) { + if (signal?.aborted) + return err( + new AnalysisCancelledError("open_binary", { cause: signal.reason }), + ); + const configured = adapter.configuredPath(config); + if (configured === undefined) continue; + const refusal = adapter.unsupportedReason(target, platform); + if (refusal !== null) + return err(extensionRefusalError(refusal, target)); + try { + const path = await realpath(configured); + const bytes = await readJar(path, resources, signal); + extensions.push({ + id: adapter.id, + path, + configured_path: configured, + sha256: createHash("sha256").update(bytes).digest("hex"), + entry_class: adapter.entryClass, + integration_api: 1, + }); + } catch (cause: unknown) { + return err( + ghidraProfileArtifactError(cause, "resolve_analysis_profile", { + extension: adapter.id, + path: configured, + }), + ); + } + } + if (signal?.aborted) + return err( + new AnalysisCancelledError("open_binary", { cause: signal.reason }), + ); + return ok(extensions); + }) + .catch((cause: unknown) => + err(ghidraProfileArtifactError(cause, "resolve_analysis_profile")), + ); /** Require explicit local configuration before admitting executable code from a profile. */ export const validateGhidraExtensionProfile = ( @@ -206,35 +199,48 @@ const extensionRefusalError = ( export const snapshotGhidraExtensions = async ( extensions: readonly GhidraExtension[], runtimeRoot: string, -): Promise => { - if (extensions.length === 0) return []; - for (const extension of extensions) { - ghidraExtensionSchema.parse(extension); - const adapter = adapters.find((value) => value.id === extension.id); - if (adapter === undefined || adapter.entryClass !== extension.entry_class) - throw new Error( - `Unregistered Ghidra extension identity: ${extension.id}`, - ); - if ((await realpath(extension.configured_path)) !== extension.path) - throw new Error( - `Ghidra extension resolved path differs from explicit configuration: ${extension.id}`, - ); - } - const directory = join(runtimeRoot, "extensions"); - await mkdir(directory, { mode: 0o700 }); - const result: GhidraExtension[] = []; - for (const extension of extensions) { - const bytes = await readJar(extension.path); - if (createHash("sha256").update(bytes).digest("hex") !== extension.sha256) - throw new Error( - `Ghidra extension digest differs from committed profile: ${extension.id} (${extension.path})`, - ); - const path = join(directory, `${extension.id}.jar`); - await writeFile(path, bytes, { flag: "wx", mode: 0o600 }); - result.push({ ...extension, path }); - } - return result; -}; + resources: ArtifactResourceScope, + signal?: AbortSignal, +): Promise => + resources + .run(async () => { + assertArtifactReadActive(signal); + if (extensions.length === 0) return []; + for (const extension of extensions) { + ghidraExtensionSchema.parse(extension); + const adapter = adapters.find((value) => value.id === extension.id); + if ( + adapter === undefined || + adapter.entryClass !== extension.entry_class + ) + throw new Error( + `Unregistered Ghidra extension identity: ${extension.id}`, + ); + if ((await realpath(extension.configured_path)) !== extension.path) + throw new Error( + `Ghidra extension resolved path differs from explicit configuration: ${extension.id}`, + ); + } + const directory = join(runtimeRoot, "extensions"); + await mkdir(directory, { mode: 0o700 }); + const result: GhidraExtension[] = []; + for (const extension of extensions) { + const bytes = await readJar(extension.path, resources, signal); + if ( + createHash("sha256").update(bytes).digest("hex") !== extension.sha256 + ) + throw new Error( + `Ghidra extension digest differs from committed profile: ${extension.id} (${extension.path})`, + ); + const path = join(directory, `${extension.id}.jar`); + await writeGhidraProfileArtifact(path, bytes, resources, signal); + result.push({ ...extension, path }); + } + return result; + }) + .catch((cause: unknown) => { + throw ghidraProfileArtifactError(cause, "open_binary"); + }); /** Require every configured producer result to match its artifact and registered ABI. */ export const validateGhidraExtensionResults = ( diff --git a/src/ida/IdaSessionClient.ts b/src/ida/IdaSessionClient.ts index 3dac1112ec..ead692db12 100644 --- a/src/ida/IdaSessionClient.ts +++ b/src/ida/IdaSessionClient.ts @@ -105,7 +105,6 @@ interface UpstreamSession { /** A failed cleanup retains its private workspace and reports the remaining resource. */ class IdaCleanupError extends ProviderAdapterError { override readonly cleanupIncomplete = true; - override readonly cleanupResources: readonly string[]; constructor( path: string, cause: unknown, @@ -146,7 +145,7 @@ class IdaCleanupError extends ProviderAdapterError { }), }, }); - this.cleanupResources = [path]; + this.setCleanupResources([path]); } } diff --git a/src/kuna/KunaInstallation.ts b/src/kuna/KunaInstallation.ts index bde43dcc64..66dc551de8 100644 --- a/src/kuna/KunaInstallation.ts +++ b/src/kuna/KunaInstallation.ts @@ -87,7 +87,7 @@ export const createKunaFileCloseScope = ( return { owner: { retain: (owner, resource) => { - const lease = new AnalysisCleanupLease(() => owner.close()); + const lease = new AnalysisCleanupLease(() => owner.close(), [resource]); leases.set(owner, lease); parent.retain({ close: () => lease.release() }, resource); }, diff --git a/src/kuna/KunaProjectCache.ts b/src/kuna/KunaProjectCache.ts index 56485e2998..9dc5a17898 100644 --- a/src/kuna/KunaProjectCache.ts +++ b/src/kuna/KunaProjectCache.ts @@ -1048,7 +1048,7 @@ export class KunaProjectCache { key, staging, resource, - lease: new AnalysisCleanupLease(async () => owner.close()), + lease: new AnalysisCleanupLease(async () => owner.close(), [resource]), }; this.#hashRecords.set(owner, retained); this.#retainedHashFiles.set(owner, retained); diff --git a/src/kuna/KunaTargetCommands.ts b/src/kuna/KunaTargetCommands.ts index a3e11c296d..3ebfe7c80b 100644 --- a/src/kuna/KunaTargetCommands.ts +++ b/src/kuna/KunaTargetCommands.ts @@ -118,7 +118,10 @@ export class KunaTargetCommands { reportedTimeoutMs: request.timeoutMs, launcher: this.lifetime.launcher, retainFailedFileClose: (owner, resource) => { - const lease = new AnalysisCleanupLease(() => owner.close()); + const lease = new AnalysisCleanupLease( + () => owner.close(), + [resource], + ); this.#resources.retain({ kind: "lease", resource, lease }); failedHashClose = { owner, lease }; }, diff --git a/src/process/SharedStartupAttempt.test.ts b/src/process/SharedStartupAttempt.test.ts index 1ed9e3f2dd..05bed1443c 100644 --- a/src/process/SharedStartupAttempt.test.ts +++ b/src/process/SharedStartupAttempt.test.ts @@ -70,3 +70,35 @@ it("does not lose an unexpected producer rejection after its final caller cancel reject?.(cause); await expect(waiting).rejects.toBe(cause); }); + +it.each(["caller", "owner"])( + "preserves %s interruption when owning cancellation and closure occur together", + async (first) => { + const completed = createDeferred(); + const controller = new AbortController(); + const reason = { caller: "operation owner" }; + const attempt = new SharedStartupAttempt( + () => completed.promise, + controller.signal, + ); + const waiting = attempt.wait(); + if (first === "caller") { + controller.abort(reason); + attempt.close(); + } else { + attempt.close(); + expect(attempt.signal.aborted).toBe(true); + expect(attempt.signal.reason).toBe(STARTUP_OWNER_CLOSED); + controller.abort(reason); + } + expect(attempt.signal.reason).toBe( + first === "caller" ? reason : STARTUP_OWNER_CLOSED, + ); + expect(attempt.settled).toBe(false); + completed.resolve("finalized"); + await expect(waiting).resolves.toEqual({ + status: "completed", + value: "finalized", + }); + }, +); diff --git a/src/process/SharedStartupAttempt.ts b/src/process/SharedStartupAttempt.ts index 16bf3b8d8b..7dde69a262 100644 --- a/src/process/SharedStartupAttempt.ts +++ b/src/process/SharedStartupAttempt.ts @@ -18,11 +18,20 @@ type StartupSettlement = export class SharedStartupAttempt { readonly #controller = new AbortController(); readonly promise: Promise; + readonly #signal: AbortSignal; #waiters = 0; #settlement: StartupSettlement = { status: "pending" }; - constructor(start: (signal: AbortSignal) => Promise) { - this.promise = start(this.#controller.signal); + constructor( + start: (signal: AbortSignal) => Promise, + /** An owning operation's cancellation is independent of individual waiters. */ + ownerSignal?: AbortSignal, + ) { + this.#signal = + ownerSignal === undefined + ? this.#controller.signal + : AbortSignal.any([ownerSignal, this.#controller.signal]); + this.promise = start(this.#signal); void this.promise.then( (value) => { this.#settlement = { status: "completed", value }; @@ -35,7 +44,7 @@ export class SharedStartupAttempt { /** Signal consumed by the startup producer, never by an individual waiter. */ get signal(): AbortSignal { - return this.#controller.signal; + return this.#signal; } /** Completion includes the producer's final cleanup, not just its abort. */ diff --git a/tests/boundary/browser/electronActiveHook.test.ts b/tests/boundary/browser/electronActiveHook.test.ts index 2709dc174b..6abe4b6365 100644 --- a/tests/boundary/browser/electronActiveHook.test.ts +++ b/tests/boundary/browser/electronActiveHook.test.ts @@ -259,6 +259,1433 @@ process.stdout.write(JSON.stringify(snapshot) + "\\n"); }); }); +it("removes observed ipcMain listeners through their original function", async () => { + const root = await createTestTempDirectory("rea-electron-hook-ipc-removal-"); + temporary.push(root); + await writeElectronFixture(root); + const script = join(root, "ipc-removal.cjs"); + await writeFile( + script, + `const { ipcMain } = require("electron"); +const event = { sender: { id: 9 }, senderFrame: { routingId: 7 } }; +let calls = 0; +const listener = () => { calls += 1; }; +ipcMain.on("repeated", listener); +ipcMain.on("repeated", listener); +ipcMain.removeListener("repeated", listener); +const repeatedAfterFirstRemoval = ipcMain.listenerCount("repeated"); +ipcMain.off("repeated", listener); +ipcMain.once("pending-once", listener); +ipcMain.removeListener("pending-once", listener); +ipcMain.on("fired-once", listener); +ipcMain.once("fired-once", listener); +ipcMain.emit("fired-once", event); +ipcMain.removeListener("fired-once", listener); +ipcMain.emit("repeated", event); +ipcMain.emit("pending-once", event); +ipcMain.emit("fired-once", event); +const snapshot = globalThis.__reaElectronActiveSnapshot(); +process.stdout.write(JSON.stringify({ + calls, + repeated_after_first_removal: repeatedAfterFirstRemoval, + listeners: ["repeated", "pending-once", "fired-once"].map((channel) => ipcMain.listenerCount(channel)), + invocations: snapshot.events.filter((item) => item.kind === "main-event-invocation").map((item) => item.channel), +}) + "\\n"); +`, + ); + const execution = await runNode(script, root); + expect(execution.code, execution.stderr).toBe(0); + expect(JSON.parse(execution.stdout)).toEqual({ + calls: 2, + repeated_after_first_removal: 1, + listeners: [0, 0, 0], + invocations: ["fired-once", "fired-once"], + }); +}); + +it("removes the newest matching ipcMain registration across unpatched aliases", async () => { + const root = await createTestTempDirectory("rea-electron-hook-ipc-aliases-"); + temporary.push(root); + await writeElectronFixture(root); + const script = join(root, "ipc-aliases.cjs"); + await writeFile( + script, + `const { ipcMain } = require("electron"); +const event = { sender: { id: 9 }, senderFrame: { routingId: 7 } }; +const order = []; +const f = () => { order.push("f"); }; +const g = () => { order.push("g"); }; +ipcMain.on("mixed", f); +ipcMain.on("mixed", g); +ipcMain.addListener("mixed", f); +ipcMain.removeListener("mixed", f); +ipcMain.emit("mixed", event); +let onceCalls = 0; +const fn = () => { onceCalls += 1; }; +ipcMain.once("once-then-add", fn); +ipcMain.addListener("once-then-add", fn); +ipcMain.removeListener("once-then-add", fn); +ipcMain.emit("once-then-add", event); +ipcMain.emit("once-then-add", event); +const snapshot = globalThis.__reaElectronActiveSnapshot(); +process.stdout.write(JSON.stringify({ + order, + once_calls: onceCalls, + listeners: ["mixed", "once-then-add"].map((channel) => ipcMain.listenerCount(channel)), + invocations: snapshot.events.filter((item) => item.kind === "main-event-invocation").map((item) => item.channel), +}) + "\\n"); +`, + ); + const execution = await runNode(script, root); + expect(execution.code, execution.stderr).toBe(0); + expect(JSON.parse(execution.stdout)).toEqual({ + order: ["f", "g"], + once_calls: 1, + listeners: [2, 0], + invocations: ["mixed", "mixed", "once-then-add"], + }); +}); + +it("records ipcMain listeners registered by a newListener callback during once", async () => { + const root = await createTestTempDirectory("rea-electron-hook-ipc-nested-"); + temporary.push(root); + await writeElectronFixture(root); + const script = join(root, "ipc-nested.cjs"); + await writeFile( + script, + `const { ipcMain } = require("electron"); +const event = { sender: { id: 9 }, senderFrame: { routingId: 7 } }; +let nestedCalls = 0; +ipcMain.addListener("newListener", (channel) => { + if (channel === "outer") ipcMain.on("nested", () => { nestedCalls += 1; }); +}); +ipcMain.once("outer", () => undefined); +ipcMain.emit("nested", event); +ipcMain.emit("outer", event); +const snapshot = globalThis.__reaElectronActiveSnapshot(); +process.stdout.write(JSON.stringify({ + nested_calls: nestedCalls, + invocations: snapshot.events.filter((item) => item.kind === "main-event-invocation").map((item) => item.channel), +}) + "\\n"); +`, + ); + const execution = await runNode(script, root); + expect(execution.code, execution.stderr).toBe(0); + expect(JSON.parse(execution.stdout)).toEqual({ + nested_calls: 1, + invocations: ["nested", "outer"], + }); +}); + +it("keeps removal working after many fired ipcMain once registrations", async () => { + const root = await createTestTempDirectory("rea-electron-hook-ipc-repeat-"); + temporary.push(root); + await writeElectronFixture(root); + const script = join(root, "ipc-repeat.cjs"); + await writeFile( + script, + `const { ipcMain } = require("electron"); +const event = { sender: { id: 9 }, senderFrame: { routingId: 7 } }; +let calls = 0; +const listener = () => { calls += 1; }; +for (let index = 0; index < 200; index += 1) { + ipcMain.once("repeated-once", listener); + ipcMain.emit("repeated-once", event); +} +const afterOnce = ipcMain.rawListeners("repeated-once").length; +ipcMain.on("repeated-once", listener); +ipcMain.removeListener("repeated-once", listener); +ipcMain.emit("repeated-once", event); +process.stdout.write(JSON.stringify({ + calls, + after_once: afterOnce, + listeners: ipcMain.listenerCount("repeated-once"), +}) + "\\n"); +`, + ); + const execution = await runNode(script, root); + expect(execution.code, execution.stderr).toBe(0); + expect(JSON.parse(execution.stdout)).toEqual({ + calls: 200, + after_once: 0, + listeners: 0, + }); +}); + +it("removes the newest registration of an ipcMain listener that has its own listener property", async () => { + const root = await createTestTempDirectory("rea-electron-hook-ipc-wrapped-"); + temporary.push(root); + await writeElectronFixture(root); + const script = join(root, "ipc-wrapped.cjs"); + await writeFile( + script, + `const { ipcMain } = require("electron"); +const event = { sender: { id: 9 }, senderFrame: { routingId: 7 } }; +const order = []; +const f = () => { order.push("f"); }; +f.listener = () => { order.push("inner"); }; +const g = () => { order.push("g"); }; +ipcMain.on("wrapped", f); +ipcMain.on("wrapped", g); +ipcMain.addListener("wrapped", f); +ipcMain.removeListener("wrapped", f); +ipcMain.emit("wrapped", event); +process.stdout.write(JSON.stringify({ + order, + listeners: ipcMain.listenerCount("wrapped"), +}) + "\\n"); +`, + ); + const execution = await runNode(script, root); + expect(execution.code, execution.stderr).toBe(0); + expect(JSON.parse(execution.stdout)).toEqual({ + order: ["f", "g"], + listeners: 2, + }); +}); + +it("reports original ipcMain listeners to newListener and removeListener observers", async () => { + const root = await createTestTempDirectory("rea-electron-hook-ipc-meta-"); + temporary.push(root); + await writeElectronFixture(root); + const script = join(root, "ipc-meta.cjs"); + await writeFile( + script, + `const { ipcMain } = require("electron"); +const event = { sender: { id: 9 }, senderFrame: { routingId: 7 } }; +const channels = new Set(["on", "removed-once", "fired-once"]); +const added = []; +const removed = []; +ipcMain.addListener("newListener", (channel, listener) => { if (channels.has(channel)) added.push([channel, listener]); }); +ipcMain.addListener("removeListener", (channel, listener) => { if (channels.has(channel)) removed.push([channel, listener]); }); +const onListener = () => undefined; +const removedOnce = () => undefined; +let firedCalls = 0; +const firedOnce = () => { firedCalls += 1; }; +ipcMain.on("on", onListener); +ipcMain.removeListener("on", onListener); +ipcMain.once("removed-once", removedOnce); +ipcMain.removeListener("removed-once", removedOnce); +ipcMain.once("fired-once", firedOnce); +ipcMain.emit("fired-once", event); +ipcMain.emit("fired-once", event); +const expected = [["on", onListener], ["removed-once", removedOnce], ["fired-once", firedOnce]]; +const same = (actual) => actual.length === expected.length && + actual.every(([channel, listener], index) => channel === expected[index][0] && listener === expected[index][1]); +const snapshot = globalThis.__reaElectronActiveSnapshot(); +process.stdout.write(JSON.stringify({ + added_originals: same(added), + removed_originals: same(removed), + fired_calls: firedCalls, + invocations: snapshot.events.filter((item) => item.kind === "main-event-invocation").map((item) => item.channel), +}) + "\\n"); +`, + ); + const execution = await runNode(script, root); + expect(execution.code, execution.stderr).toBe(0); + expect(JSON.parse(execution.stdout)).toEqual({ + added_originals: true, + removed_originals: true, + fired_calls: 1, + invocations: ["fired-once"], + }); +}); + +it("registers and removes ipcMain once listeners through decorators installed after the hook", async () => { + const root = await createTestTempDirectory("rea-electron-hook-ipc-decor-"); + temporary.push(root); + await writeElectronFixture(root); + const script = join(root, "ipc-decorators.cjs"); + await writeFile( + script, + `const { ipcMain } = require("electron"); +const event = { sender: { id: 9 }, senderFrame: { routingId: 7 } }; +const seen = []; +const hookedOn = ipcMain.on; +const hookedRemoveListener = ipcMain.removeListener; +ipcMain.on = function decoratedOn(channel, listener) { + if (channel === "decorated") seen.push("on"); + return hookedOn.call(this, channel, listener); +}; +ipcMain.removeListener = function decoratedRemoveListener(channel, listener) { + if (channel === "decorated") seen.push("removeListener"); + return hookedRemoveListener.call(this, channel, listener); +}; +let calls = 0; +const listener = () => { calls += 1; }; +const returned = ipcMain.once("decorated", listener); +const registered = ipcMain.listenerCount("decorated", listener); +ipcMain.emit("decorated", event); +ipcMain.emit("decorated", event); +const snapshot = globalThis.__reaElectronActiveSnapshot(); +process.stdout.write(JSON.stringify({ + seen, + returns_emitter: returned === ipcMain, + registered, + remaining: ipcMain.listenerCount("decorated"), + calls, + invocations: snapshot.events.filter((item) => item.kind === "main-event-invocation").map((item) => item.channel), +}) + "\\n"); +`, + ); + const execution = await runNode(script, root); + expect(execution.code, execution.stderr).toBe(0); + expect(JSON.parse(execution.stdout)).toEqual({ + seen: ["on", "removeListener"], + returns_emitter: true, + registered: 1, + remaining: 0, + calls: 1, + invocations: ["decorated"], + }); +}); + +it("keeps hooked ipcMain listener semantics identical to an unhooked EventEmitter", async () => { + const root = await createTestTempDirectory("rea-electron-hook-ipc-diff-"); + temporary.push(root); + await writeElectronFixture(root); + const script = join(root, "ipc-differential.cjs"); + await writeFile( + script, + `const { ipcMain } = require("electron"); +const { EventEmitter } = require("node:events"); +const event = { sender: { id: 9 }, senderFrame: { routingId: 7 } }; +const scenario = (emitter) => { + const log = []; + const names = new Map(); + const named = (name) => { + const listener = () => { log.push("call:" + name); }; + names.set(listener, name); + return listener; + }; + const label = (listener) => names.get(listener) ?? "unknown"; + emitter.on("newListener", (channel, listener) => { + if (channel === "ch") log.push("new:" + label(listener)); + }); + emitter.on("removeListener", (channel, listener) => { + if (channel === "ch") log.push("remove:" + label(listener)); + }); + const [a, b, c, d] = ["a", "b", "c", "d"].map(named); + const state = () => { + log.push("count:" + emitter.listenerCount("ch") + ":" + [a, b, c, d].map((listener) => emitter.listenerCount("ch", listener)).join(",")); + log.push("listeners:" + emitter.listeners("ch").map(label).join(",")); + log.push("raw:" + emitter.rawListeners("ch").map((listener) => label(listener.listener ?? listener)).join(",")); + }; + emitter.on("ch", a); + emitter.once("ch", b); + emitter.on("ch", c); + emitter.addListener("ch", a); + emitter.once("ch", d); + emitter.prependListener("ch", c); + emitter.prependOnceListener("ch", d); + state(); + emitter.removeListener("ch", a); + state(); + emitter.emit("ch", event); + state(); + emitter.off("ch", c); + emitter.once("ch", b); + emitter.removeListener("ch", b); + emitter.once("ch", d); + state(); + emitter.emit("ch", event); + state(); + emitter.off("ch", c); + // An app function's own listener property is not a hook wrapper. + const e = named("e"); + e.listener = c; + emitter.on("ch", a); + emitter.on("ch", c); + emitter.prependOnceListener("ch", d); + emitter.prependListener("ch", e); + emitter.on("ch", a); + state(); + emitter.removeAllListeners("ch"); + state(); + emitter.on("ch", b); + emitter.on("ch", c); + emitter.removeAllListeners(); + state(); + return log; +}; +process.stdout.write(JSON.stringify({ + plain: scenario(new EventEmitter()), + hooked: scenario(ipcMain), +}) + "\\n"); +`, + ); + const execution = await runNode(script, root); + expect(execution.code, execution.stderr).toBe(0); + const { plain, hooked } = JSON.parse(execution.stdout) as { + plain: string[]; + hooked: string[]; + }; + expect(plain).not.toContain("new:unknown"); + expect(hooked).toEqual(plain); +}); + +it("keeps a replacement registered by a removeListener observer during ipcMain removeAllListeners", async () => { + const root = await createTestTempDirectory("rea-electron-hook-ipc-replace-"); + temporary.push(root); + await writeElectronFixture(root); + const script = join(root, "ipc-replacement.cjs"); + await writeFile( + script, + `const { ipcMain } = require("electron"); +const { EventEmitter } = require("node:events"); +const event = { sender: { id: 9 }, senderFrame: { routingId: 7 } }; +const scenario = (emitter) => { + const log = []; + const names = new Map(); + const named = (name) => { + const listener = () => { log.push("call:" + name); }; + names.set(listener, name); + return listener; + }; + // A wrapper is labelled by the listener its .listener names. + const label = (listener) => + names.get(listener) ?? + (names.has(listener.listener) ? "wrapper(" + names.get(listener.listener) + ")" : "unknown"); + const [a, b, c, replacement] = ["a", "b", "c", "replacement"].map(named); + let replaced = false; + emitter.on("removeListener", (channel, listener) => { + if (channel !== "ch") return; + log.push("remove:" + label(listener)); + if (replaced) return; + replaced = true; + emitter.on("ch", replacement); + }); + emitter.on("ch", a); + emitter.once("ch", b); + emitter.on("ch", c); + const returned = emitter.removeAllListeners("ch"); + log.push("returns_emitter:" + (returned === emitter)); + log.push("listeners:" + emitter.listeners("ch").map(label).join(",")); + emitter.emit("ch", event); + emitter.removeAllListeners(); + log.push("cleared:" + emitter.listenerCount("ch") + ":" + emitter.listenerCount("removeListener")); + return log; +}; +const plain = scenario(new EventEmitter()); +const hooked = scenario(ipcMain); +const snapshot = globalThis.__reaElectronActiveSnapshot(); +process.stdout.write(JSON.stringify({ + plain, + hooked, + // The hooked removeListener observer records its own invocations. + invocations: snapshot.events.filter((item) => item.kind === "main-event-invocation" && item.channel === "ch").map((item) => item.channel), +}) + "\\n"); +`, + ); + const execution = await runNode(script, root); + expect(execution.code, execution.stderr).toBe(0); + const { plain, hooked, invocations } = JSON.parse(execution.stdout) as { + plain: string[]; + hooked: string[]; + invocations: string[]; + }; + expect(plain).toEqual([ + "remove:c", + "remove:wrapper(b)", + "remove:a", + "returns_emitter:true", + "listeners:replacement", + "call:replacement", + "remove:replacement", + "cleared:0:0", + ]); + expect(hooked).toEqual(plain); + expect(invocations).toEqual(["ch"]); +}); + +it("keeps ipcMain once self-removal through an on decorator that wraps the hook", async () => { + const root = await createTestTempDirectory("rea-electron-hook-ipc-wrap-"); + temporary.push(root); + await writeElectronFixture(root); + const script = join(root, "ipc-wrapping-decorator.cjs"); + await writeFile( + script, + `const { ipcMain } = require("electron"); +const { EventEmitter } = require("node:events"); +const event = { sender: { id: 9 }, senderFrame: { routingId: 7 } }; +// Installed after the hook: wraps each listener and names it via .listener. +const decorate = (emitter) => { + const decoratedOn = emitter.on; + emitter.on = function wrappingOn(channel, listener) { + const layer = function decoratedListener(...args) { + return listener.apply(this, args); + }; + layer.listener = listener; + return decoratedOn.call(this, channel, layer); + }; +}; +const scenario = (emitter) => { + decorate(emitter); + let calls = 0; + const listener = () => { calls += 1; }; + const returned = emitter.once("decorated", listener); + const registered = emitter.listenerCount("decorated"); + emitter.emit("decorated", event); + const afterFirst = emitter.listenerCount("decorated"); + emitter.emit("decorated", event); + emitter.once("decorated", listener); + emitter.emit("decorated", event); + return { + returns_emitter: returned === emitter, + registered, + after_first: afterFirst, + remaining: emitter.listenerCount("decorated"), + raw: emitter.rawListeners("decorated").length, + calls, + }; +}; +const plain = scenario(new EventEmitter()); +const hooked = scenario(ipcMain); +const snapshot = globalThis.__reaElectronActiveSnapshot(); +process.stdout.write(JSON.stringify({ + plain, + hooked, + invocations: snapshot.events.filter((item) => item.kind === "main-event-invocation").map((item) => item.channel), +}) + "\\n"); +`, + ); + const execution = await runNode(script, root); + expect(execution.code, execution.stderr).toBe(0); + const { plain, hooked, invocations } = JSON.parse(execution.stdout) as { + plain: Record; + hooked: Record; + invocations: string[]; + }; + expect(plain).toEqual({ + returns_emitter: true, + registered: 1, + after_first: 0, + remaining: 0, + raw: 0, + calls: 2, + }); + expect(hooked).toEqual(plain); + expect(invocations).toEqual(["decorated", "decorated"]); +}); + +it.each(["stable", "replaced", "readonly", "getter", "proxy"])( + "normalizes raw ipcMain on entries before %s removal decorators", + async (mode) => { + const root = await createTestTempDirectory( + "rea-electron-hook-ipc-remove-decorator-", + ); + temporary.push(root); + await writeElectronFixture(root); + const script = join(root, "ipc-removal-decorator.cjs"); + await writeFile( + script, + `const { ipcMain } = require("electron"); +const { EventEmitter } = require("node:events"); +const scenario = (emitter) => { + const seen = []; + const onListener = () => undefined; + const onceListener = () => undefined; + const nativeRemove = emitter.removeListener; + const decorate = (name) => function removalDecorator(channel, listener) { + if (this !== emitter) throw new Error("decorator receiver changed"); + if (channel === "ch") { + const label = listener === onListener ? "on" : + listener.listener === onceListener ? "once" : "unknown"; + seen.push(name + ":" + label); + if (label === "unknown") throw new Error("unexpected recording wrapper"); + } + return nativeRemove.call(this, channel, listener); + }; + emitter.on("removeListener", (channel) => { + if (${JSON.stringify(mode)} === "replaced" && channel === "ch") + emitter.removeListener = decorate("replacement"); + }); + emitter.on("ch", onListener); + emitter.once("ch", onceListener); + emitter.removeListener = decorate("first"); + if (${JSON.stringify(mode)} === "readonly") Object.defineProperty(emitter, "removeListener", { + value: emitter.removeListener, writable: false, configurable: false, + }); + if (${JSON.stringify(mode)} === "getter") Object.defineProperty(emitter, "removeListener", { + get() { + if (this !== emitter) throw new Error("getter receiver changed"); + return decorate("first"); + }, + }); + if (${JSON.stringify(mode)} === "proxy") emitter.removeListener = new Proxy(emitter.removeListener, { + get() { throw new Error("decorator metadata must not be read"); }, + }); + let failure = null; + let returnsEmitter = false; + try { returnsEmitter = emitter.removeAllListeners("ch") === emitter; } + catch (cause) { failure = cause.message; } + return { seen, returns_emitter: returnsEmitter, remaining: emitter.listenerCount("ch"), failure }; +}; +process.stdout.write(JSON.stringify({ plain: scenario(new EventEmitter()), hooked: scenario(ipcMain) }) + "\\n"); +`, + ); + const execution = await runNode(script, root); + expect(execution.code, execution.stderr).toBe(0); + const { plain, hooked } = JSON.parse(execution.stdout) as { + plain: Record; + hooked: Record; + }; + expect(plain).toEqual({ + seen: [ + "first:once", + `${mode === "replaced" ? "replacement" : "first"}:on`, + ], + returns_emitter: true, + remaining: 0, + failure: null, + }); + expect(hooked).toEqual(plain); + }, +); + +it("preserves native removal before mutated raw listener metadata throws", async () => { + const root = await createTestTempDirectory( + "rea-electron-hook-ipc-mutated-metadata-", + ); + temporary.push(root); + await writeElectronFixture(root); + const script = join(root, "ipc-mutated-listener-metadata.cjs"); + await writeFile( + script, + `const { ipcMain } = require("electron"); +const { EventEmitter } = require("node:events"); +const scenario = (emitter) => { + const marker = new Error("application metadata failure"); + let reads = 0; + emitter.on("removeListener", () => undefined); + emitter.on("ch", () => undefined); + Object.defineProperty(emitter.rawListeners("ch")[0], "listener", { get() { + reads += 1; + throw marker; + }}); + let failure; + try { emitter.removeAllListeners("ch"); } catch (cause) { failure = cause; } + return { reads, remaining: emitter.listenerCount("ch"), error_preserved: failure === marker }; +}; +process.stdout.write(JSON.stringify({ plain: scenario(new EventEmitter()), hooked: scenario(ipcMain) }) + "\\n"); +`, + ); + const execution = await runNode(script, root); + expect(execution.code, execution.stderr).toBe(0); + const { plain, hooked } = JSON.parse(execution.stdout) as { + plain: Record; + hooked: Record; + }; + expect(plain).toEqual({ reads: 1, remaining: 0, error_preserved: true }); + expect(hooked).toEqual(plain); +}); + +it.each(["single-on", "multiple-on", "empty-once", "event-once"])( + "preserves native callable method reads during %s dispatch", + async (mode) => { + const root = await createTestTempDirectory( + "rea-electron-hook-ipc-dispatch-", + ); + temporary.push(root); + await writeElectronFixture(root); + const script = join(root, "ipc-native-dispatch.cjs"); + await writeFile( + script, + `const { ipcMain } = require("electron"); +const { EventEmitter } = require("node:events"); +const mode = ${JSON.stringify(mode)}; +const scenario = (emitter) => { + const calls = []; + const reads = []; + const listener = new Proxy(function() { calls.push("first"); }, { + get(target, key, receiver) { + if (key === "apply" || key === "call") { + reads.push(key); + throw new Error("application invocation metadata"); + } + return Reflect.get(target, key, receiver); + }, + }); + if (mode.endsWith("once")) emitter.once("ch", listener); + else emitter.on("ch", listener); + if (mode === "multiple-on") emitter.on("ch", () => calls.push("second")); + let failure = null; + try { + if (mode === "empty-once") emitter.emit("ch"); + else emitter.emit("ch", { sender: { id: 9 } }); + } catch (cause) { failure = cause.message; } + return { calls, reads, failure, remaining: emitter.listenerCount("ch") }; +}; +console.log(JSON.stringify({ plain: scenario(new EventEmitter()), hooked: scenario(ipcMain) })); +`, + ); + const execution = await runNode(script, root); + expect(execution.code, execution.stderr).toBe(0); + const { plain, hooked } = JSON.parse(execution.stdout) as { + plain: Record; + hooked: Record; + }; + expect(plain).toEqual({ + calls: mode === "multiple-on" ? ["first", "second"] : [], + reads: + mode === "multiple-on" + ? [] + : [mode === "empty-once" ? "call" : "apply"], + failure: + mode === "multiple-on" ? null : "application invocation metadata", + remaining: mode === "multiple-on" ? 2 : mode === "single-on" ? 1 : 0, + }); + expect(hooked).toEqual(plain); + }, +); + +it.each([ + "arguments", + "array-like", + "null", + "undefined", + "missing", + "primitive", +])("preserves raw callback apply with %s argument lists", async (mode) => { + const root = await createTestTempDirectory("rea-electron-hook-ipc-apply-"); + temporary.push(root); + await writeElectronFixture(root); + const script = join(root, "ipc-apply.cjs"); + await writeFile( + script, + `const { ipcMain } = require("electron"); +const { EventEmitter } = require("node:events"); +const mode = ${JSON.stringify(mode)}; +const scenario = (emitter) => { + let calls = 0, reads = 0, correctReceiver = false, arity = null; + const event = { sender: { id: 9 } }; + let first = null; + emitter.on("ch", function(...args) { + calls++; correctReceiver = this === emitter; arity = args.length; + first = args.length ? args[0] === event : null; + }); + const listener = emitter.rawListeners("ch")[0]; + let list; + if (mode === "arguments") list = (function() { return arguments; })(event, 42); + if (mode === "array-like") list = { length: 2, get 0() { reads++; return event; }, 1: 42 }; + if (mode === "null") list = null; + if (mode === "primitive") list = 42; + let typeError = false; + try { + if (mode === "missing") listener.apply(emitter); + else listener.apply(emitter, list); + } catch (cause) { typeError = cause instanceof TypeError; } + return { calls, reads, correctReceiver, arity, first, typeError }; +}; +console.log(JSON.stringify({ plain: scenario(new EventEmitter()), hooked: scenario(ipcMain) })); +`, + ); + const execution = await runNode(script, root); + expect(execution.code, execution.stderr).toBe(0); + const { plain, hooked } = JSON.parse(execution.stdout) as { + plain: Record; + hooked: Record; + }; + const populated = mode === "arguments" || mode === "array-like"; + expect(plain).toEqual({ + calls: mode === "primitive" ? 0 : 1, + reads: mode === "array-like" ? 1 : 0, + correctReceiver: mode !== "primitive", + arity: mode === "primitive" ? null : populated ? 2 : 0, + first: populated ? true : null, + typeError: mode === "primitive", + }); + expect(hooked).toEqual(plain); +}); + +it("preserves the native once argument-list object passed to custom apply", async () => { + const root = await createTestTempDirectory( + "rea-electron-hook-ipc-once-args-", + ); + temporary.push(root); + await writeElectronFixture(root); + const script = join(root, "ipc-once-arguments.cjs"); + await writeFile( + script, + `const { ipcMain } = require("electron"); +const { EventEmitter } = require("node:events"); +const scenario = (emitter) => { + const event = { sender: { id: 9 } }; + let calls = 0, argumentObject = false, correctReceiver = false; + const listener = function(value) { calls++; correctReceiver = this === emitter && value === event; }; + listener.apply = function(receiver, list) { + argumentObject = Object.prototype.toString.call(list) === "[object Arguments]"; + if (!argumentObject) throw new Error("once changed argument-list identity"); + return Reflect.apply(Function.prototype.apply, this, [receiver, list]); + }; + emitter.once("ch", listener); + let failure = null; + try { emitter.emit("ch", event); } catch (cause) { failure = cause.message; } + return { calls, argumentObject, correctReceiver, failure, remaining: emitter.listenerCount("ch") }; +}; +console.log(JSON.stringify({ plain: scenario(new EventEmitter()), hooked: scenario(ipcMain) })); +`, + ); + const execution = await runNode(script, root); + expect(execution.code, execution.stderr).toBe(0); + const { plain, hooked } = JSON.parse(execution.stdout) as { + plain: Record; + hooked: Record; + }; + expect(plain).toEqual({ + calls: 1, + argumentObject: true, + correctReceiver: true, + failure: null, + remaining: 0, + }); + expect(hooked).toEqual(plain); +}); + +it.each([ + "function-setter", + "function-readonly", + "object-setter", + "object-readonly", +])("owns on-wrapper metadata despite inherited %s properties", async (mode) => { + const root = await createTestTempDirectory( + "rea-electron-hook-ipc-prototype-", + ); + temporary.push(root); + await writeElectronFixture(root); + const script = join(root, "ipc-inherited-listener.cjs"); + await writeFile( + script, + `const { ipcMain } = require("electron"); +const { EventEmitter } = require("node:events"); +const mode = ${JSON.stringify(mode)}; +const scenario = (emitter) => { + const prototype = mode.startsWith("function") ? Function.prototype : Object.prototype; + const prior = Object.getOwnPropertyDescriptor(prototype, "listener"); + let calls = 0, writes = 0, failure = null; + Object.defineProperty(prototype, "listener", mode.endsWith("setter") ? { + configurable: true, set() { writes++; throw new Error("inherited metadata write"); }, + } : { configurable: true, writable: false, value: undefined }); + try { + emitter.on("ch", () => { calls++; }); + emitter.emit("ch", { sender: { id: 9 } }); + } catch (cause) { failure = cause.message; } + finally { + if (prior) Object.defineProperty(prototype, "listener", prior); + else delete prototype.listener; + } + return { calls, writes, failure, remaining: emitter.listenerCount("ch") }; +}; +console.log(JSON.stringify({ plain: scenario(new EventEmitter()), hooked: scenario(ipcMain) })); +`, + ); + const execution = await runNode(script, root); + expect(execution.code, execution.stderr).toBe(0); + const { plain, hooked } = JSON.parse(execution.stdout) as { + plain: Record; + hooked: Record; + }; + expect(plain).toEqual({ calls: 1, writes: 0, failure: null, remaining: 1 }); + expect(hooked).toEqual(plain); +}); + +it.each(["on", "multiple-on", "once"])( + "records %s invocations without consulting application Array species", + async (kind) => { + const root = await createTestTempDirectory( + "rea-electron-hook-ipc-species-", + ); + temporary.push(root); + await writeElectronFixture(root); + const script = join(root, "ipc-array-species.cjs"); + await writeFile( + script, + `const { ipcMain } = require("electron"); +const { EventEmitter } = require("node:events"); +const kind = ${JSON.stringify(kind)}; +let retainedUnderSpecies = 0; +const scenario = (emitter) => { + let calls = 0, reads = 0, failure = null; + emitter[kind === "once" ? "once" : "on"]("ch", () => { calls++; }); + if (kind === "multiple-on") emitter.on("ch", () => { calls++; }); + const prior = Object.getOwnPropertyDescriptor(Array, Symbol.species); + Object.defineProperty(Array, Symbol.species, { configurable: true, get() { + reads++; throw new Error("application array species"); + }}); + try { + emitter.emit("ch", { sender: { id: 9 } }, 42); + if (emitter === ipcMain) { + for (const event of globalThis.__reaElectronActiveSnapshot().events) + if (event.kind === "main-event-invocation") retainedUnderSpecies++; + } + } + catch (cause) { failure = cause.message; } + finally { Object.defineProperty(Array, Symbol.species, prior); } + return { calls, reads, failure }; +}; +const plain = scenario(new EventEmitter()); +const hooked = scenario(ipcMain); +const invocations = globalThis.__reaElectronActiveSnapshot().events.filter(e => e.kind === "main-event-invocation").length; +console.log(JSON.stringify({ plain, hooked, invocations, retainedUnderSpecies })); +`, + ); + const execution = await runNode(script, root); + expect(execution.code, execution.stderr).toBe(0); + const { plain, hooked, invocations, retainedUnderSpecies } = JSON.parse( + execution.stdout, + ) as { + plain: Record; + hooked: Record; + invocations: number; + retainedUnderSpecies: number; + }; + const expected = kind === "multiple-on" ? 2 : 1; + expect(plain).toEqual({ calls: expected, reads: 0, failure: null }); + expect(hooked).toEqual(plain); + expect(invocations).toBe(expected); + expect(retainedUnderSpecies).toBe(expected); + }, +); + +it.each(["array", "arguments", "array-like", "null"])( + "preserves opaque custom apply inputs for raw %s lists", + async (mode) => { + const root = await createTestTempDirectory( + "rea-electron-hook-custom-apply-", + ); + temporary.push(root); + await writeElectronFixture(root); + const script = join(root, "ipc-custom-apply.cjs"); + await writeFile( + script, + `const { ipcMain } = require("electron"); +const { EventEmitter } = require("node:events"); +const mode = ${JSON.stringify(mode)}; +const scenario = (emitter) => { + let calls = 0, reads = 0, exact = false, methodThis = false, arity = null; + const token = {}; + const listener = function(...args) { calls++; arity = args.length; return token; }; + let list = [42, 43]; + if (mode === "arguments") list = (function() { return arguments; })(42, 43); + if (mode === "array-like") list = { length: 2, get 0() { reads++; return 42; }, 1: 43 }; + if (mode === "null") list = null; + listener.apply = function(receiver, input) { + exact = input === list; + methodThis = this === listener; + if (!exact) throw new Error("custom apply input was replaced"); + return Reflect.apply(listener, receiver, input ?? []); + }; + emitter.on("ch", listener); + let returned = false, failure = null; + try { returned = emitter.rawListeners("ch")[0].apply(emitter, list) === token; } + catch (cause) { failure = cause.message; } + return { calls, reads, exact, methodThis, arity, returned, failure }; +}; +const plain = scenario(new EventEmitter()); +const hooked = scenario(ipcMain); +const snapshot = globalThis.__reaElectronActiveSnapshot(); +console.log(JSON.stringify({ plain, hooked, + invocations: snapshot.events.filter(e => e.kind === "main-event-invocation").length, + gaps: snapshot.events.filter(e => e.kind === "ipc-observation-gap").map(e => ({channel: e.channel, event: e.event})) })); +`, + ); + const execution = await runNode(script, root); + expect(execution.code, execution.stderr).toBe(0); + const { plain, hooked, invocations, gaps } = JSON.parse( + execution.stdout, + ) as { + plain: Record; + hooked: Record; + invocations: number; + gaps: unknown[]; + }; + expect(plain).toEqual({ + calls: 1, + reads: mode === "array-like" ? 1 : 0, + exact: true, + methodThis: true, + arity: mode === "null" ? 0 : 2, + returned: true, + failure: null, + }); + expect(hooked).toEqual(plain); + expect(invocations).toBe(0); + expect(gaps).toEqual([{ channel: "ch", event: "custom-apply" }]); + }, +); + +it.each([false, true])( + "preserves a borrowed raw apply method's receiver (custom: %s)", + async (custom) => { + const root = await createTestTempDirectory( + "rea-electron-hook-borrowed-apply-", + ); + temporary.push(root); + await writeElectronFixture(root); + const script = join(root, "ipc-borrowed-apply.cjs"); + await writeFile( + script, + `const { ipcMain } = require("electron"); +const { EventEmitter } = require("node:events"); +const scenario = (emitter) => { + let originalCalls = 0, borrowedCalls = 0, sameList = false; + const token = {}, list = [42, 43]; + const listener = function() { originalCalls++; }; + const other = function(...args) { borrowedCalls++; return args[0] === 42 && args[1] === 43 ? token : null; }; + if (${JSON.stringify(custom)}) listener.apply = function(receiver, input) { + sameList = input === list; + return Reflect.apply(this, receiver, input); + }; + emitter.on("ch", listener); + const returned = emitter.rawListeners("ch")[0].apply.call(other, emitter, list) === token; + return { originalCalls, borrowedCalls, sameList, returned }; +}; +const plain = scenario(new EventEmitter()), hooked = scenario(ipcMain); +const snapshot = globalThis.__reaElectronActiveSnapshot(); +console.log(JSON.stringify({ plain, hooked, + invocations: snapshot.events.filter(e => e.kind === "main-event-invocation").length })); +`, + ); + const execution = await runNode(script, root); + expect(execution.code, execution.stderr).toBe(0); + const { plain, hooked, invocations } = JSON.parse(execution.stdout); + expect(plain).toEqual({ + originalCalls: 0, + borrowedCalls: 1, + sameList: custom, + returned: true, + }); + expect(hooked).toEqual(plain); + expect(invocations).toBe(0); + }, +); + +it.each([ + "defineProperty", + "getOwnPropertyDescriptor", + "getPrototypeOf", + "create", + "get", + "set", + "Set", + "Proxy", + "WeakMap.has", + "WeakMap.get", + "WeakMap.set", + "Set.has", + "Set.add", +])( + "keeps listener registration and removal independent of late %s replacement", + async (method) => { + const root = await createTestTempDirectory( + "rea-electron-hook-late-intrinsic-", + ); + temporary.push(root); + await writeElectronFixture(root); + const script = join(root, "ipc-late-intrinsic.cjs"); + await writeFile( + script, + `const { ipcMain } = require("electron"); +const { EventEmitter } = require("node:events"); +const specification = ${JSON.stringify(method)}; +const [constructor, member] = specification.split("."); +const method = member ?? specification; +const owner = member !== undefined ? globalThis[constructor].prototype + : method === "Set" || method === "Proxy" ? globalThis + : method === "get" || method === "set" ? Reflect : Object; +const original = owner[method]; +const emitter = new EventEmitter(); +const scenario = (target) => { + const seen = []; + let failure = null; + try { + target.on("ch", () => seen.push("on")); + target.once("one", () => seen.push("once")); + target.on("removeListener", () => {}); + target.emit("ch", null, 42); + target.emit("one", null, 43); + target.emit("one", null, 44); + target.removeAllListeners(); + } catch (cause) { failure = cause.message; } + return { seen, failure, remaining: target.eventNames().length }; +}; +owner[method] = function() { throw new Error("late application intrinsic replacement"); }; +let plain, hooked; +try { plain = scenario(emitter); hooked = scenario(ipcMain); } +finally { owner[method] = original; } +const snapshot = globalThis.__reaElectronActiveSnapshot(); +console.log(JSON.stringify({ plain, hooked, + invocations: snapshot.events.filter(e => e.kind === "main-event-invocation" && (e.channel === "ch" || e.channel === "one")).length })); +`, + ); + const execution = await runNode(script, root); + expect(execution.code, execution.stderr).toBe(0); + const { plain, hooked, invocations } = JSON.parse(execution.stdout); + expect(plain).toEqual({ + seen: ["on", "once"], + failure: null, + remaining: 0, + }); + expect(hooked).toEqual(plain); + expect(invocations).toBe(2); + }, +); + +it.each([ + { + mode: "data", + depth: 16, + prototypeDepth: 0, + once: true, + gaps: 0, + remaining: 1, + }, + { + mode: "data", + depth: 17, + prototypeDepth: 0, + once: true, + gaps: 1, + remaining: 1, + }, + { + mode: "accessor", + depth: 1, + prototypeDepth: 0, + once: true, + gaps: 1, + remaining: 0, + }, + { + mode: "proxy", + depth: 1, + prototypeDepth: 0, + once: true, + gaps: 1, + remaining: 0, + }, + { + mode: "data", + depth: 17, + prototypeDepth: 0, + once: false, + gaps: 1, + remaining: 1, + }, + { + mode: "inherited-data", + depth: 1, + prototypeDepth: 1, + once: true, + gaps: 0, + remaining: 0, + }, + { + mode: "inherited-accessor", + depth: 1, + prototypeDepth: 1, + once: true, + gaps: 1, + remaining: 0, + }, + { + mode: "inherited-proxy", + depth: 1, + prototypeDepth: 1, + once: true, + gaps: 1, + remaining: 0, + }, + { + mode: "inherited-data", + depth: 1, + prototypeDepth: 16, + once: true, + gaps: 0, + remaining: 0, + }, + { + mode: "inherited-data", + depth: 1, + prototypeDepth: 17, + once: true, + gaps: 1, + remaining: 0, + }, +])( + "preserves native dispatch for $mode chains (depth $depth, prototype $prototypeDepth, once $once)", + async ({ mode, depth, prototypeDepth, once, gaps, remaining }) => { + const root = await createTestTempDirectory( + "rea-electron-hook-chain-coverage-", + ); + temporary.push(root); + await writeElectronFixture(root); + const script = join(root, "ipc-chain-coverage.cjs"); + await writeFile( + script, + `const { ipcMain } = require("electron"); +const { EventEmitter } = require("node:events"); +const mode = ${JSON.stringify(mode)}, once = ${JSON.stringify(once)}; +const scenario = (target) => { + let calls = 0, reads = 0; + for (let index = 0; index < ${depth}; index++) { + const previous = target.on; + target.on = function(channel, listener) { + let outer = function(...args) { return Reflect.apply(listener, this, args); }; + if (mode.startsWith("inherited-")) { + let prototype = Object.create(Function.prototype); + Object.defineProperty(prototype, "listener", mode === "inherited-accessor" + ? { get() { reads++; return listener; } } : { value: listener }); + if (mode === "inherited-proxy") prototype = new Proxy(prototype, { + getOwnPropertyDescriptor() { throw new Error("prototype metadata reflection"); }, + getPrototypeOf() { throw new Error("prototype traversal reflection"); } + }); + for (let index = 1; index < ${prototypeDepth}; index++) prototype = Object.create(prototype); + Object.setPrototypeOf(outer, prototype); + } + else if (mode === "accessor") Object.defineProperty(outer, "listener", { get() { reads++; return listener; } }); + else outer.listener = listener; + if (mode === "proxy") outer = new Proxy(outer, { + getOwnPropertyDescriptor() { throw new Error("application metadata reflection"); } + }); + return Reflect.apply(previous, this, [channel, outer]); + }; + } + target[once ? "once" : "on"]("ch", () => calls++); + target.emit("ch", null, 42); target.emit("ch", null, 43); + return { calls, reads, remaining: target.listenerCount("ch") }; +}; +const plain = scenario(new EventEmitter()), hooked = scenario(ipcMain); +const snapshot = globalThis.__reaElectronActiveSnapshot(); +console.log(JSON.stringify({ plain, hooked, + invocations: snapshot.events.filter(e => e.kind === "main-event-invocation").length, + gaps: snapshot.events.filter(e => e.kind === "ipc-observation-gap").map(e => ({event: e.event, channel: e.channel})) })); +`, + ); + const execution = await runNode(script, root); + expect(execution.code, execution.stderr).toBe(0); + const result = JSON.parse(execution.stdout); + expect(result.plain).toEqual({ + calls: once ? 1 : 2, + reads: mode.endsWith("accessor") ? 1 : 0, + remaining, + }); + expect(result.hooked).toEqual(result.plain); + expect(result.invocations).toBe(once ? 1 : 0); + expect(result.gaps).toEqual( + Array.from({ length: gaps }, () => ({ + event: "listener-chain", + channel: "ch", + })), + ); + }, +); + +it.each(["array", "map"])( + "snapshots retained state without application %s iterators", + async (kind) => { + const root = await createTestTempDirectory( + "rea-electron-hook-snapshot-iterator-", + ); + temporary.push(root); + await writeElectronFixture(root); + const script = join(root, "ipc-snapshot-iterator.cjs"); + await writeFile( + script, + `const { ipcMain } = require("electron"); +let calls = 0, reads = 0, failure = null, snapshot; +ipcMain.on("ch", () => { calls++; }); +ipcMain.emit("ch", { sender: { id: 9 } }, 42); +const prototype = ${JSON.stringify(kind)} === "array" ? Array.prototype : Map.prototype; +const prior = Object.getOwnPropertyDescriptor(prototype, Symbol.iterator); +Object.defineProperty(prototype, Symbol.iterator, { configurable: true, value() { + reads++; throw new Error("application iterator observed retained state"); +}}); +try { snapshot = globalThis.__reaElectronActiveSnapshot(); } +catch (cause) { failure = cause.message; } +finally { Object.defineProperty(prototype, Symbol.iterator, prior); } +const copied = snapshot?.events.find(e => e.kind === "main-event-invocation"); +if (copied) { copied.channel = "changed"; copied.argument_shapes[0] = "changed"; } +const retained = globalThis.__reaElectronActiveSnapshot().events.find(e => e.kind === "main-event-invocation"); +console.log(JSON.stringify({ calls, reads, failure, copied: !!copied, retained: {channel: retained.channel, shapes: retained.argument_shapes} })); +`, + ); + const execution = await runNode(script, root); + expect(execution.code, execution.stderr).toBe(0); + expect(JSON.parse(execution.stdout)).toEqual({ + calls: 1, + reads: 0, + failure: null, + copied: true, + retained: { channel: "ch", shapes: ["number"] }, + }); + }, +); + +it("invokes handle callbacks without reading callable method properties", async () => { + const root = await createTestTempDirectory("rea-electron-hook-ipc-callable-"); + temporary.push(root); + await writeElectronFixture(root); + const script = join(root, "ipc-callable-properties.cjs"); + await writeFile( + script, + `"use strict"; +const { ipcMain } = require("electron"); +const { EventEmitter } = require("node:events"); +const scenario = async (emitter, observed) => { + let calls = 0; + let reads = 0; + let correctReceiver = false; + let arity = null; + const listener = new Proxy(function(...args) { + calls += 1; + correctReceiver = this === undefined; + arity = args.length; + }, { get(target, key, receiver) { + if (key === "call" || key === "apply") { + reads += 1; + throw new Error("callable metadata must not be read"); + } + return Reflect.get(target, key, receiver); + }}); + const event = { sender: { id: 9 } }; + let failure = null; + try { + if (observed) { + emitter.handle("ch", listener); + const handler = emitter.handlers.get("ch"); + await handler(event, 42); + } else { + await listener(event, 42); + } + } catch (cause) { failure = cause.message; } + return { calls, reads, correct_receiver: correctReceiver, arity, failure }; +}; +(async () => { + process.stdout.write(JSON.stringify({ plain: await scenario(new EventEmitter(), false), + hooked: await scenario(ipcMain, true) }) + "\\n"); +})(); +`, + ); + const execution = await runNode(script, root); + expect(execution.code, execution.stderr).toBe(0); + const { plain, hooked } = JSON.parse(execution.stdout) as { + plain: Record; + hooked: Record; + }; + expect(plain).toEqual({ + calls: 1, + reads: 0, + correct_receiver: true, + arity: 2, + failure: null, + }); + expect(hooked).toEqual(plain); +}); + +it("keeps native removal traversal when an ipcMain observer prepends a listener", async () => { + const root = await createTestTempDirectory("rea-electron-hook-ipc-prepend-"); + temporary.push(root); + await writeElectronFixture(root); + const script = join(root, "ipc-prepend-during-removal.cjs"); + await writeFile( + script, + `const { ipcMain } = require("electron"); +const { EventEmitter } = require("node:events"); +const scenario = (emitter) => { + const calls = []; + const removed = []; + const a = () => calls.push("a"); + const b = () => calls.push("b"); + const c = () => calls.push("c"); + const replacement = () => calls.push("replacement"); + const names = new Map([[a, "a"], [b, "b"], [c, "c"], [replacement, "replacement"]]); + let inserted = false; + emitter.on("removeListener", (channel, listener) => { + if (channel !== "ch") return; + removed.push(names.get(listener) ?? "unknown"); + if (!inserted) { + inserted = true; + emitter.prependListener("ch", replacement); + } + }); + emitter.on("ch", a); + emitter.on("ch", b); + emitter.on("ch", c); + emitter.removeAllListeners("ch"); + const remaining = emitter.listeners("ch").map((listener) => names.get(listener)); + emitter.emit("ch", { sender: { id: 9 } }); + return { removed, remaining, calls }; +}; +process.stdout.write(JSON.stringify({ plain: scenario(new EventEmitter()), hooked: scenario(ipcMain) }) + "\\n"); +`, + ); + const execution = await runNode(script, root); + expect(execution.code, execution.stderr).toBe(0); + const { plain, hooked } = JSON.parse(execution.stdout) as { + plain: Record; + hooked: Record; + }; + expect(hooked).toEqual(plain); +}); + +it.each(["accessor", "proxy"])( + "does not evaluate %s listener metadata while classifying ipcMain wrappers", + async (kind) => { + const root = await createTestTempDirectory( + "rea-electron-hook-ipc-accessor-", + ); + temporary.push(root); + await writeElectronFixture(root); + const script = join(root, "ipc-listener-accessor.cjs"); + await writeFile( + script, + `const { ipcMain } = require("electron"); +const { EventEmitter } = require("node:events"); +const event = { sender: { id: 9 } }; +const scenario = (emitter) => { + let reads = 0; + let calls = 0; + let listener = () => { calls += 1; }; + const inspectMetadata = () => { + reads += 1; + throw new Error("listener metadata must not run during registration"); + }; + if (${JSON.stringify(kind)} === "accessor") { + Object.defineProperty(listener, "listener", { get: inspectMetadata }); + } else { + listener = new Proxy(listener, { + getOwnPropertyDescriptor: inspectMetadata, + get(target, key, receiver) { + if (key === "listener") return inspectMetadata(); + return Reflect.get(target, key, receiver); + }, + }); + } + let failure = null; + let registered = 0; + try { + emitter.on("accessor", listener); + emitter.emit("accessor", event); + registered = emitter.listenerCount("accessor"); + emitter.removeAllListeners("accessor"); + } catch (cause) { failure = cause.message; } + return { reads, calls, registered, failure }; +}; +process.stdout.write(JSON.stringify({ plain: scenario(new EventEmitter()), hooked: scenario(ipcMain) }) + "\\n"); +`, + ); + const execution = await runNode(script, root); + expect(execution.code, execution.stderr).toBe(0); + const { plain, hooked } = JSON.parse(execution.stdout) as { + plain: Record; + hooked: Record; + }; + expect(plain).toEqual({ reads: 0, calls: 1, registered: 1, failure: null }); + expect(hooked).toEqual(plain); + }, +); + it("isolates hook and exercise from ancestor Electron packages", async () => { const root = await createTestTempDirectory("rea-electron-hook-ancestor-"); temporary.push(root); diff --git a/tests/boundary/cli/adbCli.test.ts b/tests/boundary/cli/adbCli.test.ts index 69925565f7..e41aeb7e84 100644 --- a/tests/boundary/cli/adbCli.test.ts +++ b/tests/boundary/cli/adbCli.test.ts @@ -5,6 +5,10 @@ import { expect, it, onTestFinished } from "vitest"; import { z } from "zod"; import { toolContract } from "../../../src/contracts/toolContracts.js"; +import { + execFileOutput, + execFileOutputFailure, +} from "../../../src/process/ExecFileOutput.js"; import { expectConfigurationRefusal, serveCli as serve, @@ -14,6 +18,71 @@ import { removeTestWorkspace, } from "../../support/workspace/workspaceFixture.js"; +it.skipIf(process.platform === "win32").each([ + { exit: 1, stdout: "", stderr: "adb: error: closed", overwrite: false }, + { exit: 1, stdout: "", stderr: "adb: error: closed", overwrite: true }, + { exit: 1, stdout: "unexpected probe output", stderr: "", overwrite: false }, + { exit: 2, stdout: "", stderr: "", overwrite: false }, +])("refuses an ambiguous destination probe before push: %j", async (probe) => { + const workspace = await createTestWorkspace("rea-adb-probe-cli-"); + onTestFinished(() => removeTestWorkspace(workspace.root)); + const source = await workspace.write("payload.bin", "replacement"); + await workspace.write("device-file", "original"); + const adb = await workspace.write( + "adb", + `#!/bin/sh +root=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) +printf '%s\\n' "$3" >> "$root/calls" +if [ "$1" = version ]; then + printf 'Android Debug Bridge version 1.0.41\\nVersion 34.0.5-cli\\nInstalled as %s\\n' "$0" + exit 0 +fi +if [ "$3" = shell ] && [ "$4" = test ]; then + printf '%s' '${probe.stdout}' + printf '%s' '${probe.stderr}' >&2 + exit ${String(probe.exit)} +fi +if [ "$3" = push ]; then cp "$4" "$root/device-file"; exit 0; fi +exit 1 +`, + ); + await chmod(adb, 0o755); + const result = await execFileOutput( + process.execPath, + [ + "scripts/rea.mjs", + "push-adb-file", + "device", + source, + "/data/local/tmp/payload.bin", + ...(probe.overwrite ? ["--overwrite"] : []), + "--json", + ], + { + env: { ...process.env, REA_ADB_PATH: adb }, + timeout: 20_000, + stopSignal: "SIGTERM", + maxBuffer: 1024 * 1024, + }, + ).then( + (output) => ({ exitCode: 0, stdout: output.stdout }), + (cause: unknown) => { + const failure = execFileOutputFailure(cause); + if (failure === undefined) throw cause; + return { exitCode: failure.code, stdout: failure.stdout }; + }, + ); + expect(result.exitCode).toBe(1); + expect(JSON.parse(result.stdout)).toMatchObject({ + code: "unreadable_output", + }); + if (probe.stderr !== "") expect(result.stdout).toContain(probe.stderr); + expect(await readFile(workspace.path("device-file"), "utf8")).toBe( + "original", + ); + expect(await readFile(workspace.path("calls"), "utf8")).toBe("\nshell\n"); +}); + it.each([ [ ["inspect-adb-client", "--json"], diff --git a/tests/boundary/filesystem/ghidraProfileArtifactOwnership.test.ts b/tests/boundary/filesystem/ghidraProfileArtifactOwnership.test.ts new file mode 100644 index 0000000000..4da6e57731 --- /dev/null +++ b/tests/boundary/filesystem/ghidraProfileArtifactOwnership.test.ts @@ -0,0 +1,565 @@ +import * as extensionModule from "../../../src/ghidra/extensions/GhidraExtensions.js"; +import { createHash } from "node:crypto"; +import { basename } from "node:path"; +import { GhidraClient } from "../../../src/ghidra/GhidraClient.js"; +import { GhidraHeadlessLauncher } from "../../../src/ghidra/GhidraLauncher.js"; +import { GhidraProvider } from "../../../src/ghidra/GhidraProvider.js"; +import { ghidraProfileInstallationHost } from "../../../src/ghidra/GhidraInstallation.fixture.js"; +import { silentLogger } from "../../../src/logger.js"; +import { createDeferred } from "../../fixtures/binarySession.js"; +import { readFile, rename, writeFile, type FileHandle } from "node:fs/promises"; +import { join } from "node:path"; +import { afterEach, expect, it, vi } from "vitest"; +import { ArtifactResourceScope } from "../../../src/artifacts/ArtifactResourceScope.js"; +import { parseConfig } from "../../../src/config/parseConfig.js"; +import { + resolveGhidraAnalysisSeeds, + snapshotGhidraAnalysisSeeds, +} from "../../../src/ghidra/GhidraAnalysisSeeds.js"; +import { + resolveGhidraExtensions, + snapshotGhidraExtensions, +} from "../../../src/ghidra/extensions/GhidraExtensions.js"; +import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js"; + +const controls = vi.hoisted(() => ({ + realpath: undefined as ((path: string) => Promise) | undefined, + file: undefined as ((handle: FileHandle, path: string) => void) | undefined, +})); +vi.mock("node:fs/promises", async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + realpath: async (...args: Parameters) => { + await controls.realpath?.(String(args[0])); + return actual.realpath(...args); + }, + open: async (...args: Parameters) => { + const handle = await actual.open(...args); + controls.file?.(handle, String(args[0])); + return handle; + }, + }; +}); +afterEach(() => { + controls.file = undefined; + controls.realpath = undefined; + vi.restoreAllMocks(); +}); + +const target = { + path: "/tmp/native", + kind: "executable", + format: "elf", + architecture: "x86_64", + availableArchitectures: ["x86_64"], + sha256: "a".repeat(64), +} as const; +const configured = ( + path: string, + extra: Readonly> = {}, +) => { + const result = parseConfig({ REA_GHIDRA_NATIVEAOT_JAR: path, ...extra }); + if (!result.ok) throw result.error; + return result.value; +}; + +it.each( + (["seed", "extension"] as const).flatMap((kind) => + [false, true].map((lateAbort) => ({ kind, lateAbort })), + ), +)( + "retains $kind read failure and exact close owner through retry (late abort: $lateAbort)", + async ({ kind, lateAbort }) => { + const root = await createTestTempDirectory("rea-profile-file-"); + const path = join(root, kind === "seed" ? "seeds.tsv" : "extension.jar"); + await writeFile( + path, + kind === "seed" ? "0x1000\tcode\n" : Buffer.from([0x50, 0x4b, 3, 4, 0]), + ); + const resources = new ArtifactResourceScope(); + const controller = new AbortController(); + const physical = new Error("controlled profile read failure"); + let allowClose = false; + let closeActual: (() => Promise) | undefined; + let closes = 0; + controls.file = (handle, opened) => { + if (opened !== path) return; + controls.file = undefined; + closeActual = handle.close.bind(handle); + vi.spyOn(handle, "read").mockRejectedValue(physical); + vi.spyOn(handle, "close").mockImplementation(async () => { + closes++; + if (lateAbort) + controller.abort({ caller: "after physical profile failure" }); + if (!allowClose) throw new Error("controlled profile close failure"); + await closeActual?.(); + }); + }; + try { + const result = + kind === "seed" + ? await resolveGhidraAnalysisSeeds(path, resources, controller.signal) + : await resolveGhidraExtensions( + configured(path), + target, + "linux", + resources, + controller.signal, + ); + if (result.ok) throw new Error("Expected profile read failure"); + expect( + result.error.message + JSON.stringify(result.error.diagnostics), + ).toContain(physical.message); + expect(result.error._tag).toBe("ProviderAdapterError"); + expect(result.error.cleanupIncomplete).toBe(true); + expect(result.error.cleanupResources).toEqual([path]); + expect(resources.hasPending).toBe(true); + await expect(resources.retryCleanup()).rejects.toThrow(); + expect(closes).toBe(2); + let extraOpens = 0; + controls.file = () => { + extraOpens++; + }; + const refused = + kind === "seed" + ? await resolveGhidraAnalysisSeeds(path, resources) + : await resolveGhidraExtensions( + configured(path), + target, + "linux", + resources, + ); + expect(refused).toMatchObject({ + ok: false, + error: { cleanupIncomplete: true }, + }); + expect(extraOpens).toBe(0); + expect(closes).toBe(3); + allowClose = true; + await resources.retryCleanup(); + expect(closes).toBe(4); + expect(result.error.cleanupIncomplete).toBe(false); + if (refused.ok) throw new Error("Unexpected admission"); + expect(refused.error.cleanupIncomplete).toBe(false); + expect(resources.hasPending).toBe(false); + } finally { + allowClose = true; + await resources.close(); + await closeActual?.(); + } + }, +); + +it.each(["grow", "replace"])( + "refuses seeds that %s during the admitted read", + async (mode) => { + const root = await createTestTempDirectory("rea-seed-growth-"); + const path = join(root, "seeds.tsv"); + await writeFile(path, "0x1000\tcode\n"); + const resources = new ArtifactResourceScope(); + controls.file = (handle, opened) => { + if (opened !== path) return; + controls.file = undefined; + const read = handle.read.bind(handle); + vi.spyOn(handle, "read").mockImplementationOnce( + async (...args: Parameters) => { + const result = await read(...args); + if (mode === "grow") + await writeFile(path, "0x1000\tcode\n0x2000\tcode\n"); + else { + const next = join(root, "replacement.tsv"); + await writeFile(next, "0x1000\tcode\n"); + await rename(next, path); + } + return result; + }, + ); + }; + try { + expect((await resolveGhidraAnalysisSeeds(path, resources)).ok).toBe( + false, + ); + } finally { + await resources.close(); + } + }, +); + +it.each(["seed", "extension"] as const)( + "retains %s cleanup and exact cancellation during final close", + async (kind) => { + const root = await createTestTempDirectory("rea-profile-cancel-close-"); + const path = join(root, kind === "seed" ? "seeds.tsv" : "extension.jar"); + await writeFile( + path, + kind === "seed" ? "0x1000\tcode\n" : Buffer.from([0x50, 0x4b, 3, 4, 0]), + ); + const resources = new ArtifactResourceScope(); + const controller = new AbortController(); + const reason = { caller: "final profile close" }; + let allowClose = false; + controls.file = (handle, opened) => { + if (opened !== path) return; + controls.file = undefined; + const close = handle.close.bind(handle); + vi.spyOn(handle, "close").mockImplementation(async () => { + controller.abort(reason); + if (!allowClose) + throw new Error("controlled final profile close failure"); + await close(); + }); + }; + try { + const result = + kind === "seed" + ? await resolveGhidraAnalysisSeeds(path, resources, controller.signal) + : await resolveGhidraExtensions( + configured(path), + target, + "linux", + resources, + controller.signal, + ); + expect(result).toMatchObject({ + ok: false, + error: { + _tag: "AnalysisCancelledError", + cause: reason, + cleanupIncomplete: true, + cleanupResources: [path], + }, + }); + if (result.ok) throw new Error("Expected cancelled finalization"); + allowClose = true; + await resources.retryCleanup(); + expect(result.error.cleanupIncomplete).toBe(false); + } finally { + allowClose = true; + await resources.close(); + } + }, +); + +it.each(["seed", "extension"] as const)( + "retains %s snapshot output owner after cancellation during close", + async (kind) => { + const root = await createTestTempDirectory("rea-profile-output-close-"); + const path = join(root, kind === "seed" ? "seeds.tsv" : "extension.jar"); + const bytes = + kind === "seed" + ? Buffer.from("0x1000\tcode\n") + : Buffer.from([0x50, 0x4b, 3, 4, 0]); + await writeFile(path, bytes); + const resources = new ArtifactResourceScope(); + const seeds = await resolveGhidraAnalysisSeeds( + kind === "seed" ? path : undefined, + resources, + ); + const extensions = + kind === "extension" + ? await resolveGhidraExtensions( + configured(path), + target, + "linux", + resources, + ) + : ({ ok: true, value: [] } as const); + if (!seeds.ok || !extensions.ok) + throw new Error("Failed fixture commitment"); + const output = join( + root, + kind === "seed" ? "seeds/seeds.tsv" : "extensions/nativeaot.jar", + ); + const controller = new AbortController(); + const reason = { caller: "snapshot output close" }; + let allowClose = false; + controls.file = (handle, opened) => { + if (opened !== output) return; + controls.file = undefined; + const close = handle.close.bind(handle); + vi.spyOn(handle, "close").mockImplementation(async () => { + controller.abort(reason); + if (!allowClose) + throw new Error("controlled snapshot output close failure"); + await close(); + }); + }; + try { + const error = await ( + kind === "seed" && seeds.value !== undefined + ? snapshotGhidraAnalysisSeeds( + seeds.value, + root, + resources, + controller.signal, + ) + : snapshotGhidraExtensions( + extensions.value, + root, + resources, + controller.signal, + ) + ).then( + () => { + throw new Error("Expected snapshot failure"); + }, + (cause: unknown) => cause, + ); + expect(error).toMatchObject({ + _tag: "AnalysisCancelledError", + cause: reason, + cleanupIncomplete: true, + cleanupResources: [output], + }); + expect(await readFile(output)).toEqual(bytes); + allowClose = true; + await resources.retryCleanup(); + expect(error).toMatchObject({ cleanupIncomplete: false }); + } finally { + allowClose = true; + await resources.close(); + } + }, +); + +it("joins whole provider resolution before shutdown and gates the next admission", async () => { + const root = await createTestTempDirectory("rea-profile-completion-"); + const jar = join(root, "extension.jar"); + const seed = join(root, "seeds.tsv"); + await writeFile(jar, Buffer.from([0x50, 0x4b, 3, 4, 0])); + await writeFile(seed, "0x1000\tcode\n"); + const provider = new GhidraProvider( + configured(jar, { + GHIDRA_INSTALL_DIR: "/ghidra", + REA_GHIDRA_SEED_FILE: seed, + }), + silentLogger, + {}, + ghidraProfileInstallationHost, + ); + const entered = createDeferred(); + const release = createDeferred(); + controls.realpath = async (path) => { + if (path !== seed) return; + controls.realpath = undefined; + entered.resolve(); + await release.promise; + }; + let opens = 0; + controls.file = () => { + opens++; + }; + const resolving = provider.resolveAnalysisProfile(target); + await entered.promise; + const count = opens; + let closed = false; + const closing = provider.closeOwnedResources().then((result) => { + closed = true; + return result; + }); + const next = provider.resolveAnalysisProfile(target); + try { + await new Promise((resolve) => setImmediate(resolve)); + expect(closed).toBe(false); + expect(opens).toBe(count); + expect(provider.hasPendingOwnedResources()).toBe(true); + release.resolve(); + expect((await resolving).ok).toBe(true); + expect(await closing).toEqual({ ok: true, value: null }); + expect((await next).ok).toBe(true); + expect(provider.hasPendingOwnedResources()).toBe(false); + } finally { + release.resolve(); + await Promise.all([resolving, closing, next]); + await provider.closeOwnedResources(); + } +}); + +it.each(["seed", "extension"] as const)( + "preserves %s snapshot write failure through client cancellation and cleanup retry", + async (kind) => { + const root = await createTestTempDirectory("rea-profile-client-write-"); + const source = join(root, kind === "seed" ? "input.tsv" : "input.jar"); + await writeFile( + source, + kind === "seed" ? "0x1000\tcode\n" : Buffer.from([0x50, 0x4b, 3, 4, 0]), + ); + const scope = new ArtifactResourceScope(); + const seeds = await resolveGhidraAnalysisSeeds( + kind === "seed" ? source : undefined, + scope, + ); + const extensions = + kind === "extension" + ? await resolveGhidraExtensions( + configured(source), + target, + "linux", + scope, + ) + : ({ ok: true, value: [] } as const); + if (!seeds.ok || !extensions.ok) + throw new Error("Failed fixture commitment"); + const targetPath = join(root, "target.bin"); + await writeFile(targetPath, "synthetic target"); + const spawn = vi.fn(); + const launcher = new GhidraHeadlessLauncher({ + environment: {}, + analyzeHeadlessPath: join(root, "support/analyzeHeadless"), + configurationDirectoryName: "ghidra_12.1.4_PUBLIC", + bridgeScriptPath: "/packaged/ReaGhidraBridge.java", + ...(seeds.value === undefined ? {} : { analysisSeeds: seeds.value }), + analysisExtensions: extensions.value, + spawnProcess: spawn, + }); + const client = new GhidraClient({ + launcher, + targetPath, + targetSha256: createHash("sha256") + .update("synthetic target") + .digest("hex"), + providerVersion: "12.1.4", + profileDigest: "b".repeat(64), + }); + const controller = new AbortController(); + let allowClose = false; + let closes = 0; + let outputPath = ""; + const physical = new Error("controlled snapshot physical write failure"); + controls.file = (handle, opened) => { + if ( + basename(opened) !== (kind === "seed" ? "seeds.tsv" : "nativeaot.jar") + ) + return; + controls.file = undefined; + outputPath = opened; + const close = handle.close.bind(handle); + vi.spyOn(handle, "writeFile").mockRejectedValue(physical); + vi.spyOn(handle, "close").mockImplementation(async () => { + closes++; + controller.abort({ caller: "after snapshot write failure" }); + if (!allowClose) throw new Error("controlled snapshot close failure"); + await close(); + }); + }; + try { + const result = await client.start(controller.signal); + if (result.ok) throw new Error("Failed snapshot admitted"); + expect(result.error.kind).toBe("start"); + expect(result.error.cleanupFailure?.cleanupIncomplete).toBe(true); + expect(result.error.cleanupFailure?.cleanupResources).toContain( + outputPath, + ); + expect(result.error.cause).toMatchObject({ + primaryFailure: { diagnostics: { reason: physical.message } }, + }); + expect(spawn).not.toHaveBeenCalled(); + expect(closes).toBe(2); + allowClose = true; + await client.close(); + expect(closes).toBe(3); + expect(result.error.cleanupFailure?.cleanupIncomplete).toBe(false); + } finally { + allowClose = true; + await client.close(); + await scope.close(); + } + }, +); + +it("preserves the exact cancellation reason between extension completion and provider continuation", async () => { + const root = await createTestTempDirectory("rea-profile-handoff-"); + const jar = join(root, "extension.jar"); + await writeFile(jar, Buffer.from([0x50, 0x4b, 3, 4, 0])); + const controller = new AbortController(); + const reason = { caller: "profile continuation" }; + const resolve = extensionModule.resolveGhidraExtensions; + vi.spyOn(extensionModule, "resolveGhidraExtensions").mockImplementationOnce( + async (...args) => { + const result = await resolve(...args); + queueMicrotask(() => controller.abort(reason)); + return result; + }, + ); + const provider = new GhidraProvider( + configured(jar, { GHIDRA_INSTALL_DIR: "/ghidra" }), + silentLogger, + {}, + ghidraProfileInstallationHost, + ); + try { + const result = await provider.resolveAnalysisProfile(target, { + signal: controller.signal, + }); + expect(result).toMatchObject({ + ok: false, + error: { _tag: "AnalysisCancelledError" }, + }); + if (result.ok) throw new Error("Cancelled handoff admitted"); + expect(result.error.cause).toBe(reason); + } finally { + await provider.closeOwnedResources(); + } +}); + +it.each(["seed", "extension"] as const)( + "preserves malformed %s facts before final close failure and late cancellation", + async (kind) => { + const root = await createTestTempDirectory("rea-profile-format-close-"); + const path = join(root, kind === "seed" ? "seeds.tsv" : "extension.jar"); + await writeFile( + path, + kind === "seed" ? "0x1000\tinvalid\n" : "invalid jar", + ); + const scope = new ArtifactResourceScope(); + const controller = new AbortController(); + let allowClose = false; + controls.file = (handle, opened) => { + if (opened !== path) return; + controls.file = undefined; + const close = handle.close.bind(handle); + vi.spyOn(handle, "close").mockImplementation(async () => { + controller.abort({ caller: "after format interpretation" }); + if (!allowClose) + throw new Error("controlled malformed artifact close failure"); + await close(); + }); + }; + try { + const result = + kind === "seed" + ? await resolveGhidraAnalysisSeeds(path, scope, controller.signal) + : await resolveGhidraExtensions( + configured(path), + target, + "linux", + scope, + controller.signal, + ); + if (result.ok) throw new Error("Malformed artifact admitted"); + expect(result.error._tag).toBe("ProviderAdapterError"); + expect(result.error.cleanupIncomplete).toBe(true); + expect(result.error.cleanupResources).toEqual([path]); + if (kind === "seed") + expect(result.error.diagnostics?.primary_error).toMatchObject({ + code: "configuration_invalid", + details: { + settings: [ + { + setting: "REA_GHIDRA_SEED_FILE", + constraint: expect.stringContaining("kind must be"), + }, + ], + }, + }); + else expect(result.error.diagnostics?.reason).toContain("not a JAR"); + allowClose = true; + await scope.retryCleanup(); + expect(result.error.cleanupIncomplete).toBe(false); + } finally { + allowClose = true; + await scope.close(); + } + }, +); diff --git a/tests/boundary/filesystem/ghidraPspExtension.test.ts b/tests/boundary/filesystem/ghidraPspExtension.test.ts index 38c7b1c780..c2fdfa6988 100644 --- a/tests/boundary/filesystem/ghidraPspExtension.test.ts +++ b/tests/boundary/filesystem/ghidraPspExtension.test.ts @@ -1,3 +1,5 @@ +import { AnalysisProviderRegistry } from "../../../src/application/binary/AnalysisProviderRegistry.js"; +import { composeBinarySession } from "../../../src/application/binary/BinarySessionComposition.js"; import { createHash } from "node:crypto"; import type { Dir } from "node:fs"; import { GhidraClient } from "../../../src/ghidra/GhidraClient.js"; @@ -970,3 +972,79 @@ describe.skipIf(process.platform === "win32")( }); }, ); + +describe.skipIf(process.platform === "win32")( + "PSP profile selection completion through composed sessions", + () => { + it.each(["preview", "open"] as const)( + "preserves physical failure and live cleanup after late cancellation during %s", + async (operation) => { + const { directory, root, provider } = await installation(); + const path = join(directory, "controlled-psp.elf"); + await writeFile(path, pspElfBytes()); + const parsed = await parseBinaryTarget(path); + if (!parsed.ok) throw parsed.error; + const controller = new AbortController(); + const readFailure = new Error( + "controlled session profile read failure", + ); + let allowClose = false; + let closes = 0; + controls.file = (handle, openedPath) => { + if (openedPath !== join(root, "Module.manifest")) return; + controls.file = undefined; + vi.spyOn(handle, "read").mockRejectedValue(readFailure); + const close = handle.close.bind(handle); + vi.spyOn(handle, "close").mockImplementation(async () => { + closes++; + controller.abort({ caller: "profile inspection finalization" }); + if (!allowClose) + throw new Error("controlled session profile close failure"); + await close(); + }); + }; + const createClient = vi + .spyOn(provider, "createClient") + .mockImplementation(() => { + throw new Error("Failed profile must not start an engine"); + }); + const binary = composeBinarySession( + new AnalysisProviderRegistry([provider]), + ); + try { + const result = + operation === "preview" + ? await binary.previewTarget(parsed.value, { + providerId: "ghidra", + signal: controller.signal, + }) + : await binary.open(path, { + providerId: "ghidra", + signal: controller.signal, + }); + expect(result.ok).toBe(false); + if (result.ok) throw new Error("Failed profile admitted"); + expect(result.error._tag).toBe("AnalysisCapabilityUnavailableError"); + expect(result.error.message).toContain(readFailure.message); + expect(result.error.cleanupIncomplete).toBe(true); + expect(binary.status()).toMatchObject({ cleanup_pending: true }); + expect(createClient).not.toHaveBeenCalled(); + const failedClose = await binary.close(); + expect(failedClose.ok).toBe(false); + expect(closes).toBe(2); + expect(result.error.cleanupIncomplete).toBe(true); + allowClose = true; + expect(await binary.close()).toEqual({ ok: true, value: null }); + expect(closes).toBe(3); + expect(result.error.cleanupIncomplete).toBe(false); + if (failedClose.ok) throw new Error("Close unexpectedly succeeded"); + expect(failedClose.error.cleanupIncomplete).toBe(false); + expect(binary.status()).toMatchObject({ cleanup_pending: false }); + } finally { + allowClose = true; + await binary.close(); + } + }, + ); + }, +); diff --git a/tests/boundary/mcp/electronMcp.test.ts b/tests/boundary/mcp/electronMcp.test.ts index b97b7b8d55..9842407fa0 100644 --- a/tests/boundary/mcp/electronMcp.test.ts +++ b/tests/boundary/mcp/electronMcp.test.ts @@ -5,6 +5,7 @@ import { basename, join } from "node:path"; import { pathToFileURL } from "node:url"; import { Client, InMemoryTransport } from "@modelcontextprotocol/client"; +import { Ajv2020 } from "ajv/dist/2020.js"; import { afterEach, expect, it, vi } from "vitest"; import { z } from "zod"; @@ -450,6 +451,26 @@ it("runs active Electron scenarios with selected paths and inferred working dire const capturedInputs: unknown[] = []; const activeResult = createElectronActiveObservationFixtureResult(applicationPath); + const capturedEvent = activeResult.timeline.events[0]; + if (capturedEvent === undefined) throw new Error("IPC fixture was empty"); + const gaps = ["custom-apply", "listener-chain"].map((event, index) => ({ + ...capturedEvent, + sequence: index + 2, + kind: "ipc-observation-gap" as const, + event, + channel: event === "custom-apply" ? "custom" : "opaque-chain", + direction: null, + receiver: null, + target: "ipcMain.on", + argument_shapes: [], + result_shape: null, + })); + activeResult.timeline.events.push(...gaps); + activeResult.timeline.observed = 3; + activeResult.retention.retained = 3; + activeResult.retention.estimated_retained_bytes = 1_536; + activeResult.retention.observed_runtime = 2; + activeResult.coverage.observed_event_families.push("ipc-observation-gap"); const provider: ElectronActiveObservationPort = { identity: () => ({ id: "test-electron-active", @@ -482,6 +503,15 @@ it("runs active Electron scenarios with selected paths and inferred working dire await server.connect(serverTransport); await client.connect(clientTransport); + const advertised = (await client.listTools()).tools.find( + ({ name }) => name === "capture_electron_scenario", + ); + if (advertised?.outputSchema === undefined) + throw new Error("Active Electron output schema was not advertised"); + const validateOutput = new Ajv2020({ + strict: false, + validateFormats: false, + }).compile(z.record(z.string(), z.unknown()).parse(advertised.outputSchema)); const captured = await client.callTool({ name: "capture_electron_scenario", arguments: { @@ -495,10 +525,21 @@ it("runs active Electron scenarios with selected paths and inferred working dire }, }); expect(captured.isError, JSON.stringify(captured)).not.toBe(true); + expect( + validateOutput(captured.structuredContent), + JSON.stringify(validateOutput.errors), + ).toBe(true); expect(captured.structuredContent).toMatchObject({ normalized_result: { application: { process_ownership: "provider-owned" }, ipc: { events: [{ channel: "readiness:echo" }] }, + timeline: { + events: [ + { kind: "main-handler-invocation" }, + { kind: "ipc-observation-gap", channel: "custom" }, + { kind: "ipc-observation-gap", channel: "opaque-chain" }, + ], + }, coverage: { status: "partial_attach", pre_capture_activity: "unavailable", diff --git a/tests/boundary/mcp/providerSelectionMcp.test.ts b/tests/boundary/mcp/providerSelectionMcp.test.ts index 1d6cb7b773..13d69bce44 100644 --- a/tests/boundary/mcp/providerSelectionMcp.test.ts +++ b/tests/boundary/mcp/providerSelectionMcp.test.ts @@ -18,7 +18,9 @@ import { import { AnalysisProviderRegistry } from "../../../src/application/binary/AnalysisProviderRegistry.js"; import { composeBinarySession } from "../../../src/application/binary/BinarySessionComposition.js"; import { createAnalysisProfile } from "../../../src/domain/analysisProfile.js"; -import { ok } from "../../../src/domain/result.js"; +import { err, ok } from "../../../src/domain/result.js"; +import { AnalysisCancelledError } from "../../../src/domain/analysisErrorCore.js"; +import { ProviderCleanupError } from "../../../src/domain/providerCleanupError.js"; import { silentLogger } from "../../../src/logger.js"; import { createServer } from "../../../src/server/createServer.js"; @@ -235,3 +237,81 @@ const structured = (result: CallToolResult): Record => result.isError === true ? parseMcpToolError(result) : z.record(z.string(), z.unknown()).parse(result.structuredContent); + +it("retains attributed mixed profile cleanup through registered MCP calls and partial retry", async () => { + directory = await createTestTempDirectory("rea-profile-completion-mcp-"); + const path = join(directory, "fixture.hop"); + await writeFile(path, "fixture"); + const starts: string[] = []; + const released = new Set(); + const failures = { + alpha: new AnalysisCancelledError("resolve_analysis_profile", { + cause: { caller: "controlled producer cancellation" }, + cleanup: { reason: "alpha close failed", resources: ["alpha-file"] }, + }), + beta: new ProviderCleanupError("beta", ["beta-file"], { + reason: "beta close failed", + }), + }; + const providers = (["alpha", "beta"] as const).map((id) => ({ + ...candidate(id, starts), + resolveAnalysisProfile: async () => err(failures[id]), + closeOwnedResources: async () => { + if (!released.has(id)) return err(failures[id]); + failures[id].resolveCleanup(); + return ok(null); + }, + })); + const session = composeBinarySession(new AnalysisProviderRegistry(providers)); + const server = createServer( + { kind: "session", session }, + { logger: silentLogger }, + ); + const mcp = new Client({ name: "profile-completion", version: "1.0.0" }); + const [clientTransport, serverTransport] = + InMemoryTransport.createLinkedPair(); + resources.push(mcp, server, { close: () => session.close() }); + try { + await server.connect(serverTransport); + await mcp.connect(clientTransport); + const opening = parseMcpToolError( + await mcp.callTool({ + name: "open_binary", + arguments: { path, provider_id: "auto" }, + }), + ); + expect(opening.error).toMatchObject({ + code: "cleanup_incomplete", + details: { + resources: ["alpha-file", "beta-file"], + diagnostics: { + profile_errors: expect.arrayContaining([ + expect.objectContaining({ provider_id: "alpha" }), + expect.objectContaining({ provider_id: "beta" }), + ]), + }, + }, + }); + expect(starts).toEqual([]); + released.add("alpha"); + const closing = parseMcpToolError( + await mcp.callTool({ name: "close_binary", arguments: {} }), + ); + expect(closing.error).toMatchObject({ + code: "cleanup_incomplete", + details: { resources: ["beta-file"] }, + }); + expect(session.status()).toMatchObject({ cleanup_pending: true }); + released.add("beta"); + expect( + (await mcp.callTool({ name: "close_binary", arguments: {} })).isError, + ).not.toBe(true); + expect(session.status()).toMatchObject({ cleanup_pending: false }); + expect(failures.alpha.cleanupIncomplete).toBe(false); + expect(failures.beta.cleanupIncomplete).toBe(false); + } finally { + released.add("alpha"); + released.add("beta"); + await session.close(); + } +}); diff --git a/tests/boundary/providers/kuna/kunaProvider.test.ts b/tests/boundary/providers/kuna/kunaProvider.test.ts index 593b952c76..546ec09cf7 100644 --- a/tests/boundary/providers/kuna/kunaProvider.test.ts +++ b/tests/boundary/providers/kuna/kunaProvider.test.ts @@ -129,19 +129,33 @@ it("waits for an aborted pre-session hash before retrying its late close owner", }); try { const controller = new AbortController(); - const previewWork = binary.previewTarget(fixture.target, { - signal: controller.signal, - }); + let previewSettled = false; + const previewWork = binary + .previewTarget(fixture.target, { + signal: controller.signal, + }) + .then((result) => { + previewSettled = true; + return result; + }); await closeEntered; controller.abort(); + let closeSettled = false; + const closing = binary.close().then((result) => { + closeSettled = true; + return result; + }); + await new Promise((resolve) => setImmediate(resolve)); + expect(previewSettled).toBe(false); + expect(closeSettled).toBe(false); + expect(binary.status()).toMatchObject({ cleanup_pending: true }); + releaseClose(); const preview = await previewWork; expect(preview).toMatchObject({ ok: false, - error: { _tag: "AnalysisCancelledError" }, + error: { _tag: "ProviderAdapterError", cleanupIncomplete: true }, }); - const closing = binary.close(); - releaseClose(); const firstClose = await closing; expect(firstClose).toMatchObject({ ok: false, @@ -152,6 +166,10 @@ it("waits for an aborted pre-session hash before retrying its late close owner", close.mockRestore(); expect(await binary.close()).toEqual({ ok: true, value: null }); expect(binary.status()).toMatchObject({ cleanup_pending: false }); + if (preview.ok || firstClose.ok) + throw new Error("Cleanup unexpectedly succeeded"); + expect(preview.error.cleanupIncomplete).toBe(false); + expect(firstClose.error.cleanupIncomplete).toBe(false); } finally { releaseClose(); close.mockRestore(); diff --git a/tests/composition/analysis-sessions/binarySession.test.ts b/tests/composition/analysis-sessions/binarySession.test.ts index c5412f32cb..2a98223b70 100644 --- a/tests/composition/analysis-sessions/binarySession.test.ts +++ b/tests/composition/analysis-sessions/binarySession.test.ts @@ -1327,9 +1327,10 @@ describe("cancellation during profile resolution", () => { const [first] = await createBinarySessionTargets(); const provider = createCacheProvider([]); const controller = new AbortController(); + const failure = new Error("Profile resolution failed before cancellation"); provider.resolveAnalysisProfile = () => { - controller.abort(); - return Promise.reject(new Error("Profile resolution cancelled")); + controller.abort({ caller: "late profile rejection" }); + return Promise.reject(failure); }; const session = createTestBinarySession(provider); try { @@ -1337,7 +1338,11 @@ describe("cancellation during profile resolution", () => { session.open(first, { signal: controller.signal }), ).resolves.toMatchObject({ ok: false, - error: { _tag: "AnalysisCancelledError", operation: "open_binary" }, + error: { + _tag: "ProviderAdapterError", + operation: "resolve_analysis_profile", + cause: failure, + }, }); // Allow Node to report any rejection abandoned by the cancellation path. await new Promise((resolve) => setImmediate(resolve)); @@ -1347,35 +1352,66 @@ describe("cancellation during profile resolution", () => { } }); - it("cancels profile resolution even when the provider ignores its signal", async () => { + it("joins a signal-ignoring profile producer before cancellation and session close settle", async () => { const [first] = await createBinarySessionTargets(); const provider = createCacheProvider([]); + if (provider.resolveAnalysisProfile === undefined) + throw new Error("Expected the fixture profile resolver"); + const resolveProfile = provider.resolveAnalysisProfile.bind(provider); + const admitted = createDeferred(); + const release = createDeferred(); let observedSignal: AbortSignal | undefined; let created = 0; - provider.resolveAnalysisProfile = (_target, options) => { + provider.resolveAnalysisProfile = async (target, options) => { observedSignal = options?.signal; - return new Promise(() => undefined); + admitted.resolve(); + await release.promise; + return resolveProfile(target); }; provider.createClient = () => { - created += 1; + created++; return new ControllableAnalysisClient(); }; const session = createTestBinarySession(provider); const controller = new AbortController(); - const opening = session.open(first, { signal: controller.signal }); - while (observedSignal === undefined) - await new Promise((resolve) => setImmediate(resolve)); - expect(observedSignal).toBe(controller.signal); - - controller.abort(); - - await expect(opening).resolves.toMatchObject({ - ok: false, - error: { _tag: "AnalysisCancelledError", operation: "open_binary" }, + const reason = { caller: "profile cancellation" }; + let openSettled = false; + const opening = session + .open(first, { signal: controller.signal }) + .then((result) => { + openSettled = true; + return result; + }); + await admitted.promise; + controller.abort(reason); + let closeSettled = false; + const closing = session.close().then((result) => { + closeSettled = true; + return result; }); - expect(created).toBe(0); + try { + await new Promise((resolve) => setImmediate(resolve)); + expect(observedSignal?.reason).toBe(reason); + expect(openSettled).toBe(false); + expect(closeSettled).toBe(false); + expect(session.status()).toMatchObject({ cleanup_pending: true }); + release.resolve(); + await expect(opening).resolves.toMatchObject({ + ok: false, + error: { _tag: "AnalysisCancelledError", cause: reason }, + }); + expect(await closing).toEqual(resultOk(null)); + expect(created).toBe(0); + expect(session.status()).toMatchObject({ cleanup_pending: false }); + } finally { + release.resolve(); + await Promise.all([opening, closing]); + await session.close(); + } }); +}); +describe("cancellation during client transitions", () => { it("cancels a call while it waits for a transition", async () => { const [first] = await createBinarySessionTargets(); const health = createDeferred>(); diff --git a/tests/process-global/ghidraImportSnapshotLifetime.test.ts b/tests/process-global/ghidraImportSnapshotLifetime.test.ts index c4e81fb5b8..66160bab60 100644 --- a/tests/process-global/ghidraImportSnapshotLifetime.test.ts +++ b/tests/process-global/ghidraImportSnapshotLifetime.test.ts @@ -242,6 +242,7 @@ it("waits for mixed finalization and resolves cleanup only after both exact desc fault.blocked.delete(fault.destinations[0]!); await destination.cleanupLease.release(); expect(projected.cleanupIncomplete).toBe(true); + expect(projected.cleanupResources).toEqual([path]); fault.blocked.clear(); await resources.close(); expect(projected.cleanupIncomplete).toBe(false);