Skip to content

feat: wave-A restart-resilience — identity durability, freshclaude parity, hidden-pane rebind (6 branches, integrated)#536

Merged
danshapiro merged 85 commits into
mainfrom
integration/waveA-preflight
Jul 26, 2026
Merged

feat: wave-A restart-resilience — identity durability, freshclaude parity, hidden-pane rebind (6 branches, integrated)#536
danshapiro merged 85 commits into
mainfrom
integration/waveA-preflight

Conversation

@danshapiro

Copy link
Copy Markdown
Owner

Wave A of the restart-resilience campaign — five lanes merged and verified as a combined tree plus integration fixes.

Lanes (preserved as merge parents):

  1. feat/createrequestid-stabilization — pane createRequestId survives hydrate; REST ingress mints one; persisted in tabs-snapshots (Phase 3 precondition, P1.6)
  2. feat/freshclaude-restart-parity — freshclaude/kilroy panes survive restarts: server-side cliSessionId index, transcript-based snapshot endpoint (replaces 503), resume-in-place attach decision table (lost only on positive denial)
  3. feat/pane-identity-ledger — durable server-side pane-identity ledger (~/.freshell/pane-ledger/): write-before-answer binding rows + pending markers, crash repair, boot scan, cross-process lock, durability-degraded live warning, 3 read paths, opencode locator re-arm (P1.8 + P1.10)
  4. fix/hidden-pane-rebind — hidden panes rebind after reconnect/restart via paced RebindQueue; expensive hydration deferred to reveal; markSessionLost crash guard (P1.11/F8)
  5. fix/tabs-snapshot-defects — snapshot store fail-loud fixes: corrupt-dir eviction exemption, honest persisted/persistReason ack, deterministic device-id (P2.17)

Plus: fix/ws-attach-ready-ordering (attach.ready delivered before queued output frames — salvaged from the withdrawn scrollback lane; scrollback persistence itself was withdrawn by user ruling — provider resume covers it).

Integration commits: A1×A6 clippy fix, A4×#534 test seed, A4×A2 resume-in-place e2e contract, new cross-lane interaction spec (A1×A3, A2×A3), A6×A1 honest-ack seam pin.

Verification: cargo workspace 1841 tests green, clippy -D warnings green, coordinated JS suites green (4050 client), 36/36 e2e twice (zero flakes) incl. restore contract wall (6 expected-fail pins unchanged, re-validated), port-contract drift unchanged vs main (pre-existing, not worsened).

codex and others added 30 commits July 25, 2026 15:37
Load-bearing validation (10 verified, 2 falsified) drove these fixes:
- Task 2 Step 1: invariants capture harness is nested inside cfg(test)
  mod tests, not a direct child -- rewritten as a mechanical hoist.
- Cross-lane coordination: sibling lanes verifiably edit terminal.rs,
  server_messages.rs, invariants.rs, tabs_persist_tests.rs; one adds a
  ServerMessage variant without contract regen -- added a constraint
  note and a mandatory pre-push drift check (rebase + re-regen) in
  Task 8.
- Verified findings folded in: targeted test:vitest bypasses the
  coordinator gate; worktree npm needs no install (resolution falls
  through to the parent checkout); oracle corpora carry no tabs.sync
  traffic; live server confirmed Rust (fixes latent until redeploy);
  Task 6 line-budget contingency.

Ledger: .worktrees/.the-usual-logs/tabs-snapshot-defects/load-bearing-ledger.md

🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier)

Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
…ind plan

Validation pass (11 verified / 2 falsified; ledger in .the-usual-logs):
- A2 falsified: hydration queue has no reconnect pump; Task 5 now clears
  the stale slot, resets hydrationRegisteredRef, registers with
  queueIfStarted:true, and adds a real-queue unit test.
- A5 partially falsified: freshAgent.attach not unconditionally cheap;
  architecture wording corrected (attach pacing is load-bearing).
- A3 verified: dead-session attach contract proven; Task 6 contingency
  replaced with the INVALID_SESSION_ID facts.
- A4 edge: new Task 3 Step 4b hardens markSessionLost against missing
  session entries (empirically reproduced throw).
- A9/A10/A11/A12 refinements: create-serialization rationale, #534 merge
  notes, e2e evidence-validity notes, requestId in create job keys.
…-eyes r1)

