Skip to content

fix: harden Windows updater restart handoff - #8

Merged
eWOOD29 merged 7 commits into
developfrom
work/appdock-v0.2.2-beta2-restart-remediation
Aug 19, 2026
Merged

fix: harden Windows updater restart handoff#8
eWOOD29 merged 7 commits into
developfrom
work/appdock-v0.2.2-beta2-restart-remediation

Conversation

@eWOOD29

@eWOOD29 eWOOD29 commented Aug 14, 2026

Copy link
Copy Markdown
Owner

Status

Gate 1 complete — PR #8 merged from the independently accepted exact snapshot. Gate 2 remains pending.

PR #8 was merged into develop on 2026-08-19 using merge commit bb71453e5b53b7293ed9967d6efb1e748e7944d4. Beta.2 publication, release-asset changes, and any live updater operation remain unauthorized.

Exact snapshot

  • Required base: 6148ecd84cccfbad69e4ac94899311c87745a1e7
  • Base tree: 78e70a24f2869a9719607f26da039745482bf745
  • Prior evidence-failed head: c54be2a629deef3dbd79a2d55a0df140acd17a5b
  • Prior process-identity-failed head: f38a56e2c897eb3094eb37180ae4862a8607f7a0
  • Current frozen head: 9265cc0ad407f27755d6abcf248168b9291689f4
  • Current frozen tree: 03d4c63b9c03ab86532aa40893557cc3ad68c8f9
  • Ancestry against required base: 7 ahead / 0 behind
  • Exact-head CI: 31898113799SUCCESS

Review history and remediation

The original implementation at 6aa3779d… had two production P0 defects: retained exited Windows process objects could be accepted as live startup owners, and failed recovery could relaunch an installation not re-established as trusted. Both were fixed at production remediation head 3e2d60adb12e14c482f1ab270e1b3c4f287565cf and independently closed.

The later evidence head c54be2a… failed because normal Windows/Hermes python can expose a launcher as Popen.pid while a descendant owns the listening socket, and because test-owned Popen objects were not reliably reaped.

Head f38a56e2… corrected launcher-tree attribution and reaping, but a fresh independent review found that full ProcessIdentity equality still treated mutable parent_pid and executable observations as identity. A reparented process with unchanged PID and creation time could therefore be falsely classified as absent. The same review required durable native-boundary controls.

The current append-only remediation at 9265cc0a…:

  • centralizes stable identity as exactly (pid, creation_time);
  • preserves parent PID and executable only as diagnostics;
  • uses stable identity for root anchoring, survivors, and listener-tree membership;
  • rejects stale/reused numeric PIDs with changed creation time;
  • adds deterministic reparenting and listener PID-reuse controls;
  • adds fail-closed controls for Process32FirstW, Process32NextW, OpenProcess, and GetProcessTimes failures;
  • preserves genuine subprocess rollback/finalization proof and explicit process reaping.

A fresh final independent technical re-review of 9265cc0a… explicitly closed both prior P1/P2 technical findings. Its only blocking result was that this PR body and the Project Hub had not yet been reconciled to the new snapshot; this update addresses that record-only gate without changing code.

Exact changed-file scope

Exactly eight files differ from required develop:

  1. appdock.py
  2. scripts/update_helper.py
  3. tests/test_generation9_remediation.py
  4. tests/test_update_real_finalize_rollback_process.py
  5. tests/test_update_real_rollback_process.py
  6. tests/test_update_restart_handoff_regression.py
  7. tests/test_windows_process_tree_deterministic.py
  8. tests/windows_process_tree.py

The current remediation commit changes only the four test/evidence files numbered 4, 5, 7, and 8.

Production code is unchanged after 3e2d60adb12e14c482f1ab270e1b3c4f287565cf:

  • appdock.py: b9aa89cbf4c3d0317ddd54905186536b0522521b
  • scripts/update_helper.py: aa85150b10427f65ac74930eba265887fe8a48e9

Local Windows validation

All process runs used ResourceWarning as an error.

  • Deterministic process-identity/native controls: 9 tests, OK
  • Normal Hermes handoff regression: 8 tests, OK
  • Normal Hermes rollback and finalization real-process tests: PASS, including three repeated iterations in final independent review
  • Direct Python 3.11 focused controls: PASS
  • Direct Python 3.13 focused controls: PASS
  • Full normal Hermes Python 3.11 suite: 222 tests, OK
  • Full direct Python 3.11 suite: 222 tests, OK
  • Full direct Python 3.13 suite: 222 tests, OK
  • No attributable ResourceWarning or subprocess is still running warning
  • Changed-file Ruff, compile, JavaScript 4/4, privacy, and docs: PASS

The normal Hermes launcher topology was independently reproduced: the Popen.pid launcher had a different descendant PID owning the disposable listener; stable identity correctly attributed it to the owned tree and cleanup removed it.

Exact-head CI

