# HANDOFF — SymForge trust campaign (resume here)

**As of:** 2026-06-24. **Branch state:** everything below is merged to `main` (last release **v8.8.3**; B2/D12 merged as PR #369 `745313d`, next release pending release-please).
**Mission:** make SymForge trustworthy for the LLMs that call it and robust for users — by attacking ROOT causes of its trust defects, not patching symptoms. Engine-first: the facade is a thin honest layer; AAP consumes the engine via the `embed` feature (NOT the MCP server).

## How we work (durable rules — honor these)
- **A defect is a defect, not an "honest gap"** (now in global `~/.claude/CLAUDE.md` §0). Found an issue → name it plainly → add to the ledger → decide on the spot if it's a symptom of a larger root → if so, investigate + attack the root. Don't plug holes endlessly.
- **Deferral policy** (global CLAUDE.md §0): defer *scope/capability* (with a loud, tracked, owned refusal), never *defects*.
- **Never leave open PRs for the user** — process/merge them all yourself via **git-master** with the push mandate; carry through to merge+delete with the release-please guard `gh pr merge <N> --merge --delete-branch --body "PR #<N>"`. (agentmemory `mem_mqpbsq29`.) Still surface (don't execute) destructive ops on another lane's *live* branch.
- **Verify as user**, not "tests passed": the full cargo gate (`fmt --check`, `check`, `clippy --all-targets -D warnings`, `test --all-targets --test-threads=1`, `build --release`, `check --no-default-features --features embed`) AND behavioral/live evidence before claiming done. Full gate green before ANYTHING merges to main.
- **Ultracode** was on: use Workflow for substantive design/investigation; adversarially verify load-bearing work.

## THE source of truth
- **`docs/reviews/symforge-defect-ledger.md`** — every defect, named plainly, ROOT vs SYMPTOM, clustered to 3 culprits + the vetted attack sequence with owners. READ THIS FIRST.
- `specs/012-harness-agnostic-mcp/{spec,research,plan,integration-plan}.md` — the engine-first design + the adversarially-vetted integration plan.
- `docs/reviews/stel-v8-skeptic-audit-2026-06-17.md` + `stel-v8-field-confirmation-2026-06-19.md` — the original audit + 3-agent field evidence (where the defects came from).

## The 3 root culprits
- **A — lossy/fabricating facade**: silently drops caller params + emits unmeasured numbers. Fix = `lossless-or-loud` + `honest-envelope` contracts, enforced by conformance tests.
- **B — engine multi-view search has no per-view derived index / no live rebase**: cross-project search is scoping-less, stale, low-recall.
- **C — `/mcp` is a stateless single-index singleton**: no per-connection session.

## DONE + on main (v8.6.0)
- Engine **base+overlay `IndexView` primitive** (`src/live_index/view.rs`): immutable base shared by `Arc` keyed `(canonical-root, commit)` + per-consumer CoW overlay; O(K) rebase (spike-proven); `WorkingSet` cross-project query w/ source attribution. `embed` contract unchanged.
- **US1 cross-project query**: `index_folder(add:true)` opens projects additively; `search_symbols/search_text/find_references` + `StelRequest` gain `project`/`projects` → `Targets`; single-project path byte-identical. Daemon-only (stdio refuses honestly). Proven by a real daemon-HTTP integration test.
- **C4** wrong-repo fix: per-connection retarget + bound `project_root` in every response.
- **Honesty hardening**: facade refuses cross-project params it can't route; cross-project output bounded+disclosed; `if_match` normalized pre-flight; clean `query`-required error; glossary MCP resource.
- **A1a** — `ParamDisposition` choke point (`src/stel/planner.rs`) + conformance test (`tests/stel_param_disposition.rs`): every `StelRequest` field resolves to an explicit disposition; silent-drop class non-regressable. Zero behavior change.
- **D17** — atomic open (`src/daemon.rs register_session_for_existing_project`): eliminates the open-vs-close TOCTOU; open is fail-never (8-thread stress test asserts it).

## REMAINING — the attack sequence (do in this order)

**B1 — DONE** (implemented, full-gate-green, adversarially reviewed; D11 scoping **FIXED**, D14 ranking **PARTIAL** — per-project ranked+bounded, global cross-project interleave deferred). **A1b — DONE** (PR #358). **C-stopgap — DONE** (012d; D16 silent-wrong half CONTAINED — `/mcp` already refused cross-project via `local_cross_project_refusal`, the gap was a missing regression lock + the message not naming the `/mcp` transport, both now closed). **B2/D12 — DONE** (012e, merged PR #369: a LAZY re-intern/force-replace of the stale cross-project base on the read path; `Arc::ptr_eq` staleness signal; real-watcher live SC-1/SC-2 proof). **D13 — DONE (012f)** (Culprit D — `parsing/xref.rs` recall: the `find_references` immediate-qualifier head-match closed qualified-call construction recall — `X::method()`, `a::b::X::method()` — for Rust + C++; struct/composite literals were already covered by the unscoped `(type_identifier)` capture, so the "struct_expression missing" premise was false; reproduced 29%→all-sites-resolve fixture). **D18 — DONE (012f, PR #379)** (get_symbol on an absent symbol returns a loud `No symbol ...` auto-classified NotFound, replacing a silent content-anchored window). **R3 — DONE (012f)** (find_references type/value-usage traces carry a targeted recall-confidence caveat, best-effort modes only, mirrors the kind filter). **The 012f actionable set is COMPLETE** (D13/D18/R3/D20/bases-GC merged, v8.9.0–v8.9.4). **NEXT = precondition #1** (a real commit-gated overlay writer) which unblocks the parked overlay track + D16-full. The overlay/IndexView track is PARKED: the per-session overlay is REDUNDANT in production (every read sees edits via the shared live index or the refreshed base; no production read consumes an overlay delta), so **D15 is a DORMANT SEAM** (writer kept gated/byte-identical, the redundant read removed in 012f) and **D-B0 is MOOT** until a real overlay writer lands — neither built under the single shared live index. **LESSON invariant:** *A read overlay is dead until edits are commit-gated to it; while the writer also writes the shared base, the overlay can never be the sole fresh source — build the commit gate first, or the overlay is a no-op (and a staleness shadow).* See `docs/reviews/overlay-redundancy-decision.md`. The original B1 recipe is retained below for reference:
- The empty-overlay fast path `IndexView::search_symbols` (`view.rs:341`) calls the **preset-only** `base_search_symbols(... usize::MAX)` (hardcodes path_scope=Any, language=None). The option-honoring `search_symbols_with_options` / `search_text_with_options` (`search.rs:808/943`) ALREADY EXIST.
- Thread the caller's scoping/limit options through `IndexView`'s search methods → `WorkingSet` cross-project query passes them down → daemon `execute_cross_project_read` builds the options from `SearchSymbolsInput/SearchTextInput/FindReferencesInput` AND **removes `reject_unsupported_cross_project_scoping`** (the honesty-pass guard that loudly refuses path/language/etc.).
- Overlays are EMPTY in US1 (no-overlay-writes invariant) so the empty-overlay path is the cross-project path. Per-overlay derived index for NON-empty overlays is the deferred large item **D-B0**.
- Update the rejection tests (they assert refusal) → assert honoring; add scoping-honored cross-project tests.

Then: **A1b** — DONE (PR #358) · **C-stopgap** — DONE (012d — the `/mcp` refusal was already wired since Phase 3; 012d added the regression lock `tests/serve_http_attach.rs` + named the `/mcp` transport in the refusal) · **B2/D12** — **DONE** (012e, PR #369 — LAZY re-intern at the cross-project read; D12 FIXED).

**Tracked-large (OPEN, owners, blocked-on — NOT deferred-as-acceptable):** **D13 — FIXED (012f)** (`parsing/xref.rs` + `find_references` qualified-call construction recall, Culprit D; tracked-minor head-match Import/TypeUsage noise noted at the call site) · **D18 — DONE (012f, PR #379)** + **R3 — DONE (012f)** (loud not-found + targeted recall caveat) · D-B0 (per-view derived index — **MOOT until a real overlay writer / precondition #1 lands; decoupled from D13**) · D15 (overlay edits in ordinary reads — **DORMANT SEAM:** writer landed PR #372 + kept gated/byte-identical, redundant read removed 012f; read-migration paused; phased plan `docs/reviews/D15-readpath-coherence-migration-plan.md`) · D16 (`/mcp` per-connection daemon session) · D2 (gate decides on estimated economics — owner 013 lane) · bases-table orphan GC (tracked-minor, pre-existing, surfaced by the B2 review) · serve_port test-fragility (local Docker-Desktop port collisions).

## Working environment + gotchas
- **`E:\project\symforge-012`** worktree is now on **`main`** (clean). Branch the next attack from here. (`feat/012` was merged + deleted.)
- **`E:\project\symforge`** = the **013 predictor-calibration lane's** worktree (branch `013-stel-predictor-calibration-spec`) — has **unpushed commits**; DO NOT disturb. Its remote branch isn't pruned for that reason.
- **`E:\project\symforge-perl`** = perl-grammar lane.
- AAP repo: `E:\project\Agent_Army_Professionals` (consumes symforge via `embed`, git dep on main; diagrams in `docs/`).
- Symforge MCP/daemon can disconnect/flake mid-session; cross-project is **daemon-only**; verify behaviorally via a built binary, not just unit tests.
- Subagent rate limit historically resets ~2pm Europe/Ljubljana.
- A2 (`Figure` provenance enum) was DEMOTED to regression-guard (envelope already honest); don't prioritize it.

## Immediate first action for the next session
B2/D12 (PR #369, `745313d`) is landed — cross-project reads now stay FRESH after ANY watcher-observed change (not just commits). Mechanism: a LAZY re-intern on the cross-project read path (`DaemonState::refresh_working_set_bases`, called from `call_tool_handler` before the search) detects a stale interned base via `Arc::ptr_eq` of the working-set entry's snapshot vs the project's CURRENT published index, and force-replaces it (`intern_base_refresh`, SC-002-preserving) + re-fences the entry's empty overlay; mismatch-gated; single-active reads gated out (byte-identical + frecency-neutral). Proven by `daemon::tests::test_cross_project_read_is_fresh_after_watcher_reindex` (SC-1 add, SC-2 delete/no-ghost, mismatch-gate, lone-non-active, SC-002) + a real-watcher live daemon-HTTP transcript; full CI green; adversarial review wf `wusdyz8fx` (code-reviewer + security-reviewer APPROVE — no cross-project leak; skeptic APPROVE_WITH_NITS, applied). `Arc::ptr_eq` over-triggers on mtime-only touches (a safe spurious re-intern, documented). One file (`src/daemon.rs`); no `view.rs`/`embed.rs` change.

D15's overlay-WRITER landed (PR #372, `22e66ca`) — and a 5-agent root-cause investigation (012f) then CONFIRMED the per-session overlay is **REDUNDANT in production**: every read sees the edit via the SHARED live index (single-project `self.index = Arc::clone(&project.index)`, `daemon.rs:1446`) or via the refreshed `entry.base` (cross-project — `refresh_working_set_bases` re-interns from the live index AND empties the overlay). The writer always advances the shared base FIRST (`reindex_after_write`), so no production read path ever consumes an overlay delta. 012f (this branch) REMOVED the redundant single-project overlay READ in `get_symbol` (it also carried a narrow staleness shadow risk — overlay-first with no freshness gate) and KEPT the writer as a deliberate DORMANT 012-spec seam (US3/SC-003), gated `None` on the shared instance / local-stdio = byte-identical today. D-B0 is MOOT (no non-empty overlay exists to index). Decision: `docs/reviews/overlay-redundancy-decision.md`.

**LESSON invariant:** *A read overlay is dead until edits are commit-gated to it; while the writer also writes the shared base, the overlay can never be the sole fresh source — build the commit gate first, or the overlay is a no-op (and a staleness shadow).*

**NEXT:** Read `docs/reviews/symforge-defect-ledger.md` + `docs/reviews/overlay-redundancy-decision.md` + `docs/reviews/012f-RESULTS.md`. **The 012f actionable set is COMPLETE** (D13 recall, D18 loud-not-found, R3 recall-caveat, D20 path_prefix, bases-table orphan GC — all merged, release-please v8.9.0–v8.9.4; D15 reframed as a DORMANT SEAM). The Culprit-B/overlay track (D15 read-migration, **D-B0**, **D16-full**) is BLOCKED on **precondition #1**: a real overlay WRITER where edits land in the per-session overlay and are NOT written through to the shared base (commit-gated session isolation). Until that lands, the overlay is redundant in production — DO NOT build the parked items (that is exactly the no-op work this campaign exists to prevent). The genuine next campaign target is therefore EITHER (a) design precondition #1 (the commit-gated session-isolation substrate, which also unblocks D16-full), OR (b) advance the remaining **Culprit A** facade-honesty items (D-A0/D3/D5/D6/D8/D9/D19 — the lossless-or-loud + honest-envelope contract). Pick by field-evidence impact. Branch the next attack off `main`, follow the per-item gate (implement → code-reviewer → security-reviewer → test-runner full gate → adversarial review → live-verify → PR → self-merge via the release-please guard). DISK NOTE: the shared E: drive hit 100% once this sprint (`cargo clean` reclaimed 22 GiB); policy is now `cargo clean` after EACH build item (not a warm cache held across items) to protect the 4-agent shared disk; CI is the authoritative full gate when local `--all-targets`/`--release` can't fit.
