|
| 1 | +# Rescue Exact-Parent Discovery Implementation Plan |
| 2 | + |
| 3 | +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. |
| 4 | +
|
| 5 | +**Goal:** Make Rescue route planning discover empty-preview persisted children from Codex's exact parent spawn graph so it never prescribes a colliding spawn name. |
| 6 | + |
| 7 | +**Architecture:** Keep `listCodexThreadSpawnChildren` as the sole discovery interface, but initialize app-server with experimental API capability and scope every page to the exact `parentThreadId`. Retain all current validation and planner authorization; exact-parent rows are a complete occupied-path set, while only rows joined to private stopped-executor provenance are eligible for follow-up. |
| 8 | + |
| 9 | +**Tech Stack:** Node.js 22.13 ESM, built-in `node:test`, Codex 0.147 app-server JSONL protocol, existing Companion/planner seams, verified marketplace builder. |
| 10 | + |
| 11 | +--- |
| 12 | + |
| 13 | +### Task 1: Exact-parent app-server discovery |
| 14 | + |
| 15 | +**Files:** |
| 16 | +- Modify: `tests/helpers/fake-codex-app-server.mjs` |
| 17 | +- Modify: `tests/codex-app-server.test.mjs` |
| 18 | +- Modify: `scripts/lib/codex-app-server.mjs` |
| 19 | + |
| 20 | +- [ ] **Step 1: Write the failing empty-preview relationship test** |
| 21 | + |
| 22 | +Extend the fake server so `thread/list` can distinguish an unscoped global |
| 23 | +query from an exact `parentThreadId` query. Add a test whose direct child has |
| 24 | +`preview: ""`; assert the client returns it and records both: |
| 25 | + |
| 26 | +```js |
| 27 | +assert.deepEqual(calls[0].params.capabilities, { experimentalApi: true }); |
| 28 | +assert.equal(listCall.params.parentThreadId, parentId); |
| 29 | +assert.equal(children[0].agentPath, '/root/zcode_rescue_task'); |
| 30 | +``` |
| 31 | + |
| 32 | +The fake global behavior must omit this row so removing the exact parent filter |
| 33 | +makes the test fail with an empty child list. |
| 34 | + |
| 35 | +- [ ] **Step 2: Run the focused test and verify RED** |
| 36 | + |
| 37 | +Run: |
| 38 | + |
| 39 | +```bash |
| 40 | +node --test --test-name-pattern='empty-preview exact-parent' tests/codex-app-server.test.mjs |
| 41 | +``` |
| 42 | + |
| 43 | +Expected: FAIL because initialize still sends `capabilities: null` and |
| 44 | +`thread/list` omits `parentThreadId`, reproducing the production empty list. |
| 45 | + |
| 46 | +- [ ] **Step 3: Implement the minimal protocol change** |
| 47 | + |
| 48 | +Change only the list operation's app-server initialization/request behavior: |
| 49 | + |
| 50 | +```js |
| 51 | +const LIST_INITIALIZE_PARAMS = { |
| 52 | + clientInfo: INITIALIZE_PARAMS.clientInfo, |
| 53 | + capabilities: { experimentalApi: true }, |
| 54 | +}; |
| 55 | + |
| 56 | +request('thread/list', { |
| 57 | + parentThreadId, |
| 58 | + sourceKinds: ['subAgentThreadSpawn'], |
| 59 | + limit: pageSize, |
| 60 | + sortKey: 'created_at', |
| 61 | + sortDirection: 'desc', |
| 62 | + ...(cursor === null ? {} : { cursor }), |
| 63 | +}); |
| 64 | +``` |
| 65 | + |
| 66 | +Validate the initialize response as a known supporting Codex line (semver |
| 67 | +0.141.0 or newer) before listing, independently of the originator prefix. |
| 68 | +Codex 0.117 silently ignores the unknown parent filter, |
| 69 | +so its empty global response must fail closed rather than authorize a base-name |
| 70 | +spawn. Unknown or unparseable versions also fail closed. |
| 71 | + |
| 72 | +Do not change `thread/read` initialization. Remove the global foreign-row skip |
| 73 | +from the list path: every row returned by an exact-parent query must pass the |
| 74 | +existing full raw child validation for that parent. |
| 75 | + |
| 76 | +- [ ] **Step 4: Add fail-closed exact-parent cases** |
| 77 | + |
| 78 | +Cover missing/foreign/contradictory parent rows, unsupported capability or |
| 79 | +request errors, a Codex 0.117 server that silently ignores the parent field, |
| 80 | +an unparseable initialize version, duplicate IDs/paths across pages, cursor |
| 81 | +bounds, cancellation, and reaping. Update old global-compatibility assertions |
| 82 | +so they no longer claim that unrelated global rows are part of this interface. |
| 83 | + |
| 84 | +- [ ] **Step 5: Run Task 1 verification** |
| 85 | + |
| 86 | +Run: |
| 87 | + |
| 88 | +```bash |
| 89 | +node --test tests/codex-app-server.test.mjs |
| 90 | +npm run lint |
| 91 | +npm run typecheck |
| 92 | +git diff --check |
| 93 | +``` |
| 94 | + |
| 95 | +Expected: all commands exit zero. |
| 96 | + |
| 97 | +- [ ] **Step 6: Commit Task 1** |
| 98 | + |
| 99 | +```bash |
| 100 | +git add scripts/lib/codex-app-server.mjs tests/codex-app-server.test.mjs tests/helpers/fake-codex-app-server.mjs |
| 101 | +git commit -m "fix: discover exact persisted child graph" |
| 102 | +``` |
| 103 | + |
| 104 | +### Task 2: Lock the collision incident and distribution contract |
| 105 | + |
| 106 | +**Files:** |
| 107 | +- Modify: `tests/integration/companion.test.mjs` |
| 108 | +- Modify if required by copied critical bytes: `tests/plugin-contracts.test.mjs` |
| 109 | +- Modify generated snapshot: `marketplace/` |
| 110 | +- Modify: `README.md` |
| 111 | +- Modify: `README.zh-CN.md` |
| 112 | +- Modify: `CHANGELOG.md` |
| 113 | + |
| 114 | +- [ ] **Step 1: Write the failing Companion incident regression** |
| 115 | + |
| 116 | +Add an installed-style prepare scenario where the fake exact-parent app-server |
| 117 | +returns an empty-preview host child at `/root/zcode_rescue_task`, but the plugin |
| 118 | +has no stopped-executor provenance for it. Assert: |
| 119 | + |
| 120 | +```js |
| 121 | +assert.deepEqual(prepared.route, { |
| 122 | + version: 1, |
| 123 | + action: 'spawn', |
| 124 | + taskName: 'zcode_rescue_task_2', |
| 125 | +}); |
| 126 | +``` |
| 127 | + |
| 128 | +Also assert one exact-parent list request, zero follow-up/invoke actions, no |
| 129 | +second prepare, and no private task text in public output. |
| 130 | + |
| 131 | +- [ ] **Step 2: Run the incident regression and verify RED if Task 1 is reverted** |
| 132 | + |
| 133 | +Run the new test green on Task 1, then temporarily execute it against the Task 1 |
| 134 | +parent commit (or revert only the Task 1 production hunk without committing). |
| 135 | +Expected: the old client produces `zcode_rescue_task` or an empty discovery, |
| 136 | +proving the test catches the reported collision. Restore Task 1 immediately. |
| 137 | + |
| 138 | +- [ ] **Step 3: Update release guidance** |
| 139 | + |
| 140 | +Document bilingually that direct-child discovery uses Codex's exact relationship |
| 141 | +query because global listing omits empty-preview restored agents. State that an |
| 142 | +unsupported exact-parent API fails preparation closed and never retries a spawn. |
| 143 | +Record the fix under Unreleased in `CHANGELOG.md`. |
| 144 | + |
| 145 | +- [ ] **Step 4: Regenerate the marketplace from a clean committed source** |
| 146 | + |
| 147 | +Commit source/test/docs changes, temporarily remove only the three untracked |
| 148 | +planning files from the worktree, run the repository's verified marketplace |
| 149 | +builder, restore the planning files, and commit the generated snapshot. Never |
| 150 | +hand-edit generated provenance. |
| 151 | + |
| 152 | +- [ ] **Step 5: Run focused and full verification** |
| 153 | + |
| 154 | +Run: |
| 155 | + |
| 156 | +```bash |
| 157 | +node --test tests/codex-app-server.test.mjs tests/integration/companion.test.mjs |
| 158 | +npm run check |
| 159 | +git diff --check origin/main...HEAD |
| 160 | +``` |
| 161 | + |
| 162 | +The clean-source full gate must show zero failures. Opt-in authenticated Codex |
| 163 | +and real ZCode skips remain documented rather than counted as qualification. |
| 164 | + |
| 165 | +- [ ] **Step 6: Commit Task 2** |
| 166 | + |
| 167 | +```bash |
| 168 | +git add tests/integration/companion.test.mjs tests/plugin-contracts.test.mjs README.md README.zh-CN.md CHANGELOG.md marketplace |
| 169 | +git commit -m "test: cover empty-preview Rescue collision" |
| 170 | +``` |
| 171 | + |
| 172 | +### Task 3: Review, PR update, and CI |
| 173 | + |
| 174 | +**Files:** |
| 175 | +- Review: `origin/main...HEAD` |
| 176 | + |
| 177 | +- [ ] **Step 1: Run independent spec review** |
| 178 | + |
| 179 | +Require explicit confirmation that exact-parent discovery covers the incident, |
| 180 | +host-only rows remain occupancy-only, and unsupported APIs fail closed. |
| 181 | + |
| 182 | +- [ ] **Step 2: Run independent code-quality review** |
| 183 | + |
| 184 | +Review validation bounds, experimental capability scope, pagination, |
| 185 | +cancellation/reaping, fake-server fidelity, and whether tests would fail on the |
| 186 | +old global implementation. Resolve every important finding and re-review. |
| 187 | + |
| 188 | +- [ ] **Step 3: Push the existing PR branch** |
| 189 | + |
| 190 | +```bash |
| 191 | +git push origin fix/rescue-child-recovery |
| 192 | +``` |
| 193 | + |
| 194 | +- [ ] **Step 4: Monitor PR #41 until all checks pass** |
| 195 | + |
| 196 | +Use `gh pr checks --watch 41`. For each failure, read the exact job log, add a |
| 197 | +red regression at the correct seam, fix only the confirmed root cause, rerun |
| 198 | +local verification, and push. Finish only when the PR head matches local HEAD, |
| 199 | +the merge state is clean, and every required matrix job succeeds. |
0 commit comments