---
phase: 68
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
  - src/body_parser.rs
  - test/sql/phase67_quoted_source_tables.test
  - test/integration/test_adbc_queries.py
autonomous: true
scope_items: [A1, A2, A3, A4, A5, A6, A7]
threat_refs: [T-68-01, T-68-02]
must_haves:
  truths:
    - "DDL `o AS PRIMARY KEY (id)` (bare reserved keyword after AS) surfaces the literal pre-Phase-67 error message `Missing physical table name after AS for alias 'o' in TABLES clause.` (A1)."
    - "DDL `o AS \"unclosed` (unterminated quoted identifier in TABLES clause) is rejected at parse time with a structured ParseError, never silently captured as a malformed table name (A4)."
    - "DDL `o AS staging.\"my orders\" PRIMARY KEY (id)` (mixed bare/quoted dot-qualified source-table name) parses correctly into a TableRef with the quoted segment intact (A5)."
    - "`test_adbc_queries.py::test_attached_database_path` (or whichever test exercises line 470's `ATTACH '{other_db_path}' AS db2`) escapes `other_db_path` via `.replace(\"'\", \"''\")` consistent with `_bootstrap_extension`'s line 100 treatment of `extension_path` (A2)."
    - "`find_primary_key`'s three word-boundary checks (PRIMARY-before, PRIMARY-after, KEY-after) all exclude `_` exactly like `find_unique` does (A7)."
    - "Existing `phase67_quoted_source_tables.test` cleanup block drops the 3 default-schema base tables it currently leaves behind (A6)."
  artifacts:
    - path: "src/body_parser.rs"
      provides: "parse_single_table_entry post-A3 dead-loop-collapse + A1 reserved-keyword guard + A4 balanced-quote check + A7 word-boundary alignment of find_primary_key with find_unique"
      contains: "Missing physical table name after AS"
    - path: "test/sql/phase67_quoted_source_tables.test"
      provides: "Scenario 5 mixed bare/quoted dot-qualified fixture row + extended cleanup"
      contains: "staging.\"my orders\""
    - path: "test/integration/test_adbc_queries.py"
      provides: "Line 470 ATTACH path with SQL-string escape parity"
      contains: "other_db_path_sql"
  key_links:
    - from: "src/body_parser.rs::parse_single_table_entry"
      to: "src/ident.rs::find_identifier_end"
      via: "single call replacing the dead dot-rejoin loop (A3 collapse)"
      pattern: "find_identifier_end\\(after_as, "
---

<objective>
Land seven Phase 67 REVIEW.md follow-up items (A1, A2, A3, A4, A5, A6, A7) in a single coherent plan. All seven are surgical: six target `src/body_parser.rs`, one targets `test/integration/test_adbc_queries.py`, and two extend `test/sql/phase67_quoted_source_tables.test`.

The critical sub-bundle is A3 (collapse the unreachable dot-rejoin loop in `parse_single_table_entry`) + A1 (insert a reserved-keyword guard at the post-collapse site). Per D-02, these MUST land in a single atomic commit, ordered: A3 collapse FIRST, then A1 guard at the clean site.

Purpose: Pre-tag hygiene before v0.10.0 milestone close. Each item is non-blocking individually; together they close out the entire Phase 67 REVIEW.md mechanical-fix backlog.

Output: One commit covering A1+A3 bundled, plus follow-on commits for A2, A4+A7 (body_parser surgical edits), and A5+A6 (sqllogictest fixture extension). Final state: 5 new Rust unit tests in `src/body_parser.rs::tests`, one new scenario in `phase67_quoted_source_tables.test`, one extended ADBC test line.
</objective>

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

<context>
@.planning/PROJECT.md
@.planning/STATE.md
@.planning/phases/68-pre-tag-cleanup-phase-67-review-pr-35-code-review-follow-ups/68-CONTEXT.md
@.planning/phases/68-pre-tag-cleanup-phase-67-review-pr-35-code-review-follow-ups/SCOPE.md
@.planning/phases/68-pre-tag-cleanup-phase-67-review-pr-35-code-review-follow-ups/68-RESEARCH.md
@.planning/phases/68-pre-tag-cleanup-phase-67-review-pr-35-code-review-follow-ups/68-VALIDATION.md
@.planning/phases/67-expansion-sql-coverage-and-tech-debt-cleanup/67-REVIEW.md
@CLAUDE.md

<interfaces>
<!-- Key existing signatures the executor needs. Extracted verbatim from src/body_parser.rs and src/ident.rs. -->
<!-- Executor MUST use these directly — no exploration needed. -->

From `src/ident.rs` (Phase 64 helper, also walked across dots while outside quoted regions):

```rust
pub fn find_identifier_end(input: &str, allow_paren: bool) -> usize
// Returns input.len() saturating on unterminated quote.
// Delimiter set: whitespace, ';', '(' (when allow_paren=true). Never '.'.
// Doubled `""` inside a quoted segment is treated as an escape (does NOT close).
```

From `src/body_parser.rs:661-810` (current parse_single_table_entry shape):

```rust
fn parse_single_table_entry(entry: &str, entry_offset: usize) -> Result<TableRef, ParseError>
// Step 3 currently at lines 692-709 wraps find_identifier_end in a dot-rejoin loop.
// `name_end == 0` returns the "Missing physical table name after AS" error today.
// `table_name = after_as[..name_end].trim()` at line 710.
// Empty-check at line 711 surfaces same error if trim yielded empty.
```

From `src/body_parser.rs:815-905`:

```rust
fn find_unique(upper_text: &str) -> Option<(usize, usize)>
// before_ok: i == 0 || (!bytes[i-1].is_ascii_alphanumeric() && bytes[i-1] != b'_')
// after_ok:  end-of-text || (!bytes[i+kw_len].is_ascii_alphanumeric() && bytes[i+kw_len] != b'_')

fn find_primary_key(upper_text: &str) -> Option<(usize, usize)>
// before_ok at line 881: i == 0 || !bytes[i-1].is_ascii_alphanumeric()  ← MISSING `_` exclusion
// after_primary at line 884: !bytes[after_primary].is_ascii_alphanumeric()  ← MISSING `_` exclusion
// after_key at line 894-895: !bytes[after_key].is_ascii_alphanumeric()  ← MISSING `_` exclusion
```

From `test/integration/test_adbc_queries.py:95-103` (the parity reference for A2):

```python
def _bootstrap_extension(conn, extension_path: Path) -> None:
    extension_path_sql = str(extension_path).replace("'", "''")
    _execute(conn, f"FORCE INSTALL '{extension_path_sql}'")
    _execute(conn, "LOAD semantic_views")
    conn.commit()
```
</interfaces>
</context>

<tasks>

<task type="auto" tdd="true">
  <name>Task 1: A3 + A1 — collapse dead dot-rejoin loop and insert reserved-keyword guard (single atomic commit, A3 first then A1)</name>
  <files>src/body_parser.rs</files>
  <read_first>
    - src/body_parser.rs (lines 661-720 — current parse_single_table_entry)
    - src/ident.rs (lines 186-220 — find_identifier_end signature + dot-walk + escape semantics)
    - .planning/phases/68-pre-tag-cleanup-phase-67-review-pr-35-code-review-follow-ups/68-CONTEXT.md (D-02 + D-03 — bundle ordering and authoritative keyword set)
    - .planning/phases/68-pre-tag-cleanup-phase-67-review-pr-35-code-review-follow-ups/68-RESEARCH.md (Item-by-Item §A1 and §A3 — Landmine #1 and Landmine #2)
  </read_first>
  <behavior>
    - Test 1 (A1): `parse_single_table_entry("o AS PRIMARY KEY (id)", 0)` returns `Err(ParseError)` with `message == "Missing physical table name after AS for alias 'o' in TABLES clause."`.
    - Test 2 (A1): same for `UNIQUE`, `FOREIGN`, `REFERENCES`, `NOT` (5 variants total, case-insensitive — also test lowercase `primary`).
    - Test 3 (A3): happy path `parse_single_table_entry("o AS my_table PRIMARY KEY (id)", 0)` returns `Ok(TableRef { alias: "o", table_name: "my_table", pk_columns: ["id"], .. })` — no behavioural regression vs. pre-edit.
    - Test 4 (A3): dot-walk still works — `parse_single_table_entry("o AS schema.tbl PRIMARY KEY (id)", 0)` captures `table_name = "schema.tbl"` (find_identifier_end natively walks across dots outside quotes).
    - Test 5 (A1 message exactness): the error message MUST be the literal string `"Missing physical table name after AS for alias 'o' in TABLES clause."` — no rewording; this is the pre-Phase-67 contract restoration per D-03.
  </behavior>
  <action>
    **Commit ordering (D-02): single atomic commit, diff ordered A3 collapse FIRST then A1 guard at the clean site.**

    Step A — A3 collapse: replace the loop body at `src/body_parser.rs:692-709` with a single `find_identifier_end` call. The loop arm at lines 704-707 (`if after_as.as_bytes()[name_end] == b'.' { name_end += 1; continue; }`) is unreachable because `find_identifier_end` already walks across dots while outside quoted regions (verified by `src/ident.rs` doctest `fqn_with_quoted_parts_runs_to_whitespace`). The replacement is:

    ```
    let name_end = find_identifier_end(after_as, /* allow_paren = */ true);
    if name_end == 0 {
        return Err(ParseError {
            message: format!(
                "Missing physical table name after AS for alias '{alias}' in TABLES clause.",
            ),
            position: Some(after_as_offset),
        });
    }
    ```

    (No `mut`, no loop, no continue.)

    Step B — A1 guard: immediately after the `name_end == 0` empty-check from Step A, BEFORE line 710's `let table_name = after_as[..name_end].trim();`, insert a guard that uppercases `after_as[..name_end].trim()` and matches against the literal set `["PRIMARY", "UNIQUE", "FOREIGN", "REFERENCES", "NOT"]` (per D-03 — NOT the REVIEW.md draft `PRIMARY|UNIQUE|COMMENT|WITH`). On match, return the same `ParseError` with the same `"Missing physical table name after AS for alias '{alias}' in TABLES clause."` message and `position: Some(after_as_offset)`. Pseudocode:

    ```
    let captured = after_as[..name_end].trim();
    let upper_captured = captured.to_ascii_uppercase();
    if matches!(upper_captured.as_str(), "PRIMARY" | "UNIQUE" | "FOREIGN" | "REFERENCES" | "NOT") {
        return Err(ParseError {
            message: format!(
                "Missing physical table name after AS for alias '{alias}' in TABLES clause.",
            ),
            position: Some(after_as_offset),
        });
    }
    ```

    Add a code comment above the guard citing `D-03 (Phase 68)` so the keyword set's provenance is traceable.

    Step C — Add unit tests `test_parse_single_table_entry_reserved_keyword_after_as_PRIMARY`, `..._UNIQUE`, `..._FOREIGN`, `..._REFERENCES`, `..._NOT` in `src/body_parser.rs::tests`. Each asserts the exact error message from D-03. Also add `test_parse_single_table_entry_reserved_keyword_after_as_lowercase` asserting `o AS primary KEY (id)` triggers the guard (case-insensitive contract).

    Step D — Stage and commit. Single commit message: `fix(68-01): A1+A3 — reject reserved keywords as bare table names, collapse dead dot-rejoin loop in parse_single_table_entry`. NO `--no-verify`. If pre-commit hook flags fmt, run `cargo fmt`, re-stage, retry.
  </action>
  <verify>
    <automated>cargo test --lib body_parser::tests::test_parse_single_table_entry_reserved_keyword_after_as &amp;&amp; cargo test --lib body_parser::tests::test_parse_single_table_entry_quoted_with_internal_whitespace &amp;&amp; cargo test --lib body_parser::tests::test_parse_single_table_entry_3part_quoted_fqn_with_whitespace</automated>
  </verify>
  <acceptance_criteria>
    - `src/body_parser.rs` no longer contains the byte sequence `if after_as.as_bytes()[name_end] == b'.'` (A3 collapse verified).
    - `src/body_parser.rs` contains a guard rejecting (`to_ascii_uppercase` matches) one of `["PRIMARY", "UNIQUE", "FOREIGN", "REFERENCES", "NOT"]` between the find_identifier_end call and `let table_name = after_as[..name_end].trim();` — verifiable via `grep -n 'PRIMARY.*UNIQUE.*FOREIGN' src/body_parser.rs` returning at least one line.
    - 6 new `test_parse_single_table_entry_reserved_keyword_after_as_*` unit tests pass under `cargo test --lib`.
    - Existing TECH-DEBT #24 regression tests still pass: `test_parse_single_table_entry_quoted_with_internal_whitespace`, `test_parse_single_table_entry_quoted_containing_primary_key_substring`, `test_parse_single_table_entry_3part_quoted_fqn_with_whitespace`, `test_parse_single_table_entry_regression_no_whitespace`, `test_parse_single_table_entry_quoted_with_unique_no_pk` — all exit 0 under `cargo test --lib body_parser::tests`.
    - The commit landing A1+A3 is a single atomic commit (verified via `git log -1 --oneline | grep "A1+A3"` or equivalent commit message check).
    - `cargo fmt --check` exits 0 and pre-commit hook passes without `--no-verify`.
  </acceptance_criteria>
  <done>A1 + A3 land bundled in one commit. The literal pre-Phase-67 error message is restored for all 5 reserved keywords. Dead loop arm is gone. No behavioural regression on existing fixtures.</done>
</task>

<task type="auto" tdd="true">
  <name>Task 2: A4 + A7 — balanced-quote check for unterminated quoted identifier + find_primary_key word-boundary alignment</name>
  <files>src/body_parser.rs</files>
  <read_first>
    - src/body_parser.rs (lines 692-720 — post-Task-1 parse_single_table_entry; lines 815-905 — find_unique and find_primary_key)
    - src/ident.rs (lines 186-220 — find_identifier_end's quoted-escape `""` rule, for A4 helper to mirror)
    - .planning/phases/68-pre-tag-cleanup-phase-67-review-pr-35-code-review-follow-ups/68-CONTEXT.md (D-05 — A4/A7 are surgical edits)
    - .planning/phases/68-pre-tag-cleanup-phase-67-review-pr-35-code-review-follow-ups/68-RESEARCH.md (Item-by-Item §A4 and §A7 — Landmine #4)
  </read_first>
  <behavior>
    - Test 1 (A4): `parse_single_table_entry("o AS \"unclosed", 0)` (no closing quote) returns `Err(ParseError)` with message containing `"Unterminated quoted identifier"` (or similar literal string the executor chooses, but pinned by the unit test).
    - Test 2 (A4): doubled-quote escape is balanced — `parse_single_table_entry("o AS \"a\"\"b\" PRIMARY KEY (id)", 0)` returns `Ok(_)` (no false positive on `""` escape).
    - Test 3 (A4): mixed unbalanced — `parse_single_table_entry("o AS \"a\"\"b PRIMARY KEY (id)", 0)` (odd unescaped quotes) returns `Err(ParseError)` with the same message.
    - Test 4 (A7, optional but recommended): `find_primary_key("my_PRIMARY KEY")` returns `None` (the `_PRIMARY` prefix should not match because `_` is now excluded from the before-boundary). Add `test_find_primary_key_word_boundary_underscore`.
  </behavior>
  <action>
    Step A — A4: Add a private helper `fn is_quoting_balanced(s: &str) -> bool` in `src/body_parser.rs` (5-10 LOC). Walk `s.as_bytes()`; when entering a quote (`"`) toggle an `in_quote` flag, BUT skip a doubled-quote `""` as an escape (advance two bytes, do not toggle). At end of string, return `!in_quote`. This mirrors the escape rule used inside `src/ident.rs::find_identifier_end`. Do NOT use `s.matches('"').count() % 2 == 0` — that is wrong (see RESEARCH Landmine #4).

    Call site: in `parse_single_table_entry`, immediately after Task 1's A1 guard and before `let table_name = after_as[..name_end].trim();`, run `is_quoting_balanced(&after_as[..name_end])`. If `false`, return `Err(ParseError { message: format!("Unterminated quoted identifier in source-table name for alias '{alias}' in TABLES clause."), position: Some(after_as_offset) })`. The exact message string is the contract pinned by the unit test below.

    Add unit tests `test_parse_single_table_entry_unterminated_quote`, `test_parse_single_table_entry_quoted_with_doubled_escape_balanced` (must succeed), `test_parse_single_table_entry_unbalanced_after_doubled_escape` (must fail) in `src/body_parser.rs::tests`.

    Step B — A7: Align `find_primary_key`'s three word-boundary checks with `find_unique`. Locations in `src/body_parser.rs`:
    - Line 881 (PRIMARY before-check): change `i == 0 || !bytes[i - 1].is_ascii_alphanumeric()` to `i == 0 || (!bytes[i - 1].is_ascii_alphanumeric() && bytes[i - 1] != b'_')`.
    - Line 884 (PRIMARY after-check): change `!bytes[after_primary].is_ascii_alphanumeric()` to `!bytes[after_primary].is_ascii_alphanumeric() && bytes[after_primary] != b'_'`.
    - Line 894-895 (KEY after-check): change `!bytes[after_key].is_ascii_alphanumeric()` to `!bytes[after_key].is_ascii_alphanumeric() && bytes[after_key] != b'_'`.

    Add an optional unit test `test_find_primary_key_word_boundary_underscore` asserting `find_primary_key(&"my_PRIMARY KEY".to_ascii_uppercase()) == None` and `find_primary_key(&"PRIMARY KEY_extra".to_ascii_uppercase()) == None`.

    Step C — Stage and commit. Single commit message: `fix(68-01): A4+A7 — reject unterminated quoted identifier in TABLES clause; align find_primary_key word boundaries with find_unique`.
  </action>
  <verify>
    <automated>cargo test --lib body_parser::tests::test_parse_single_table_entry_unterminated_quote &amp;&amp; cargo test --lib body_parser::tests::test_parse_single_table_entry_quoted_with_doubled_escape_balanced &amp;&amp; cargo test --lib body_parser::tests::test_find_primary_key_word_boundary_underscore</automated>
  </verify>
  <acceptance_criteria>
    - `src/body_parser.rs` defines `fn is_quoting_balanced(s: &str) -> bool` (or equivalent private helper) — verifiable via `grep -n 'fn is_quoting_balanced' src/body_parser.rs` returning ≥ 1 line.
    - `parse_single_table_entry("o AS \"unclosed", 0)` returns `Err(_)` with message containing literal substring `"Unterminated quoted identifier"`.
    - `parse_single_table_entry("o AS \"a\"\"b\" PRIMARY KEY (id)", 0)` returns `Ok(_)` (doubled-quote escape is balanced — no false-positive rejection).
    - `find_primary_key`'s three word-boundary checks all reference `b'_'` as an exclusion — verifiable via `grep -c "b'_'" src/body_parser.rs` returning ≥ 5 (3 new + 2 existing in `find_unique`).
    - 3+ new unit tests pass under `cargo test --lib`.
    - All pre-existing tests still pass: `cargo test --lib body_parser::tests` exits 0.
    - `cargo fmt --check` exits 0; clippy passes; no `--no-verify`.
  </acceptance_criteria>
  <done>A4 surface adds a structured ParseError for unterminated quoted identifiers without false-positive on `""` escape. A7 surface aligns three boundary checks. New tests pin both contracts.</done>
</task>

<task type="auto" tdd="true">
  <name>Task 3: A2 + A5 + A6 — ADBC SQL-string escape parity; mixed bare/quoted scenario; cleanup hygiene</name>
  <files>test/integration/test_adbc_queries.py, test/sql/phase67_quoted_source_tables.test, src/body_parser.rs</files>
  <read_first>
    - test/integration/test_adbc_queries.py (lines 88-105 — `_bootstrap_extension`'s escape pattern; lines 460-490 — the test using line 470's ATTACH path)
    - test/sql/phase67_quoted_source_tables.test (full file — current 4 scenarios + cleanup block lines 139-156)
    - .planning/phases/68-pre-tag-cleanup-phase-67-review-pr-35-code-review-follow-ups/68-CONTEXT.md (D-04 + D-06)
    - .planning/phases/68-pre-tag-cleanup-phase-67-review-pr-35-code-review-follow-ups/68-RESEARCH.md (Item-by-Item §A2, §A5, §A6 — Landmine #7)
  </read_first>
  <behavior>
    - Test 1 (A2): the test currently exercising `ATTACH '{other_db_path}'` (around line 470) interpolates a path containing a literal `'` correctly — escaped via `.replace("'", "''")` before f-string substitution. No new test required; existing test must continue passing.
    - Test 2 (A5 sqllogictest): Scenario 5 in `phase67_quoted_source_tables.test` creates a semantic view using a mixed bare/quoted dot-qualified source-table name (`staging."my orders"`) and runs a query against it that returns at least one row.
    - Test 3 (A5 Rust unit): `test_parse_single_table_entry_mixed_quoted_and_bare` asserts `parse_single_table_entry("o AS staging.\"my orders\" PRIMARY KEY (id)", 0)` returns `Ok(TableRef { alias: "o", table_name: "staging.\"my orders\"", pk_columns: ["id"], .. })`.
    - Test 4 (A6): after the cleanup block runs, no `phase67_quoted_source_tables.test` base tables are left in the default schema.
  </behavior>
  <action>
    Step A — A2: In `test/integration/test_adbc_queries.py` around line 470, change the f-string:

    ```
    _execute(conn, f"ATTACH '{other_db_path}' AS db2")
    ```

    to:

    ```
    other_db_path_sql = str(other_db_path).replace("'", "''")
    _execute(conn, f"ATTACH '{other_db_path_sql}' AS db2")
    ```

    Match `_bootstrap_extension` (line 100) byte-for-byte — same variable-suffix convention (`_sql`), same escape call. Add a brief inline comment: `# A2 (Phase 68): SQL-string escape parity with line 100`. Do NOT extract a shared helper — the parity is local and an extraction would over-engineer per RESEARCH Landmine #7.

    Step B — A5 sqllogictest: Insert a new Scenario 5 block in `test/sql/phase67_quoted_source_tables.test` (before the cleanup block at line 139). Scenario 5 must:
    1. `statement ok\nCREATE SCHEMA staging;`
    2. `statement ok\nCREATE TABLE staging."my orders" (id BIGINT PRIMARY KEY, region VARCHAR, total DOUBLE);`
    3. `statement ok\nINSERT INTO staging."my orders" VALUES (1, 'US', 100.0), (2, 'EU', 250.0);`
    4. `statement ok\nCREATE SEMANTIC VIEW p67_mixed_quoting AS TABLES (o AS staging."my orders" PRIMARY KEY (id)) FACTS (o.total AS o.total) DIMENSIONS (o.region AS o.region) METRICS (o.total_sum AS sum(o.total));`
    5. `query I\nSELECT count(*) > 0 FROM semantic_view('p67_mixed_quoting', dimensions := ['region'], metrics := ['total_sum']);\n----\ntrue`
    6. Optionally one `explain_semantic_view` query asserting the staging-qualified name appears intact in the generated SQL.

    Step C — A5 Rust unit: Add `test_parse_single_table_entry_mixed_quoted_and_bare` in `src/body_parser.rs::tests` covering both `staging."my orders"` and the symmetric case `"my db".sch.t`. Pin `table_name` to the exact captured string.

    Step D — A6: Extend the cleanup block in `test/sql/phase67_quoted_source_tables.test` (currently lines 139-156). Append three new `statement ok` rows (the 3 base tables currently leaked per RESEARCH §A6):

    ```
    statement ok
    DROP TABLE IF EXISTS "my orders";

    statement ok
    DROP TABLE IF EXISTS "weird PRIMARY KEY name";

    statement ok
    DROP TABLE IF EXISTS p67_plain_orders;
    ```

    Also, if Scenario 5 from Step B created `staging."my orders"`, append:

    ```
    statement ok
    DROP SCHEMA staging CASCADE;
    ```

    This block MUST be appended AFTER the existing `DROP SCHEMA "stage 2" CASCADE;` row so existing ordering is preserved.

    Step E — Build the extension (so `just test-sql` picks up Task-1/Task-2 changes) and verify:

    ```
    just build > /tmp/claude/build_a5.log 2>&1
    RC=$?
    tail -50 /tmp/claude/build_a5.log
    ```

    Then run the affected fixture and ADBC integration:

    ```
    just test-sql phase67_quoted_source_tables > /tmp/claude/sqllog.log 2>&1
    tail -50 /tmp/claude/sqllog.log
    uv run pytest test/integration/test_adbc_queries.py -k attached > /tmp/claude/adbc.log 2>&1
    tail -50 /tmp/claude/adbc.log
    ```

    Step F — Stage and commit as ONE commit covering A2 + A5 + A6: `test(68-01): A2 ADBC escape parity; A5 mixed-quoting scenario + Rust unit; A6 cleanup hygiene`.
  </action>
  <verify>
    <automated>cargo test --lib body_parser::tests::test_parse_single_table_entry_mixed_quoted_and_bare &amp;&amp; just test-sql phase67_quoted_source_tables</automated>
  </verify>
  <acceptance_criteria>
    - `test/integration/test_adbc_queries.py` near line 470 contains a `replace("'", "''")` call applied to `other_db_path` before interpolation — verifiable via `grep -n 'other_db_path' test/integration/test_adbc_queries.py` showing both an `other_db_path_sql` (or equivalently-named) variable assignment AND the f-string referencing the escaped variable.
    - `test/sql/phase67_quoted_source_tables.test` contains a new scenario referencing `staging."my orders"` — verifiable via `grep -n 'staging\."my orders"' test/sql/phase67_quoted_source_tables.test` returning ≥ 2 lines (CREATE TABLE + CREATE SEMANTIC VIEW).
    - `just test-sql phase67_quoted_source_tables` exits 0 (sandbox bypass acceptable for `just` per CLAUDE.md).
    - `cargo test --lib body_parser::tests::test_parse_single_table_entry_mixed_quoted_and_bare` exits 0.
    - The cleanup block in `phase67_quoted_source_tables.test` drops `"my orders"`, `"weird PRIMARY KEY name"`, `p67_plain_orders` (and `staging` schema if Scenario 5 created it) — verifiable via `grep -c 'DROP TABLE IF EXISTS' test/sql/phase67_quoted_source_tables.test` returning ≥ 3.
    - The ADBC test (`test_adbc_queries.py`) exercising the attached-database scenario still passes (`uv run pytest test/integration/test_adbc_queries.py` exit 0 for the relevant test).
    - Single commit message references A2 + A5 + A6.
  </acceptance_criteria>
  <done>ADBC ATTACH path has SQL-string escape parity. New mixed-quoting scenario lands in both sqllogictest and Rust unit. Cleanup block no longer leaks 3 base tables.</done>
</task>

</tasks>

<threat_model>
## Trust Boundaries

| Boundary | Description |
|----------|-------------|
| Caller DDL → `parse_single_table_entry` | Untrusted DDL text from a SQL connection (could be authored by an analyst or downstream test). Malformed input must surface a structured ParseError, never silently corrupt downstream state. |
| Python test SQL string interpolation → DuckDB | `extension_path`/`other_db_path` originate from `tmp_path` (pytest-controlled in practice, but could include `'` on hostile filesystems). Defense-in-depth via `.replace("'", "''")`. |

## STRIDE Threat Register

| Threat ID | Category | Component | Disposition | Mitigation Plan |
|-----------|----------|-----------|-------------|-----------------|
| T-68-01 | Tampering | `parse_single_table_entry` accepting malformed DDL (bare reserved keyword as table name; unterminated quoted identifier) | mitigate | A1 inserts an explicit reserved-keyword guard with the pre-Phase-67 error message. A4 inserts a balanced-quote helper that mirrors `find_identifier_end`'s `""`-escape semantics; mismatch surfaces a structured ParseError. |
| T-68-02 | Tampering | SQL-string injection via `ATTACH '{other_db_path}'` in `test_adbc_queries.py:470` (defense-in-depth — `tmp_path` is pytest-controlled today) | mitigate | A2 brings line 470 to parity with line 100's `.replace("'", "''")` treatment. Severity LOW — practical exposure is bounded to pytest fixture paths. |
| T-68-03 | Information disclosure | Diverging word-boundary semantics between `find_primary_key` and `find_unique` could allow `_`-prefixed identifiers to be mistakenly captured as PK clauses | accept | A7 alignment reduces the divergence surface to zero. No observable behaviour change on current fixtures (per RESEARCH §A7); pure defensive consolidation. |
</threat_model>

<verification>
Run after all three tasks complete:

```bash
cargo test --lib body_parser > /tmp/claude/ut.log 2>&1
RC=$?
tail -60 /tmp/claude/ut.log
[ $RC -eq 0 ] || exit 1

just build > /tmp/claude/build.log 2>&1
RC=$?
tail -30 /tmp/claude/build.log
[ $RC -eq 0 ] || exit 1

just test-sql phase67_quoted_source_tables > /tmp/claude/sqllog.log 2>&1
tail -40 /tmp/claude/sqllog.log

uv run pytest test/integration/test_adbc_queries.py > /tmp/claude/adbc.log 2>&1
tail -40 /tmp/claude/adbc.log
```

All four commands MUST exit 0. Sandbox bypass acceptable per CLAUDE.md for `just build`/`just test-sql`/`uv run pytest`.
</verification>

<success_criteria>
- 7 SCOPE.md items (A1, A2, A3, A4, A5, A6, A7) closed.
- A1+A3 land in a single atomic commit, diff ordered A3 collapse first then A1 guard.
- A1 guard rejects the exact CONTEXT.md D-03 keyword set: `PRIMARY|UNIQUE|FOREIGN|REFERENCES|NOT` (case-insensitive).
- A1 error message is the literal `"Missing physical table name after AS for alias '{alias}' in TABLES clause."` — pinned by 6 new unit tests.
- A4 helper `is_quoting_balanced` mirrors `find_identifier_end`'s `""`-escape rule (no false positive on doubled-quote escape).
- A7 aligns three boundary checks in `find_primary_key` with `find_unique`'s `_`-exclusion pattern.
- A2 line 470 has SQL-string escape parity with line 100.
- A5 Scenario 5 fixture + Rust unit test pin the mixed bare/quoted contract.
- A6 cleanup block drops 3+ leaked base tables.
- All pre-existing tests continue to pass (`cargo test --lib body_parser::tests` + `just test-sql phase67_quoted_source_tables` + relevant ADBC test exit 0).
- No `--no-verify` used.
</success_criteria>

<output>
Create `.planning/phases/68-pre-tag-cleanup-phase-67-review-pr-35-code-review-follow-ups/68-01-SUMMARY.md` when done.
</output>