Run 31898113799 is bound to 9265cc0ad407f27755d6abcf248168b9291689f4 and completed SUCCESS, with all 10 jobs passing:

  • Windows Python 3.11 and 3.13: 222 tests, OK each
  • Ubuntu Python 3.11 and 3.13: 222 tests, OK each
  • Raw logs: zero ResourceWarning; zero subprocess is still running
  • Compile, JavaScript, privacy/secret, and documentation: PASS
  • Windows portable build/inspection/privacy/install/uninstall: PASS
  • Ubuntu portable build: PASS
  • Windows/Ubuntu package-byte and ZIP-metadata equality: PASS
  • Windows private fixtures on both hosts: PASS
  • Private preview/discovery/ZIP-byte equality: PASS

Informational only: GitHub forced older Node 20 artifact actions onto Node 24; no gate was affected.

Gate 1 merge result

Ethan explicitly authorized Gate 1. Immediately before merge, GitHub still reported the exact accepted head/tree/base, MERGEABLE / CLEAN, and all 10 exact-head checks successful. The guarded merge used GitHub's exact-head match and preserved the seven reviewed commits.

  • Merged at: 2026-08-19T19:58:58Z
  • Merge commit: bb71453e5b53b7293ed9967d6efb1e748e7944d4
  • Merge parents: 6148ecd84cccfbad69e4ac94899311c87745a1e7 and 9265cc0ad407f27755d6abcf248168b9291689f4
  • Resulting develop tree: 03d4c63b9c03ab86532aa40893557cc3ad68c8f9
  • Merge-commit CI: 32296018632SUCCESS, 10/10 jobs

No tag, release, release-asset, updater, live AppDock, Startup, Tailscale, channel, private-state, or retained-evidence operation occurred under Gate 1.

Safety and approval gates

The live machine remains healthy on managed v0.2.1 / Stable. Startup, Tailscale, channel, private state, and retained beta.1 failure evidence remain preserved.

A final different record-aware independent verifier directly read the current PR body and authenticated Google Docs Project Hub, confirmed no branch drift or remaining technical/record blocker, and returned RESULT: PASS.

Next bounded action: prepare and independently verify the beta.2 version commit and release artifacts without publishing them, then return for Gate 2: beta.2 publication authorization. Any live updater run remains a separate Gate 3 authorization.

eWOOD29 and others added 7 commits August 14, 2026 15:31
Use creation-time-bound Toolhelp snapshots to prove candidate and restored-old listener ownership across Windows launcher descendants. Capture the candidate tree again at the real stop boundary and explicitly terminate and reap every test-owned Popen.
Use PID plus creation time as the stable identity key across reparenting, listener membership, and survivor checks. Add deterministic stale-PID and native Toolhelp/query failure controls.
@eWOOD29
eWOOD29 marked this pull request as ready for review August 19, 2026 19:58
@eWOOD29
eWOOD29 merged commit bb71453 into develop Aug 19, 2026
10 checks passed
@eWOOD29

eWOOD29 commented Aug 19, 2026

Copy link
Copy Markdown
Owner Author

Gate 3 live updater validation: failed safely

Correction: the first version of this comment incorrectly attributed a retained v0.2.1 stderr traceback to the new beta.2 attempt. Hash/line-number reconciliation proved that traceback predated Gate 3. The corrected failure boundary is below.

An authorized live validation of the exact published v0.2.2-beta.2 asset exposed a Windows apply-path defect:

  • The v0.2.1 parent selected beta.2, verified the published checksum/inventory, and staged all 37 ZIP members byte-for-byte.
  • Apply returned 202 / restart_pending, stopped the old listener, and launched the exact staged helper.
  • The transaction entered swapping but rolled back inside apply_update before logging update applied or launching the beta.2 candidate.
  • launch_update_helper() starts the helper without a cwd, so it inherits the live AppDock process's working directory inside the installation root.
  • apply_update() then attempts os.replace(install, backup). On Windows, renaming a directory while the process's cwd is inside it fails with WinError 32. The exact condition was independently reproduced in a disposable directory as PermissionError: [WinError 32] The process cannot access the file because it is being used by another process.
  • The inner swap exception is currently collapsed to the generic update failed and was rolled back, leaving the transaction journal without the causal error.

The transactional rollback behaved correctly: transaction d9a72d9e65f2472394762c037e157c59 is durably rolled_back / restore-old, v0.2.1 was restored healthy, and the channel was returned to Stable. Program files, registrations/settings, Startup, and network proxy configuration were verified unchanged. There was no retry or manual restart.

The published beta.2 tag/release/assets remain unchanged, but beta.2 is not live-updater qualified and its candidate restart path was not exercised. A successor needs to launch the helper from a trusted stable cwd outside the installation and staged trees, retain a regression that starts the parent from inside the install root and performs the real Windows swap/finalization path, preserve the causal swap error in evidence, and pass another separately authorized live update gate.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant