Skip to content

fix(resume): harden session-resolve — off-thread lookups, correct match ranking, provider-health channel, dialog guards - #586

Merged
danshapiro merged 6 commits into
mainfrom
feat/resume-hardening
Jul 30, 2026
Merged

fix(resume): harden session-resolve — off-thread lookups, correct match ranking, provider-health channel, dialog guards#586
danshapiro merged 6 commits into
mainfrom
feat/resume-hardening

Conversation

@danshapiro

Copy link
Copy Markdown
Owner

Summary

Best-of-both integration following the accidental parallel implementation of the resume-button spec (#583 merged; a fully-reviewed sibling implementation existed on feat/resume-button). An independent deep-dive comparison (committed methodology, defects cited file:line) found three live defects in the merged version — all fixed here by transplanting the sibling's engine into #583's shell:

  1. Event-loop stall — opencode by-id lookups ran sync sqlite on the main thread (~15s/candidate on a locked DB, uncapped). Now: worker-thread with 15s timeout, shape gates, per-request budget.
  2. Wrong-session hazard — indexed prefix matches outranked unindexed exact matches and auto-resumed. Now: per-token exact-index → exact-fallback → prefix ordering, ses_ ids case-sensitive, candidate cap.
  3. Silent provider failures — locator/DB errors read as "not found". Now: degraded status with per-provider errors, unsearched-provider reporting, manual retry UX, homeDir prefill.

Plus: dialog race/stale-input guards + focus trap; claude locator multi-root + subagents + typed error propagation; canonical provider-list derivation.

Commits (6, RED-first TDD)

  • c2689a7 fix(server): harden claude transcript locator — multi-root, subagents, error propagation
  • bd28331 fix(client): graft safety guards into Resume dialog — stale-response, edit-invalidation, cwd gating, focus trap
  • 0d3d54a fix(server): run opencode by-id resume lookups off-thread with shape gates and a per-request budget
  • 78f7a1b fix(server): port reference match-ranking semantics into resume resolve core
  • b128174 feat(resume): provider-health channel — broken providers surface as degraded, never "not found"
  • 4ee5f31 refactor(server): derive KNOWN_RESUME_PROVIDERS from shared canonical list

Test plan

Generated with Amplifier

danshapiro and others added 6 commits July 29, 2026 22:13
…, error propagation

Replace the merged locator with the reference implementation's robust
version, preserving the exported interface (ClaudeTranscriptHit with
sourceFile; locateClaudeTranscript now accepts one root or several):

- multi-root: accepts string | readonly string[] so secondary claude
  project roots are searchable without caller changes
- subagent layout: second pass probes
  <project>/<parent>/subagents/<id>.jsonl for index-missed child sessions
  (direct layout still wins on pass ordering)
- error contract: only ENOENT/ENOTDIR read as a miss; every other fs
  failure (EACCES, EIO, ...) propagates as the typed
  ClaudeTranscriptLocatorError — the health seam for the provider-health
  lane. Provider failure must never read as "not found".

Tests: RED first (multi-root, subagent, and both EACCES cases fail
against the old locator), then green — 12/12 locator integration tests,
14/14 sessions-resolve-router, typecheck clean.

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

Co-Authored-By: Amplifier <[email protected]>
… edit-invalidation, cwd gating, focus trap

Port the reference implementation's (feat/resume-button) client-side
guards into the merged Resume dialog, keeping the merged dialog's phase
machine, warming bounded auto-retry, zod contract parse, and testids:

- Stale-response guard: a resolve sequence ref; only the LATEST request
  may mutate state, so an out-of-order response can never override
  results or auto-resume the WRONG session. Closing also invalidates
  in-flight resolves.
- Edit-invalidation: typing bumps the sequence and resets the phase, so
  stale "Resume anyway"/disambiguation actions can never act on old
  tokens.
- cwd-required gating: auto-resume only for a single match WITH a
  recorded cwd on a ready response; cwd-less matches render in the
  match list with a required editable working-directory field (blank
  blocks with an inline error); "Resume anyway" is disabled while the
  cwd field is blank. '~' keeps the documented server-home convention.
- Never-auto-resume on non-ready responses, with a clearly marked
  DEGRADED SEAM comment where status==='degraded' handling plugs in
  once the provider-health lane extends the contract.
- Modal a11y per the repo confirm-modal convention: Tab focus trap,
  focus restore on close, background scroll lock.

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

Co-Authored-By: Amplifier <[email protected]>
…gates and a per-request budget

Port the reference implementation's (feat/resume-button) session-lookup
engine onto the merged Resume feature, fixing the event-loop-stall
defect: the resolve endpoint's opencode exact-id fallback ran
OpencodeProvider.resolveOpencodeSessionRoots — synchronous
DatabaseSync with a 5 s busy timeout x 3 attempts ON THE MAIN THREAD,
once per ses_-shaped candidate, uncapped — so a locked opencode DB
could stall the whole server ~15 s per candidate.

- opencode-by-id-{query,runner}.ts + opencode-by-id.worker.ts: exact-id
  sqlite lookup (child + archived sessions included) executed in a
  short-lived worker thread with a 500 ms busy timeout, 15 s hard
  timeout, sentinel-guarded auto-run (Vitest thread-pool safe),
  full message-shape validation, and no double-settle. Errors REJECT
  (provider unavailable != not found).
- resolve-fallbacks.ts: full-id shape gates enforced BEFORE the
  per-request budget (wrong-shape tokens are free no-op misses),
  FALLBACK_BUDGET_PER_REQUEST=2 per fallback, and metadata-store
  sessionType so freshclaude/kilroy/freshopencode sessions reopen
  through their recorded runtime instead of a hardcoded provider
  default.
- resolve-session.ts: the exact-id fallback loop now consumes
  budget-wrapped ResolveFallbacks; fallback failures are logged and
  never reject the request (degraded/providerErrors channel is
  follow-up work). Matching core, contract, and match cap unchanged.
- sessions-router.ts + index.ts: wire buildResolveFallbacks(providers,
  { sessionMetadataStore, locateClaudeTranscript }) in place of the
  main-thread resolveOpencodeSessionRoots fallback.
- opencode.ts: make getDatabasePath() public for the fallback builder;
  resolveOpencodeSessionRoots itself is untouched (WS reconcile).

Tests: ported by-id query suite (4), by-id runner suite incl. a REAL
worker integration test (13), resolve-fallbacks suite (12); resolve
router integration updated to the fallback seam plus new pins for
shape gating, per-request budget freshness, and locked-DB
non-failure (17).

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

Co-Authored-By: Amplifier <[email protected]>
…ve core

Replace the merged resolver's ranking with the reference implementation's
(feat/resume-button session-resolver) correct per-token ordering, fixing the
live wrong-session defect:

- Per candidate token, in priority order: exact index hit, then exact-id
  fallback lookups, then and only then prefix matches. A prefix match can
  never outrank any exact resolution of the same or higher-priority token
  (an indexed lookalike no longer beats the exact id you pasted whose
  session just isn't indexed yet).
- Case-fold only UUID/hex-family tokens; ses_ base62 ids now match
  case-SENSITIVELY (folding could resolve the wrong session).
- Exact ids still reach subagent/child sessions; prefix DISCOVERY now
  excludes them (disambiguation noise).
- sessionType defaults to the provider name when the index has none.
- Parser: restrict xxx_ extraction to known id families (arbitrary
  snake_case identifiers no longer rank first) and cap candidates at
  MAX_RESUME_CANDIDATES=8 (server work budget).

Kept from the merged shape: shared zod contract, (provider,id) dedupe with
most-recent survivor, per-request withRequestBudget fallback wrapping, and
the catch-and-log fallback seam (degraded channel is the provider-health
follow-up; typed ClaudeTranscriptLocatorError still propagates to the seam).

Ported/adapted the reference's resolver test suite (20 tests; 5 were RED
against the old core) plus parser family/cap pins.

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

Co-Authored-By: Amplifier <[email protected]>
…egraded, never "not found"

Port the reference implementation's provider-health surface (feat/resume-button,
comparison item 3) onto the merged resolve pipeline, closing the silent-empty-
results incident class: a locked opencode DB, unreadable claude/codex/amplifier
store, or failed index scan now answers "something's wrong", never "session not
found".

Contract (shared/resume-resolve-contract.ts, additive + backward-tolerant):
- status gains 'degraded'; providerErrors (per-provider {provider, code?,
  message?}); unsearchedProviders (settings-disabled, so absence never
  overclaims); homeDir (concrete cwd prefill instead of the '~' sentinel).

Server:
- resolve-session.ts: the per-token fallback catch now collects providerErrors
  (typed ClaudeTranscriptLocatorError errno in .code) while resolution
  CONTINUES; fallbacks iterate as [provider, fallback] PAIRS so identity
  travels with the entry, never its position; any error makes the result
  degraded — even with matches, because a failed higher-priority exact search
  may have hidden the right session.
