---
phase: 62
plan: 02
type: execute
wave: 1
depends_on: ["62-01"]
files_modified:
  - src/parse.rs
  - src/lib.rs
  - cpp/src/shim.cpp
autonomous: true
requirements: []
notes: |
  Wave 1 — LRU removal. Replace the `parser_override_catalog` HashMap+LRU with a
  per-`SemanticViewsParserInfo`-owned `Box<OverrideContext>`. Encodes the Q2
  destruction-order showstopper resolution from RESEARCH §4 Refinement 1: the
  destructor MUST NOT call `duckdb_disconnect` on the catalog connection — leak
  the Connection object for the rest of process life (matches the v0.8.0
  baseline, commit 680a967). This plan does NOT touch caret rendering — every
  validation error still arrives via the synthesised `SELECT error('...')` path
  through Wave 1 (Plan 03 swaps this for `parse_function`-based caret rendering).

must_haves:
  truths:
    - "src/parse.rs no longer contains module `parser_override_catalog`"
    - "src/parse.rs no longer contains `pub use parser_override_catalog::set as set_catalog_for_parser_override`"
    - "src/lib.rs no longer calls `set_catalog_for_parser_override`"
    - "src/parse.rs exports `OverrideContext` as a top-level type and `sv_make_override_context` / `sv_drop_override_context` FFI entries"
    - "cpp/src/shim.cpp `SemanticViewsParserInfo` holds `void *rust_state` instead of `db_token`"
    - "cpp/src/shim.cpp `~SemanticViewsParserInfo()` calls `sv_drop_override_context(rust_state)` and DOES NOT call `duckdb_disconnect`"
    - "src/parse.rs `Drop for OverrideContext` does NOT call `duckdb_disconnect` on the inner CatalogReader (intentional leak per Q2)"
    - "Multi-DB isolation: 17 sequential ATTACH+CREATE all succeed (B15)"
    - "All existing transactional DDL behaviour preserved — `just test-all` green"
  artifacts:
    - path: "src/parse.rs"
      provides: "OverrideContext owning struct + FFI ctor/dtor; sv_parser_override_rust takes ctx_ptr instead of db_token"
      contains: "pub struct OverrideContext"
    - path: "src/lib.rs"
      provides: "Wires catalog_conn into sv_register_parser_hooks; no LRU set call"
      contains: "sv_register_parser_hooks"
    - path: "cpp/src/shim.cpp"
      provides: "SemanticViewsParserInfo holds rust_state; destructor leaks Connection"
      contains: "rust_state"
  key_links:
    - from: "cpp/src/shim.cpp ~SemanticViewsParserInfo"
      to: "sv_drop_override_context (Rust FFI)"
      via: "rust_state pointer"
      pattern: "sv_drop_override_context"
    - from: "src/lib.rs init_extension"
      to: "sv_register_parser_hooks(db_handle, catalog_conn, is_file_backed)"
      via: "C++ shim FFI signature"
      pattern: "sv_register_parser_hooks"
    - from: "sv_parser_override_rust"
      to: "&OverrideContext (cast from ctx_ptr)"
      via: "void* round-trip via parser_info"
      pattern: "ctx_ptr.*OverrideContext|OverrideContext.*ctx_ptr"
---

<objective>
Replace the bounded LRU (`parser_override_catalog` module + `db_token` routing)
with direct ownership: each `SemanticViewsParserInfo` (one per
extension-LOAD-per-DB) holds an opaque `Box<OverrideContext>` owned via FFI. The
`OverrideContext` carries the `CatalogReader` and `is_file_backed` flag that
`rewrite_create` / `rewrite_drop_or_alter` / `rewrite_yaml_file_create` already
need. Lifetime is tied to `DBConfig` destruction (when DB unloads), eliminating
the silent-eviction class of bugs (TECH-DEBT 20).

CRITICAL — Q2 destruction-order showstopper (from RESEARCH §4 Refinement 1):
the destructor MUST NOT call `duckdb_disconnect`. By the time
`~SemanticViewsParserInfo` fires (inside `~DBConfig`, AFTER
`~DatabaseInstance` has reset `connection_manager`), `~Connection()` would
deref a destroyed `ConnectionManager` — use-after-free. We free the
`Box<OverrideContext>` (Rust-side allocation) but LEAK the
`duckdb_connection`. This matches v0.8.0 commit 680a967, which shipped
successfully with this exact leak pattern. Bound: one Connection object per
DB ever opened in the process (~few KB each).

Purpose: TECH-DEBT 20 resolved; multi-DB isolation preserved without an LRU.

Output: LRU module deleted; `OverrideContext` direct-owned; multi-DB isolation
test (B15) green for ≥17 sequential DBs.
</objective>

<execution_context>
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
@$HOME/.claude/get-shit-done/templates/summary.md
</execution_context>

<context>
@.planning/STATE.md
@.planning/phases/62-caret-restoration-lru-removal/62-RESEARCH.md
@.planning/phases/62-caret-restoration-lru-removal/62-VALIDATION.md
@_notes/v0.8.0_phase_62_ultraplan.md
@CLAUDE.md
@TECH-DEBT.md

<interfaces>
<!-- Current LRU module to delete (src/parse.rs:40-129) -->
```rust
mod parser_override_catalog {
    pub struct OverrideContext {
        pub catalog: CatalogReader,
        pub is_file_backed: bool,
    }
    static CONTEXTS: Lazy<Mutex<...>> = ...;
    pub(crate) const CAPACITY: usize = 16;
    fn set(&mut self, db_token: u64, ctx: OverrideContext) { /* LRU */ }
    fn get(&self, db_token: u64) -> Option<OverrideContext> { ... }
    pub fn set(...) { ... }
    pub fn get(db_token: u64) -> Option<OverrideContext> { ... }
}
pub use parser_override_catalog::set as set_catalog_for_parser_override;
```

<!-- Current FFI signature (src/parse.rs:2547-2600 approx) -->
```rust
#[no_mangle]
pub extern "C" fn sv_parser_override_rust(
    db_token: u64,
    query_ptr: *const u8, query_len: usize,
    sql_out: *mut *mut u8, sql_len_out: *mut usize,
    error_out: *mut u8, error_out_len: usize,
) -> u8 { ... }
```

<!-- Target FFI shape (Phase 62 Wave 1) -->
```rust
#[repr(C)]  // not needed — opaque pointer round-trip
pub struct OverrideContext {
    pub catalog: CatalogReader,
    pub is_file_backed: bool,
}

#[no_mangle]
pub unsafe extern "C" fn sv_make_override_context(
    conn: ffi::duckdb_connection,
    is_file_backed: bool,
) -> *mut std::ffi::c_void;

#[no_mangle]
pub unsafe extern "C" fn sv_drop_override_context(
    ctx_ptr: *mut std::ffi::c_void,
);

#[no_mangle]
pub unsafe extern "C" fn sv_parser_override_rust(
    ctx_ptr: *const std::ffi::c_void,  // was: db_token: u64
    query_ptr: *const u8, query_len: usize,
    sql_out: *mut *mut u8, sql_len_out: *mut usize,
    error_out: *mut u8, error_out_len: usize,  // dead param, kept for now
) -> u8;
```

<!-- Current C++ shim signature (cpp/src/shim.cpp:165-209) -->
```cpp
extern "C" {
    bool sv_register_parser_hooks(duckdb_database db_handle, uint64_t *out_db_token);
}
struct SemanticViewsParserInfo : public ParserExtensionInfo {
    uint64_t db_token;
    explicit SemanticViewsParserInfo(uint64_t token) : db_token(token) {}
};
```

<!-- Target C++ shim signature (Wave 1) -->
```cpp
extern "C" {
    bool sv_register_parser_hooks(duckdb_database db_handle,
                                  duckdb_connection catalog_conn,
                                  bool is_file_backed);
}
struct SemanticViewsParserInfo : public ParserExtensionInfo {
    void *rust_state;  // Box<OverrideContext>* opaque
    explicit SemanticViewsParserInfo(void *state) : rust_state(state) {}
    ~SemanticViewsParserInfo() override {
        if (rust_state) {
            sv_drop_override_context(rust_state);
            rust_state = nullptr;
        }
        // The duckdb_connection inside the OverrideContext's CatalogReader
        // is INTENTIONALLY LEAKED — see Phase 62 RESEARCH.md §Q2 / §4 Refinement 1.
    }
};
```
</interfaces>
</context>

<tasks>

<task type="auto" tdd="true">
  <name>Task 1: Add OverrideContext + FFI ctor/dtor in src/parse.rs; rewrite write-side rewriters to take &OverrideContext</name>
  <files>src/parse.rs</files>
  <behavior>
    - `OverrideContext` is now a top-level public struct in `src/parse.rs` with `catalog: CatalogReader, is_file_backed: bool` (verbatim from the deleted LRU module).
    - `Drop for OverrideContext` is implemented but the body MUST NOT call `duckdb_disconnect` on `self.catalog.conn` — leak it intentionally with a documenting comment referencing TECH-DEBT 20 + RESEARCH §Q2.
    - `sv_make_override_context(conn, is_file_backed) -> *mut c_void` boxes an `OverrideContext` and returns the leaked raw pointer.
    - `sv_drop_override_context(ptr)` re-boxes from raw and drops normally; the `Drop` impl runs (no `duckdb_disconnect`); the Rust allocation is freed.
    - `sv_parser_override_rust` takes `ctx_ptr: *const c_void` instead of `db_token: u64`. Casts to `&OverrideContext` via `&*(ctx_ptr as *const OverrideContext)` inside the panic guard.
    - `rewrite_create`, `rewrite_drop_or_alter`, `rewrite_yaml_file_create`, `rewrite_to_native_sql` (and any helper currently doing `parser_override_catalog::get(db_token)`) take `&OverrideContext` directly. The "catalog context evicted" friendly-error branches at lines ~1826, ~1895, ~2069 are removed.
    - The LRU module `parser_override_catalog` (lines 40–129) is deleted in full; the `pub use ... as set_catalog_for_parser_override` re-export at line 132 is deleted.
    - The LRU regression test `parser_override_catalog_lru_evicts_oldest` (around lines 2761–2786) is deleted.
    - `sql_throwing` (lines ~2602–2620) and the FALLBACK_OVERRIDE-workaround branch in `sv_parser_override_rust` are PRESERVED in this plan — Plan 03 removes them after `parse_function` is wired. (See task verification: `cargo test` after this task should still pass with synthesised SELECT error('...') in place.)

    Test additions (proptests / unit tests):
    - New `cargo test parse::tests::override_context_drop_does_not_disconnect` — constructs an `OverrideContext` with a stub `CatalogReader` whose `conn` is a sentinel raw pointer, drops it, asserts the sentinel is unchanged (memory under the pointer is not touched). Uses `unsafe` + a heap-allocated marker the Drop impl shouldn't write to.
    - New `cargo test parse::tests::sv_make_and_drop_override_context_round_trip` — calls `sv_make_override_context` with a null/sentinel conn, then `sv_drop_override_context` on the returned pointer, asserts no panic.
    - The existing `parser_override_catalog_lru_evicts_oldest` is REMOVED (the module it tests is gone).
  </behavior>
  <read_first>
    - src/parse.rs ENTIRE file — but pay special attention to lines 1–135 (LRU module + re-export), 1620–1700 (`write_error_to_buffer`, `sql_throwing` neighbourhood), 1700–1900 (rewriters that call `parser_override_catalog::get`), 2540–2620 (`sv_parser_override_rust` + `sql_throwing`), 2756–2790 (the LRU test to delete)
    - src/catalog.rs lines 80–110 (CatalogReader Copy semantics — `conn` is `duckdb_connection`, a raw pointer; CatalogReader is Copy)
    - src/errors.rs (ParseError fields)
    - .planning/phases/62-caret-restoration-lru-removal/62-RESEARCH.md §2 Q1, §2 Q2, §4 Refinement 1 (the destruction-order showstopper — encode the leak pattern)
    - .planning/phases/62-caret-restoration-lru-removal/62-RESEARCH.md §6 row B17 (LRU module is gone) + B15 (multi-DB isolation)
    - _notes/v0.8.0_phase_62_ultraplan.md "src/parse.rs" section
  </read_first>
  <action>
    Sequence (do NOT skip the destruction-order comment — it's the showstopper documentation):

    1. **Define `OverrideContext` at module scope.** Move the struct definition from inside `parser_override_catalog` to a top-level `pub struct OverrideContext` (with `pub catalog: CatalogReader, pub is_file_backed: bool` — same fields).

    2. **Implement `Drop`** with a documenting comment:
       ```rust
       impl Drop for OverrideContext {
           fn drop(&mut self) {
               // Phase 62 Q2 — INTENTIONAL LEAK of self.catalog.conn (the duckdb_connection).
               //
               // ~SemanticViewsParserInfo (and therefore Drop for OverrideContext) fires
               // during ~DBConfig, AFTER ~DatabaseInstance has already reset
               // connection_manager (duckdb.cpp:276819). Calling duckdb_disconnect here
               // would invoke ~Connection() → ConnectionManager::RemoveConnection() on
               // the destroyed manager — use-after-free.
               //
               // The leak is bounded at ONE duckdb_connection per DB ever opened in this
               // process (a few KB each). This matches v0.8.0 commit 680a967 which shipped
               // successfully with the same leak. The Rust-side Box<OverrideContext>
               // allocation itself IS reclaimed (this Drop runs and the Box dealloc fires).
               //
               // See: .planning/phases/62-caret-restoration-lru-removal/62-RESEARCH.md §Q2.
               // Resolves TECH-DEBT item 20 (silent LRU eviction class) by removing the LRU.
           }
       }
       ```

    3. **Add FFI entry points** (placed near `sv_parser_override_rust`):
       ```rust
       #[no_mangle]
       pub unsafe extern "C" fn sv_make_override_context(
           conn: ffi::duckdb_connection,
           is_file_backed: bool,
       ) -> *mut std::ffi::c_void {
           // Constructs CatalogReader from raw duckdb_connection. CatalogReader::new(conn)
           // or CatalogReader { conn } per the existing constructor convention in src/catalog.rs.
           let ctx = Box::new(OverrideContext {
               catalog: CatalogReader::from_raw(conn),  // confirm exact constructor name from catalog.rs
               is_file_backed,
           });
           Box::into_raw(ctx) as *mut std::ffi::c_void
       }

       #[no_mangle]
       pub unsafe extern "C" fn sv_drop_override_context(ctx_ptr: *mut std::ffi::c_void) {
           if ctx_ptr.is_null() { return; }
           // Re-box and drop. Drop impl above documents the intentional leak of conn.
           let _ = Box::from_raw(ctx_ptr as *mut OverrideContext);
       }
       ```

    4. **Rewrite `sv_parser_override_rust`** to take `ctx_ptr: *const c_void`:
       ```rust
       #[no_mangle]
       pub unsafe extern "C" fn sv_parser_override_rust(
           ctx_ptr: *const std::ffi::c_void,
           query_ptr: *const u8, query_len: usize,
           sql_out: *mut *mut u8, sql_len_out: *mut usize,
           error_out: *mut u8, error_out_len: usize,
       ) -> u8 {
           std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
               if ctx_ptr.is_null() { return 2_u8; }
               let ctx = &*(ctx_ptr as *const OverrideContext);
               // ... existing UTF-8 check + detect_ddl_kind + validate_and_rewrite logic,
               // but pass `ctx` to rewriters instead of looking up by db_token.
               // The "catalog context evicted" friendly-error branches GO AWAY.
               // sql_throwing-based error path STAYS for now — Plan 03 deletes it.
           })).unwrap_or(2)
       }
       ```

    5. **Rewriter signatures** (`rewrite_create`, `rewrite_drop_or_alter`, `rewrite_yaml_file_create`, `rewrite_to_native_sql`): change from `db_token: u64` (or whatever they currently take) to `ctx: &OverrideContext`. Remove the `let Some(ctx) = parser_override_catalog::get(db_token) else { return Err(...) }` boilerplate. Rewriter bodies now use `ctx.catalog` and `ctx.is_file_backed` directly.

    6. **Delete:** `mod parser_override_catalog { ... }` (lines 40–129); `pub use parser_override_catalog::set as set_catalog_for_parser_override;` (line 132); the LRU test `parser_override_catalog_lru_evicts_oldest` (lines ~2761–2786).

    7. **Add new tests** as described in `<behavior>`.

    8. **Confirm `CatalogReader` constructor name** — `src/catalog.rs:88-91` defines `CatalogReader { conn: duckdb_connection }`. If there's no `from_raw`, construct directly: `CatalogReader { conn }` (with the appropriate `pub` visibility on `conn` if needed).

    Do NOT touch `sql_throwing` or the rc=1 path in `sv_parser_override_rust` — they survive into Plan 03 where `parse_function` replaces them.
  </action>
  <verify>
    <automated>cd /Users/paul/Documents/Dev/Personal/duckdb-semantic-views && cargo build --no-default-features 2>&1 | tail -20 && cargo test --no-default-features parse:: 2>&1 | tail -30</automated>
  </verify>
  <acceptance_criteria>
    - `rg "parser_override_catalog" src/` returns ZERO hits
    - `rg "set_catalog_for_parser_override" src/` returns ZERO hits
    - `rg "MAX_CATALOG_ENTRIES|LRU_CAPACITY|catalog context.*evicted" src/` returns ZERO hits
    - `src/parse.rs` contains `pub struct OverrideContext`
    - `src/parse.rs` contains `pub unsafe extern \"C\" fn sv_make_override_context`
    - `src/parse.rs` contains `pub unsafe extern \"C\" fn sv_drop_override_context`
    - `src/parse.rs` `Drop for OverrideContext` body does NOT contain `duckdb_disconnect`
    - `src/parse.rs` Drop impl contains a comment referencing `Q2` or `RESEARCH.md` or `TECH-DEBT`
    - `src/parse.rs` does NOT contain `parser_override_catalog_lru_evicts_oldest`
    - `cargo test --no-default-features` exits 0 (existing tests + 2 new round-trip tests pass; LRU test gone)
  </acceptance_criteria>
  <done>OverrideContext is direct-owned; LRU module deleted; new FFI ctor/dtor wired; existing transactional DDL behaviour preserved at the Rust level; cargo test green.</done>
</task>

<task type="auto">
  <name>Task 2: Update C++ shim — SemanticViewsParserInfo holds rust_state; destructor leaks Connection</name>
  <files>cpp/src/shim.cpp</files>
  <read_first>
    - cpp/src/shim.cpp ENTIRE file (211 lines) — pay special attention to lines 90–98 (`SemanticViewsParserInfo`), 99 (`sv_next_db_token`), 112–157 (`sv_parser_override`), 165–210 (`sv_register_parser_hooks`)
    - cpp/include/parser_extension_compat.hpp lines 67–87 (`ParserExtensionParseResult`) and lines 130–155 (`ParserExtension` registration)
    - cpp/include/duckdb.cpp lines 276813–276834 (`~DatabaseInstance` destruction order — confirm `connection_manager.reset()` precedes config destruction)
    - cpp/include/duckdb.cpp lines 275794–275799 (`~Connection`)
    - .planning/phases/62-caret-restoration-lru-removal/62-RESEARCH.md §Q2 + §4 Refinement 1 (THE destruction-order rationale — encode in destructor comment)
    - _notes/v0.8.0_phase_62_ultraplan.md "cpp/src/shim.cpp" step 2 (NB: that step is WRONG re: duckdb_disconnect — RESEARCH §4 Refinement 1 is the correct version we encode)
  </read_first>
  <action>
    Sequence:

    1. **Add forward declaration** of the new Rust FFI entries (near the existing `extern "C"` block at line 30):
       ```cpp
       extern "C" {
           // ... existing declarations (sv_parser_override_rust, sv_free_buffer) ...

           // Phase 62: Box<OverrideContext> ownership FFI.
           void *sv_make_override_context(duckdb_connection conn, bool is_file_backed);
           void  sv_drop_override_context(void *ctx_ptr);
       }
       ```

       Update `sv_parser_override_rust` declaration to its new signature:
       ```cpp
       uint8_t sv_parser_override_rust(
           const void *ctx_ptr,                   // was: uint64_t db_token
           const char *query_ptr, size_t query_len,
           uint8_t **sql_out, size_t *sql_len_out,
           char *error_buf, size_t error_buf_len);
       ```

    2. **Reshape `SemanticViewsParserInfo`** (replace lines 90–99):
       ```cpp
       // Per-extension-load info struct attached to ParserExtension::parser_info.
       // Holds an opaque Box<OverrideContext>* (rust_state) that owns the catalog
       // connection + is_file_backed flag for THIS database. Lifetime is tied to
       // DBConfig, so destruction fires on DB unload — no LRU needed.
       struct SemanticViewsParserInfo : public ParserExtensionInfo {
           void *rust_state;  // Box<OverrideContext>* opaque pointer (Rust-owned).
           explicit SemanticViewsParserInfo(void *state) : rust_state(state) {}

           ~SemanticViewsParserInfo() override {
               if (rust_state) {
                   sv_drop_override_context(rust_state);
                   rust_state = nullptr;
               }
               // CRITICAL — Phase 62 Q2 destruction-order showstopper:
               // We deliberately do NOT call duckdb_disconnect on the
               // duckdb_connection contained within OverrideContext's CatalogReader.
               //
               // By the time this destructor fires, ~DatabaseInstance has already
               // reset connection_manager (duckdb.cpp:276819). ~Connection() would
               // call ConnectionManager::RemoveConnection() on the destroyed
               // manager — use-after-free.
               //
               // The Rust Drop impl on OverrideContext (in src/parse.rs) documents
               // the same constraint. The duckdb_connection object leaks for the
               // remainder of process life — bounded at one Connection per DB ever
               // opened (~few KB each). This matches v0.8.0 commit 680a967 which
               // shipped successfully with this same leak pattern.
               //
               // See .planning/phases/62-caret-restoration-lru-removal/62-RESEARCH.md §Q2.
           }
       };
       ```

       DELETE the `static std::atomic<uint64_t> sv_next_db_token{1};` line — no more tokens.

    3. **Update `sv_parser_override`** (line 112) to read `rust_state` instead of `db_token`:
       ```cpp
       static ParserOverrideResult sv_parser_override(
           ParserExtensionInfo *info, const string &query, ParserOptions &) {

           auto *sv_info = dynamic_cast<SemanticViewsParserInfo *>(info);
           if (!sv_info || !sv_info->rust_state) {
               return ParserOverrideResult();  // DISPLAY_ORIGINAL_ERROR
           }

           SvOwnedBuffer sql_buf;
           char error_buf[1024];
           memset(error_buf, 0, sizeof(error_buf));

           uint8_t rc = sv_parser_override_rust(
               sv_info->rust_state,
               query.c_str(), query.size(),
               &sql_buf.ptr, &sql_buf.len,
               error_buf, sizeof(error_buf));

           // Branches unchanged from v0.8.1:
           //   rc=2 → DISPLAY_ORIGINAL_ERROR (defer to default parser)
           //   rc=1 → DISPLAY_EXTENSION_ERROR (current sql_throwing path; Plan 03 deletes)
           //   rc=0 → re-parse rewritten SQL with default parser
           // ... existing rc==2, rc==1, rc==0 logic ...
       }
       ```

    4. **Update `sv_register_parser_hooks`** (line 166) to take `(duckdb_database, duckdb_connection, bool)` and call `sv_make_override_context`:
       ```cpp
       extern "C" {
           bool sv_register_parser_hooks(duckdb_database db_handle,
                                         duckdb_connection catalog_conn,
                                         bool is_file_backed) {
               try {
                   auto *wrapper = reinterpret_cast<duckdb::DatabaseWrapper *>(
                       db_handle->internal_ptr);
                   auto &db = *wrapper->database->instance;

                   void *rust_state = sv_make_override_context(catalog_conn, is_file_backed);
                   if (!rust_state) {
                       fprintf(stderr, "sv_register_parser_hooks: sv_make_override_context returned null\n");
                       return false;
                   }

                   ParserExtension ext;
                   ext.parser_override = sv_parser_override;
                   std::shared_ptr<ParserExtensionInfo> info_std(
                       new SemanticViewsParserInfo(rust_state));
                   ext.parser_info = duckdb::shared_ptr<ParserExtensionInfo>(info_std);
                   auto &config = DBConfig::GetConfig(db);
                   ParserExtension::Register(config, ext);

                   config.SetOption("allow_parser_override_extension", Value("FALLBACK"));

                   return true;
               } catch (const std::exception &e) {
                   fprintf(stderr, "sv_register_parser_hooks failed: %s\n", e.what());
                   return false;
               }
           }
       }
       ```

       NOTE: the `out_db_token` out-parameter is REMOVED. Plan 03 may further reshape (adding `parse_function` registration). For Wave 1 we only register `parser_override`.

    5. Comment block around `config.SetOption("allow_parser_override_extension", Value("FALLBACK"))` (lines 192–202): update to remove the "TECH-DEBT 22" reference about caret regression — that's resolved in Plan 03. For now leave a one-liner: `// FALLBACK_OVERRIDE: hook runs before default parser; misses fall through cleanly.`
  </action>
  <verify>
    <automated>cd /Users/paul/Documents/Dev/Personal/duckdb-semantic-views && just build 2>&1 | tail -20</automated>
  </verify>
  <acceptance_criteria>
    - `cpp/src/shim.cpp` contains `void *rust_state` (NOT `uint64_t db_token`)
    - `cpp/src/shim.cpp` does NOT contain `sv_next_db_token`
    - `cpp/src/shim.cpp` `~SemanticViewsParserInfo` body contains `sv_drop_override_context`
    - `cpp/src/shim.cpp` `~SemanticViewsParserInfo` body does NOT contain `duckdb_disconnect`
    - `cpp/src/shim.cpp` `~SemanticViewsParserInfo` body contains a comment referencing `Q2` or `RESEARCH` or `TECH-DEBT`
    - `cpp/src/shim.cpp` `sv_register_parser_hooks` signature takes `duckdb_connection catalog_conn`
    - `just build` exits 0 (the shim compiles against the new Rust FFI)
  </acceptance_criteria>
  <done>C++ shim compiles; SemanticViewsParserInfo holds rust_state; destructor leaks Connection per Q2 contract.</done>
</task>

<task type="auto">
  <name>Task 3: Update src/lib.rs init_extension wiring + run full quality gate</name>
  <files>src/lib.rs</files>
  <read_first>
    - src/lib.rs lines 320–490 (init_extension block — locate `set_catalog_for_parser_override`, `sv_register_parser_hooks`, and read-side table-function registrations)
    - src/lib.rs lines 389–482 (read-side registrations — confirm they pass `&catalog_reader` value-copy, NOT looking up by db_token; per RESEARCH §Q4 these need NO change)
    - .planning/phases/62-caret-restoration-lru-removal/62-RESEARCH.md §Q4 (read-side unaffected — confirms the surgery is contained to write-side parser_override wiring)
  </read_first>
  <action>
    Sequence:

    1. **Locate the FFI declaration of `sv_register_parser_hooks`** in `src/lib.rs` (it's declared via an `extern "C"` block — find it near where it's currently called). Update the signature to:

       ```rust
       extern "C" {
           fn sv_register_parser_hooks(
               db_handle: ffi::duckdb_database,
               catalog_conn: ffi::duckdb_connection,
               is_file_backed: bool,
           ) -> bool;
       }
       ```

       (Drop the `out_db_token: *mut u64` parameter that was there.)

    2. **In `init_extension`** (around lines 355–386):
       - DELETE the call to `crate::parse::set_catalog_for_parser_override(db_token, catalog_conn, is_file_backed)`.
       - DELETE the local `let mut db_token: u64 = 0;` declaration (and any other LRU-token plumbing).
       - REPLACE the `sv_register_parser_hooks(db_handle, &mut db_token)` call with:
         ```rust
         let ok = unsafe {
             sv_register_parser_hooks(db_handle, catalog_conn, is_file_backed)
         };
         if !ok {
             return Err("sv_register_parser_hooks failed".into());
         }
         ```

    3. **Read-side registrations** (lines 389–482) — DO NOT TOUCH. Per RESEARCH §Q4 these all use `&catalog_reader` value-copy and are unaffected. The same `duckdb_connection` is shared (by value-copy) between the read-side table functions and the new `OverrideContext` we hand to the C++ shim.

    4. **Quality gate (run end-to-end):**
       - `cargo build --no-default-features` — Rust crate compiles.
       - `just build` — extension binary builds.
       - `cargo test` — Rust unit tests + proptests green.
       - `just test-sql` — sqllogictest green (existing v080_transactional_ddl + peg_compat must stay byte-identical; the Wave 0 halt-fixtures are no-ops).
       - `just test-multi-db` — should now pass for ≥2-DB existing test AND the staged 17-DB test (which is still skipped). Any reportable improvement: verify the existing test still routes correctly per-DB.
       - `just test-all` — full suite green.

    5. If any test breaks, the most likely culprit is:
       - **Read-side table function regression** — RESEARCH §Q4 says no, but verify by running `just test-sql` and reading any failures.
       - **CatalogReader constructor mismatch** — the FFI in `sv_make_override_context` must construct `CatalogReader` correctly; if the existing constructor takes additional fields, update accordingly.
       - **`duckdb_disconnect` accidentally called** — grep `cpp/src/shim.cpp` for `duckdb_disconnect` in any destruction path. The ONLY remaining call should be in `init_extension` cleanup paths in `src/lib.rs` for FAILED loads (where the Connection was created but registration failed before transferring ownership).
  </action>
  <verify>
    <automated>cd /Users/paul/Documents/Dev/Personal/duckdb-semantic-views && just test-all 2>&1 | tail -30</automated>
  </verify>
  <acceptance_criteria>
    - `rg "set_catalog_for_parser_override" src/` returns ZERO hits
    - `rg "db_token" src/lib.rs cpp/src/shim.cpp` returns ZERO hits
    - `src/lib.rs` `init_extension` calls `sv_register_parser_hooks(db_handle, catalog_conn, is_file_backed)`
    - `just build` exits 0
    - `cargo test` exits 0
    - `just test-sql` exits 0
    - `just test-multi-db` exits 0 (existing 2-DB test still passes; 17-DB staged test skips cleanly)
    - `just test-adbc` exits 0 (transactional DDL preserved)
    - `just test-concurrent` exits 0 (race shape preserved per TECH-DEBT 23)
    - `just test-all` exits 0
  </acceptance_criteria>
  <done>LRU fully removed end-to-end; OverrideContext direct-attached via FFI; full quality gate green; transactional DDL behaviour byte-identical to pre-Wave-1 except for the LRU silent-eviction class which is gone.</done>
</task>

</tasks>

<threat_model>
## Trust Boundaries

| Boundary | Description |
|----------|-------------|
| Rust → C++ FFI (`sv_make_override_context` / `sv_drop_override_context`) | Opaque pointer round-trip; ownership transferred via `Box::into_raw`. C++ side stores as `void*` and returns it to Rust unchanged on drop. |
| C++ destructor → Rust drop | `~SemanticViewsParserInfo` calls back into Rust via FFI during DBConfig destruction. |

## STRIDE Threat Register

| Threat ID | Category | Component | Disposition | Mitigation Plan |
|-----------|----------|-----------|-------------|-----------------|
| T-62-W1-01 | Tampering | OverrideContext pointer round-trip across FFI | mitigate | `sv_drop_override_context` checks `ctx_ptr.is_null()` before re-boxing. C++ side nulls `rust_state` after drop to prevent double-free. |
| T-62-W1-02 | DoS / memory | Intentional Connection leak per DB | accept | Bounded at one Connection (~few KB) per DB ever opened in process. Matches v0.8.0 baseline. Documented in destructor comment with rationale. Alternative (calling duckdb_disconnect) is use-after-free. |
| T-62-W1-03 | DoS | Use-after-free during DB shutdown if Q2 mishandled | mitigate | Destructor explicitly does NOT call duckdb_disconnect; comment encodes the rationale; `acceptance_criteria` greps for the absence of duckdb_disconnect in the destructor. |
| T-62-W1-04 | Information disclosure | Catalog connection leak holding sensitive query state | accept | The leaked `Connection` object holds a private read-only catalog handle, no user-data buffers. Lifetime ends with the process. Same exposure surface as v0.8.0. |

No high-severity threats in Wave 1. The intentional-leak design is the SAFE choice under the destruction-order constraint; the alternative is UAF (T-62-W1-03 mitigated by acceptance criteria).
</threat_model>

<verification>
- `just build` exits 0
- `cargo test --no-default-features` exits 0
- `just test-sql` exits 0
- `just test-adbc` exits 0 (transactional DDL via ADBC preserved)
- `just test-multi-db` exits 0 (2-DB existing test passes; 17-DB staged test skips)
- `just test-concurrent` exits 0 (race shape per TECH-DEBT 23 preserved)
- `just test-all` exits 0 — Wave 1 leaves the suite as green as it was before
- Manually: `rg "parser_override_catalog|set_catalog_for_parser_override|db_token" src/ cpp/src/` returns zero hits
- Manually: `rg "duckdb_disconnect" cpp/src/shim.cpp` returns zero hits inside any destructor body (only init_extension failure-path may call it, if at all)
</verification>

<success_criteria>
- LRU module deleted; `OverrideContext` direct-attached to `SemanticViewsParserInfo`
- Q2 destruction-order showstopper encoded as a documenting comment in BOTH `Drop for OverrideContext` (Rust) and `~SemanticViewsParserInfo` (C++)
- TECH-DEBT 20 silent-eviction error class is no longer reachable (LRU gone)
- All existing v0.8.x transactional DDL behaviour preserved (sqllogictest + ADBC + concurrent green)
- Suite green for `just test-all`
</success_criteria>

<output>
After completion, create `.planning/phases/62-caret-restoration-lru-removal/62-02-SUMMARY.md` documenting:
- The exact FFI signatures shipped
- The destructor comment text in BOTH files
- A grep result confirming `parser_override_catalog`, `set_catalog_for_parser_override`, `db_token` are gone
- Test results from `just test-all`
</output>
