---
phase: 65-overridecontext-connection-teardown
plan: 03
type: execute
wave: 1
depends_on: []
files_modified:
  - src/parse.rs
  - src/lib.rs
  - src/conn_guard.rs
  - src/ddl/define.rs
  - cpp/src/shim.cpp
  - cpp/src/shim.hpp
  - test/sql/65_pk_error.test
  - test/sql/65_metadata_via_sql.test
  - test/sql/TEST_LIST
autonomous: true
requirements:
  - LIFE-02
tags:
  - duckdb
  - rust
  - ffi
  - parser-override
  - lifecycle
  - slimming
  - breaking-change

must_haves:
  truths:
    - "src/conn_guard.rs does not exist (D-02 deletion)"
    - "src/parse.rs::OverrideContext matches the v0.9.0 (Phase 62) shape: fields are `catalog: crate::catalog::CatalogReader` and `is_file_backed: bool` (D-01)"
    - "src/parse.rs::OverrideContext has Drop impl restoring the v0.9.0 INTENTIONAL LEAK comment text verbatim (Plan 06 retires it; this is a transitional restore per D-01)"
    - "src/parse.rs::sv_make_override_context signature is `(catalog_conn: duckdb_connection, is_file_backed: bool) -> *mut c_void` (D-01)"
    - "cpp/src/shim.cpp::sv_register_parser_hooks signature is `(duckdb_connection catalog_conn, bool is_file_backed)` (D-01)"
    - "src/ddl/define.rs::resolve_pk_from_catalog function no longer exists (D-05)"
    - "src/parse.rs has no call sites referencing resolve_pk_from_catalog (D-05)"
    - "src/ddl/define.rs::enrich_definition_for_create no longer takes a `conn` parameter and no longer touches the catalog connection for §1 PK lookup, §5 metadata, §6 LIMIT-0 type inference, or §7 fact typeof (D-05, D-16, metadata-via-SQL)"
    - "emit_native_create_sql emits an INSERT whose SELECT projection embeds `json_merge_patch(<def>::JSON, json_object('created_on', strftime(now(), '%Y-%m-%dT%H:%M:%SZ'), 'database_name', current_database(), 'schema_name', current_schema()))::VARCHAR` so metadata is captured by DuckDB on the caller's connection"
    - "On CREATE/ALTER where a TABLES entry has no PRIMARY KEY (or UNIQUE) AND is FK-referenced by another TABLES entry via REFERENCES target(cols), the rewrite path returns a ParseError whose message matches: \"Table 'X' has no PRIMARY KEY declared but is referenced by FK in 'Y'. Add PRIMARY KEY (cols) or UNIQUE (cols) to the TABLES clause for X. (v0.10.0: physical-catalog PK auto-inference removed -- see CHANGELOG.)\" (D-06)"
    - "Existing v0.9.0-written definitions in semantic_layer._definitions (with inferred PKs already persisted) load, query via semantic_view(), and introspect via SHOW/DESCRIBE successfully on v0.10.0 (D-07)"
    - "H1 catalog_conn allocation at src/lib.rs:386-410 is STILL PRESENT but unused by any parser_override code path (Plan 06 retires the allocation itself)"
    - "just test-sql exits 0 with all 47 sqllogictest files passing PLUS the 2 new tests (65_pk_error.test, 65_metadata_via_sql.test); the Plan 02 partial 4/47 regression is resolved"
    - "test/integration/test_adbc_transactions.py passes (D-21 invariant -- transactional DDL stays green)"
    - "src/parse.rs imports `crate::conn_guard::ConnGuard` are removed"
  artifacts:
    - path: "src/parse.rs"
      provides: "OverrideContext + parser_override rewrite paths in v0.9.0 shape with metadata-via-SQL upgrade and D-06 hard error"
      contains: "pub struct OverrideContext"
    - path: "src/ddl/define.rs"
      provides: "Slimmed enrich_definition_for_create (validation + serialize only)"
      contains: "fn enrich_definition_for_create"
    - path: "cpp/src/shim.cpp"
      provides: "v0.9.0 sv_register_parser_hooks signature"
      contains: "sv_register_parser_hooks(duckdb_connection"
    - path: "test/sql/65_pk_error.test"
      provides: "D-06 hard-error regression test"
      contains: "Table 'o' has no PRIMARY KEY declared but is referenced by FK"
    - path: "test/sql/65_metadata_via_sql.test"
      provides: "Metadata-via-SQL behavior regression"
      contains: "current_database"
  key_links:
    - from: "src/parse.rs::emit_native_create_sql"
      to: "DuckDB caller's connection (caller of parser_override)"
      via: "INSERT...SELECT with embedded now() / current_database() / current_schema() inside json_merge_patch"
      pattern: "json_merge_patch.*current_database\\(\\)"
    - from: "src/ddl/define.rs::enrich_definition_for_create"
      to: "D-06 hard error path"
      via: "ParseError emission when FK references a table without declared PK/UNIQUE"
      pattern: "no PRIMARY KEY declared but is referenced by FK"