Independent cross-model review (fresh eyes, iteration 1) found two
blocking plan defects; both fixed:
- Task 5 Step 1/2: the tabs.sync.ack roundtrip test was written in
  constructed-value style, but the real helper is
  server_roundtrip(wire: &str, type_name: &str) -> ServerMessage.
  Rewritten in the file's wire-string style (frames for both ack
  shapes, type_name "tabs.sync.ack"), with Step 2's expected RED
  state restated as the second frame's schema-conformance failure.
- Task 1 Step 1 + Global Constraints: baseline expectation
  'git log -1 => c491aee' contradicted the branch HEAD (plan docs
  commits sit atop the base). Replaced with a merge-base ancestry
  check, and the Base constraint restated as merge-base c491aee
  with origin/main allowed to advance (Task 8 drift check covers it).

Minor/nit findings from the review are logged, not fixed:
.worktrees/.the-usual-logs/tabs-snapshot-defects/fresheyes-plan.md

🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier)

Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
…alidation

Seven falsified assumptions fixed: CLAUDE_CONFIG_DIR-first candidate store
roots (CLI ignores CLAUDE_HOME), cwd-scoped resume (original cwd from the
transcript + .jsonl-path fallback), real prompt shape + meta-line skips in
the parser fixtures, identity-guarded consumer-exit eviction, single shared
env lock for tests, unconditional tempfile dev-dep, corrected direct-read
rationale. Verified facts and accepted residuals recorded in the plan's
self-review and the validation ledger.
…ed assumptions

Load-bearing validation findings applied:
- Task 1: inheritance is now hydrate-scoped via opt-in flag (updatePaneContent
  resume/repair paths rely on mint-when-absent rotation); adds rotation lock test
  and fresh-agent string type guard
- Task 3: covers respawn_pane as third spawn_terminal_pane caller; documents
  rotation-on-respawn as intentional legacy parity; makes probe_create_request_id
  pub (documented fence exception); named-arg comments for Lane A5 rebase
- Task 4: rebuilt as tests-only tolerance locks in a new test file (strict
  read-path type check would render all device snapshots unreadable on one
  wrong-typed field; Lane A6 co-edits tabs_persist_tests.rs)
- Task 7: sibling-lane drift re-check + EDEV-08 port-parity bookkeeping step
- Non-goals: documented design-intent inferences (reopen/restore fresh identity,
  fresh-agent persist exemption rationale)
…call signatures (fresh-eyes iteration 1)

- Task 6 test 1: require streamId non-null and changed (only terminal.attach.ready
  writes it; terminal.created resets it) so the poll proves the hidden background
  attach, not just the pre-existing census re-create path
- Task 6 test 2: require createRequestId changed (minted only by the .lost recovery,
  which needs the server's INVALID_SESSION_ID round trip) so stale pre-restart Redux
  state can no longer satisfy the recovery poll; drop the sessionId-equality escape
  hatch (fake sidecar resumes with the same sessionId)
- Task 6 test 2: pass required cwd arg to createFreshclaudePane(page, harness, cwd)
- Task 5 queue pin tests: call onActiveTabReady('tab-1', ['tab-1']) matching the real
  two-argument API so the STOP gate cannot trip spuriously
- Update evidence-validity note, Step 2 expectations, and add fresh-eyes self-review
  addendum
…restart-parity plan

Fresh-eyes independent review (iteration 1) found 4 blocking executable-plan
defects; all fixed:

- Task 3 golden fixture: add the missing turn for the object-with-string-content
  prompt (sample line 5, 'cli string content question') so the golden matches
  the 9-line sample and the parser: 6 turns, tool_use at :4, tool_result at :5,
  latestTurnId ':5' (was 5 turns / ':4', making Step 5 'all PASS' unachievable).
- Task 8: replace the wall's leafDurableIdentity (first arm content.sessionId =
  create-time nanoid, never rekeyed for claude) with a local liveDurableIdentity
  reading sessionRef.sessionId ?? resumeSessionId -- the fields the
  sdk.session.init merge actually writes -- in both the pre-restart poll and the
  final same-identity assertion; drop leafDurableIdentity from the copy list.
- Task 9 Step 3: correct the composed-ruler probe grep to the actual test title
  ('THE RULER: all pane types live, one SIGKILL') -- the old pattern matched
  zero tests, making the probe/decision step unexecutable.
- Tasks 1/6: make the spawn_consumer contract consistent -- Task 1's sketch now
  shows the 4-arg signature (new sidecar_session_id eviction identity guard)
  plus the post-loop identity-guarded eviction of the sessions-map entry and
  cli_index entries (ledger A9); Task 6's resume_for_attach call updated to
  pass sidecar_session_id.clone() as the 4th argument. Self-review section 3
  updated to record the now-true consistency claim.
