[PR #5675] [MERGED] fix(process): token-check identityless exit-refresh replacements #7031

Closed
opened 2026-11-01 21:44:51 +00:00 by bsakaguchi · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/morluto/rea/pull/5675
Author: @morluto
Created: 10/20/2026
Status: ✅ Merged
Merged: 10/21/2026
Merged by: @morluto

Base: main ← Head: cursor/fix-identityless-exit-refresh-f13d


📝 Commits (10+)

  • 1354d1e fix(process): token-check identityless exit-refresh replacements
  • d9ee1e9 Merge remote-tracking branch 'origin/main' into cursor/fix-identityless-exit-refresh-f13d
  • ba2e97c fix(process): re-read identityless refresh rows when relations change
  • d91c3a1 style: format files oxfmt rejects on main
  • 1f541ed fix(process): keep owned groups found while confirming exit
  • 25eb56c fix(process): reclassify lineage rows when tree membership changes
  • 815bd31 Merge remote-tracking branch 'origin/main' into cursor/fix-identityless-exit-refresh-f13d
  • 581d877 fix(process): re-read cached identityless lineage tokens
  • 6c56261 fix(process): revalidate an owned group that reappears
  • 5920363 fix(process): recheck a helper that changes groups during confirmation

📊 Changes

6 files changed (+1669 additions, -27 deletions)

View changed files

📝 docs/process-capture.md (+27 -1)
📝 docs/testing.md (+17 -1)
📝 src/process/ProcessOwnership.part3.test.ts (+592 -1)
📝 src/process/ProcessOwnership.ts (+260 -14)
📝 src/process/ProcessOwnership.validation.test.ts (+571 -0)
📝 src/process/ProcessOwnershipObservation.ts (+202 -10)

📄 Description

Summary

Fixes #5206 for the identityless process-ownership path that remained after the native run ledger. A launcher-exit refresh no longer reports cleanup complete, or a verified empty lineage, while a replacement row from that refresh is unexamined.

Native lineage retention in #5270 is unchanged and is not duplicated here.

Problem and expected behavior

On an identityless host, cleanup could see launcher PID 100 with a stale command and child 101, then a refresh containing only reparented PID 102 (parent 1, its own group). That row shares no prior PID, group, or ancestry. Cleanup returned { cleaned: true, signaled: false } without reading 102's run token.

The same refresh collapse made lineage publish status: "verified" and descendants: [] when 101's token was unreadable and 102 was never read.

A matching run token on a new group leader is signaled only after that group's members match. A readable foreign or absent token is not signaled. An unreadable token for a still-live new row, or a matching token whose group leader is absent, stays unresolved.

Review follow-ups on this PR:

  • A pre-existing process-table row is not treated as a newcomer. It is read again only when its parent, group, or launcher-tree membership changes, including when an ancestor moves it into the tree.
  • A row whose token was read is read again on the next refresh. Parent and group do not identify a process generation, so a reused PID cannot keep a cached descendant. That includes a descendant whose token already matched in the first pass.
  • Every lineage refresh reads the launcher PID. A missing, unreadable, or unowned launcher is unavailable. After the token matches, launcherIdentityFailure still applies: a same-token launcher whose parent, group, or command no longer matches is unavailable.
  • After a refresh, a previously verified descendant that is absent from that table is omitted. If no refresh happened, the initial verified rows stay.
  • The table that confirms an unreadable refresh row exited is classified too. A token-owned group that appears only there is signaled. A matched member that changes groups on that table is token-checked again and signaled in its new group.
  • A PID whose first token was foreign is read again on the confirmation table. The earlier PID does not identify a process that reused it.
  • A pre-exit PID whose parent or group changes is token-checked. A PID whose token was already read is read again on the next confirmation even when its parent and group return to the pre-exit values. An unchanged pre-existing row that was never token-read stays unread.
  • A group discovered while revalidating a planned group is queued in the same cleanup. A group that reappears after it was already processed is revalidated once; another appearance stays unresolved. Confirmation reads are bounded; a table that keeps producing unreadable replacements stays unresolved.
  • Lineage publishes one row per PID. A refreshed row replaces the initial row, including when a descendant leaves its group and later returns.

Change and scope

The producing boundary is identityless settleLauncherExit and observeOwnedProcessLineage, at the process-table refresh after a launcher identity mismatch or an unreadable descendant token. Hosts with native identity reads keep the existing ledger path.

Follow the contribution scope.
Not applicable: this is a fail-closed correction of existing process cleanup and lineage observation. It does not add target execution, injection, mutation, or protection recovery.

Intentionally not included:

  • #5270 native unresolved-descendant retention.
  • A cross-attempt identityless ledger. A later call that never observes the launcher still cannot reconstruct a descendant absent from that call's snapshots.
  • #4505 Windows CloseHandle ownership. Handle.reset still ignores the close result. Native close-failure and handle-reuse behavior was not verified here, so no retry design is added.
  • #4322's remaining real-Linux cleanup_incomplete reports. The native token-mismatch race was already addressed in 49e67aa1d; this change does not requalify Linux or Windows process exit.

style: format files oxfmt rejects on main only reformats tests/composition/reverseEngineeringAdmission.test.ts and tests/support/cli/cliFixture.ts. oxfmt --check already rejects those files on current main, which fails the repo-wide static check. No behavior change.

Contract and boundary impact

  • Semantic owner and earliest changed stage: identityless settleLauncherExit and observeOwnedProcessLineage, at the process-table refresh after a launcher identity mismatch or an unreadable descendant token.
  • Contribution scope fit and linked maintainer decision: ordinary fix within existing process-cleanup boundaries; not applicable.
  • CLI and MCP/tool-catalog contract: none.
  • Provider, bridge, target-format, or platform compatibility: identityless and custom hosts only. Linux and Darwin system hosts that supply processIdentities keep the native path.
  • Evidence, artifact, provenance, or reconstruction contract: none.
  • Target selection, authority, upstream behavior, and cleanup details (if applicable): no new signal authority. A group is signaled only when its leader and members present the owned run token, which is the existing cleanup rule.
  • Generated metadata (docs/public/product-catalog.json), package, or installation impact (CI generates and validates ignored catalogs and skills/; edit authored instructions in .agents/skills/reverse-engineer-anything/ and never commit build outputs): none.

Expected effects (check known effects; use documentation-only on its own).
Unchecked items do not establish absence; leave uncertainties visible.

  • Read existing files or captured data.
  • Launch or attach to a process.
  • Inject instrumentation or run scripts/code.
  • Change process memory, registers, files, or configuration.
  • Contact network services.
  • Recover protected code or work around protections.
  • Documentation or usability only; no target interaction.
  • Not sure yet; investigation or maintainer guidance needed.

Evidence and regression coverage

  • Tests added or updated: identityless exit-refresh cases in src/process/ProcessOwnership.validation.test.ts and src/process/ProcessOwnership.part3.test.ts.
  • Base reproduction or other evidence: before the fix, those tests observed { cleaned: true, signaled: false } with zero token reads, and lineage status: "verified" with descendants: []. After the fix they require a token read, signal only a token-owned group leader, and refuse a verified empty lineage while a token-owned replacement is outside the tree.
  • User-visible CLI/MCP output (if applicable): none.
  • Remaining proof gaps: no real Linux, Darwin, or Windows process-exit/PID-reuse run. Controlled fixtures do not establish native reparent timing. Non-sweep cleanup can still report cleaned after signaling without waiting for exit; token sweep keeps the existing final family check. A later identityless call that never sees the launcher still has no ledger. A prior PID whose parent and group are unchanged, and that was never token-read, is still not scanned.

For evidence-bearing changes:

  • Observed, derived, and inferred claims remain distinguishable.
  • Artifact identity, source provenance, and failed attempts remain preserved.
  • Unsupported, incomplete, unavailable, or uncertain outcomes remain visible.

Validation performed

  • npm run test:local -- src/process/ProcessOwnership.validation.test.ts src/process/ProcessOwnership.part3.test.ts before the fix — 7 failed: cleanup cleaned: true / signaled: false with no token read, and verified empty lineage.
  • npm run test:local -- src/process/ProcessOwnership.part3.test.ts src/process/ProcessOwnership.observation.test.ts after publishing from the latest refresh table (7943c697d) — 34 passed.
  • npm run format:check — passed on 7943c697d.
  • npm run test:local -- src/process/ProcessOwnership.validation.test.ts src/process/ProcessOwnership.part3.test.ts src/process/ProcessOwnership.observation.test.ts after 931d9fce0 (parent-mismatch fixture; includes the cec10b8b3 main merge) — 80 passed.
  • npm run test:local -- src/process/ProcessOwnership.validation.test.ts src/process/ProcessOwnership.part3.test.ts after 8e1dccd54 — 64 passed.
  • npm run check:fast after 8e1dccd54 — typecheck and lint passed.

Compatibility, safety, and release

  • Breaking changes or migration steps: none.
  • Real Hopper/Ghidra, browser, or OS coverage: not run. Source-level ownership fixtures only.
  • Package or release metadata impact: none.
  • Security, privacy, process, or containment review: signaling stays limited to groups whose leader and members present the owned run token. Unreadable live replacements stay unresolved instead of being treated as exited.

Author readiness

Human owner: @morluto

  • I reviewed the final diff and relevant existing code, understand the change and its edge cases, and take responsibility for the complete PR, including AI-assisted work.
  • I checked the evidence above, confirmed it supports the current change, and recorded meaningful validation gaps.
  • I declare this PR ready for initial maintainer review.

Before merge

  • This PR addresses a concrete problem or an agreed enhancement.
  • Material scope and authority questions are resolved, and any required maintainer scope decision covers the final diff.
  • The PR has one focused outcome and the title follows type(scope): outcome.
  • Related issue is linked, or the reason for not linking one is stated above.
  • Tests cover changed observable behavior and meaningful failure paths.
  • Owning docs, contracts, and generated metadata are updated where needed.
  • User-visible CLI/MCP changes include representative output.
  • I checked the final diff for secrets, unrelated cleanup, and unsupported claims.
Open in Web Open in Cursor 

🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/morluto/rea/pull/5675 **Author:** [@morluto](https://github.com/morluto) **Created:** 10/20/2026 **Status:** ✅ Merged **Merged:** 10/21/2026 **Merged by:** [@morluto](https://github.com/morluto) **Base:** `main` ← **Head:** `cursor/fix-identityless-exit-refresh-f13d` --- ### 📝 Commits (10+) - [`1354d1e`](https://github.com/morluto/rea/commit/1354d1ec6c10f180ad93daab6d8b4b3960f1bef4) fix(process): token-check identityless exit-refresh replacements - [`d9ee1e9`](https://github.com/morluto/rea/commit/d9ee1e983ba6d8a106b51482c99a4e85b4bdafb9) Merge remote-tracking branch 'origin/main' into cursor/fix-identityless-exit-refresh-f13d - [`ba2e97c`](https://github.com/morluto/rea/commit/ba2e97cb92107e8ed99b39c480368b92cbe994f7) fix(process): re-read identityless refresh rows when relations change - [`d91c3a1`](https://github.com/morluto/rea/commit/d91c3a1d5e0c9ce4ff50e90faec81f07b8dda413) style: format files oxfmt rejects on main - [`1f541ed`](https://github.com/morluto/rea/commit/1f541ed0de572e1478aa8b589a2b4f27a990eed8) fix(process): keep owned groups found while confirming exit - [`25eb56c`](https://github.com/morluto/rea/commit/25eb56c3c69e089ca9d3fec84de73c91ed8d4b0d) fix(process): reclassify lineage rows when tree membership changes - [`815bd31`](https://github.com/morluto/rea/commit/815bd31382aee7be9e4f3d58faf64fd6c3efaea1) Merge remote-tracking branch 'origin/main' into cursor/fix-identityless-exit-refresh-f13d - [`581d877`](https://github.com/morluto/rea/commit/581d877541620ad9a74a93951c4c5006dea3277a) fix(process): re-read cached identityless lineage tokens - [`6c56261`](https://github.com/morluto/rea/commit/6c562614fab03abb41d947df95e22f2d4fe6c327) fix(process): revalidate an owned group that reappears - [`5920363`](https://github.com/morluto/rea/commit/592036323261ce53fb4ebc54ed84bb8691b4dceb) fix(process): recheck a helper that changes groups during confirmation ### 📊 Changes **6 files changed** (+1669 additions, -27 deletions) <details> <summary>View changed files</summary> 📝 `docs/process-capture.md` (+27 -1) 📝 `docs/testing.md` (+17 -1) 📝 `src/process/ProcessOwnership.part3.test.ts` (+592 -1) 📝 `src/process/ProcessOwnership.ts` (+260 -14) 📝 `src/process/ProcessOwnership.validation.test.ts` (+571 -0) 📝 `src/process/ProcessOwnershipObservation.ts` (+202 -10) </details> ### 📄 Description <!-- CURSOR_AGENT_PR_BODY_BEGIN --> ## Summary Fixes #5206 for the identityless process-ownership path that remained after the native run ledger. A launcher-exit refresh no longer reports cleanup complete, or a verified empty lineage, while a replacement row from that refresh is unexamined. Native lineage retention in #5270 is unchanged and is not duplicated here. ## Problem and expected behavior On an identityless host, cleanup could see launcher PID 100 with a stale command and child 101, then a refresh containing only reparented PID 102 (parent 1, its own group). That row shares no prior PID, group, or ancestry. Cleanup returned `{ cleaned: true, signaled: false }` without reading 102's run token. The same refresh collapse made lineage publish `status: "verified"` and `descendants: []` when 101's token was unreadable and 102 was never read. A matching run token on a new group leader is signaled only after that group's members match. A readable foreign or absent token is not signaled. An unreadable token for a still-live new row, or a matching token whose group leader is absent, stays unresolved. Review follow-ups on this PR: - A pre-existing process-table row is not treated as a newcomer. It is read again only when its parent, group, or launcher-tree membership changes, including when an ancestor moves it into the tree. - A row whose token was read is read again on the next refresh. Parent and group do not identify a process generation, so a reused PID cannot keep a cached descendant. That includes a descendant whose token already matched in the first pass. - Every lineage refresh reads the launcher PID. A missing, unreadable, or unowned launcher is unavailable. After the token matches, `launcherIdentityFailure` still applies: a same-token launcher whose parent, group, or command no longer matches is unavailable. - After a refresh, a previously verified descendant that is absent from that table is omitted. If no refresh happened, the initial verified rows stay. - The table that confirms an unreadable refresh row exited is classified too. A token-owned group that appears only there is signaled. A matched member that changes groups on that table is token-checked again and signaled in its new group. - A PID whose first token was foreign is read again on the confirmation table. The earlier PID does not identify a process that reused it. - A pre-exit PID whose parent or group changes is token-checked. A PID whose token was already read is read again on the next confirmation even when its parent and group return to the pre-exit values. An unchanged pre-existing row that was never token-read stays unread. - A group discovered while revalidating a planned group is queued in the same cleanup. A group that reappears after it was already processed is revalidated once; another appearance stays unresolved. Confirmation reads are bounded; a table that keeps producing unreadable replacements stays unresolved. - Lineage publishes one row per PID. A refreshed row replaces the initial row, including when a descendant leaves its group and later returns. ## Change and scope The producing boundary is identityless `settleLauncherExit` and `observeOwnedProcessLineage`, at the process-table refresh after a launcher identity mismatch or an unreadable descendant token. Hosts with native identity reads keep the existing ledger path. Follow the [contribution scope](https://github.com/morluto/rea/blob/main/CONTRIBUTING.md#contribution-scope). Not applicable: this is a fail-closed correction of existing process cleanup and lineage observation. It does not add target execution, injection, mutation, or protection recovery. Intentionally not included: - #5270 native unresolved-descendant retention. - A cross-attempt identityless ledger. A later call that never observes the launcher still cannot reconstruct a descendant absent from that call's snapshots. - #4505 Windows `CloseHandle` ownership. `Handle.reset` still ignores the close result. Native close-failure and handle-reuse behavior was not verified here, so no retry design is added. - #4322's remaining real-Linux `cleanup_incomplete` reports. The native token-mismatch race was already addressed in `49e67aa1d`; this change does not requalify Linux or Windows process exit. `style: format files oxfmt rejects on main` only reformats `tests/composition/reverseEngineeringAdmission.test.ts` and `tests/support/cli/cliFixture.ts`. `oxfmt --check` already rejects those files on current main, which fails the repo-wide static check. No behavior change. ## Contract and boundary impact - Semantic owner and earliest changed stage: identityless `settleLauncherExit` and `observeOwnedProcessLineage`, at the process-table refresh after a launcher identity mismatch or an unreadable descendant token. - Contribution scope fit and linked maintainer decision: ordinary fix within existing process-cleanup boundaries; not applicable. - CLI and MCP/tool-catalog contract: none. - Provider, bridge, target-format, or platform compatibility: identityless and custom hosts only. Linux and Darwin system hosts that supply `processIdentities` keep the native path. - Evidence, artifact, provenance, or reconstruction contract: none. - Target selection, authority, upstream behavior, and cleanup details (if applicable): no new signal authority. A group is signaled only when its leader and members present the owned run token, which is the existing cleanup rule. - Generated metadata (`docs/public/product-catalog.json`), package, or installation impact (CI generates and validates ignored catalogs and `skills/`; edit authored instructions in `.agents/skills/reverse-engineer-anything/` and never commit build outputs): none. Expected effects (check known effects; use documentation-only on its own). Unchecked items do not establish absence; leave uncertainties visible. - [x] Read existing files or captured data. - [ ] Launch or attach to a process. - [ ] Inject instrumentation or run scripts/code. - [ ] Change process memory, registers, files, or configuration. - [ ] Contact network services. - [ ] Recover protected code or work around protections. - [ ] Documentation or usability only; no target interaction. - [ ] Not sure yet; investigation or maintainer guidance needed. ## Evidence and regression coverage - Tests added or updated: identityless exit-refresh cases in `src/process/ProcessOwnership.validation.test.ts` and `src/process/ProcessOwnership.part3.test.ts`. - Base reproduction or other evidence: before the fix, those tests observed `{ cleaned: true, signaled: false }` with zero token reads, and lineage `status: "verified"` with `descendants: []`. After the fix they require a token read, signal only a token-owned group leader, and refuse a verified empty lineage while a token-owned replacement is outside the tree. - User-visible CLI/MCP output (if applicable): none. - Remaining proof gaps: no real Linux, Darwin, or Windows process-exit/PID-reuse run. Controlled fixtures do not establish native reparent timing. Non-sweep cleanup can still report cleaned after signaling without waiting for exit; token sweep keeps the existing final family check. A later identityless call that never sees the launcher still has no ledger. A prior PID whose parent and group are unchanged, and that was never token-read, is still not scanned. For evidence-bearing changes: - [x] Observed, derived, and inferred claims remain distinguishable. - [x] Artifact identity, source provenance, and failed attempts remain preserved. - [x] Unsupported, incomplete, unavailable, or uncertain outcomes remain visible. ## Validation performed - `npm run test:local -- src/process/ProcessOwnership.validation.test.ts src/process/ProcessOwnership.part3.test.ts` before the fix — 7 failed: cleanup `cleaned: true` / `signaled: false` with no token read, and verified empty lineage. - `npm run test:local -- src/process/ProcessOwnership.part3.test.ts src/process/ProcessOwnership.observation.test.ts` after publishing from the latest refresh table (`7943c697d`) — 34 passed. - `npm run format:check` — passed on `7943c697d`. - `npm run test:local -- src/process/ProcessOwnership.validation.test.ts src/process/ProcessOwnership.part3.test.ts src/process/ProcessOwnership.observation.test.ts` after `931d9fce0` (parent-mismatch fixture; includes the `cec10b8b3` main merge) — 80 passed. - `npm run test:local -- src/process/ProcessOwnership.validation.test.ts src/process/ProcessOwnership.part3.test.ts` after `8e1dccd54` — 64 passed. - `npm run check:fast` after `8e1dccd54` — typecheck and lint passed. ## Compatibility, safety, and release - Breaking changes or migration steps: none. - Real Hopper/Ghidra, browser, or OS coverage: not run. Source-level ownership fixtures only. - Package or release metadata impact: none. - Security, privacy, process, or containment review: signaling stays limited to groups whose leader and members present the owned run token. Unreadable live replacements stay unresolved instead of being treated as exited. ## Author readiness Human owner: @morluto - [x] I reviewed the final diff and relevant existing code, understand the change and its edge cases, and take responsibility for the complete PR, including AI-assisted work. - [x] I checked the evidence above, confirmed it supports the current change, and recorded meaningful validation gaps. - [x] I declare this PR ready for initial maintainer review. ## Before merge - [x] This PR addresses a concrete problem or an agreed enhancement. - [x] Material scope and authority questions are resolved, and any required maintainer scope decision covers the final diff. - [x] The PR has one focused outcome and the title follows `type(scope): outcome`. - [x] Related issue is linked, or the reason for not linking one is stated above. - [x] Tests cover changed observable behavior and meaningful failure paths. - [x] Owning docs, contracts, and generated metadata are updated where needed. - [ ] User-visible CLI/MCP changes include representative output. - [x] I checked the final diff for secrets, unrelated cleanup, and unsupported claims. <!-- CURSOR_AGENT_PR_BODY_END --> <div><a href="https://cursor.com/agents/bc-f080e52a-9007-5227-8cad-0e1452d1f13d?cursor_ref=pr_footer&cursor_cta=open_in_web"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/assets/images/open-in-web-dark.png"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/assets/images/open-in-web-light.png"><img alt="Open in Web" width="114" height="28" src="https://cursor.com/assets/images/open-in-web-dark.png"></picture></a>&nbsp;<a href="https://cursor.com/background-agent?bcId=bc-f080e52a-9007-5227-8cad-0e1452d1f13d&cursor_ref=pr_footer&cursor_cta=open_in_cursor"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/assets/images/open-in-cursor-dark.png"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/assets/images/open-in-cursor-light.png"><img alt="Open in Cursor" width="131" height="28" src="https://cursor.com/assets/images/open-in-cursor-dark.png"></picture></a>&nbsp;</div> --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
bsakaguchi 2026-11-01 21:44:51 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
morluto/rea#7031
No description provided.