diff --git a/docs/reference/terminal-session-correctness-handoff-2026-08-07/finalized-design.html b/docs/reference/terminal-session-correctness-handoff-2026-08-07/finalized-design.html new file mode 100644 index 00000000000..afe8bcbbc86 --- /dev/null +++ b/docs/reference/terminal-session-correctness-handoff-2026-08-07/finalized-design.html @@ -0,0 +1,275 @@ + + +
+ + +Synthesis of the “one protocol, three routes” proposal with a control-plane track it does not cover
+Verdict in one paragraph
+The proposal is correct and its root cause is real — I verified it. Adopt it. But it is a data-plane design: framing, encoding, credit windows, backpressure. The bug that started this work is a control-plane bug, and none of its thirteen steps touch the records involved. The two tracks are independent and can run in parallel. My shipped work is a stopgap on the second track, framed around a symptom rather than a cause, and most of it should be deleted once the real fix lands.
+Nearly every disagreement here dissolves once you separate these.
+Verified: none of the ladder’s core files reference the lease or binding records at all. You could ship all thirteen steps and still get 19 ghost panes on the third reconnect.
+ +This is the strongest finding in either document, and it is correct.
+Orca copied a well-known persistent protocol’s 13-byte frame header, and kept two of its nine message types:
+export const MessageType = { Regular: 1, KeepAlive: 9 } as const
+// dropped: Control · Ack · Disconnect · ReplayRequest · Pause · Resume · None
+ The seven dropped are exactly the ones that make the protocol persistent. The ACK field that survived feeds only unackedTimestamps — a seq→timestamp map read solely by the death timer. Nothing is retained; nothing is replayed.
This reframes the duplication as three teams correctly solving a missing primitive, not carelessness. Fix the transport and the duplication loses its reason to exist. That is a genuine root cause — the framing I shipped earlier (“identity compared with the wrong key”) named a symptom class instead.
+“SSH should behave like local, plus its own reconnection logic.” The codebase already proves it — for the other routes.
+function getProvider(connectionId) {
+ if (!connectionId) return localProvider // local AND daemon — one slot
+ const provider = sshProviders.get(connectionId) // SSH — a parallel registry
+}
+ The daemon is a 3,604-line provider that adds zero branches to the shared spine. SSH gets a second registry, and the tag then leaks outward as a condition ~390 call sites re-derive independently. Your instinct was not a preference — SSH is the only route that broke a pattern the codebase already implements correctly twice.
+ +| Claim | Status | Evidence |
|---|---|---|
| “Identity compared with the wrong key” is the root cause | Symptom | The real cause is one layer down — a transport with no delivery guarantee, and a tag instead of a substitution |
| My change consolidates | Inverted | ~310 of ~405 added lines are SSH-only special case; every new function in the largest file is lease machinery |
| The superseded-keystroke fence protects reconnect | Inert there | Its maps have two writers, both spawn paths; relay reattach never populates them, so the guard permits every reattached PTY |
| “Less code proves a better design” | Too blunt | The right control-plane fix is roughly net-neutral in lines; the payoff is a dissolved failure class, not a smaller diff |
The fence failure is the thesis in miniature
+A guard placed on the shared spine silently does not apply to the one route that does not ride the spine. That is not a coding slip — it is the predicted failure mode of tag-based divergence, and it happened to me while I was writing the fix for it.
+Two questions, one record, keyed for neither.
+Because the lease is keyed by shell rather than pane, “one pane, one live shell” is not expressible — it has to be enforced. My +209 lines are that enforcement: supersession, rank comparison, deferral, rollback. They exist only to compensate for the key.
+ +| Proposal | Verdict | Why |
|---|---|---|
| Merge the lease into the pane binding | Blocked | A lease must be able to name a live shell no pane owns, and must outlive the binding — expiry deletes the binding, then authorizes recovery for 30s |
| Delete the lease; let the host be authoritative | Refuted | See below — tested adversarially and it failed on all five probes |
I verified all three personally. The client-side record is load-bearing precisely because it works offline.
+ +Don’t merge the records and don’t delete one. Give each question the key that answers it.
+Ownership becomes the same shape for local, daemon and SSH — one record per pane per host, with transport-specific payload (lease state and timestamps for SSH, nothing extra for local). That is your principle applied at the record layer. Inventory becomes its own small list, because “a live shell nobody owns” is genuinely a different fact, not a degenerate ownership row.
+The 30-second recovery grant survives as a terminal state on the pane-keyed record, so nothing load-bearing is lost. And the offline consumers keep reading local disk synchronously, exactly as today.
+ +| Claim in the proposal | Assessment |
|---|---|
| “Main is no longer in the data path at all” for a phone on an SSH host | Overstated — the pivot step forwards the host’s loopback socket to a port on your machine. Pairing, E2EE and the broker remain main’s. The ledger duplication collapses; the hop does not. |
| Target is “one wire contract” | Incomplete — no step migrates the local daemon onto it. After the final step you have two protocols, not one. There is an unstated additional step, and it is large. |
| “The method layer is already transport-agnostic” | True, and it volunteers its own counterexample. But 191 remoteness branches sit in the renderer above that boundary — including five separate re-declarations of one predicate — and no step addresses them. |
| Measured encoding cost (wire and CPU) | Honestly reported — it states plainly that the absolute throughput is not a bottleneck, and argues from where the cost is paid instead. Not oversold. |
The tracks touch disjoint files, so they can proceed simultaneously. The control-plane track should land its oracles first, because re-keying a durable record is a migration with a corruption story, and the existing tests pin the current key rather than the intended behaviour.
+ +| Change | Now | After the control-plane track |
|---|---|---|
| Reattach binds, never creates | Holds the line | Keep — correct at any key |
| Pane-keyed lease supersession (+209) | Holds the line | Delete — the key makes it structural |
| Corrupted-lease healing | Needed for existing installs | Folds into the migration |
| “Needs re-establishing” ≠ “expired” | Fixes duplicate agent resume | Keep — an error-taxonomy fix, orthogonal |
| Three-valued liveness | Correct generalization | Keep |
| Superseded-keystroke fence | Inert on reattach | Fix or withdraw — decision needed |
Stated explicitly, because two earlier claims in this program were retracted after checking.
+| Finding | Status |
|---|---|
| Only 2 of 9 message types kept; ACK feeds the death timer only | Personally verified |
| Daemon substitutes via the provider slot; SSH uses a parallel registry | Personally verified |
| Pane maps have two writers, both spawn; fence inert on reattach | Personally verified |
| Relay PTY table is in-memory only; identity RPC has no production caller | Personally verified |
| Lease consulted from a synchronous, offline disk path | Personally verified |
| Method layer transport-agnostic (0 / 0 / 27 references) | Personally verified |
| ~390 branch count, LOC ratios, the 9 disagreement states, ~374 reconciliation lines | Agent-reported, spot-checked |
| Encoding throughput measurements in the proposal | Not re-measured |
| # | Decision |
|---|---|
| 1 | Adopt the data-plane proposal as written? My review found no defect in its reasoning — only two overstated claims and one missing step. It is independent of everything below. |
| 2 | Approve the control-plane track? Re-keying a durable record needs a migration and a corruption story. It is roughly net-neutral in lines and deletes a failure class rather than a file. |
| 3 | The inert fence — populate the pane maps on the reattach path, or withdraw the guard and its test? Shipping it as-is means a test asserting a protection that does not apply where it matters. |
| 4 | The open PRs — merge as a stopgap that keeps the reported bug closed, or hold them and go straight at the control-plane track? |
report.html (evidence for the shipped work). Click any diagram to zoom · scroll to scale · drag to pan.
+