- Task 3 split-test snippet: bind through msg (msg["payload"]["newContent"]
  ["createRequestId"]) instead of unbound 'payload' variable, matching the
  actual test's binding in pane_ops.rs
- Task 4 Step 3: pass both test filters after '--' (cargo test -p freshell-ws
  --lib -- semantically_corrupt corrupt_generation_file); cargo accepts only
  one positional TESTNAME
… module

Pure move: tabs_persist.rs sits at the 1,000-line repo limit and the
upcoming defect fixes need room. No behavior change.

🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier)

Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
…drop alarms loudly

Defect 2 (storage half, campaign P2.17): an oversize generation was
WARN + Ok(()) — indistinguishable from success. Now it is an ERROR-class
invariant alarm (tabs_snapshot_dropped_oversize) and a Skipped outcome.
The 1 MiB cap itself is kept: records are ~1-2KB/tab metadata and the
store's 2.5 GiB hard bound depends on it. Honesty is the fix.

🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier)

Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
… launchRetry gate (fresh-eyes iteration 2)

- Task 1 Step 3: expect HEAD = latest docs(plan) commit and verify baseline
  via 'git merge-base HEAD origin/main' = c491aee (the old 'HEAD at c491aee'
  expectation could never be satisfied once plan commits landed); apply
  'git -C <worktree>' to every chained git invocation
- Task 5 Step 5: correct launchRetry path to test/unit/client/components/
  and require positive confirmation the suite executed (vitest silently drops
  non-matching positional path filters, so the #532 gate passed vacuously)
- Append self-review addendum (iteration 2) covering the edited steps
…e spec skeleton executable

The independent reviewer flagged the Task 8 spec skeleton as non-executable:
- __dirname used in an ESM project; added the donor's fileURLToPath(import.meta.url)
  shim (restore-contract-wall-rust.spec.ts:36-40 convention) plus the node:url import
- helper imports gained the .js extensions the donor uses
- getPaneLayout() called without the required tabId; both tests now read
  tabId via harness.getActiveTabId() after the picker settles and pass it to
  every getPaneLayout call and to sendFreshAgentTurn's 4-arg donor signature
- adaptation notes updated to match the corrected skeleton
Defect 1 (campaign P2.17): .ok()?-swallowed scoring gave an all-corrupt
device dir capturedAt=0 and evicted it FIRST — destroying exactly the
evidence most in need of forensic recovery. Corrupt dirs are now exempt
and loud; eviction only ever removes cleanly-parseable dirs; when that
makes the cap unenforceable the incoming write fails loudly instead.
…pped tabs_persist.rs

Independent review (fresheyes iteration 2) found that appending the 3-line
#[cfg(test)] registration to tabs_persist.rs (999 lines) would push it past
the port/AGENTS.md:81 <=1,000-line cap the plan itself treats as binding.

Fix: register tabs_persist_validation_tests.rs from the end of
tabs_persist_validation.rs (563 -> 566 lines) instead. That file is a #[path]
child of tabs_persist (mod validation, tabs_persist.rs:96-97), so the test
module stays a descendant of tabs_persist and pub(crate)/private items remain
reachable; the test file's import becomes 'use super::super::*;'. Precedent:
tabs_snapshots_tests.rs:987 registers a sibling test file the same way for the
same cap reason. Updated scope fence, File Structure table, and Task 4
Files/Interfaces/Steps 1-2/commit command accordingly. tabs_persist.rs is now
explicitly Do-NOT-modify.
…sistReason)

Defect 2 (wire half, campaign P2.17): the push ack said accepted:true even
when the generation was silently dropped. The ack now carries
persisted:false + reason on any failed/skipped persistence attempt;
fields are omitted on success so pre-change acks stay byte-identical.
accepted semantics unchanged. Verified: the frozen SPA has no
tabs.sync.ack handler and no runtime validation of server messages.

🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier)

Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
…w agrees with the golden fixture

The dictated summarize() sketch returned the first text-OR-thinking item's
text, which for golden turn 1 (items [thinking "pondering", text "first
answer"]) yields "pondering" — contradicting the golden's
"summary": "first answer" and making Task 3 Step 5's 'Expected: all PASS'
unachievable.

Fix: summarize() now prefers the first non-empty text item, falling back to
the first non-empty thinking item, then tool labels. The golden (the
hand-authored cross-language contract pin consumed by Task 4's zod test) is
unchanged; turn 1 now summarizes to "first answer". Doc comment states the
preference rule and cites golden turn 1 as the pinned example.
Task 1 of the freshclaude restart-parity plan (§2.8 item 2), entirely in
crates/freshell-freshagent/src/claude.rs:

- FreshClaudeState.cli_index: durable Claude UUID (cliSessionId from
  sdk.session.init) -> sessions-map key, recorded by the stdout consumer.
- ClaudeSession.sidecar_session_id: the id the sidecar keys the session by;
  handle_send/handle_interrupt now address the sidecar with it (a no-op for
  created sessions, load-bearing for Task 6's resumed-on-attach sessions).
- ClaudeSession.cli_session_id: best-effort copy of the durable id.
- spawn_consumer gains a 4th param (sidecar_session_id) and an identity-
  guarded consumer-exit eviction: when a sidecar dies, its session AND its
  cli_index entries are evicted, but a stale consumer can never evict a
  newer session re-registered under the same map key (ledger A9).
- handle_kill evicts the killed session's cli_index entries; shutdown
  clears the index.
- Fake sidecar now emits sdk.session.init + sdk.status after created, logs
  the whole create request per spawn, and exits on send text "__exit__" so
  tests can exercise the eviction path through the public API.

🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier)

Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
The module declaration at crates/freshell-freshagent/src/lib.rs:40
was changed from 'pub mod claude_snapshot' to 'pub(crate) mod claude_snapshot'
to align with the plan specification that enforces internal-only visibility.

All 4 claude_snapshot tests pass. Clippy and fmt check pass with no violations.

🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier)

Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
…th golden fixture

Extends claude_snapshot.rs with the JSONL->turns parser (legacy
extractChatMessagesFromJsonl contract + ledger A5 real-store fixes:
object-with-string-content prompts kept, isMeta/isSidechain/
isCompactSummary/isVisibleInTranscriptOnly skipped, malformed lines
never fatal), build_claude_snapshot_json (FreshAgentSnapshotSchema-exact,
zod .strict()), ClaudeSnapshotError, and get_claude_snapshot (locate +
read + build; revision = transcript mtime ms, fallback turn count).

Golden fixture claude-snapshot-golden.json is the contract artifact
Task 4 zod-parses; shared sample transcript covers all parse shapes.
Stale #[allow(dead_code)] removed from the now-called locators; a single
allow remains on get_claude_snapshot (wired by Task 5) and the Io
message field (read by Task 5's 500 body).

🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier)

Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
Pins the Rust claude snapshot adapter's output against the FROZEN client's strict zod contract (FreshAgentSnapshotSchema). If the Rust builder drifts from the schema, this test fails before any e2e does, catching cross-language contract violations early.

Golden fixture (generated by crates/freshell-freshagent/src/claude_snapshot.rs) contains a representative sample with all item types (text, thinking, tool_use, tool_result) and verifies:
- Parse succeeds under strict schema
- First turn is user with expected text item
- All turnIds are unique

Generated with [Amplifier](https://github.com/microsoft/amplifier)

Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
danshapiro and others added 29 commits July 25, 2026 21:33
…ure time, create never blocked

🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier)

Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
…es the pending marker

Adds the shared async ledger_resolve_identity resolution hook (awaited
spawn_blocking around resolve_pending + surface_write_failure) and wires
it into the codex adoption bind block after set_meta and BEFORE the
associated broadcast (durable-before-answer). handle_codex_candidate_persisted
becomes async; its dispatch call site gains .await. Rejected candidates
still write nothing (all guards return before the hook).

🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier)

Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
…n P1.10 restore re-arm

Wire the shared ledger_resolve_identity hook (Task 8) into the opencode
and amplifier locator bind blocks: after set_meta, awaited BEFORE the
associated broadcast — binding row first, then the spawn-time pending
marker is deleted (pinned order). Closes triggers (c)/(d) so every
MARKER_MODES resolver also resolves its marker.

Pins P1.10: a restore-created opencode pane lacking identity still arms
the locator (resume None), carries a durable pending marker across the
identity-in-flight window, and resolution lands the binding + deletes
the marker. The amplifier resolve-path test gains the mirror-image
ledger assertions via a real-ledger fixture.

🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier)

Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
…down transcripts yield loud dead_session (P1.8 read 2)

🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier)

Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
…ead 3)

