You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: docs/superpowers/plans/2026-09-01-zcode-0165-permission-turn-lifecycle.md
+43Lines changed: 43 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -6,6 +6,8 @@
6
6
7
7
**Architecture:** Add a non-destructive completion observer alongside the existing destructive waiter in the protocol/client layer. Migrate only `executeJob` to that observer and explicitly release its local turn during unconditional teardown; retain compatibility fallbacks for injected test clients that expose only the historical interface.
8
8
9
+
**Follow-up architecture:** Carry that lifecycle boundary through the managed broker. The broker forwards legacy completion without consuming its upstream turn, and the managed client acknowledges the executor's authoritative cleanup through an exact-owner broker control request. Also accept the observed bounded `requestedAt` field on 0.16.5 permission requests.
10
+
9
11
**Tech Stack:** Node.js 22.13, ECMAScript modules, `node:test`, the existing JSON-RPC protocol client and job executor.
10
12
11
13
---
@@ -17,6 +19,10 @@
17
19
- Modify `scripts/lib/review.mjs`: use non-destructive observation for `legacyWake` and release the local turn during teardown.
- Modify `scripts/zcode-broker.mjs`: retain the upstream route through legacy wake and release it only on exact managed-client acknowledgement or existing authoritative cleanup.
If there are no generated tracked changes, skip this commit.
225
+
226
+
### Task 4: Reproduce the managed-broker and 0.16.5 schema failures
227
+
228
+
**Files:**
229
+
- Test: `tests/zcode-client.test.mjs`
230
+
- Test fixture: `tests/fixtures/fake-zcode-cli.mjs`
231
+
- Test: `tests/process-zcode.test.mjs`
232
+
233
+
-[ ] Add an opt-in captured fixture permission request after the false legacy completion and before the authoritative v4 terminal. Include the real numeric `requestedAt` field.
234
+
-[ ] Add a direct protocol test proving the current validator rejects that captured field.
235
+
-[ ] Add a managed broker test proving the early completion currently removes the route and the later permission is not allowed.
236
+
-[ ] Run only these tests and record deterministic RED output before changing production code.
237
+
238
+
### Task 5: Retain and explicitly acknowledge managed broker turns
239
+
240
+
**Files:**
241
+
- Modify: `scripts/lib/zcode-protocol.mjs`
242
+
- Modify: `scripts/lib/zcode-client.mjs`
243
+
- Modify: `scripts/zcode-broker.mjs`
244
+
- Test: `tests/zcode-client.test.mjs`
245
+
- Test: `tests/process-zcode.test.mjs`
246
+
247
+
-[ ] Accept only bounded optional `requestedAt` permission timestamps.
248
+
-[ ] Replace broker-side destructive terminal consumption with forwarding/non-destructive observation that retains the exact upstream turn.
249
+
-[ ] Add an authenticated exact-session broker acknowledgement used by managed `releaseTurn()`. It locally releases the upstream turn, settles exact pending permissions, removes the route, and is idempotent for the same completed downstream cleanup without stopping the ZCode session or changing durable ownership.
250
+
-[ ] Reject foreign, stale, malformed, and conflicting acknowledgements without touching a newer turn.
251
+
-[ ] Run focused tests to GREEN, self-review, and commit.
252
+
253
+
### Task 6: Lock the production fresh/resumed path and re-verify release readiness
254
+
255
+
**Files:**
256
+
- Modify: `tests/integration/companion.test.mjs`
257
+
- Modify if required by generated parity checks: marketplace snapshot through the existing builder only
258
+
259
+
-[ ] Extend the captured 0.16.5 managed integration to require an allowed permission response after the false legacy completion for both fresh and resumed sends.
260
+
-[ ] Run focused protocol, broker, executor, and companion tests.
261
+
-[ ] Run `npm run check`, review the complete follow-up diff, refresh the marketplace snapshot if required, and complete spec then quality review before pushing PR #52.
Copy file name to clipboardExpand all lines: docs/superpowers/specs/2026-09-01-zcode-0165-permission-turn-lifecycle-design.md
+24-1Lines changed: 24 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -6,17 +6,25 @@ The executor already treats ZCode 0.16.5 `state.updated` notifications with reas
6
6
7
7
ZCode 0.16.5 can emit that legacy notification immediately after admission, then request tool permission while the real runtime turn is still active. Once the legacy waiter deletes the turn, the permission request fails the exact-active-turn check and is returned as JSON-RPC `-32000` (`ZCODE_PERMISSION_SESSION_INVALID`). This is independent of the configured Codex permission mode: the incident job already carried `bypassPermissions`.
8
8
9
+
### Follow-up incident: managed broker boundary
10
+
11
+
The first implementation preserved the executor-side broker client turn, but production uses two protocol layers. The managed broker still registered `consumeTerminalsWith()` on its upstream appserver connection. That callback consumed the same admission-time legacy notification, settled permission routes, and deleted the broker's active session before the executor could perform authoritative v4/snapshot reconciliation. The resumed incident therefore failed before `decidePermission()` was reached even though the persisted snapshot was `bypassPermissions` and the installed plugin matched the fix byte-for-byte.
12
+
13
+
ZCode 0.16.5 permission requests also carry a numeric `requestedAt` field. The captured fixture omitted it and the strict protocol validator rejected it. Compatibility must accept this bounded transport metadata while continuing to reject unknown fields.
14
+
9
15
## Goals
10
16
11
17
- Keep the exact turn armed after an admission-time legacy completion wake so later permission requests can be evaluated normally.
12
18
- Preserve the current destructive behavior of `waitForCompletion()` for every existing caller.
13
19
- Clear local turn state when `executeJob` reaches its real terminal or cleanup boundary.
20
+
- Keep the managed broker's upstream turn and permission route alive until the executor confirms its authoritative terminal/cleanup boundary.
21
+
- Accept the observed 0.16.5 `requestedAt` permission-request field.
14
22
- Cover the captured 0.16.5 event ordering with deterministic regression tests.
15
23
16
24
## Non-goals
17
25
18
26
- No change to `decidePermission`, permission snapshots, risk mapping, or offered-response validation.
19
-
- No new appserver flag, permission field, broker authorization rule, or persisted schema.
27
+
- No new appserver flag, permission policy, broker ownership rule, or persisted schema.
20
28
- No reinterpretation of legacy completion as authoritative success or failure.
21
29
- No broad refactor of the protocol or executor lifecycle.
22
30
@@ -38,10 +46,23 @@ Because the wake no longer consumes local state, `executeJob` must explicitly re
38
46
39
47
The cleanup operation is local and idempotent. It must not send `session/stop`, alter durable job state, or replace the existing cancellation paths. Successful terminal, provider failure, remote interruption, local abort, timeout, and error cleanup all converge on the same local turn release when a session was created or resumed.
40
48
49
+
### Managed broker terminal acknowledgement
50
+
51
+
For a direct appserver client, `releaseTurn()` remains local. For an authenticated managed-broker client, release first sends a narrow broker-only acknowledgement for the exact session turn, then clears the downstream local turn. The broker validates session ownership and the exact active socket/token, locally releases the corresponding upstream protocol turn, settles only that turn's pending permission tasks, and removes its active route. It must not call `session/stop`, release durable session ownership, or accept a stale/foreign acknowledgement.
52
+
53
+
The broker no longer treats a legacy `prompt_completed` notification as authority to delete its route. It forwards the validated notification to the active client as a wake and retains the upstream turn. Existing authoritative stop, owner-release, disconnect, protocol-close, and explicit terminal-acknowledgement paths remain responsible for cleanup.
54
+
55
+
### Permission request compatibility
56
+
57
+
The strict request validator accepts optional `requestedAt` only when it is a finite, non-negative safe integer timestamp. All existing required fields, risk levels, option validation, exact offered-response validation, and unknown-field rejection remain unchanged. The captured 0.16.5 fixture includes this field so broker and direct-client tests exercise the production request shape.
58
+
41
59
### Safety invariants
42
60
43
61
- A validated early legacy completion leaves `turnState(sessionId) === 'armed'`.
44
62
- A later permission request for that session reaches the configured handler and returns one offered response.
63
+
- A managed broker retains the exact route after an early legacy completion and forwards a later permission request.
64
+
- Only the exact owning client can acknowledge and release the broker turn.
65
+
- A 0.16.5 request with bounded `requestedAt` is accepted; malformed timestamps and unknown fields remain rejected.
- Ordinary `waitForCompletion()` still leaves `turnState(sessionId) === null` immediately after resolution.
47
68
- Permission policy and durable job outcomes are unchanged.
@@ -53,6 +74,8 @@ The cleanup operation is local and idempotent. It must not send `session/stop`,
53
74
3. Add an executor regression with captured 0.16.5 ordering: admission, early legacy wake, later permission request, authoritative terminal, successful result, and final local turn cleanup.
54
75
4. Exercise cleanup on a non-success path so an observer cannot leave an armed turn behind.
55
76
5. Run focused tests, then `npm run check` before review and PR creation.
77
+
6. Add a broker-level captured-order regression where permission follows the false legacy completion, and assert explicit acknowledgement releases both protocol layers.
78
+
7. Exercise fresh and resumed managed execution with the same captured ordering.
0 commit comments