Files
360626434d fix(electron): delegate once dispatch to the active runtime (#4605)
* fix(electron): delegate once dispatch to the native runtime

Preserve the selected runtime callback dispatch and metadata semantics, recording actual entry or explicit observation gaps. Add paired cross-runtime controls and verify the real application hook source identity.

Co-authored-by: gensokyo-dev <342580529+gensokyo-dev@users.noreply.github.com>
Co-authored-by: Kaoru0822 <208281322+kaoru0822-kitauji@users.noreply.github.com>

* refactor(electron): remove superseded once dispatch branches

The native once runtime owns callable-method selection. Both remaining observation-helper callers pass actual invocation frames, so remove the obsolete fifth argument and its unreachable call/apply dispatch paths.

Co-authored-by: gensokyo-dev <342580529+gensokyo-dev@users.noreply.github.com>
Co-authored-by: Kaoru0822 <208281322+kaoru0822-kitauji@users.noreply.github.com>

* fix(electron): retain once gaps when registration bypasses on

Keep the unobserved synchronous registration scope with its owning channel and restore the previous scope after errors or reentry. Report the once limitation even when an application getter selects a native alias instead of patched on, while avoiding duplicate same-channel gaps.

Co-authored-by: gensokyo-dev <342580529+gensokyo-dev@users.noreply.github.com>
Co-authored-by: Kaoru0822 <208281322+kaoru0822-kitauji@users.noreply.github.com>

* docs(electron): preserve native once verification cohorts

Record paired runtime controls, actual Joplin verification, and the unresolved startup, cleanup, and observation limits.

Co-authored-by: gensokyo-dev <342580529+gensokyo-dev@users.noreply.github.com>
Co-authored-by: Kaoru0822 <208281322+kaoru0822-kitauji@users.noreply.github.com>

* fix(electron): retain once observation gaps on failed operations

Finalize unobserved registration scopes for both success and failure, preserving native errors and recording failed non-callable dispatch metadata.

Co-authored-by: gensokyo-dev <342580529+gensokyo-dev@users.noreply.github.com>
Co-authored-by: Kaoru0822 <208281322+kaoru0822-kitauji@users.noreply.github.com>

* docs(electron): record failed once operation regressions

Preserve review findings, red/green controls, final runtime comparisons and real application limits without relabeling earlier cohorts.

Co-authored-by: gensokyo-dev <342580529+gensokyo-dev@users.noreply.github.com>
Co-authored-by: Kaoru0822 <208281322+kaoru0822-kitauji@users.noreply.github.com>

---------

Co-authored-by: gensokyo-dev <342580529+gensokyo-dev@users.noreply.github.com>
Co-authored-by: Kaoru0822 <208281322+kaoru0822-kitauji@users.noreply.github.com>
2026-10-17 06:38:13 +08:00

90 lines
3.5 KiB
Python

#!/usr/bin/env python3
"""Serial native/hooked producer controls; not real-application evidence."""
import argparse
import hashlib
import json
import os
from pathlib import Path
import shutil
import subprocess
import tempfile
def sha256(path):
with path.open("rb") as stream:
return hashlib.file_digest(stream, "sha256").hexdigest()
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--hook-root", type=Path, required=True)
parser.add_argument("--node", type=Path, action="append", required=True)
parser.add_argument("--electron", type=Path)
parser.add_argument("--output", type=Path, required=True)
arguments = parser.parse_args()
arguments.output.mkdir(parents=True, exist_ok=True)
packet = Path(tempfile.mkdtemp(prefix="native-once-", dir=arguments.output))
stage = packet / "staged"
module = stage / "node_modules/electron"
module.mkdir(parents=True)
fixture = module / "index.js"
fixture.write_text(
'const { EventEmitter } = require("node:events");\n'
"module.exports = { app: new EventEmitter(), ipcMain: new EventEmitter() };\n"
)
sources = [
arguments.hook_root / "scripts/electron-active-hook.cjs",
arguments.hook_root / "scripts/electron-active-hook-boundaries.cjs",
Path(__file__).with_name("electron-once-runtime.mjs"),
]
pins = {path.name: sha256(path) for path in sources}
pins["electron-fixture"] = sha256(fixture)
for source in sources:
shutil.copyfile(source, stage / source.name)
engines = [(engine, False) for engine in arguments.node]
if arguments.electron is not None:
engines.append((arguments.electron, True))
results = []
for index, (engine, run_as_node) in enumerate(engines):
selected = engine.absolute()
environment = dict(os.environ)
if run_as_node:
environment["ELECTRON_RUN_AS_NODE"] = "1"
execution = subprocess.run(
[str(selected), str(stage / sources[-1].name), str(stage / sources[0].name)],
env=environment,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
timeout=15,
check=False,
)
(packet / f"engine-{index}.stdout").write_bytes(execution.stdout)
(packet / f"engine-{index}.stderr").write_bytes(execution.stderr)
receipt = {
"selected_binary": str(engine),
"absolute_binary": str(selected),
"binary_sha256": sha256(selected),
"electron_run_as_node": run_as_node,
"exit_code": execution.returncode,
"stdout_sha256": hashlib.sha256(execution.stdout).hexdigest(),
"stderr_sha256": hashlib.sha256(execution.stderr).hexdigest(),
}
if execution.returncode == 0:
receipt["result"] = json.loads(execution.stdout)
results.append(receipt)
report = {"hook_root": str(arguments.hook_root), "source_sha256": pins,
"engines": results, "scope": __doc__}
(packet / "report.json").write_text(json.dumps(report, indent=2) + "\n")
if execution.returncode != 0:
raise RuntimeError(f"Engine failed; preserved receipt at {packet}")
print(json.dumps({"packet": str(packet), "engines": [
{"node": row["result"]["node"], "electron": row["result"]["electron"],
"cases": row["result"]["cases"],
"disagreements": row["result"]["disagreements"]} for row in results
]}))
if __name__ == "__main__":
main()