resolve_claude_restore_session_id's rungs 1-2 (in-process, PR #530) now
fall THROUGH to the durable pane ledger when the in-process homes have no
lineage (a fresh boot), while the A13 live-guard stays a HARD stop. The
new rung answers only bound rows and gc_expired tombstones (closed/
superseded rows are never resurrected) and applies an A13-equivalent
live-guard against BOTH identity homes: a live identity-registry owner
and a REST-shaped live registry row (mode claude + resume_session_id +
Running -- invisible to identity and createRequestId lineage).

🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier)

Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
Two Playwright walls (Rust-only, RustServer.restartAbrupt()):
- Wall 1 (SIGKILL-within-5s-of-pane-creation): claude binding row and
  codex pending marker are on disk within 5s of pane creation, the
  claude row carries the same --session-id the fake CLI received, and
  everything survives an abrupt SIGKILL + boot scan (marker preserved,
  nothing quarantined).
- Wall 2 (SIGKILL-inside-locator-window): an opencode pane killed mid
  identity-establishment (row gate never satisfied) leaves a durable
  pending marker that survives the restart boot scan with zero binding
  rows -- fresh-by-race stays distinguishable from fresh-by-intent.

Registered in playwright.config.ts in BOTH RUST_ONLY_SPECS and the
rust-chromium project's testMatch.

🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier)

Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
… doc comments

🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier)

Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
gc_locked held the index Mutex across the entire pass, so a large GC
batch blocked async readers (handshake stamping, restore rung,
ever_bound) for N x ~15-64ms fsyncs. The periodic gc path now snapshots
the work list under the guard, then drops and re-acquires the guard per
item; each per-item helper (gc_marker_locked / gc_row_locked) re-reads
current index state under the re-acquired guard and skips items that no
longer qualify. The write-through invariant is preserved: every file
mutation and its index update still happen under ONE guard acquisition.
boot_scan keeps its single-guard shape (pre-serve, no concurrent
readers) and drives the same shared helpers, so semantics, logs, and
reports are unchanged on both paths.

Also: the 6h GC loop no longer swallows the spawn_blocking JoinError; a
panicked or cancelled GC task now logs pane_ledger_gc_join_failed.

🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier)

Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
…r raises a false durability alarm

resolve_pending writes the binding row FIRST (the durable identity),
then deletes the pending marker. A marker-delete failure used to
propagate as the fn's Err, so ledger_resolve_identity emitted a
durability.degraded frame + ERROR claiming the identity "could not be
durably recorded" -- factually wrong: it WAS recorded, only cleanup
failed, and the boot/GC sweep repairs the stale marker. The failure is
now logged at WARN (pane_ledger_marker_delete_failed_on_resolve, with
terminal_id + error) and the fn returns Ok(()). A binding-write failure
still returns Err -- that is the real durability alarm. New unit test
pins the corrected semantics.

Also: surface_write_failure's doc comment (and the matching invariants
log line) said the frame goes "to attached clients" -- state.broadcast_tx
fans to ALL connected clients; the wording now says so.

🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier)

Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
attach.ready travels the direct conn_rx channel while replay/live output
travels the TERM-09 ConnectionOutputQueue; the connection loop's unbiased
select! could deliver already-queued replay frames BEFORE the attach.ready
enqueued ahead of them, inverting the documented 'attach.ready, then
replay, then live' order. The client arms its pendingReplay window only on
ready (src/lib/terminal-attach-seq-state.ts:143), so pre-ready replay
frames are mis-sequenced and restored scrollback is intermittently lost.
Drain pending direct frames (try_recv) before draining queued output.

Reproduced ~4/10 runs by the seeded-replay ordering assertions landing in
the next commit (tests/scrollback_restore_seed.rs).
… contract

Additive, optional, server->client-only contract change directed by
campaign P2.17 defect 2 (honest oversize-drop signal). Client tolerance
verified: the SPA has no tabs.sync.ack handler and performs no runtime
validation of server messages (shared/ws-protocol.ts design contract).
A new roundtrip test pins schema conformance of both ack shapes.

DEVIATION (recorded): the plan called for a full 'npm run contract:generate'
regen, but shared/ws-protocol.ts on main has PRE-EXISTING drift the frozen
contract never absorbed (amplifier.activity.* + terminal.idle added without
regen; pane.reconcile.* present in the inventory but absent from the TS), so
the port freeze suite is already red on main (8/13) and a full regen breaks
freshell-protocol's inventory tests (they pin the Rust port surface). This
commit therefore applies ONLY the generator's tabs.sync.ack delta, byte-
identical to the generator's output for that entry (verified against an
actual regen). Reconciling the wider TS<->contract drift belongs to the
lanes that introduced it and is reported as follow-up.
Defect 3 (campaign P2.17): device identity came from whatever *.json
read_dir returned first — nondeterministic for half-migrated or
hand-edited dirs. Every generation in a dir must now agree on deviceId;
disagreement is a loud InvalidData error + invariant alarm, never a guess.
Sweep from campaign P2.17: the orphan-.tmp reaper already has three
tests; PERSIST_LOCK poison tolerance and the empty-push ack shape had
none. No semantic changes (empty-push design belongs to kata h9vt).
…fields