---

<objective>
Slim parser_override to ZERO catalog reads. Revert the Plan 02 partial damage to OverrideContext / sv_make_override_context / sv_register_parser_hooks back to v0.9.0 shape; delete src/conn_guard.rs; delete PK auto-inference (resolve_pk_from_catalog) and replace its failure mode with the D-06 hard error; move CREATE-time metadata capture (`now()`, `current_database()`, `current_schema()`) from Rust-side `execute_sql_raw` to SQL expressions embedded inside the rewritten INSERT (via `json_merge_patch`); fold existence pre-checks into INSERT ON CONFLICT (CASE+error pattern); drop CREATE-time type inference entirely (Plan 05 moves it to read-side bind callbacks per D-16). After this plan: parser_override has zero catalog access; H1 catalog_conn is still allocated in init_extension (unused) and Plan 06 retires the allocation.

Purpose: Removing the catalog reads inside parser_override is the load-bearing architectural change for the read-elimination plan. Without it, H1 catalog_conn cannot be retired -- which is the LIFE-01 root-cause fix. Reverting Plan 02 partial is mandatory because its db_handle plumbing is not reusable foundation under read-elimination (per D-01).

Output: A parser_override that emits transactional DDL using only SQL expressions on the caller's connection, plus the D-06 hard error path for missing PK + FK references.
</objective>

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

<context>
@CLAUDE.md
@.planning/STATE.md
@.planning/ROADMAP.md
@.planning/REQUIREMENTS.md
@.planning/phases/65-overridecontext-connection-teardown/65-CONTEXT.md
@.planning/phases/65-overridecontext-connection-teardown/65-RESEARCH.md
@.planning/phases/65-overridecontext-connection-teardown/65-PATTERNS.md
@.planning/phases/65-overridecontext-connection-teardown/65-02-PARTIAL-SUMMARY.md

<!-- Spike evidence (load-bearing for architecture) -->
@.planning/phases/65-overridecontext-connection-teardown/65-02-A7-test-sql-evidence.log
@.planning/phases/65-overridecontext-connection-teardown/65-OPTION-B-SPIKE.md
@.planning/phases/65-overridecontext-connection-teardown/65-EXEC-TIME-SPIKE.md

<!-- Code that this plan reverts / slims -->
@src/parse.rs
@src/lib.rs
@src/ddl/define.rs
@src/conn_guard.rs
@cpp/src/shim.cpp
@cpp/src/shim.hpp

<!-- Existing tests that must stay green -->
@test/integration/test_adbc_transactions.py
@test/sql/phase45_alter_comment.test
@test/sql/phase39_metadata_storage.test

<interfaces>
<!-- Pre-revert (CURRENT / Plan-02-partial) OverrideContext shape (src/parse.rs:66-87) -->

```rust
#[cfg(feature = "extension")]
pub struct OverrideContext {
    // PHASE-65-GUARD: do not reintroduce duckdb_connection or CatalogReader field here.
    pub db_handle: libduckdb_sys::duckdb_database,
    pub catalog_table_present: bool,
    pub is_file_backed: bool,
}
```

<!-- Post-revert (v0.9.0 / Plan 03 target) OverrideContext shape  -->

```rust
#[cfg(feature = "extension")]
pub struct OverrideContext {
    pub catalog: crate::catalog::CatalogReader,
    pub is_file_backed: bool,
}

#[cfg(feature = "extension")]
impl Drop for OverrideContext {
    fn drop(&mut self) {
        // Phase 62 Q2 -- INTENTIONAL LEAK of self.catalog.conn (the duckdb_connection).
        // (Full comment per RESEARCH §2 / git show 0d2c0b7^:src/parse.rs)
    }
}
```

<!-- v0.9.0 FFI signatures (Plan 03 target) -->

```rust
// src/parse.rs
pub unsafe extern "C" fn sv_make_override_context(
    catalog_conn: libduckdb_sys::duckdb_connection,
    is_file_backed: bool,
) -> *mut std::ffi::c_void;
```

```cpp
// cpp/src/shim.cpp
bool sv_register_parser_hooks(duckdb_connection catalog_conn,
                              bool is_file_backed);
```

<!-- D-06 hard-error message template (used by Task 2; do not paraphrase) -->

Table 'X' has no PRIMARY KEY declared but is referenced by FK in 'Y'. Add PRIMARY KEY (cols) or UNIQUE (cols) to the TABLES clause for X. (v0.10.0: physical-catalog PK auto-inference removed -- see CHANGELOG.)

<!-- Metadata-via-SQL INSERT shape (Task 3 target) -->

INSERT INTO semantic_layer._definitions (name, definition)
SELECT
  CASE WHEN EXISTS (SELECT 1 FROM semantic_layer._definitions WHERE name = '<name_escaped>')
       THEN error('semantic view ''<name_escaped>'' already exists; use CREATE OR REPLACE SEMANTIC VIEW to overwrite')
       ELSE '<name_escaped>' END,
  json_merge_patch(
    '<enriched_json_without_metadata>'::JSON,
    json_object(
      'created_on', strftime(now(), '%Y-%m-%dT%H:%M:%SZ'),
      'database_name', current_database(),
      'schema_name', current_schema()
    )
  )::VARCHAR
RETURNING name AS view_name
</interfaces>
</context>

<tasks>

<task type="auto" tdd="true">
  <name>Task 1 (Wave 0): Revert Plan 02 partial commits and delete src/conn_guard.rs</name>
  <files>src/parse.rs, src/lib.rs, src/conn_guard.rs, src/ddl/define.rs, cpp/src/shim.cpp, cpp/src/shim.hpp</files>
  <read_first>
    - src/parse.rs (current Plan-02-partial state, lines 66-87 OverrideContext, 2513-2557 sv_make_override_context, 1788-2100 rewrite_* sites, 1940-1994 emit_native_create_sql ConnGuard blocks, 2200-2249 rewrite_drop, 2252-2311 rewrite_alter_rename, 2314-2383 rewrite_alter_comment, 2051-2148 rewrite_yaml_file_create)
    - cpp/src/shim.cpp (current sv_register_parser_hooks at 353-407, SemanticViewsParserInfo at 159-181)
    - cpp/src/shim.hpp (declarations matching shim.cpp)
    - src/conn_guard.rs (entire file -- to be deleted)
    - src/lib.rs (current `mod conn_guard;` declaration; sv_register_parser_hooks call site at ~420)
    - 65-CONTEXT.md `<decisions>` D-01, D-02, D-04
    - 65-RESEARCH.md §1.1 Tasks 1, 2 + §2 (v0.9.0 OverrideContext shape verbatim)
    - 65-PATTERNS.md "Plan 03 -- parser_override slimming wave" section, starting at line 64 of PATTERNS.md
    - 65-02-PARTIAL-SUMMARY.md (records which commits to revert and in what order)
    - git history reference: `git show 0d2c0b7^:src/parse.rs` (v0.9.0 shape) and `git show 0d2c0b7^:cpp/src/shim.cpp` (v0.9.0 shim)
  </read_first>
  <behavior>
    - Test 1 (compile gate): `cargo check --features extension --no-default-features` succeeds after the revert -- no dangling reference to db_handle, conn_guard, or the 3-arg sv_register_parser_hooks.
    - Test 2 (link gate): `just build` succeeds -- C++ shim and Rust agree on the v0.9.0 sv_register_parser_hooks ABI (2-arg, duckdb_connection + bool).
    - Test 3 (negative): grep -c "ConnGuard" src/parse.rs returns 0; grep -c "mod conn_guard" src/lib.rs returns 0; src/conn_guard.rs does not exist on disk.
    - Test 4 (positive): grep -n "pub struct OverrideContext" src/parse.rs shows only `pub catalog: crate::catalog::CatalogReader` and `pub is_file_backed: bool` fields.
    - Test 5 (positive): grep -n "INTENTIONAL LEAK" src/parse.rs and cpp/src/shim.cpp both return a match (the v0.9.0 comment text restored).
  </behavior>
  <action>
Hard-revert the Plan 02 partial commits in this order (C++ first per RESEARCH §1.1, so the Rust side does not transiently reference a missing ABI):
  1. Revert commit f9caafe (cpp/src/shim.cpp + shim.hpp). This restores sv_register_parser_hooks to the v0.9.0 2-arg signature `(duckdb_connection catalog_conn, bool is_file_backed)` and restores the INTENTIONAL LEAK comment on `~SemanticViewsParserInfo` per PATTERNS.md lines 283-285.
  2. Revert commit 0d2c0b7 (src/parse.rs). This restores OverrideContext to the v0.9.0 shape with `catalog: CatalogReader, is_file_backed: bool` fields plus the Drop impl with the INTENTIONAL LEAK comment per PATTERNS.md lines 80-93 (use the comment text excerpted in 65-RESEARCH.md §2).

Conflict-resolution rule for the revert: KEEP any commits between Plan 02 partial and current HEAD that touch only `sv_register_table_function` (the C++ Catalog API shim) per D-04 -- but verify with `git log --oneline cpp/src/shim.cpp` that no such commit exists in HEAD (per RESEARCH §5.1 + A2 resolution: there are NO surviving sv_register_table_function commits in HEAD; Plan 04 introduces it from scratch). After verification, the revert is straightforward.

Then perform the two additive deletes:
  3. Delete src/conn_guard.rs entirely (D-02). Remove the `pub mod conn_guard;` declaration from src/lib.rs (currently around line 3, verify with grep).
  4. Verify src/parse.rs no longer imports ConnGuard: grep `use crate::conn_guard` and `ConnGuard::` and remove any remaining references that the revert may have missed because of post-Plan-02 cosmetic edits.

Restore the v0.9.0 init_extension call shape per PATTERNS.md lines 287-303: `sv_register_parser_hooks(catalog_conn, is_file_backed)` where `catalog_conn` is the duckdb_connection allocated at src/lib.rs:386-410 (Plan 06 retires this allocation later).

Do NOT yet touch enrich_definition_for_create or the D-06 hard-error path -- Tasks 2 and 3 own those.

Verify build green before committing.
  </action>
  <verify>
    <automated>cargo check --features extension --no-default-features 2>&amp;1 | tail -20 ; just build 2>&amp;1 | tail -5 ; test ! -f src/conn_guard.rs &amp;&amp; echo "conn_guard.rs deleted OK" ; grep -c "INTENTIONAL LEAK" src/parse.rs cpp/src/shim.cpp</automated>
  </verify>
  <acceptance_criteria>
    - `cargo check --features extension --no-default-features` exits 0
    - `just build` exits 0
    - `test -f src/conn_guard.rs` returns 1 (file does not exist)
    - `grep -c 'pub mod conn_guard' src/lib.rs` returns 0
    - `grep -c 'ConnGuard' src/parse.rs` returns 0
    - `grep -n 'pub struct OverrideContext' src/parse.rs` shows a struct with exactly `pub catalog: ... CatalogReader,` and `pub is_file_backed: bool,` as the fields
    - `grep -c 'INTENTIONAL LEAK' src/parse.rs` returns at least 1; `grep -c 'INTENTIONAL LEAK' cpp/src/shim.cpp` returns at least 1
    - `grep -E 'sv_register_parser_hooks\(' cpp/src/shim.cpp` shows the 2-arg signature `(duckdb_connection catalog_conn, bool is_file_backed)`
    - `grep -E 'sv_make_override_context\s*\(' src/parse.rs` shows the 2-arg signature `(catalog_conn: ... duckdb_connection, is_file_backed: bool)`
  </acceptance_criteria>
  <done>
The Plan 02 partial is reverted. parser_override compiles, links, and runs against the v0.9.0 OverrideContext shape. conn_guard.rs is gone. Plan 03 is now positioned to perform the slimming proper in Tasks 2 + 3.
  </done>
</task>

<task type="auto" tdd="true">
  <name>Task 2 (Wave 1): Delete resolve_pk_from_catalog and add D-06 hard-error path; add 65_pk_error.test</name>
  <files>src/ddl/define.rs, src/parse.rs, test/sql/65_pk_error.test, test/sql/TEST_LIST</files>
  <read_first>
    - src/ddl/define.rs (current state, especially lines 19-76 resolve_pk_from_catalog, line 105 caller inside enrich_definition_for_create)
    - src/parse.rs (call sites at 1519 and 3863 referencing resolve_pk_from_catalog or the "defer to bind time" comment; existing test at 3860-3874 `skips_when_target_has_no_pk_and_no_explicit_ref`)
    - 65-CONTEXT.md `<decisions>` D-05, D-06, D-07
    - 65-RESEARCH.md §1.1 Task 3 (lines 92-94) + §1.1 metadata-via-SQL bullets
    - 65-PATTERNS.md "src/ddl/define.rs::enrich_definition_for_create" section (lines 237-261) and "test/sql/65_pk_error.test (new)" section (lines 305-316)
    - test/sql/phase45_alter_comment.test (existing negative-test sqllogictest shape for the test file's structure)
    - test/sql/TEST_LIST (must add new test entry for the runner to pick it up -- see Phase 63 Plan 02 decision)
    - One in-tree analog showing a `statement error` block: `test/sql/phase45_alter_comment.test` near top of file
  </read_first>
  <behavior>
    - Test 1: CREATE SEMANTIC VIEW v_bad AS TABLES (o AS orders, i AS items REFERENCES o(order_id)) ... with `orders` having NO PRIMARY KEY declared in the TABLES clause fails with a ParseError whose message matches the exact D-06 template in `<interfaces>` (substring match on "Table 'o' has no PRIMARY KEY declared but is referenced by FK in 'i'" is sufficient for the sqllogictest).
    - Test 2: CREATE SEMANTIC VIEW v_good AS TABLES (o AS orders PRIMARY KEY (order_id), i AS items REFERENCES o(order_id)) ... succeeds -- no false-positive on the new error path.
    - Test 3: ALTER SEMANTIC VIEW (any of the 3 existing variants -- RENAME / SET COMMENT / UNSET COMMENT) on a definition that v0.9.0 had auto-inferred a PK for continues to succeed (D-07 -- the validation triggers only at CREATE / ALTER ADD RELATIONSHIP-equivalent paths, which don't exist as of A7).
    - Test 4 (compile gate): grep `resolve_pk_from_catalog` across src/ returns 0 matches.
  </behavior>
  <action>
Delete src/ddl/define.rs::resolve_pk_from_catalog function entirely (lines 19-76 inclusive). Remove the caller at line 105 inside enrich_definition_for_create -- it currently fills in inferred PKs from `duckdb_constraints()`. Without it, downstream graph validation (steps 2-4 of enrich, per PATTERNS.md lines 240-257) detects the missing PK in the FK→PK check at PATTERNS.md lines 246-250 ("3. Catch joins that still have FK columns but no resolved ref_columns").

Replace the existing soft-error message there with the D-06 hard-error template (use the exact text from the `<interfaces>` block above; do NOT paraphrase the message because Task 4's sqllogictest greps it). Implementation: when a join has `!fk_columns.is_empty() && ref_columns.is_empty()` AND the FK target table has no explicit PRIMARY KEY / UNIQUE entry in `def.tables`, emit `ParseError { message: format!("Table '{target}' has no PRIMARY KEY declared but is referenced by FK in '{fk_source}'. Add PRIMARY KEY (cols) or UNIQUE (cols) to the TABLES clause for {target}. (v0.10.0: physical-catalog PK auto-inference removed -- see CHANGELOG.)"), position: None }`.

In src/parse.rs, locate the comment at lines 1518-1523 referencing "At bind time, resolve_pk_from_catalog will attempt to fill in pk_columns..." and replace with a comment pointing to the D-06 path inside enrich_definition_for_create. Delete the inline test `skips_when_target_has_no_pk_and_no_explicit_ref` at parse.rs:3860-3874 -- replace with a new inline test `errors_when_target_has_no_pk_and_no_explicit_ref` asserting the D-06 ParseError message fires when CREATE SEMANTIC VIEW has a FK reference to a table without PK in TABLES.

Create test/sql/65_pk_error.test using the negative-test shape from PATTERNS.md lines 309-316. Two cases: (a) the missing-PK error fires with the expected substring; (b) the same view declared with PRIMARY KEY succeeds. Use `require semantic_views` directive (per existing test convention).

Append `65_pk_error.test` to test/sql/TEST_LIST so the sqllogictest runner picks it up (Phase 63 Plan 02 documented this gate).

Verify the v0.9.0 persisted-definition compat (D-07): a hand-crafted JSON in `_definitions` with a pk_columns array (as v0.9.0 would have written) round-trips through `semantic_view()` SELECT without re-running the D-06 validation. The validation triggers only on the CREATE / ALTER write path, not on the read path. (Implementation note: the D-06 check is gated inside enrich_definition_for_create which is called only from emit_native_create_sql / rewrite_alter -- it is NOT called from read-side bind callbacks. Confirm this gating by grep.)
  </action>
  <verify>
    <automated>just test-sql 2>&amp;1 | tail -20 ; grep -c "resolve_pk_from_catalog" src/ -r ; cargo test --lib --features extension --no-default-features parse::tests::errors_when_target 2>&amp;1 | tail -10</automated>
  </verify>
  <acceptance_criteria>
    - `grep -rc 'resolve_pk_from_catalog' src/` returns 0 (function and all call sites deleted)
    - `cargo test --lib --features extension --no-default-features parse::tests::errors_when_target_has_no_pk_and_no_explicit_ref` exits 0 with the new test passing
    - `cargo test --lib --features extension --no-default-features parse::tests::skips_when_target_has_no_pk_and_no_explicit_ref` returns "test not found" or equivalent (the old test was deleted)
    - `just test-sql test/sql/65_pk_error.test` returns exit 0 with both test cases passing
    - test/sql/TEST_LIST contains a line referencing `65_pk_error.test`
    - The error message in 65_pk_error.test matches the D-06 template, with at least the substring "has no PRIMARY KEY declared but is referenced by FK in" and the parenthetical "(v0.10.0: physical-catalog PK auto-inference removed -- see CHANGELOG.)"
    - Pre-existing sqllogictest files that exercise PK-auto-inference (likely under test/sql/phase26_*.test) either continue to pass because they explicitly declare PRIMARY KEY in TABLES, or are updated to declare PK explicitly. If the runner shows fresh failures, audit and fix in this task.
  </acceptance_criteria>
  <done>
PK auto-inference is gone from the codebase. CREATE / ALTER with a FK reference to a PK-less table fails fast with the D-06 actionable message. The new sqllogictest exercises both the failing and the succeeding case. Persisted v0.9.0 definitions continue to load.
  </done>
</task>

<task type="auto" tdd="true">
  <name>Task 3 (Wave 1): Move metadata capture to SQL expressions; fold existence checks; slim enrich_definition_for_create; add 65_metadata_via_sql.test</name>
  <files>src/parse.rs, src/ddl/define.rs, test/sql/65_metadata_via_sql.test, test/sql/TEST_LIST</files>
  <read_first>
    - src/ddl/define.rs (lines 98-263 enrich_definition_for_create -- post-Task-2 state with §1 already deleted; this task additionally strips §5 metadata at lines 137-161, §6 LIMIT 0 probe at lines 163-219, §7 fact typeof at lines 221-260)
    - src/parse.rs (emit_native_create_sql at 1899-2038, including the two ConnGuard blocks that Task 1 reverted to v0.9.0 lookup+CASE pattern; the existing CASE+error pattern at lines 2024-2035 stays verbatim)
    - 65-CONTEXT.md `<decisions>` D-13 (race-guard pattern unchanged), D-16 (defer type inference to read-side), D-17 (no persisted type cache), `<specifics>` Plan 03 end-state bullets
    - 65-RESEARCH.md §1.1 Task 4 (metadata-via-SQL upgrade, lines 96-115), §1.1 Task 5 (ConnGuard block deletion, IF NOT EXISTS via INSERT OR IGNORE, lines 117-121)
    - 65-PATTERNS.md "emit_native_create_sql" section (lines 198-235) including the post-metadata-via-SQL INSERT shape, and the "enrich_definition_for_create" excerpt at lines 237-261
    - test/sql/phase39_metadata_storage.test (existing assertions about created_on / database_name / schema_name -- these MUST stay green byte-identical)
    - src/parse.rs::race_guard_select (lines 2190-2197) -- D-13 reference; do NOT modify
  </read_first>
  <behavior>
    - Test 1 (positive, mechanism): The native SQL produced by emit_native_create_sql contains the substring `json_merge_patch(` and the substring `current_database()` -- this is the proof that metadata moves to SQL expressions.
    - Test 2 (positive, behavior): After CREATE SEMANTIC VIEW v ..., `SELECT created_on, database_name, schema_name FROM list_semantic_views() WHERE name = 'v'` returns non-null values whose database_name matches `current_database()` and schema_name matches `current_schema()` from the caller's connection.
    - Test 3 (positive, existing behavior preserved): test/sql/phase39_metadata_storage.test continues to pass with no edits -- the user-visible behavior of created_on / database_name / schema_name population is identical to v0.9.0.
    - Test 4 (negative, slimming proof): grep src/ddl/define.rs for `execute_sql_raw\|duckdb_prepare\|conn:.*duckdb_connection` returns 0 hits inside enrich_definition_for_create's function body (the function no longer touches conn).
    - Test 5 (positive, type inference deferred): after CREATE SEMANTIC VIEW v ..., the persisted JSON in semantic_layer._definitions has `column_type_names: []` or absent (D-17 -- no CREATE-time inference); SHOW SEMANTIC COLUMNS still returns rows with `data_type` populated because Plan 05's read-side bind will probe at SHOW time. Plan 03's deliverable is that the JSON shape no longer carries types -- the read-side population is Plan 05's responsibility.
    - Test 6 (D-13 unchanged): rewrite_drop and rewrite_alter_rename still emit the two-statement race-guard pattern (`SELECT CASE WHEN NOT EXISTS ... THEN error(); DELETE/UPDATE ... RETURNING`).
  </behavior>
  <action>
**Slim enrich_definition_for_create** per PATTERNS.md lines 237-261. Drop §5 metadata (lines 137-161 in current src/ddl/define.rs), §6 LIMIT-0 type inference (lines 163-219), §7 fact typeof (lines 221-260). Keep §2 cardinality inference, §3 FK→ref-column check (now hosting the D-06 hard error from Task 2), §4 graph validations, plus the final `serde_json::to_string(&def)`. After the slim, the function signature becomes `pub fn enrich_definition_for_create(name: &str, mut def: SemanticViewDefinition) -> Result<String, String>` (no `conn` param, no `infer_types` param).

The `column_type_names`, `column_types_inferred`, and fact-typeof fields on SemanticViewDefinition stay in the struct (D-17 -- they get populated at read-time via the process-local cache Plan 05 introduces). Plan 03 stops populating them at serialization time, so they serialize as absent via the existing `#[serde(skip_serializing_if = "Vec::is_empty")]` attributes. Verify these `skip_serializing_if` attributes exist on the relevant model.rs fields.

**Rewrite emit_native_create_sql** (src/parse.rs:1899-2038) to:
  - Drop the two ConnGuard blocks at 1940-1969 and 1971-1994 entirely (the existence pre-check is no longer needed; the CASE+error pattern in the INSERT does the work on the caller's connection).
  - Call the slimmed enrich_definition_for_create(name, def) -- no conn, no infer-types flag.
  - Wrap the enriched JSON in the metadata-via-SQL shape from PATTERNS.md lines 220-232 (also shown in the `<interfaces>` block at the top of this PLAN.md):
    `INSERT INTO semantic_layer._definitions (name, definition) SELECT CASE WHEN EXISTS(...) THEN error('...') ELSE '<name_escaped>' END, json_merge_patch('<enriched_json>'::JSON, json_object('created_on', strftime(now(), '%Y-%m-%dT%H:%M:%SZ'), 'database_name', current_database(), 'schema_name', current_schema()))::VARCHAR RETURNING name AS view_name`
  - The existing CASE+error message at parse.rs:2024-2035 stays verbatim -- this is the "friendly already-exists" message and the test surface (phase39, phase45) depends on the exact wording.
  - For OR REPLACE: drop the CASE+EXISTS pre-check; emit straight `INSERT ... ON CONFLICT (name) DO UPDATE SET definition = excluded.definition RETURNING name AS view_name` per CONTEXT.md `<specifics>` Plan 03 end-state.
  - For IF NOT EXISTS: emit `INSERT OR IGNORE INTO ... RETURNING name AS view_name` per RESEARCH §1.1 Task 5; this silently absorbs PK conflict and produces 0 rows on collision, matching the existing v0.9.0 sentinel behavior.

**Existence-check fold for DROP/ALTER (rewrite_drop, rewrite_alter_rename):** D-13 says the Phase 60 two-statement race-guard pattern carries forward unchanged. Verify rewrite_drop at parse.rs:2200-2249 still emits `{race_guard_select}; DELETE ... RETURNING name` (no catalog read needed -- the race guard does the work on the caller's connection). For DROP IF EXISTS, drop the race-guard prefix entirely (just emit `DELETE ... RETURNING name` -- the no-row case silently succeeds, which is the IF EXISTS semantic). Apply analogous structure to rewrite_alter_rename per RESEARCH §1.1 Task 6 -- you may keep the dual-pre-check (which uses CatalogReader from OverrideContext, still allowed in Plan 03 since H1 is unretired) OR adopt the combined CASE+error shape from PATTERNS.md lines 150-158. Use the existing dual-pre-check for minimum churn; Plan 04 may consolidate during the ALTER architecture wave.

**For rewrite_alter_comment:** Plan 03 reverts to the v0.9.0 lookup+mutate+reserialize shape (catalog.lookup() on the OverrideContext's CatalogReader). Plan 04 upgrades to json_merge_patch -- do NOT do that upgrade in this task to keep the diff minimal.

**For rewrite_yaml_file_create:** Plan 03 reverts to v0.9.0 CatalogReader-via-OverrideContext read_text path per RESEARCH §1.1 Task 7 option (a). Plan 04 owns the helper-TF migration.

**Create test/sql/65_metadata_via_sql.test** with three assertions:
  1. After CREATE SEMANTIC VIEW v ..., the emitted native SQL (captured via EXPLAIN or by re-creating and querying list_semantic_views) populates database_name = current_database() and schema_name = current_schema().
  2. The current_database() / current_schema() values come from the caller's connection, not from a stashed extension-side connection -- assert by using ATTACH 'tmp.db' AS db2; USE db2; CREATE SEMANTIC VIEW v2 ...; SELECT database_name FROM list_semantic_views() WHERE name = 'v2' -- expect 'db2'.
  3. created_on is non-null and parseable as a timestamp.

Append `65_metadata_via_sql.test` to test/sql/TEST_LIST.

**Phase 39 regression guard:** run `just test-sql test/sql/phase39_metadata_storage.test` and confirm it passes byte-identical with no edits. If it fails, the metadata-via-SQL shape diverges from v0.9.0 and you must reconcile before declaring the task done.
  </action>
  <verify>
    <automated>just test-sql 2>&amp;1 | tail -30 ; just test-sql test/sql/65_metadata_via_sql.test 2>&amp;1 | tail -10 ; just test-sql test/sql/phase39_metadata_storage.test 2>&amp;1 | tail -10 ; cargo test --lib --features extension --no-default-features 2>&amp;1 | tail -10 ; uv run test/integration/test_adbc_transactions.py 2>&amp;1 | tail -10</automated>
  </verify>
  <acceptance_criteria>
    - `just test-sql` returns exit 0 with at least 49 tests passing (47 baseline + 65_pk_error + 65_metadata_via_sql). Any failures unrelated to metadata semantics must be reconciled before completion.
    - `just test-sql test/sql/phase39_metadata_storage.test` exits 0 (user-visible metadata behavior unchanged from v0.9.0).
    - `just test-sql test/sql/65_metadata_via_sql.test` exits 0 (all three new assertions pass).
    - `cargo test --lib --features extension --no-default-features` exits 0.
    - `uv run test/integration/test_adbc_transactions.py` exits 0 -- D-21 transactional DDL invariant.
    - grep `execute_sql_raw\|duckdb_prepare\|duckdb_bind` src/ddl/define.rs returns 0 matches inside the body of enrich_definition_for_create (function no longer touches conn).
    - grep `json_merge_patch.*current_database` src/parse.rs returns at least 1 match (metadata-via-SQL emitted).
    - grep `infer_types` src/ddl/define.rs returns 0 matches (CREATE-time type inference removed per D-16).
    - The persisted JSON of a freshly-CREATEd semantic view contains `created_on`, `database_name`, `schema_name` keys, but NOT `column_type_names` or `column_types_inferred` (D-17 -- type inference deferred to read-side; Plan 05 populates at SHOW/DESCRIBE bind time).
  </acceptance_criteria>
  <done>
parser_override emits transactional DDL with metadata captured by SQL on the caller's connection. No call site under parser_override consumes a connection for §1/§5/§6/§7 purposes. The H1 catalog_conn allocation in init_extension is still present (Plan 06 retires it) but no parser_override code path needs it. just test-sql is green at 49/49. ADBC transactional tests stay green.
  </done>
</task>

</tasks>

<threat_model>
## Trust Boundaries

| Boundary | Description |
|----------|-------------|
| C-API ↔ Rust FFI (sv_make_override_context, sv_parser_override_rust) | Crossed at extension load + every parser_override call; untrusted pointers and panic-on-unwind UB |
| Rust ↔ C++ shim (sv_register_parser_hooks) | Crossed at extension load; ABI mismatch causes link-time / runtime crashes |
| User SQL → parser_override rewrite path | Untrusted view names, comments, file paths reach our emitted SQL; SQL injection / JSON injection class |
| Caller's connection ↔ semantic_layer._definitions writes | Transactional DDL contract (D-21) -- the rewrite must preserve transaction semantics |

## STRIDE Threat Register

| Threat ID | Category | Component | Disposition | Mitigation Plan |
|-----------|----------|-----------|-------------|-----------------|
| T-65-03-01 | Tampering | parser_override emitted SQL (metadata-via-SQL INSERT) | mitigate | Embed user-supplied view name and JSON via `escape_sql_arg` (existing helper at src/parse.rs:2170); never concatenate raw strings into the SQL literal. JSON content built via `serde_json::to_string` (handles internal `"` escape). Verify Task 3's emitted SQL passes `escape_sql_arg` through every VARCHAR slot. |
| T-65-03-02 | Information Disclosure | D-06 error message | mitigate | The exact message template in `<interfaces>` is user-actionable and lists no catalog internals (no table OIDs, no internal schema details). Plan 06 mirror in CHANGELOG. |
| T-65-03-03 | Denial of Service | enrich_definition_for_create graph validation | accept | Existing validators (graph::validate_graph, validate_facts, validate_derived_metrics, validate_using_relationships) are unchanged; bounded by O(views * tables) which is small for any realistic semantic-view payload. |
| T-65-03-04 | Tampering | FFI signature mismatch between Rust sv_make_override_context and C++ sv_register_parser_hooks after revert | mitigate | Build gate at Task 1 (`cargo check` + `just build`) catches mismatch at link time. The 2-arg shape (duckdb_connection, bool) is the single source of truth; both sides must match. |
| T-65-03-05 | Tampering | OverrideContext Drop impl's INTENTIONAL LEAK | accept | One duckdb_connection leaked per DB ever opened in the process; few KB each (per Phase 62 §Q2 documented analysis). Plan 06 retires H1 entirely so the leak count drops to zero. Restoring the comment in Plan 03 is transitional -- not a regression. |
| T-65-03-06 | Repudiation / Audit | created_on timestamp via SQL `now()` vs Rust-side `strftime` | mitigate | `now()` on the caller's connection records the user's transaction boundary, which is MORE accurate than v0.9.0's Rust-side capture (which ran on the long-lived catalog_conn outside any transaction). Net improvement for audit semantics. |
| T-65-03-SC | Tampering | npm/pip/cargo installs | mitigate | No new package installs in Plan 03 -- the revert + slimming uses only existing dependencies (serde_json, libduckdb-sys, syn at most). RESEARCH did not flag any [ASSUMED]/[SUS] packages. |
</threat_model>

<verification>
- `cargo check --features extension --no-default-features` exits 0
- `just build` exits 0
- `cargo test --lib --features extension --no-default-features` exits 0
- `just test-sql` exits 0 with all baseline tests + 65_pk_error.test + 65_metadata_via_sql.test passing (49+ total)
- `just test-all` exits 0 (Rust + sqllogictest + DuckLake CI)
- `uv run test/integration/test_adbc_transactions.py` exits 0 (D-21 transactional invariant)
- `test ! -f src/conn_guard.rs` (file deleted)
- `grep -c 'resolve_pk_from_catalog' src/` returns 0
- `grep -c 'json_merge_patch.*current_database' src/parse.rs` returns at least 1
- A persisted definition (post-CREATE) shows database_name/schema_name from the caller's connection context, not the extension-side connection
</verification>

<success_criteria>
- parser_override has zero catalog reads (no call to CatalogReader, no execute_sql_raw, no duckdb_prepare inside any rewrite_* code path)
- D-06 hard error fires deterministically when CREATE / ALTER has a FK reference to a PK-less table
- v0.9.0 persisted definitions continue to load + query + introspect (D-07)
- Phase 58 ADBC transactional tests stay green (D-21)
- H1 catalog_conn allocation in init_extension is still present but unused; Plan 06 retires it
- Test surface count: 47 baseline sqllogictests + 2 new (65_pk_error, 65_metadata_via_sql) = 49 minimum passing
</success_criteria>

<output>
Create `.planning/phases/65-overridecontext-connection-teardown/65-03-SUMMARY.md` documenting:
- Commits reverted (with SHAs and conflict-resolution notes)
- Exact diff stat of files touched
- Confirmation that just test-sql is green at 49/49 (or higher)
- Confirmation that test/integration/test_adbc_transactions.py passes
- Any TECH-DEBT entries surfaced by D-22 (e.g. if the metadata-via-SQL shape produced a subtle behavior change in an edge case, document for Phase 66 follow-up)
- Forward pointer: "H1 catalog_conn allocation remains at src/lib.rs:386-410 unused; Plan 06 retires it."
</output>