- session-indexer.ts: scanFailures channel (recorded per attempt, cleared on
  success, pruned for disabled providers so a failed-then-disabled provider
  can't trap Retry), isReady() (first refresh completed), requestRefresh()
  fire-and-forget wrapper.
- providers/{claude,codex,amplifier}.ts: root-level guards — root ABSENCE is a
  legitimate empty result (ENOENT/ENOTDIR), any other root error REJECTS so
  the indexer records a scan failure.
- sessions-router.ts: readiness = startupState OR'd with the indexer signal;
  merges fallback errors with enabled-provider scan failures (fallback errors
  win the dedupe); reports disabled providers as unsearched; fire-and-forgets
  requestRefresh() on degraded so Retry converges; returns homeDir.
- index.ts: claude transcript locator rewired to claudeProvider.getSessionRoots()
  (multi-root activation, evaluated per lookup).

Client (ResumeSessionDialog):
- 'degraded' renders an explicit "could not be searched" state with per-provider
  details and MANUAL retry (no warming auto-retry budget), never auto-resumes,
  and still offers surviving matches for manual confirmation.
- cwd prefilled from homeDir when the user hasn't edited the field ('~' still
  accepted for back-compat); no-match copy names disabled/unsearched providers.

Tests: contract extension suite, provider-root-failures suite (ported from the
reference), indexer health suite, router degraded/unsearched/homeDir/readiness
cases, dialog degraded/prefill cases; the two lane-4 seam tests extended to
assert providerErrors content. RED verified against pre-change tree (29 new
failures), now green; full coordinated sweep green (unit 4466, integration 478).

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

Co-Authored-By: Amplifier <[email protected]>
… list

Review follow-up (Minor): the router-local literal duplicated
DEFAULT_ENABLED_CLI_PROVIDERS from shared/coding-cli-defaults.ts — a drift
hazard when a fifth provider lands. Import the canonical constant instead;
behavior byte-identical today (verified: sessions-resolve-router 25/25,
tsc server clean).

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

Co-Authored-By: Amplifier <[email protected]>
@danshapiro
danshapiro merged commit f903e8a into main Jul 30, 2026
3 checks passed
pull Bot pushed a commit to HinchK/freshell that referenced this pull request Jul 31, 2026
…erminal identity (danshapiro#584), deck visual tweaks (danshapiro#585)

Conflict resolution (crates/freshell-freshagent/src/terminal_tabs.rs,
settle_gated_create): kept BOTH sides, ordered spawn-gate acquire (D-C-R,
ours) BEFORE the PIN2 claude prespawn durable binding (theirs) — a gate
rejection must not leave a stale prespawn ledger row, so the durable write
happens only after a permit is secured.

Post-merge semantic fix: pinned FRESHELL_CODEX_MANAGED_LAUNCH=0 in
rest_locator_identity.rs::rest_created_codex_pane_binds_identity_row_and_ledger
(new in danshapiro#584, written pre-flip; exercises the plain-CLI codex path with a
sleeper fake that cannot serve an app-server sidecar).

Pre-merge analysis recorded no redundancy between the danshapiro#582 review-hardening
(d9705fd) and the remote delta; danshapiro#586 touches resume-resolve-contract.ts,
not session-contract.ts — CrashTrace work composes cleanly.

Generated with Amplifier

Co-Authored-By: Amplifier <[email protected]>
danshapiro added a commit that referenced this pull request Jul 31, 2026
…legacy contract (SYNC-06)

Ports the resume-resolve matching core against the retired pre-#586
Node resolve contract, not the hardened in-tree implementation.
Hardened #586 behavior (per-token exact->fallback->prefix ranking,
provider-health/degraded status, budgeted shape-gated fallbacks)
landed in later commits on this branch.
danshapiro added a commit that referenced this pull request Jul 31, 2026
…586 legacy contract (SYNC-06)

Adds the resolve route against the retired pre-#586 Node contract,
not the hardened in-tree implementation. The hardened #586 wire
contract (degraded status, providerErrors/unsearchedProviders/homeDir)
landed in later commits on this branch.
danshapiro added a commit that referenced this pull request Jul 31, 2026
…ion 1)

Reviewer (independent cross-model, gpt): FAILED with 16 major issues.
Fixes, all verified against the worktree:
- Task 1: spec doc is already committed — verify (clean tree), no re-commit
- Spec doc: contract bullet updated to hardened #586 response shape
  (degraded/providerErrors/unsearchedProviders/homeDir, never-silent)
- set -o pipefail on every piped cargo gate (Tasks 1/6/7); workspace
  passed-count now summed from per-binary 'test result:' lines
- Task 3: explicit 23-test mapping to the Node core suite (every it()
  title -> verbatim/carryover/new Rust test)
- Task 4: real locked-DB test (second connection holds BEGIN EXCLUSIVE,
  busy_timeout 500ms -> Err)
- Task 5: coding_cli_enabled_providers is async (tokio RwLock, mirrors
  get(); session_overrides' std Mutex is not the pattern); scan-recovery
  test polls via wait_until (stale-while-revalidate refresh is detached)
- Task 6: settings fixtures corrected to .freshell/config.json wrapped
  {version,settings}; JoinError (panicked resolver) -> explicit 500,
  recorded deviation (Node: unhandled rejection), never ready-empty;
  checked claude locator uses Node's authoritative subagents layout +
  transcript_cwd_checked propagating non-absence errors; fallbacks
  ALWAYS wired (Node does not settings-gate fallbacks); homeDir from OS
  user home (HOME/USERPROFILE), never resolve_home()/FRESHELL_HOME
- Task 7: e2e claims scoped to what resume-button.spec.ts proves;
  explicit --project=legacy-chromium --project=rust-chromium filters

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

Co-Authored-By: Amplifier <[email protected]>
danshapiro added a commit that referenced this pull request Jul 31, 2026
…586)

Fresh-eyes review found the Rust port targeted the retired pre-#586
resolve-session.ts rather than the hardened in-tree implementation, and
corrects the false parity claims made in commits 9a59749 and 7ac205d.

- resume_input.rs: add MAX_RESUME_CANDIDATES=8 work-budget cap (truncate
  before hint derivation, mirroring TS) and replace the generic
  [a-z]{2,10}_ prefixed-id regex with the hardened 9-family pattern
  (ses|sess|session|thread|thr|run|msg|task|amp)_[0-9A-Za-z]{8,64}
- resume_resolve.rs: per-token exact -> exact-id-fallback -> prefix
  ordering (prefix never outranks an exact fallback hit); case-insensitive
  comparison only for uuid/hex-like tokens (ses_ base62 now
  case-SENSITIVE); exclude subagent sessions from prefix discovery;
  always emit session_type (defaults to provider)
- module docs rewritten honestly: the hardened response surface
  (degraded/providerErrors/homeDir, warming default) is NOT yet ported,
  tracked in docs/plans/2026-07-30-rust-resolve-parity-hardened.md
  Tasks 3/5/6; stale resolve-session.ts line refs removed
- parser-cases.json: fix abc_12345678 case (no candidates under hardened
  semantics), add 10-uuid -> first-8 cap case (32 cases total)
- resume-input-parser.test.ts: wire the fixture into the TS unit suite so
  the cross-language anti-drift claim is actually true (fixture now
  consumed by BOTH the TS test and the Rust parity test)
- resume_resolve.rs tests: honest module doc (matching-semantics subset,
  not a 1:1 mirror), rewritten ordering/case tests, new tests for
  fallback-beats-prefix, subagent exclusion, ses_ case sensitivity

cargo test -p freshell-sessions: 207 passed; -p freshell-server: 430
passed; clippy -D warnings clean; vitest resume-input-parser: 70 passed
danshapiro added a commit that referenced this pull request Jul 31, 2026
…rules, provider-health channel, budgeted fallbacks

Per-token exact→fallback→prefix order (a prefix match never outranks an
exact resolution); ses_ ids case-SENSITIVE, uuid/hex case-folded;
subagents excluded from prefix discovery; sessionType defaults to the
provider name; fallbacks are shape-gated + budgeted (2/request/provider)
and their failures surface as degraded + providerErrors — never a silent
empty not-found. Mirrors test/unit/server/coding-cli/resolve-session.test.ts.

Refactors atop the matching semantics already landed in 13a3428 and
7109a6e (ordering, case gating, subagent rules, shape gates, budgets):
adds the Degraded status, ProviderFailure/ResumeResolveProviderError
channel (fallback closures now Result — provider failure ≠ miss),
OpencodeByIdHit with title/lastActivityAt + the sessionType overlay on
fallback hits, and ResumeResolveOutcome replacing ResumeResolveResponse
(the wire response moves to resolve.rs; LegacyWire keeps today's
{status,matches,hint} shape until Task 6). Module divergence docs
trimmed to what genuinely remains (Tasks 4/5/6).
(SYNC-06)

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

Co-Authored-By: Amplifier <[email protected]>
danshapiro added a commit that referenced this pull request Jul 31, 2026
…ers/homeDir, scan-failure merge, degraded fire-and-forget refresh

POST /api/sessions/resolve now emits the full #586 contract; production
fallbacks report failures (checked claude locator, propagating opencode
by-id) instead of swallowing them; degraded-path proven on the wire
(broken store -> degraded + providerErrors, never silent not-found).
Async hygiene verified: all blocking fallback IO runs inside the
endpoint's spawn_blocking; work bounded by cap-8 candidates x budget-2
fallbacks. (SYNC-06)

Recorded deviation: a JoinError (panicked resolver) now answers an
explicit 500 instead of a fabricated ready-empty — Node has no defined
behavior there (unhandled rejection, no response), and presenting an
unsearchable state as a healthy not-found is the incident class this
plan closes.

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

Co-Authored-By: Amplifier <[email protected]>
danshapiro added a commit that referenced this pull request Jul 31, 2026
…nces; reconcile spec divergences; add commit-message errata

Fixes three blocking documentation findings from independent review:

1. 2026-07-30-rust-resolve-parity-hardened.md: the 10 Task-4 and 5 Task-6
   test blocks had comment-only bodies that would pass vacuously if pasted
   literally. Each body now keeps its behavioral spec as comments and ends
   in unimplemented!() (a literal paste fails loudly), with post-execution
   notes pointing at the real verifiers that landed under the same names in
   crates/freshell-sessions/tests/opencode_row_by_id.rs and the resolve.rs
   test module. The self-review placeholder-scan line is corrected to admit
   the fifteen placeholders it previously denied. Trailing whitespace at the
   flagged lines removed.

2. 2026-07-29-rust-resolve-parity-spec.md: Requirement 1's blanket
   "identical validation and error shapes" claim is corrected to name the
   three deliberate, ledgered divergences recorded in resolve.rs's
   "Accepted deviations" (malformed/scalar JSON body 400 shape, 2 MB vs
   1 MB body limit, non-POST 405 routing); the parser reference now
   describes all nine prefixed-id families with 8-64-char suffixes instead
   of only ses_.

3. 2026-07-14-rust-tauri-parity-completion-checklist.md: SYNC-06 gains an
   ERRATA entry recording that commits 9a59749 and 7ac205d overclaimed
   Node parity (retired pre-#586 contract), that 13a3428/f50da2e05
   documented and disabled it, and that 08cc7df/48b67f838 delivered the
   hardened parity. History is pushed and not rewritten; the note is the
   accurate record.

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

Co-Authored-By: Amplifier <[email protected]>
danshapiro added a commit that referenced this pull request Jul 31, 2026
…fallback; archive superseded plan; fix SYNC-06 checkbox contradiction

Three blocking documentation findings from independent cross-model review:

1. 2026-07-30-rust-resolve-parity-hardened.md: the Task 6 Step 3b wiring
   instructed a CLAUDE_HOME -> HOME-only fallback for the exact-id claude
   locator (and a raw HOME||USERPROFILE homeDir chain that accepted an
   empty HOME verbatim), which cannot achieve native-Windows parity where
   Tauri leaves HOME unset. The wiring snippets now describe the LANDED
   behavior — resolve_claude_exact_id_fallback() and resolve_wire_home_dir()
   in main.rs, both resolving homes via session_directory::provider_home()
   (HOME then USERPROFILE, empty treated as unset) — and a new verifier
   list names the landed tests, including the USERPROFILE-only exact-id
   fallback test
   claude_exact_id_fallback_finds_transcript_in_a_userprofile_only_environment.

2. 2026-07-29-rust-resolve-parity.md: added an unmissable ARCHIVED /
   SUPERSEDED / DO NOT EXECUTE banner at the top. The plan implements the
   retired pre-#586 OpenCode parent-chain resolver contract, its parity
   reference (lines ~62-72) describes the obsolete pre-#586 behavior, and
   its expected test counts (~line 3103) are stale; re-executing it would
   reintroduce defects resolved by the hardened plan. Body unchanged —
   the banner is the fix.

3. 2026-07-29-rust-resolve-parity-spec.md: Requirement 8 said "Mark
   SYNC-06 done" while the same spec declares the required PW-TAURI-WIN
   validation out of scope and the implementation plan requires the
   checkbox stay unchecked. Resolved in favor of the plan/checklist: the
   spec now instructs recording the evidence as a PARTIAL bullet with the
   checkbox left UNCHECKED, and the Goal is reworded to "deliver the
   Rust-server half" so no instruction contradicts the out-of-scope
   declaration.

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

Co-Authored-By: Amplifier <[email protected]>
danshapiro added a commit that referenced this pull request Jul 31, 2026
…false Node-parity commit messages

The two SYNC-06 commits that claimed "Node-parity semantics"/"Node-parity
behavior" were reworded in place (message-only rewrite, identical trees) to
state the retired pre-#586 legacy contract they actually implemented. Every
descendant SHA changed; update the checklist's commit references to the new
history and record the correction in the errata, including that
origin/feat/rust-resolve-parity still holds the pre-correction history.

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

Co-Authored-By: Amplifier <[email protected]>
danshapiro added a commit that referenced this pull request Jul 31, 2026
…lve contract (SYNC-06) (#592)

* docs: add implementation plan for rust-resolve-parity

* docs(plan): harden rust-resolve-parity plan from load-bearing validation

Falsified and fixed: zod 400 details literals rewritten to probed zod
4.3.6 wire output (A2/V2); opencode by-id fallback replaced with a
bug-for-bug port of Node's parent-chain walk incl. legacy-schema and
truthy-directory quirks (A3/V3); resolve snapshot now filtered through
deleted session overrides matching Node and the Rust sidebar (A4/A14/
V4/V7). Recorded accepted deviations (transport/405, locator deltas,
index membership edges), added e2e npm-ci precondition, aligned the
checklist step with the SYNC-05 PARTIAL convention.

* docs(plan): fix two blocking fresheyes findings in rust-resolve-parity plan

- Task 1 fixture: restore the existing suite's bare-v7-uuid id-shape hint
  case (the only bare-id coverage of the version==='7' -> codex deriveHint
  branch); fixture is now 31 cases, all >=30 guards and expected pass
  counts (TS 32, Task 9 evidence/gates) bumped to match.
- Task 6 Step 1: un-gate ONLY SessionMetadataStore::get_all; keep get
  behind #[cfg(test)] (no production caller in a binary crate, so
  un-gating it would trip dead_code and fail the clippy -D warnings
  gates in Task 6 Step 6 / Task 9 Step 1). Replace get_all's stale
  'Test-only today' doc line with the SYNC-06 production-read comment.

* docs(plan): fix three blocking fresheyes findings in rust-resolve-parity plan

- Task 1: add Step 0 node-deps guard (test -d node_modules || npm ci) so
  Task 1 Step 3's vitest run (tsx is an uninstalled devDependency), Task 6
  Step 5's cargo test (safe11 fixture imports 'ws'), and Task 7 Step 5's
  typecheck/vitest can pass on the fresh worktree; idempotent guards
  repeated inline at Task 6 Step 5 and Task 7 Step 5.
- Task 2: replace hex_tokens.sort_by(|a, b| b.len().cmp(&a.len())) with
  sort_by_key(Reverse(len)) — the former trips clippy's warn-by-default
  unnecessary_sort_by under the plan's -D warnings gates; still a stable
  sort, behavior identical.
- Task 6: replace snapshot.as_ref().map(|s| s.as_slice()) with
  snapshot.as_deref() — the former trips clippy's warn-by-default
  option_as_ref_deref under -D warnings.

* test(shared): extract resume-input parser cases into cross-language fixture (SYNC-06)

* feat(sessions): port resume-input parser to Rust, pinned by shared fixture (SYNC-06)

* feat(sessions): opencode by-id directory lookup for resume-resolve fallback (SYNC-06)

* feat(freshagent): export transcript_cwd for resume-resolve claude fallback (SYNC-06)

* chore(cargo): record regex dependency in lockfile (SYNC-06)

* fix(freshagent): drop unrelated dead helper that leaked into transcript_cwd export commit (SYNC-06)

* feat(sessions): resume-resolve matching core for the retired pre-#586 legacy contract (SYNC-06)

Ports the resume-resolve matching core against the retired pre-#586
Node resolve contract, not the hardened in-tree implementation.
Hardened #586 behavior (per-token exact->fallback->prefix ranking,
provider-health/degraded status, budgeted shape-gated fallbacks)
landed in later commits on this branch.

* feat(server): POST /api/sessions/resolve implementing the retired pre-#586 legacy contract (SYNC-06)

Adds the resolve route against the retired pre-#586 Node contract,
not the hardened in-tree implementation. The hardened #586 wire
contract (degraded status, providerErrors/unsearchedProviders/homeDir)
landed in later commits on this branch.

* feat(server): declare sessionResolve feature flag from the Rust server (SYNC-06)

* test(e2e): run resume-button spec on both server kinds (SYNC-06 PW-RUST)

* docs: record SYNC-06 rust resolve parity evidence in completion checklist

* docs(server): correct stale test-only note on session metadata store (SYNC-06)

* docs: add implementation plan for rust-resolve-parity-hardened

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

Co-Authored-By: Amplifier <[email protected]>

* docs(plan): load-bearing validation — correct Node core suite mirror count to 23 tests

Load-bearing pass over the hardened-parity plan: 24 assumptions verified
with run/inspect evidence (worktree rebase state, hardened parser regex +
cap-8 + fixture-probe run proving the plan's 5 new cases byte-exact and
exactly one stale case failing, router merge semantics + wire field order,
RESOLVE_MATCH_CAP=20, budget-2 shape-gated fallbacks, ses_ case
sensitivity, by-id row query, Rust drift + API surface for Tasks 3-6,
cargo baseline green at the rebased HEAD). One falsification: the Node
core suite has 23 it() tests, not 25 — Task 3 Step 1 corrected. Ledger:
.worktrees/.the-usual-logs/rust-resolve-parity-hardened/load-bearing-ledger.md

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

Co-Authored-By: Amplifier <[email protected]>

* docs(plan): fix 16 blocking issues from fresheyes plan review (iteration 1)

Reviewer (independent cross-model, gpt): FAILED with 16 major issues.
Fixes, all verified against the worktree:
- Task 1: spec doc is already committed — verify (clean tree), no re-commit
- Spec doc: contract bullet updated to hardened #586 response shape
  (degraded/providerErrors/unsearchedProviders/homeDir, never-silent)