Wire-compat pin for the frozen SPA (campaign P2.17 defect 2): the client
has no tabs.sync.ack handler and no runtime validation of server
messages, so the new optional ack fields must be inert — no throw, no
dispatch, no re-push. Test-only; client src/ untouched.
…ight

# Conflicts:
#	test/e2e-browser/playwright.config.ts
…-preflight

# Conflicts:
#	test/e2e-browser/playwright.config.ts
# Conflicts:
#	test/e2e-browser/playwright.config.ts
…clippy)

Lane A6 (tabs-snapshot-defects) made persist_generation return
#[must_use] PersistOutcome; Lane A1's mirrored put() helper in
tabs_persist_validation_tests.rs predates that. Same 'let _ =' shape
A6's own tabs_persist_tests.rs:32 put() helper uses.

🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier)

Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
Cross-lane interaction with main: PR #534 (landed after wave-A lanes
forked) added a layout-membership gate to TerminalView.attachTerminal —
collectAllTerminalIds(panes.layouts) must reference the terminal id or
the attach is suppressed. A4's hidden-rebind test store used empty
layouts, so the gate silently swallowed the background-rebind attach.

Production is unaffected: hidden panes are always present in
panes.layouts, and terminal.created writes the terminalId into the leaf
via updatePaneContent before the hydration queue grants the trigger.
The test now mirrors that reality by seeding a leaf layout for
tab-1/pane-1.

🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier)

Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
…A4xA2)

Cross-lane interaction: freshclaude-restart-parity (A2) adds a
restart-parity attach arm (claude.rs handle_attach decision table) that
resumes an untracked session IN PLACE when the attach carries a durable
UUID and the transcript exists. On the A4 lane alone (pre-parity
fallback #529) the server round-tripped INVALID_SESSION_ID and recovery
was proven by a CHANGED createRequestId via the client's .lost
re-create; in the merged tree that discriminator can never fire for a
claude pane with durable identity.

New combined-tree evidence, verified against a live run:
- pre-restart: poll for the durable UUID on the pane (deterministic
  attach identity)
- post-restart WITHOUT reveal: usable status AND server-side resume
  proof from the sidecar request log (FAKE_CLAUDE_SIDECAR_LOG create
  with resumeSessionId = durable UUID or its transcript .jsonl path --
  the log previously pointed at FAKE_CLAUDE_ARGV_LOG, which the sidecar
  never writes)
- createRequestId asserted STABLE: the .lost re-create fallback must
  not fire (no duplicate-create storm)

🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier)

Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
…herence, A2xA3 dual claude identity stores)

Two seams no single lane's spec covers:

- A1xA3: reload + abrupt restart keeps the pane<->ledger
  createRequestId join coherent. Reload must NOT re-mint (A1's
  contract). The census re-create after restart MAY re-mint (BindingRow
  doc: advisory, latest-observed) -- the pinned contract is CONVERGENCE:
  the ledger row's advisory key equals the pane's current key with no
  stale row left behind to strand lookup_by_create_request_id.

- A2xA3: one abrupt restart restores BOTH claude identity stores --
  the terminal pane's ledger binding (terminal.rs create path) and the
  freshclaude pane's sidecar cliSessionId index (claude.rs resume arm)
  -- with no cross-talk: the fresh-agent UUID never appears in the pane
  ledger, the terminal's session stays bound, nothing quarantined.

🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier)

Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
… the honest ack

Wave-A cross-lane pin: a tabs.sync.push whose pane payloads carry Lane
A1's createRequestId (both mint shapes: 32-hex server mint and 21-char
client nanoid) must ride through Lane A6's honest-persist ack path
unchanged -- accepted, persisted (success ack OMITS persisted/
persistReason on the wire), and the key round-trips into the persisted
generation verbatim.

🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier)

Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
@danshapiro
danshapiro merged commit 2dfbba5 into main Jul 26, 2026
2 checks passed
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.

2 participants