- set -o pipefail on every piped cargo gate (Tasks 1/6/7); workspace
  passed-count now summed from per-binary 'test result:' lines
- Task 3: explicit 23-test mapping to the Node core suite (every it()
  title -> verbatim/carryover/new Rust test)
- Task 4: real locked-DB test (second connection holds BEGIN EXCLUSIVE,
  busy_timeout 500ms -> Err)
- Task 5: coding_cli_enabled_providers is async (tokio RwLock, mirrors
  get(); session_overrides' std Mutex is not the pattern); scan-recovery
  test polls via wait_until (stale-while-revalidate refresh is detached)
- Task 6: settings fixtures corrected to .freshell/config.json wrapped
  {version,settings}; JoinError (panicked resolver) -> explicit 500,
  recorded deviation (Node: unhandled rejection), never ready-empty;
  checked claude locator uses Node's authoritative subagents layout +
  transcript_cwd_checked propagating non-absence errors; fallbacks
  ALWAYS wired (Node does not settings-gate fallbacks); homeDir from OS
  user home (HOME/USERPROFILE), never resolve_home()/FRESHELL_HOME
- Task 7: e2e claims scoped to what resume-button.spec.ts proves;
  explicit --project=legacy-chromium --project=rust-chromium filters

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

Co-Authored-By: Amplifier <[email protected]>

* docs(plan): fix 10 blocking issues from fresheyes plan review (iteration 2)

- Test 1 of the 23-mirror: assert the id-shape claude hint (Some), not
  hint==None — a v4 UUID always derives the hint on both parsers
- Scan-failure tracking extended to file-backed sources (claude/codex/
  amplifier) via SessionSource::discover_checked; false 'recorded
  deviation' removed; file-backed failure test added
- Enabled-provider set read BEFORE core resolution and used to FILTER
  the snapshot (Node index-gating parity); fallbacks stay ungated;
  disabled-indexed-miss + disabled-fallback-hit endpoint tests added
- Rust default enabledProviders aligned to Node's four-provider
  DEFAULT_ENABLED_CLI_PROVIDERS (adds amplifier) instead of pinning the
  current three-provider default; settings.rs added to Task 5
- Checked claude locator treats NotFound OR NotADirectory as expected
  absence (ENOENT||ENOTDIR parity); ENOTDIR test added
- transcript_cwd_checked bounded to the first 64 KiB (CWD_SCAN_BYTES
  parity); beyond-64KiB test added
- OpencodeByIdError preserves rusqlite SQLITE_* codes via
  sqlite_error_code(); production closure forwards them; missing/
  corrupt/locked tests assert codes — degraded wire test now
  production-true
- degraded request_refresh convergence endpoint test added
- Permission test #[cfg(unix)]-gated with a root/CAP_DAC_OVERRIDE skip
  probe
- Task 7 Step 2 now RUNS the shared contract test and the client suite
  (ResumeSessionDialog degraded/retry/homeDir coverage) as evidence

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

Co-Authored-By: Amplifier <[email protected]>

* fix(resolve): align Rust resume-resolve with hardened Node semantics (#586)

Fresh-eyes review found the Rust port targeted the retired pre-#586
resolve-session.ts rather than the hardened in-tree implementation, and
corrects the false parity claims made in commits 9a597494f and 7ac205d5d.

- resume_input.rs: add MAX_RESUME_CANDIDATES=8 work-budget cap (truncate
  before hint derivation, mirroring TS) and replace the generic
  [a-z]{2,10}_ prefixed-id regex with the hardened 9-family pattern
  (ses|sess|session|thread|thr|run|msg|task|amp)_[0-9A-Za-z]{8,64}
- resume_resolve.rs: per-token exact -> exact-id-fallback -> prefix
  ordering (prefix never outranks an exact fallback hit); case-insensitive
  comparison only for uuid/hex-like tokens (ses_ base62 now
  case-SENSITIVE); exclude subagent sessions from prefix discovery;
  always emit session_type (defaults to provider)
- module docs rewritten honestly: the hardened response surface
  (degraded/providerErrors/homeDir, warming default) is NOT yet ported,
  tracked in docs/plans/2026-07-30-rust-resolve-parity-hardened.md
  Tasks 3/5/6; stale resolve-session.ts line refs removed
- parser-cases.json: fix abc_12345678 case (no candidates under hardened
  semantics), add 10-uuid -> first-8 cap case (32 cases total)
- resume-input-parser.test.ts: wire the fixture into the TS unit suite so
  the cross-language anti-drift claim is actually true (fixture now
  consumed by BOTH the TS test and the Rust parity test)
- resume_resolve.rs tests: honest module doc (matching-semantics subset,
  not a 1:1 mirror), rewritten ordering/case tests, new tests for
  fallback-beats-prefix, subagent exclusion, ses_ case sensitivity

cargo test -p freshell-sessions: 207 passed; -p freshell-server: 430
passed; clippy -D warnings clean; vitest resume-input-parser: 70 passed

* fix(resolve): roll back sessionResolve capability and de-matrix resume e2e until hardened port lands

Fresh-eyes review flagged shipping the resolve endpoint under
sessionResolve: true while its response surface still lacks the hardened
degraded/providerErrors/homeDir fields the shared dialog consumes.

- main.rs: declare sessionResolve: false (held until the hardened
  response surface from docs/plans/2026-07-30-rust-resolve-parity-hardened.md
  Tasks 3/5/6 is ported); flag-pinning tests updated
- playwright.config.ts: remove resume-button.spec.ts from MATRIX_SPECS
  (chromium-only again) so CI does not gate on the Rust matrix run
  until the flag is genuinely earned

* docs(checklist): correct false SYNC-06 evidence

Fresh-eyes review found the SYNC-06 entry asserted evidence that is
false at HEAD: dual fixture consumption (the TS test did not consume the
fixture until now), a '14 passed, unchanged' Node suite count (the
in-tree hardened suite runs 25 tests), and evidence commit c38422a0
which is not an ancestor of HEAD.

Entry rewritten truthfully: PARTIAL/REOPENED — hardened matching
semantics (ordering, case gating, subagent exclusion, work budget,
sessionType emission) landed; hardened response surface deferred to
the hardened plan Tasks 3/5/6; sessionResolve rolled back to false and
resume-button.spec.ts de-matrixed until it lands

* docs(plan): fix 6 blocking issues from fresheyes plan review (iteration 3)

Cross-model reviewer (openai, zero-context) found 6 blocking plan defects;
all fixed, facts verified against the worktree (reports/iter3-facts.md):

1. OpenCode wire parity: Node's production worker boundary strips .code
   (worker serializes {name,message} only; runner rebuilds Error without
   code), so the wire is message-only. Production closure now maps
   OpencodeByIdError to ProviderFailure{code: None}, logs the internal
   SQLITE_* code via tracing; core + endpoint tests assert code-ABSENT
   wire shape; Task 4 doc + Self-Review 1b reworded (internal code =
   logging/message fidelity, not wire).
2. Claude locator roots: locate_transcript_checked now takes
   projects_roots as a parameter (Node's locator takes projectsDir);
   caller resolves CLAUDE_HOME||$HOME/.claude + /projects (Node
   getSessionRoots parity; claude_home_candidates explicitly forbidden
   - CLAUDE_CONFIG_DIR root excluded); global two-pass
   direct-then-subagent ordering with precedence test (f).
3. errno_code preserves the raw OS errno symbolically via libc
   (EPERM stays EPERM; EIO/EMFILE/ENOTDIR/etc. covered; unknown => None;
   non-unix kind-based fallback) with unit tests incl. EPERM!=EACCES.
4. 64 KiB cwd scan parses EVERY segment including the final one (Node
   split('\n') has no discard-tail rule); new tests (g) complete final
   line without trailing newline parses, (h) boundary-straddling
   fragment skipped without error.
5. settings_store.rs legacy migration DEFAULTS widens to four providers
   (adds amplifier, mirroring Node DEFAULT_ENABLED_CLI_PROVIDERS);
   legacy [claude,codex] migration test added.
6. Checked-locator tests hermetic by construction (injected temp roots,
   zero env mutation; CLAUDE_ENV_LOCK + EnvVarsRestore pattern cited if
   ever needed).

Reviewer's minor (unstructured scan-failure logging) and nit (---###
separator) intentionally not fixed per step policy (blocking-only).

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

Co-Authored-By: Amplifier <[email protected]>

* docs(plan): separate Task 6 heading from preceding horizontal rule

The '---### Task 6:' run-on line broke markdown heading parsing and the
execute stage's task-brief extraction (Task 5 briefs swallowed Task 6;
Task 6 briefs came back empty). Formatting only, no content change.

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

Co-Authored-By: Amplifier <[email protected]>

* fix(resolve): port Node's budgeted, shape-gated exact-id fallbacks + bounded cwd read

The previous port's fallback gates diverged from Node's resolve-fallbacks.ts
in two wire-observable ways:

- The opencode fallback gate was only `kind == PrefixedId &&
  starts_with("ses_")` (any 8-64 base62 chars), while Node's
  FALLBACK_ID_SHAPES gate is the FULL id `^ses_[0-9a-zA-Z]{26}$`. On a
  legacy-schema opencode DB (universal-HIT by-id lookup), any wrong-length
  `ses_*` token produced a FALSE exact hit where Node answers a miss with
  zero work.
- There was NO per-request fallback budget: up to MAX_RESUME_CANDIDATES = 8
  fallback invocations per request (each claude one a full projects-dir
  scan), while Node's withRequestBudget caps each fallback at
  FALLBACK_BUDGET_PER_REQUEST = 2 REAL invocations per request. A third
  fallback-requiring token resolved on Rust but is not-found on Node.

Mirror Node exactly (resolve-fallbacks.ts withRequestBudget semantics):

- Full-id shape gates: `^[0-9a-fA-F]{8}-…-[0-9a-fA-F]{12}$` for the claude
  fallback (replacing the equivalent-but-implicit `kind == Uuid` check with
  Node's explicit shape), `^ses_[0-9a-zA-Z]{26}$` for opencode.
- FALLBACK_BUDGET_PER_REQUEST = 2, ONE counter PER FALLBACK (Node's
  budgeted() closes over a separate `used` per key), fresh per request,
  checked shape-FIRST budget-SECOND (Node: "order is load-bearing" — a
  wrong-shape token must not consume budget), consumed by the real
  invocation itself, hit or miss.
- The inline comment that falsely claimed the old gates matched "Node's
  budgeted fallbacks" now describes the actual mirrored semantics.

Also bound the claude fallback's transcript cwd read (main.rs wiring) to
Node's CWD_SCAN_BYTES = 64 KiB: new transcript_cwd_bounded() reads at most
the first 64 KiB, splits on newline, and parses every segment INCLUDING the
final unterminated one (Node's head.split('\n') loop has no
discard-the-tail rule) — the hardened plan (Task 3a/3b,
docs/plans/2026-07-30-rust-resolve-parity-hardened.md:1807) explicitly
forbids mirroring the unbounded BufRead::lines() transcript_cwd for
resolve, where one request could scan a multi-GB transcript.
transcript_cwd itself is untouched for its other consumers; the
error-PROPAGATING checked variant stays deferred to plan Task 3.

Tests (crates/freshell-sessions/tests/resume_resolve.rs +
claude_snapshot.rs unit tests):
- wrong-length/wrong-charset ses_* tokens never reach the opencode
  fallback (panicking lookup) and yield ready-empty,
- the claude gate accepts a full UUID in any hex case and rejects bare
  hex-prefix tokens,
- a third fallback-requiring ses_ token in one request is budget-gated
  (exactly 2 real lookups, not-found even though the third would hit),
- shape-gated tokens do not consume budget (a valid id after two
  wrong-shape ones still resolves),
- budgets are tracked per provider (two opencode misses do not exhaust
  the claude budget),
- transcript_cwd_bounded never scans past the 64 KiB prefix and still
  parses a complete unterminated final line.

* docs(resolve): enumerate ALL remaining Node divergences; fix stale refs and SYNC-06 overstatement

The KNOWN DIVERGENCE / NOT YET PORTED disclosures in the two resolve
module docs claimed the only remaining delta was the response surface
(degraded/providerErrors/unsearchedProviders/homeDir/readiness merge)
while claiming to port 'the HARDENED matching semantics'. Undisclosed
MATCHING-SEMANTICS divergences remained; the in-code record is what a
follow-up implementer relies on, so both module docs
(freshell-sessions/src/resume_resolve.rs, freshell-server/src/resolve.rs)
now enumerate every remaining divergence:

- opencode by-id fallback runs the RETIRED resolveOpencodeSessionRoots
  parent-walk, not Node's hardened direct row query
  (providers/opencode-by-id-query.ts): orphaned/cyclic child rows are a
  Rust MISS where Node HITs; a legacy-schema DB is a Rust universal HIT
  for any full-shape ses_* id where Node hits only real rows; Rust hits
  omit Node's title/lastActivityAt.
- fallback hits hardcode sessionType "claude"/"opencode" instead of
  consulting the session-metadata overlay (Node's sessionTypeFor) — a
  freshclaude/freshopencode session resolved via fallback would resume
  under the wrong runtime.
- the claude fallback's locate_transcript probes
  <project>/<subdir>/<id>.jsonl and never Node's
  <project>/<parent>/subagents/<id>.jsonl layout, so subagent child
  transcripts miss; read errors are swallowed as misses (no
  provider-error channel). The cwd read IS now bounded to Node's 64 KiB
  (previous commit); the checked locator + error propagation stay
  deferred to the hardened plan's Task 3.
- response surface: degraded/providerErrors/unsearchedProviders/homeDir
  and the scan-failure/warming merge (plan Tasks 3, 5, 6).

Stale doc refs fixed in the same blocks:
- resume_resolve.rs referenced nonexistent 'resolveOpencodeSessionIds'
  (real name: resolveOpencodeSessionRoots).
- parse/opencode.rs by-id doc said the walk is 'consumed by
  resolve-session.ts:59-85' — stale: hardened Node consumes
  resolve-session.ts -> resolve-fallbacks.ts -> the by-id worker (a
  direct row query); the walk remains only the Rust fallback's interim
  lookup, recorded as a divergence.

Checklist (SYNC-06, 2026-07-14-rust-tauri-parity-completion-checklist.md):
the entry claimed the Rust port 'matches the HARDENED in-tree matching
semantics' with a DEFERRED list naming only the response surface, while
budgeted/shape-gated fallbacks (now genuinely ported, previous commit)
and the by-id lookup / sessionType-overlay / subagent-locator gaps are
matching behavior. Rewritten truthfully: AT PARITY = parser (fixture
anti-drift), matching core incl. Node's FALLBACK_ID_SHAPES gates +
FALLBACK_BUDGET_PER_REQUEST=2 per-fallback budget + 64 KiB bounded cwd
read, route shell; DEFERRED = the matching-semantics divergences above
plus the response surface, flag re-enable, and e2e matrix.

* test(sessions): pin thread_/task_ known families and the {8,64} suffix bound in the shared fixture

Residual fixture delta atop 13a342817's already-landed Task 2 work: the
known-family prefix regex and MAX_RESUME_CANDIDATES=8 cap are already
committed on both sides, but the anti-drift table had no positive
thread_/task_ family case and no suffix longer than 40 chars (the old
Rust regex capped at 40, so a regression there would go uncaught). Adds
two cases pinning the thread_ family and a 46-char task_ suffix within
{8,64} cross-language; both parsers pass unchanged. (SYNC-06)

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

Co-Authored-By: Amplifier <[email protected]>

* feat(sessions): hardened #586 resolve core — per-token ranking, case rules, provider-health channel, budgeted fallbacks

Per-token exact→fallback→prefix order (a prefix match never outranks an
exact resolution); ses_ ids case-SENSITIVE, uuid/hex case-folded;
subagents excluded from prefix discovery; sessionType defaults to the
provider name; fallbacks are shape-gated + budgeted (2/request/provider)
and their failures surface as degraded + providerErrors — never a silent
empty not-found. Mirrors test/unit/server/coding-cli/resolve-session.test.ts.

Refactors atop the matching semantics already landed in 13a342817 and
7109a6ead (ordering, case gating, subagent rules, shape gates, budgets):
adds the Degraded status, ProviderFailure/ResumeResolveProviderError
channel (fallback closures now Result — provider failure ≠ miss),
OpencodeByIdHit with title/lastActivityAt + the sessionType overlay on
fallback hits, and ResumeResolveOutcome replacing ResumeResolveResponse
(the wire response moves to resolve.rs; LegacyWire keeps today's
{status,matches,hint} shape until Task 6). Module divergence docs
trimmed to what genuinely remains (Tasks 4/5/6).
(SYNC-06)

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

Co-Authored-By: Amplifier <[email protected]>

* feat(sessions): hardened opencode exact-id lookup — direct by-id row query (archived+child included, errors propagate)

Ports opencode-by-id-query.ts, replacing the #583 parent-walk. Full row
(title/timestamps) feeds the resolve match; a missing/locked/corrupt DB
is Err — provider unavailable ≠ not found. (SYNC-06)

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

Co-Authored-By: Amplifier <[email protected]>

* feat(sessions): scan-failure tracking + fire-and-forget refresh on SessionIndex; enabled-providers reader + Node-parity default on SettingsStore

getScanFailures()/requestRefresh() parity plumbing for the hardened
resolve route (SYNC-06). All four real sources participate in failure
tracking: direct-listed (opencode) and file-backed (claude/codex/
amplifier) root-listing failures are recorded, never silently treated
as an empty listing. Default enabledProviders now includes amplifier
(DEFAULT_ENABLED_CLI_PROVIDERS parity).

Consequential parity updates: the captured handshake fixture
(port/oracle/fixtures/handshake-transcript.json) and the protocol
roundtrip assertion pinned the pre-amplifier three-provider default;
both now carry Node HEAD's four-provider set (the fixture test is
designed to fail loudly when the original's default tree shifts —
it shifted at shared/coding-cli-defaults.ts:3).

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

Co-Authored-By: Amplifier <[email protected]>

* fix(sessions): publish scan failures in a short lock instead of holding the mutex across the sweep

perform_refresh locked the scan_failures std mutex at the top of the
spawn_blocking sweep closure and held it for the entire discovery+parse
sweep, so the sync accessor scan_failures() (called by the resolve route
from async context right after a stale-while-revalidate snapshot())
blocked a tokio runtime thread for the full sweep duration — exactly
when degraded responses are being served.

The sweep now clones the failure set in a short lock, mutates the LOCAL
copy (refresh_snapshot's record/clear semantics unchanged for both the
direct-listed and file-backed branches), and swaps it back into the
shared mutex in a short lock at publish time, alongside the snapshot
publish. The JoinError (panic) path leaves the shared set untouched,
mirroring its preserve-the-published-snapshot stance. Lost updates are
impossible: refresh_lock guarantees at most one sweep at a time.

Regression test: scan_failures_is_readable_while_a_sweep_is_in_flight
blocks a sweep inside discover_checked() and asserts a mid-sweep
scan_failures() read completes within 500ms (fails by timeout on the
old code).

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

Co-Authored-By: Amplifier <[email protected]>

* feat(server): hardened resolve wire — providerErrors/unsearchedProviders/homeDir, scan-failure merge, degraded fire-and-forget refresh

POST /api/sessions/resolve now emits the full #586 contract; production
fallbacks report failures (checked claude locator, propagating opencode
by-id) instead of swallowing them; degraded-path proven on the wire
(broken store -> degraded + providerErrors, never silent not-found).
Async hygiene verified: all blocking fallback IO runs inside the
endpoint's spawn_blocking; work bounded by cap-8 candidates x budget-2
fallbacks. (SYNC-06)

Recorded deviation: a JoinError (panicked resolver) now answers an
explicit 500 instead of a fabricated ready-empty — Node has no defined
behavior there (unhandled rejection, no response), and presenting an
unsearchable state as a healthy not-found is the incident class this
plan closes.

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

Co-Authored-By: Amplifier <[email protected]>

* feat(server): restore sessionResolve capability and re-matrix resume e2e — hardened port landed

Reverses the f50da2e05 rollback now that the hardened resolve response
surface (degraded/providerErrors/unsearchedProviders/homeDir, warming
default) is fully ported (hardened plan Tasks 2-6):

- main.rs: declare sessionResolve: true (flag-pinning tests flipped
  first, RED, then the flag, GREEN)
- playwright.config.ts: restore resume-button.spec.ts to MATRIX_SPECS
  so it runs on both legacy-chromium and rust-chromium

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

Co-Authored-By: Amplifier <[email protected]>

* docs: record SYNC-06 hardened-contract parity evidence in completion checklist

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

Co-Authored-By: Amplifier <[email protected]>

* docs(resolve): close stale divergence ledgers post flag re-enable; pin exact cap-boundary assert

The module-doc divergence ledgers in resolve.rs and resume_resolve.rs
still claimed the sessionResolve capability flag was held false pending
plan Task 7; 48b67f838 flipped it to true and ran the resume-button e2e
matrix green. Rewrite both ledgers to the truthful state: flag declared
true in build_platform_payload, no known unported divergences beyond the
RECORDED DEVIATIONS (explicit 500 on resolver panic; homeDir omitted
when no resolvable home). Also tighten the Node-mirror test #7 tail
assertion from >= Some(5) to exactly Some(5) (25 sessions, activity
24..0, cap 20).

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

Co-Authored-By: Amplifier <[email protected]>

* fix(sessions): refresh provider health every sweep and publish snapshot+failures atomically

Three blocking findings from the independent cross-model review of the
resolve-parity port, fixed at the system level:

1. OpenCode health gated on the mtime token (directory_index.rs): a db
   that becomes locked/unreadable WITHOUT an mtime change (chmod moves
   ctime, not mtime; locks move nothing) kept answering ready from stale
   evidence. The sweep now consults a per-sweep direct_health_check()
   whenever the change token is unchanged (Node parity:
   refreshDirectProvider() runs on every full scan) — the token still
   gates only the expensive re-query of session DATA. OpencodeProvider
   gains health_check() (read-only open + one sqlite_master page read,
   the same open path direct_list uses). The masking test's artificial
   always-changing token was replaced with a CONSTANT token, plus a
   real-OpencodeSource chmod-000 test proving unchanged-mtime outages
   are recorded and cached sessions preserved.

2. TOCTOU in file-backed discovery (directory_index.rs, amplifier.rs):
   the disposable ensure_root_listable() preflight followed by a second,
   error-swallowing read_dir meant a failure between the two calls — or
   a per-entry iterator error — returned Ok(empty), CLEARING the
   provider's scan failure and pruning its cache from a sweep that never
   listed anything. Claude/codex/amplifier discover_checked() are now
   SINGLE-PASS: the one read_dir handle opened (open_root_dir) is the
   one traversed, and root per-entry errors propagate with `?`. Missing
   roots stay a genuine empty; nested errors stay tolerant (Node
   parity). New tests exercise the REAL sources (chmod-000 roots), not
   injected discover_checked doubles.

3. Torn snapshot/failure reads (directory_index.rs, resolve.rs): the
   snapshot and scan_failures published under separate locks at separate
   times, so a request could pair a failed-scan empty snapshot with a
   subsequently cleared failure set (healthy-looking ready+matches:[])
   or a recovered snapshot with stale failures. scan_failures now lives
   INSIDE CachedSnapshot — one lock write publishes the whole
   generation — and the route takes ONE coherent read via the new
   snapshot_with_failures() instead of two temporally separated reads.
   Short-lock discipline preserved (nothing held across a sweep or
   await). New atomic-generation test pins that no incoherent pair is
   ever observable.

Verified: cargo test -p freshell-sessions (151 lib + integration, 0
fail), cargo test -p freshell-server (438+, 0 fail), cargo fmt --check,
cargo clippy --workspace --all-targets -D warnings.

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

Co-Authored-By: Amplifier <[email protected]>

* fix(server): resolve provider homes via HOME||USERPROFILE approximation and bound the whole resolver in a 15s deadline

Two blocking findings from the cross-model review of the hardened
resolve port:

1. provider_home() (session_directory.rs) read only HOME, but production
   Tauri inherits the desktop environment WITHOUT setting HOME, so a
   native-Windows USERPROFILE-only environment built no session index
   (session_index: None) and /api/sessions/resolve answered warming
   forever while the sessionResolve capability stayed advertised. Now
   HOME-then-USERPROFILE - an APPROXIMATION of Node's os.homedir(), not
   its actual platform contract (Windows os.homedir() reads USERPROFILE
   and never consults HOME; POSIX falls back to the passwd entry when
   HOME is unset) - aligned with the then-existing homeDir wire-field
   resolution. Exact os.homedir() platform semantics landed in a later
   commit on this branch ("fix(server,sessions): resolve user home via
   Node os.homedir() platform semantics"). Pinned by
   provider_home_falls_back_to_userprofile_when_home_unset and
   provider_home_prefers_home_over_userprofile; the none-case test now
   clears USERPROFILE too.

2. The resolve route awaited the whole spawn_blocking resolver with no
   deadline: a filesystem/SQLite op stalled outside SQLite's 500ms busy
   handling held the request and a blocking-pool worker forever. The
   route now bounds the ENTIRE resolver task with a 15s
   RESOLVE_OUTER_DEADLINE. The 15s value is borrowed from Node's by-id
   runner timeout (opencode-by-id-runner.ts DEFAULT_TIMEOUT_MS), but the
   SCOPE is not Node's: Node applies that timeout per individual worker,
   not around the whole resolver - a later commit on this branch
   rescoped the deadline to each fallback dispatch to match. On elapse
   the task is abandoned (recorded deviation: blocking tasks cannot be
   cancelled, Node terminates its worker) and the request answers a
   degraded 200 with message-only providerErrors (every enabled
   provider reported unsearchable, never a healthy-looking not-found),
   hint preserved, refresh fire-and-forgotten. Deadline injectable via
   ResolveState.resolve_deadline; pinned by
   a_stalled_resolver_answers_degraded_at_the_outer_deadline_never_hangs.

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

Co-Authored-By: Amplifier <[email protected]>

* docs(plans): replace vacuous plan test bodies with landed-test references; reconcile spec divergences; add commit-message errata

Fixes three blocking documentation findings from independent review:

1. 2026-07-30-rust-resolve-parity-hardened.md: the 10 Task-4 and 5 Task-6
   test blocks had comment-only bodies that would pass vacuously if pasted
   literally. Each body now keeps its behavioral spec as comments and ends
   in unimplemented!() (a literal paste fails loudly), with post-execution
   notes pointing at the real verifiers that landed under the same names in
   crates/freshell-sessions/tests/opencode_row_by_id.rs and the resolve.rs
   test module. The self-review placeholder-scan line is corrected to admit
   the fifteen placeholders it previously denied. Trailing whitespace at the
   flagged lines removed.

2. 2026-07-29-rust-resolve-parity-spec.md: Requirement 1's blanket
   "identical validation and error shapes" claim is corrected to name the
   three deliberate, ledgered divergences recorded in resolve.rs's
   "Accepted deviations" (malformed/scalar JSON body 400 shape, 2 MB vs
   1 MB body limit, non-POST 405 routing); the parser reference now
   describes all nine prefixed-id families with 8-64-char suffixes instead
   of only ses_.

3. 2026-07-14-rust-tauri-parity-completion-checklist.md: SYNC-06 gains an
   ERRATA entry recording that commits 9a597494f and 7ac205d5d overclaimed
   Node parity (retired pre-#586 contract), that 13a342817/f50da2e05
   documented and disabled it, and that 08cc7df09/48b67f838 delivered the
   hardened parity. History is pushed and not rewritten; the note is the
   accurate record.

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

Co-Authored-By: Amplifier <[email protected]>

* fix(server): resolve claude fallback + homeDir via provider_home and cap resolver tasks with permit-scoped admission + cooperative cancel

Finding 1 (native Windows/Tauri claude fallback): the exact-id claude
locator read only CLAUDE_HOME || HOME/.claude, so the documented
USERPROFILE-only Tauri environment answered a healthy-looking ready-empty
where Node (os.homedir()) finds the transcript. Extract the locator body
to resolve_claude_exact_id_fallback() and route its home — plus the
homeDir wire field (which also accepted an EMPTY HOME verbatim) and the
opencode data-home helper in freshell-sessions — through the shared
HOME||USERPROFILE, empty-as-unset resolution (provider_home() / a pure
home_dir_from()). Tested at the fallback itself: a USERPROFILE-only env
with a .claude transcript fixture now resolves it.

Finding 2 (blocking-pool exhaustion): the 15s outer deadline bounded only
the HTTP wait — an abandoned spawn_blocking resolver ran on (and repeated
requests could accumulate stalled tasks until Tokio's blocking pool
exhausted). Now: (a) an 8-permit semaphore whose permit MOVES INTO the
blocking task caps concurrent (abandoned included) resolver tasks; a
permit-starved request degrades within the same deadline with the same
timeout-shaped 200 instead of queueing; (b) a cancel flag checked before
every fallback invocation stops an abandoned resolver at its next
fallback boundary. Precisely: task COUNT is bounded and provider work
stops at fallback boundaries — an individual stalled FS/SQLite op inside
a fallback still cannot be killed and holds its permit until it returns
(recorded deviation from Node's worker.terminate()). Node's 15s degraded
response shape is unchanged.

Tests: fallback-level USERPROFILE-only resolution; empty-HOME fallthrough
for the homeDir helper, provider_home(), and the opencode home helper;
permit saturation degrades request N+1 without invoking an (N+1)th
resolver body and the abandoned task returns its permit when its op
completes; a cancelled resolver skips the second fallback invocation
(with a non-cancelled control proving two would otherwise run).

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

Co-Authored-By: Amplifier <[email protected]>

* docs(plans): align resolve-parity docs with landed HOME->USERPROFILE fallback; archive superseded plan; fix SYNC-06 checkbox contradiction

Three blocking documentation findings from independent cross-model review:

1. 2026-07-30-rust-resolve-parity-hardened.md: the Task 6 Step 3b wiring
   instructed a CLAUDE_HOME -> HOME-only fallback for the exact-id claude
   locator (and a raw HOME||USERPROFILE homeDir chain that accepted an
   empty HOME verbatim), which cannot achieve native-Windows parity where
   Tauri leaves HOME unset. The wiring snippets now describe the LANDED
   behavior — resolve_claude_exact_id_fallback() and resolve_wire_home_dir()
   in main.rs, both resolving homes via session_directory::provider_home()
   (HOME then USERPROFILE, empty treated as unset) — and a new verifier
   list names the landed tests, including the USERPROFILE-only exact-id
   fallback test
   claude_exact_id_fallback_finds_transcript_in_a_userprofile_only_environment.

2. 2026-07-29-rust-resolve-parity.md: added an unmissable ARCHIVED /
   SUPERSEDED / DO NOT EXECUTE banner at the top. The plan implements the
   retired pre-#586 OpenCode parent-chain resolver contract, its parity
   reference (lines ~62-72) describes the obsolete pre-#586 behavior, and
   its expected test counts (~line 3103) are stale; re-executing it would
   reintroduce defects resolved by the hardened plan. Body unchanged —
   the banner is the fix.

3. 2026-07-29-rust-resolve-parity-spec.md: Requirement 8 said "Mark
   SYNC-06 done" while the same spec declares the required PW-TAURI-WIN
   validation out of scope and the implementation plan requires the
   checkbox stay unchecked. Resolved in favor of the plan/checklist: the
   spec now instructs recording the evidence as a PARTIAL bullet with the
   checkbox left UNCHECKED, and the Goal is reworded to "deliver the
   Rust-server half" so no instruction contradicts the out-of-scope
   declaration.

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

Co-Authored-By: Amplifier <[email protected]>

* docs(plans): remap checklist SHAs after in-history reword of the two false Node-parity commit messages

The two SYNC-06 commits that claimed "Node-parity semantics"/"Node-parity
behavior" were reworded in place (message-only rewrite, identical trees) to
state the retired pre-#586 legacy contract they actually implemented. Every
descendant SHA changed; update the checklist's commit references to the new
history and record the correction in the errata, including that
origin/feat/rust-resolve-parity still holds the pre-correction history.

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

Co-Authored-By: Amplifier <[email protected]>

* fix(server,sessions): resolve user home via Node os.homedir() platform semantics

Correct the interim HOME->USERPROFILE approximation (commits c6f3220b0 and
bb357a598 applied HOME-then-USERPROFILE on ALL platforms) to Node
os.homedir() / libuv uv_os_homedir platform rules: Windows reads
USERPROFILE and never consults HOME; POSIX reads HOME when set and
non-empty, else the effective user's passwd-entry home (getpwuid_r) -- so
POSIX now resolves a home (and main.rs builds a real session index, no
permanent warming) even with HOME unset, and a Windows process with both
variables set indexes USERPROFILE, never HOME.

- session_directory::provider_home() and parse/opencode's home_dir() now
  delegate to std::env::home_dir() (un-deprecated since 1.87; MSRV 1.96),
  which implements exactly these platform semantics. The homeDir wire
  helper and the claude exact-id fallback in main.rs route through
  provider_home() and inherit the fix. Provider-specific overrides
  (CLAUDE_HOME/CODEX_HOME/FRESHELL_AMPLIFIER_HOME) keep their precedence.
- Tests: the cross-platform HOME||USERPROFILE pins are replaced with
  platform-conditional ones -- cfg(unix) tests assert the passwd-entry
  fallback (via a getpwuid_r oracle) and that USERPROFILE is NEVER
  consulted; cfg(windows) tests pin USERPROFILE-wins-over-HOME. The
  opencode pure home_dir_from tests become env-mutating tests serialized
  on a new crate-wide HOME_ENV_TEST_LOCK (directory_index's persist-path
  test now takes the same lock).
- docs: SYNC-06 ERRATA line recording the interim approximation and this
  correction.

* fix(server): scope resolve deadline/admission to the fallback dispatch (interim: a shared cancel flag still skipped and blamed later providers)

Corrects the earlier whole-resolver deadline/admission scoping (an
interim approximation introduced by "fix(server): resolve claude
fallback + homeDir via provider_home and cap resolver tasks with
permit-scoped admission + cooperative cancel"): the 8-permit semaphore
and the 15 s deadline previously wrapped the ENTIRE resolver - permit
wait, input parsing, in-memory index matching, warming and no-candidate
paths - so eight stalled fallbacks made every later request (exact
index hits, garbage input, warming) wait for admission and then return
a fabricated degraded result blaming every enabled provider.

Node scopes its 15 s timeout to the individual by-id worker
(opencode-by-id-runner.ts DEFAULT_TIMEOUT_MS); its cheap paths never
wait on fallback-worker availability. This commit moves the deadline
toward that scoping but does NOT fully reach Node's per-worker
behavior:

- each blocking fallback invocation now runs on its OWN spawn_blocking
  task via bounded_fallback(): permit wait + fallback bounded by the
  (renamed) RESOLVE_FALLBACK_DEADLINE, permit MOVED INTO the task so a
  stalled syscall holds it until it returns (accumulation still capped
  at RESOLVE_MAX_CONCURRENCY = 8), and a panicking fallback still
  resumes into the resolver task so the explicit 500 contract holds
- parsing/validation, warming checks, index-only resolution, and
  no-candidate responses run WITHOUT a permit and WITHOUT the deadline
  and now succeed under full saturation
- REMAINING DIVERGENCE from Node: one cooperative cancellation flag is
  SHARED across all of a request's fallback dispatches, so the first
  timeout skips every subsequent fallback - including other providers'
  never-dispatched work - and records fabricated timeout providerErrors
  for them. A timeout can therefore blame providers whose fallbacks
  were never dispatched, unlike Node, where a worker timeout affects
  only that worker's provider. Corrected in a later commit on this
  branch ("fix(resolve): bound each fallback dispatch independently -
  a timeout never skips or blames other providers").

Tests (RED first against the old scoping): exact index hit / garbage
no-candidate input / warming all succeed with a zero-permit semaphore;
the existing stall/starvation/cancel tests reshaped to pin the new
scoping (request C exact hit succeeds while the only permit is still
held). Checklist ERRATA line added recording the correction.

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

Co-Authored-By: Amplifier <[email protected]>

* docs(plans): convert hardened resolve-parity plan into an executed completion record

The plan was fully implemented on feat/rust-resolve-parity but still read
as an active, unchecked executable task sequence (blocking review
finding). Add a prominent EXECUTED / COMPLETION RECORD / DO NOT EXECUTE
banner and annotate the four staleness defects in place:

- Task 1 Step 4 drift findings marked as the pre-execution baseline
  (all five items have landed at HEAD)
- Task 3 Step 2 and Task 6 Step 1 'expected compile FAILURE' runs marked
  as historical RED gates satisfied during execution (commits 5a3332be3,
  1480e2a71), no longer reproducible at HEAD
- Task 7 Step 4's superseded pre-reword c38422a0 reference annotated to
  the reworded history (checklist remap 97ab164a0, evidence bullet
  citing 22022a848)
- Task 7 Step 5's plain first-time push annotated: origin holds
  divergent pre-reword history; publishing requires a deliberate
  git push --force-with-lease (safety tag pre-reword-backup)

Checkboxes are preserved in their historical unchecked working form and
declared as such under the banner. Annotation over deletion throughout;
no code changes (cargo check clean).

* fix(resolve): project matches through Node's post-override view for titleOverride and deleted-override key shapes

The resolve route previously filtered the index snapshot with only a
composite-key ('provider:sessionId') deleted check, so a renamed session
could resolve with its stale parsed title and a session deleted under a
bare-session-id or legacy Claude transcript-basename override key stayed
visible to prefix matching.

Port Node's override lookup chain and its deleted/titleOverride
application (session-indexer.ts:1173-1187 lookup chain, applyOverride
at 204-220, consumed by resolve-session.ts:85):

- lookup_session_override: composite key, then bare session id, then
  (claude only, when the transcript basename differs from the parsed
  session id) legacy 'claude:{basename}' then bare '{basename}' -- first
  present entry wins, matching Node's || chain.
- project_session_through_overrides: 'deleted' hides the session;
  a non-empty 'titleOverride' replaces the title before matching.
  Other override fields (summary/createdAt/archived) never reach the
  resolve wire shape and are not projected.

NOT ported here: Node's provider-generated titleSource suppression
branch (applyOverride, session-indexer.ts:210-211 - a dir/first-message
sourced titleOverride never clobbers a current provider-generated
title). The Rust index did not yet track titleSource, so this
projection unconditionally substitutes a non-empty titleOverride; the
suppression branch was implemented in a later commit on this branch
("fix(resolve): port Node's provider-generated-title suppression into
the override projection").

Tests (TDD, each watched fail RED first):
- title_override_projects_onto_resolve_matches
- bare_session_id_deleted_override_hides_prefix_matches
- legacy_claude_composite_basename_deleted_override_hides_the_session
- legacy_claude_bare_basename_deleted_override_hides_the_session
The existing composite-key deletion test is unchanged.

* fix(resolve): bound each fallback dispatch independently — a timeout never skips or blames other providers

ffa4aac1a's interim cooperative cancel flag was SHARED across both
providers' fallback dispatches: once one dispatch timed out, every later
fallback in the same request returned a fabricated timeout without
invoking its provider. For input with an opencode ses_ id followed by a
claude UUID, an opencode stall skipped the healthy claude lookup,
omitted the claude match, and blamed claude for a timeout it never had —
contradicting Node, which bounds each by-id worker individually, records
the rejection for that ONE provider, and continues through later
candidates/providers (resolve-session.ts:133-156,
opencode-by-id-runner.ts per-worker timeout).

Remove the shared flag: each dispatch keeps its own 15 s deadline and
8-permit admission (ffa4aac1a's scoping otherwise intact); a timeout
blames only the provider whose dispatch elapsed, and later dispatches —
same or other provider — run with their own bounds. Abandoned-task
accumulation stays bounded WITHOUT the flag: the per-provider budget (2)
caps dispatches within a request, the semaphore caps tasks across
requests.

Tests: new cross-provider pin (opencode stalls, claude healthy — claude
match returned, only opencode blamed); the shared-flag skip test is
reshaped to pin the corrected contract (second candidate's dispatch
still runs; first-error-wins keeps one wire entry). Checklist ERRATA
records the interim flaw.

* fix(resolve): fail-fast fallback admission before the blocking task exists

Through 37be35b9a each fallback dispatch awaited acquire_owned() inside
the outer resolver spawn_blocking worker (via Handle::block_on), so
under full permit saturation a dispatch parked that unbounded outer
blocking-pool worker for its entire 15 s deadline; with the per-provider
budget (2) across two providers, one crafted request could pin an outer
worker for ~60 s, and enough authenticated requests could exhaust
Tokio's blocking pool - the exact failure RESOLVE_MAX_CONCURRENCY
claims to prevent (iteration-5 reviewer finding).

Corrected: admission is now a synchronous try_acquire_owned() BEFORE
any fallback task exists. A permit-starved dispatch fails fast into the
existing degraded provider-error shape ('resolve concurrency limit
reached', message-only like Node's worker-timeout rejection) instead of
queueing; the per-dispatch deadline now bounds only the admitted
fallback task; per-provider blame and the cheap-path bypass (parsing,
index-only, warming, no-candidate never touch admission) are unchanged.
Node needs no analogue of this state: worker.terminate() reclaims its
stalled worker at the timeout, so it never caps admission
(opencode-by-id-runner.ts; resolve-session.ts:133-156 records the
rejection per provider and continues).

Tests: new permit_starvation_fails_fast_without_pinning_an_outer_worker
_for_the_deadline proves a starved dispatch answers degraded well under
a generous 5 s deadline with the fallback body never invoked (RED on
the old code: it queued the full 5 s and answered the timeout shape).
Strengthened saturated_permits_degrade_the_next_request_without_
spawning_another_resolver: request B now runs with a generous 10 s
deadline and must answer within 2 s, so an implementation that queues
for the deadline fails (the old 5 s allowance against a 100 ms deadline
let a full-deadline queuer pass).

Docs: ERRATA notes added to the hardened plan completion record and the
parity checklist recording that the interim design admitted inside the
outer worker.

* fix(resolve): port Node's provider-generated-title suppression into the override projection

The iter4 projection (75636c7ef) omitted one branch of Node's
applyOverride (session-indexer.ts:210-211): a stored titleOverride whose
recorded titleSource is 'dir' or 'first-message' must NOT replace a
CURRENT provider-generated title — Node preserves the provider's own
title; the Rust projection always substituted the stale override,
changing the title metadata resolve returns.

The Rust index carried no parse-layer title provenance, so this plumbs
Node's ParsedSessionTitleSource (types.ts:100 — sole variant
'provider-generated', hence a bool) faithfully through the snapshot:

- ParsedSessionMeta/IndexedSession gain title_provider_generated
  (serde(default) keeps pre-existing persisted parse-caches loadable);
- claude parse sets it iff a custom-title/agent-name record produced the
  title (providers/claude.ts:505; Node's third input, the
  generated-summary-title extractor, has no ported extraction here so no
  Rust-parsed title can originate from it);
- amplifier sets it iff the metadata 'name' is present
  (providers/amplifier.ts:93); codex/opencode never set it, like Node;
- project_session_through_overrides now suppresses the override exactly
  per Node's negated conjunction — every other override source ('user',
  'ai', 'legacy', absent) still substitutes.

TDD: provider_generated_title_survives_dir_sourced_override and
provider_generated_title_survives_first_message_sourced_override were
RED first (title came back 'stale placeholder');
user_rename_still_overrides_a_provider_generated_title pins the
suppression's scoping. Existing rename/deleted-override tests unchanged
and green.

Docs: ERRATA line in the parity completion checklist recording the
omission and this correction.

Verified: cargo test -p freshell-server -p freshell-sessions (all
green), cargo fmt --all --check, cargo clippy --workspace --all-targets
-D warnings.

* fix(resolve): gate body parsing on express.json()'s Content-Type matcher

Node's global express.json() (server/index.ts:185, body-parser 1.20.4
default type 'application/json') only parses bodies whose media type is
exactly application/json (parameters stripped, case-insensitive; no
application/*+json); a skipped body leaves req.body = {} and the route
400s with the missing-input zod issue (sessions-router.ts:259-264). The
Rust route parsed the bytes as JSON regardless of Content-Type, so a
valid object under text/plain resolved on Rust while Node rejects it —
an unrecorded divergence.

Now the route parses JSON only when Content-Type matches the same
matcher; otherwise the body is treated as {} and answers Node's exact
400. New route tests pin text/plain and application/vnd.api+json
rejection, charset/case-variant acceptance, and the missing-header
case; spec ledger gains an ERRATA note that the divergence existed
unrecorded until this commit.

* docs(plans): remap SHAs after second in-history reword of three overstated commit messages

An independent review found three commit messages materially overstated
their commits (HOME||USERPROFILE claimed as os.homedir() parity + a
whole-resolver deadline framed as Node's per-worker timeout; a shared
cancel flag claimed to blame only actually-dispatched providers; an
override projection claimed "full"/"exactly" while omitting the
provider-generated-title suppression branch). Those three messages were
reworded IN-HISTORY (message-only, trees byte-identical, safety tag
pre-reword-backup-iter5), changing the SHAs of the three commits and
every descendant.

This commit remaps the stale SHA references in the completion-checklist
ERRATA lines and the hardened-plan completion record to the post-reword
SHAs, and adds a new checklist ERRATA entry recording the full old->new
SHA map, the in-source resolve.rs comments that still cite pre-reword
SHAs (source deliberately untouched), and that origin still holds the
pre-correction history (publishing requires the user's deliberate
git push --force-with-lease).

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

Co-Authored-By: Amplifier <[email protected]>

* fix(freshagent): restore fail_json_code_retry_after dropped as dead pre-rebase

Semantic rebase conflict: efe4ddfcf removed fail_json_code_retry_after as an
unused helper, but origin/main (#589 graceful restore/resume) now calls it
from the spawn-gate QueueFull path in terminal_tabs.rs. Restore the helper
verbatim from origin/main.

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

Co-Authored-By: Amplifier <[email protected]>

---------

Co-authored-by: Amplifier <[email protected]>
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.

1 participant