---
phase: 64
plan: 04
type: execute
wave: 3
depends_on: [02, 03]
files_modified:
  - test/sql/phase64_quoted_idents.test
  - test/sql/TEST_LIST
  - fuzz/seeds/fuzz_ddl_parse/seed_phase64_quoted_bare.txt
  - fuzz/seeds/fuzz_ddl_parse/seed_phase64_quoted_fqn.txt
  - fuzz/seeds/fuzz_ddl_parse/seed_phase64_mixed_quoting.txt
  - tests/quoted_idents_regression.rs
  - fuzz/fuzz_targets/fuzz_ddl_parse.rs
  - CHANGELOG.md
  - .planning/REQUIREMENTS.md
  - .planning/ROADMAP.md
  - TECH-DEBT.md
autonomous: true
requirements:
  - QID-01
  - QID-02
  - QID-03
  - QID-04
  - QID-05
  - QID-06
  - QID-07
must_haves:
  truths:
    - "End-to-end sqllogictest fixture exercises (a) fully-quoted FQN CREATE + bare-name lookup (QID-01), (b) partial-quoting variants (QID-02), (c) DROP/ALTER/DESCRIBE/SHOW COLUMNS quoted forms (QID-03), (d) no triple-quoting in expanded SQL (QID-04), (e) GET_DDL round-trip preserves bare-name shape (QID-05), (f) error messages reference unquoted bare name (QID-06)"
    - "test/sql/phase64_quoted_idents.test registered in test/sql/TEST_LIST so the runner picks it up"
    - "fuzz_ddl_parse seed bank gains three quoted-FQN samples in the tracked fuzz/seeds/fuzz_ddl_parse/ directory (NOT the gitignored fuzz/corpus/)"
    - "tests/quoted_idents_regression.rs is a workspace integration test that exercises the three quoted-FQN inputs through validate_and_rewrite as a permanent regression guard"
    - "CHANGELOG [0.9.0] section gains a Fixed bullet describing the quoted-identifier bugfix"
    - ".planning/REQUIREMENTS.md gains a Quoted Identifier Handling subsection with QID-01..QID-07 and the Coverage count bumps from 14 → 21"
    - ".planning/ROADMAP.md Phase 64 Requirements: TBD updated to list QID-01..QID-07"
    - "just test-all exits 0"
    - "just ci exits 0"
  artifacts:
    - path: "test/sql/phase64_quoted_idents.test"
      provides: "sqllogictest acceptance for QID-01..QID-06"
      contains: "CREATE OR REPLACE SEMANTIC VIEW \"memory\".\"main\".\"orders_sv\""
    - path: "test/sql/TEST_LIST"
      provides: "Runner registration"
      contains: "test/sql/phase64_quoted_idents.test"
    - path: "fuzz/seeds/fuzz_ddl_parse/seed_phase64_quoted_bare.txt"
      provides: "Tracked fuzz seed — fully-quoted bare name CREATE"
      contains: "memory"
    - path: "fuzz/seeds/fuzz_ddl_parse/seed_phase64_quoted_fqn.txt"
      provides: "Tracked fuzz seed — fully-quoted FQN CREATE"
      contains: "memory"
    - path: "fuzz/seeds/fuzz_ddl_parse/seed_phase64_mixed_quoting.txt"
      provides: "Tracked fuzz seed — mixed-quoting FQN CREATE"
    - path: "tests/quoted_idents_regression.rs"
      provides: "Workspace integration test asserting the three quoted-FQN inputs round-trip through validate_and_rewrite without panicking and produce a bare lookup key"
      contains: "memory.*main.*orders_sv"
    - path: "fuzz/fuzz_targets/fuzz_ddl_parse.rs"
      provides: "Doc-comment header pointer to the new seed files"
    - path: "CHANGELOG.md"
      provides: "Fixed bullet under [0.9.0]"
      contains: "quoted"
    - path: ".planning/REQUIREMENTS.md"
      provides: "QID-01..QID-07 + traceability matrix rows + Coverage 21/21"
      contains: "QID-01"
    - path: ".planning/ROADMAP.md"
      provides: "Phase 64 Requirements: QID-01..QID-07"
      contains: "QID-01"
  key_links:
    - from: "test/sql/phase64_quoted_idents.test"
      to: "test/sql/TEST_LIST"
      via: "registry membership"
      pattern: "phase64_quoted_idents.test"
    - from: "tests/quoted_idents_regression.rs"
      to: "src/parse.rs::validate_and_rewrite + src/ident.rs"
      via: "integration test driving the public crate API"
      pattern: "validate_and_rewrite|normalize_view_name"
    - from: ".planning/ROADMAP.md (Phase 64 entry)"
      to: ".planning/REQUIREMENTS.md (QID-01..07)"
      via: "Requirements: QID-01, QID-02, ... list"
      pattern: "QID-01.*QID-07|QID-0[1-7]"
---

<objective>
Close out Phase 64 with end-to-end validation + housekeeping:

1. **sqllogictest acceptance fixture** (`test/sql/phase64_quoted_idents.test`) covering the six requirements end-to-end via the full extension load → parser_override → INSERT → catalog read → expand pipeline.
2. **Runner registration** (`test/sql/TEST_LIST`) — without this the fixture is silently skipped (lesson from Phase 63 Plan 02 — see STATE.md decision entry).
3. **Tracked fuzz seeds + regression integration test** — `fuzz/corpus/` is gitignored (`.gitignore:36`), so seeds go into the tracked `fuzz/seeds/fuzz_ddl_parse/` directory (same convention as `seed_basic.txt`, `seed_facts.txt`, etc.). In addition, a workspace integration test at `tests/quoted_idents_regression.rs` drives the same three inputs through `validate_and_rewrite` so the inputs are exercised by every `cargo test` run (not just by future fuzzing sessions). This survives the gitignored corpus and gives a true regression guard.
4. **CHANGELOG** — one `Fixed` bullet under the existing `[0.9.0]` section (do NOT introduce a per-phase subhead; CLAUDE.md milestone-completion rules).
5. **Requirements ledger** — append QID-01..QID-07 to `.planning/REQUIREMENTS.md` under a new "Quoted Identifier Handling" subsection in the v0.9.0 milestone; add traceability rows mapping each to Phase 64; bump Coverage `v1 requirements: 14 total` → `v1 requirements: 21 total`.
6. **ROADMAP** — Phase 64's `**Requirements**: QID-01..QID-07` line is already pre-populated; verify and re-populate if reverted.
7. **TECH-DEBT** — document the deferred body-parser TABLES-clause-with-whitespace-in-quoted-parts limitation (Pitfall 5 from RESEARCH.md) as a new known limitation entry.
8. **Quality gate** — `just test-all` and `just ci` green before marking the phase complete.

Coverage: all seven QID-* IDs (the helper-level proof lives in 64-01 unit/proptests; this plan provides the user-visible / integration-level proof).
</objective>

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

<context>
@.planning/phases/64-fix-quoted-identifier-handling/64-RESEARCH.md
@.planning/phases/64-fix-quoted-identifier-handling/64-01-SUMMARY.md
@.planning/phases/64-fix-quoted-identifier-handling/64-02-SUMMARY.md
@.planning/phases/64-fix-quoted-identifier-handling/64-03-SUMMARY.md
@./CLAUDE.md
@./TECH-DEBT.md
@.planning/REQUIREMENTS.md
@.planning/ROADMAP.md
@CHANGELOG.md

<interfaces>
<!-- The existing readonly_load.test fixture is the closest style template — read its head/body before writing phase64_quoted_idents.test. -->

```
test/sql/readonly_load.test  (style reference — pattern: `require semantic_views`, `LOAD semantic_views;`, statements, queries, statement error patterns)
```

<!-- sqllogictest directives we will use: -->
- `require semantic_views`  — gate on extension presence
- `statement ok`            — DDL that should succeed
- `statement error <regex>` — DDL/query that must fail with matching message
- `query <typecols>` ... `----` <expected rows> — read-back assertion

<!-- TEST_LIST format (one path per line, sorted-ish; see existing file). -->

<!-- Tracked fuzz seed directory convention — peer files: -->
- fuzz/seeds/fuzz_ddl_parse/seed_basic.txt
- fuzz/seeds/fuzz_ddl_parse/seed_facts.txt
- fuzz/seeds/fuzz_ddl_parse/seed_homoglyph.txt
- (etc. — plain-text payloads, one per file, libfuzzer consumes bytes)

<!-- Workspace integration test convention — peer files: -->
- tests/vector_reference_test.rs
- tests/expand_proptest.rs
- tests/parse_proptest.rs
- tests/yaml_proptest.rs
- (Rust integration tests; run as part of `cargo test`)
</interfaces>
</context>

<tasks>

<task type="auto">
  <name>Task 1: Create test/sql/phase64_quoted_idents.test acceptance fixture + register in TEST_LIST</name>
  <files>test/sql/phase64_quoted_idents.test, test/sql/TEST_LIST</files>
  <read_first>
    - test/sql/readonly_load.test (entire file — style template for sqllogictest fixtures in this project)
    - test/sql/TEST_LIST (entire file — see existing entries, append in same style)
    - .planning/phases/64-fix-quoted-identifier-handling/64-RESEARCH.md §Example C (the sqllogictest skeleton with scenarios A-D mapped to QID-01..06)
    - test/sql/error_caret_create.test (entire file — for the `statement error` regex style with our error messages)
    - test/sql/v080_transactional_ddl.test (skim for CREATE SEMANTIC VIEW examples in the project's test style)
  </read_first>
  <behavior>
    Fixture coverage map (each block of the fixture maps to one QID):

    HEADER:
      # Phase 64 — Quoted identifier handling
      #
      # Verifies that CREATE / DROP / ALTER / DESCRIBE / SHOW COLUMNS
      # SEMANTIC VIEW accept any of these quoting forms uniformly:
      #   bare:        orders_sv
      #   full quote:  "orders_sv"
      #   partial fqn: main.orders_sv  |  "main".orders_sv  |  main."orders_sv"
      #   full fqn:    "memory"."main"."orders_sv"  |  any partial-quoting variant thereof
      # and that all of them resolve to the same bare key for semantic_view() lookup.
      #
      # Also verifies that the expansion path emits exactly ONE pair of
      # double quotes per identifier part for TABLES (...) source-table
      # references, even when the source table reference is itself quoted
      # in the DDL.
      #
      # See .planning/phases/64-fix-quoted-identifier-handling/64-RESEARCH.md
      # for the full bug analysis.

      require semantic_views

      statement ok
      LOAD semantic_views;

      # Set up base table (re-used across scenarios).
      statement ok
      CREATE TABLE orders (id INTEGER PRIMARY KEY, amount DECIMAL(10,2), region VARCHAR);

      statement ok
      INSERT INTO orders VALUES (1, 100.00, 'US'), (2, 200.00, 'EU');

    QID-01 — fully-quoted FQN CREATE, lookup by short name:
      statement ok
      CREATE OR REPLACE SEMANTIC VIEW "memory"."main"."orders_sv" AS
        TABLES (o AS orders PRIMARY KEY (id))
        DIMENSIONS (o.region AS o.region)
        METRICS (o.total AS SUM(o.amount));

      # semantic_view() with the bare name resolves the row stored under quoted FQN.
      query R
      FROM semantic_view('orders_sv', metrics := ['total']);
      ----
      300.00

    QID-02 — partial-quoting variants:
      statement ok
      DROP SEMANTIC VIEW orders_sv;

      statement ok
      CREATE SEMANTIC VIEW main."orders_sv" AS
        TABLES (o AS orders PRIMARY KEY (id))
        DIMENSIONS (o.region AS o.region)
        METRICS (o.total AS SUM(o.amount));

      query R
      FROM semantic_view('orders_sv', metrics := ['total']);
      ----
      300.00

      statement ok
      DROP SEMANTIC VIEW orders_sv;

      statement ok
      CREATE SEMANTIC VIEW "main".orders_sv AS
        TABLES (o AS orders PRIMARY KEY (id))
        DIMENSIONS (o.region AS o.region)
        METRICS (o.total AS SUM(o.amount));

      query R
      FROM semantic_view('orders_sv', metrics := ['total']);
      ----
      300.00

    QID-03 — DESCRIBE / ALTER RENAME / DROP accept the same forms:
      # DESCRIBE with quoted name resolves
      statement ok
      DESCRIBE SEMANTIC VIEW "orders_sv";

      # ALTER RENAME source AND target may both be quoted
      statement ok
      ALTER SEMANTIC VIEW "orders_sv" RENAME TO "memory"."main"."orders_sv_v2";

      # Both old and new bare names work in lookup
      statement error semantic view 'orders_sv' does not exist
      DESCRIBE SEMANTIC VIEW orders_sv;

      query R
      FROM semantic_view('orders_sv_v2', metrics := ['total']);
      ----
      300.00

      # Runtime arg quoted: semantic_view('"orders_sv_v2"', ...) also resolves
      query R
      FROM semantic_view('"orders_sv_v2"', metrics := ['total']);
      ----
      300.00

      statement ok
      DROP SEMANTIC VIEW "main"."orders_sv_v2";

    QID-04 — no triple-quoting in expanded SQL when TABLES uses a quoted FQN:
      statement ok
      CREATE SEMANTIC VIEW orders_sv AS
        TABLES (o AS "memory"."main"."orders" PRIMARY KEY (id))
        DIMENSIONS (o.region AS o.region)
        METRICS (o.total AS SUM(o.amount));

      # explain_semantic_view returns the expanded SQL as a single VARCHAR row.
      # Verify the FROM clause uses exactly one pair of quotes per part.
      # Trick: match the substring """ ('"' three times in a row); if it appears,
      # we triple-quoted. Use NOT NULL filter + LIKE.
      query I
      SELECT COUNT(*) FROM explain_semantic_view('orders_sv', metrics := ['total'])
        WHERE plan LIKE '%"""%';
      ----
      0

      # And the correct shape DOES appear:
      query I
      SELECT COUNT(*) FROM explain_semantic_view('orders_sv', metrics := ['total'])
        WHERE plan LIKE '%"memory"."main"."orders"%';
      ----
      1

      # Query actually runs against the fully-quoted FQN source-table reference
      query R
      FROM semantic_view('orders_sv', metrics := ['total']);
      ----
      300.00

    QID-05 — GET_DDL round-trip preserves bare-name shape:
      # GET_DDL emits CREATE OR REPLACE SEMANTIC VIEW orders_sv ... (bare name,
      # because the stored row's `name` column IS the bare name).
      query T
      SELECT GET_DDL('SEMANTIC_VIEW', 'orders_sv') LIKE 'CREATE OR REPLACE SEMANTIC VIEW orders_sv AS%';
      ----
      true

      # Round-trip: extract DDL, DROP, re-CREATE from the GET_DDL output, verify lookup.
      statement ok
      DROP SEMANTIC VIEW orders_sv;

      # (Manual re-creation here — sqllogictest can't capture variables.
      # The point is: a user copy-pasting GET_DDL output back into SQL produces
      # an equivalent view.)
      statement ok
      CREATE OR REPLACE SEMANTIC VIEW orders_sv AS
        TABLES (o AS "memory"."main"."orders" PRIMARY KEY (id))
        DIMENSIONS (o.region AS o.region)
        METRICS (o.total AS SUM(o.amount));

      query R
      FROM semantic_view('orders_sv', metrics := ['total']);
      ----
      300.00

    QID-06 — error messages reference unquoted bare name:
      # DESCRIBE on missing view — error says 'nonexistent_view', NOT '"...nonexistent_view"'
      statement error semantic view 'nonexistent_view' does not exist
      DESCRIBE SEMANTIC VIEW "memory"."main"."nonexistent_view";

      # DROP on missing view — same shape
      statement error semantic view 'nonexistent_view' does not exist
      DROP SEMANTIC VIEW "memory"."main"."nonexistent_view";

      # CREATE duplicate — error references bare name
      statement error semantic view 'orders_sv' already exists
      CREATE SEMANTIC VIEW "memory"."main"."orders_sv" AS
        TABLES (o AS orders PRIMARY KEY (id))
        DIMENSIONS (o.region AS o.region)
        METRICS (o.total AS SUM(o.amount));

    Final clean-up:
      statement ok
      DROP SEMANTIC VIEW orders_sv;

      statement ok
      DROP TABLE orders;

    TEST_LIST entry: append a single line `test/sql/phase64_quoted_idents.test` to the END of `test/sql/TEST_LIST` (per Phase 63 Plan 02 convention — append, don't sort).
  </behavior>
  <action>
    1. Build the extension binary so sqllogictest runs against fresh code (mandatory per CLAUDE.md: `just test-sql` requires `just build` first).

       Command: `just build`

    2. Create `test/sql/phase64_quoted_idents.test` with the structure under <behavior>. Match the comment-header / require / LOAD style of `test/sql/readonly_load.test`.

    3. Append `test/sql/phase64_quoted_idents.test` to `test/sql/TEST_LIST`.

    4. Run JUST the new fixture to iterate fast:

       Command: `just test-sql phase64_quoted_idents 2>&1 | tail -40`

       If failures surface, the most likely causes are (in order of probability):
       - Error message wording drift (`semantic view 'X' does not exist` vs `semantic view "X" does not exist` vs `Catalog Error: ...`). Check the actual output and adjust the `statement error <regex>` patterns to match. The regex is a substring match in sqllogictest, so anchor only on the meaningful part.
       - `explain_semantic_view` schema mismatch — check the actual column name (`plan` vs `sql` vs something else) by running `DESCRIBE explain_semantic_view('orders_sv', metrics := ['total'])` ad-hoc against a built extension.
       - `GET_DDL('SEMANTIC_VIEW', 'orders_sv')` argument case sensitivity — the project uses uppercase `'SEMANTIC_VIEW'` in existing tests; verify.

    5. Once `just test-sql phase64_quoted_idents` is green, run the full `just test-sql` to verify no cross-fixture regression. Some fixtures may rely on `quote_table_ref` behaviour we just changed — run them all.

       Command: `just test-sql 2>&1 | tail -40`

    Note: do NOT proceed to fuzz/CHANGELOG/REQUIREMENTS work until the sqllogictest fixture is green. If a test won't go green because the underlying code is buggy, ROUTE BACK to 64-02 or 64-03 — don't paper over with weakened assertions.
  </action>
  <verify>
    <automated>just build && just test-sql phase64_quoted_idents 2>&1 | tail -20</automated>
  </verify>
  <acceptance_criteria>
    - File `test/sql/phase64_quoted_idents.test` exists.
    - `grep -n "CREATE OR REPLACE SEMANTIC VIEW \"memory\".\"main\".\"orders_sv\"" test/sql/phase64_quoted_idents.test` returns at least one match.
    - `grep -n "FROM semantic_view('orders_sv'" test/sql/phase64_quoted_idents.test` returns at least 3 matches (QID-01, QID-02 variants).
    - `grep -n "ALTER SEMANTIC VIEW \"orders_sv\" RENAME TO" test/sql/phase64_quoted_idents.test` returns at least 1 match.
    - `grep -n "DESCRIBE SEMANTIC VIEW" test/sql/phase64_quoted_idents.test` returns at least 2 matches (one ok, one error).
    - `grep -n "%\"\"\"" test/sql/phase64_quoted_idents.test` returns one match (the triple-quote-negative LIKE filter for QID-04).
    - `grep -n "GET_DDL" test/sql/phase64_quoted_idents.test` returns at least 1 match.
    - `grep -n "phase64_quoted_idents.test" test/sql/TEST_LIST` returns one match.
    - `just build` exits 0.
    - `just test-sql phase64_quoted_idents` exits 0 (or, if the runner doesn't filter by name, the full `just test-sql` exits 0 and the fixture's name appears in successful output).
  </acceptance_criteria>
  <done>
    Acceptance fixture lives at test/sql/phase64_quoted_idents.test, is registered in TEST_LIST, and exits 0 against a freshly built extension.
  </done>
</task>

<task type="auto">
  <name>Task 2: Add tracked fuzz seeds + workspace regression integration test for quoted-identifier inputs</name>
  <files>fuzz/seeds/fuzz_ddl_parse/seed_phase64_quoted_bare.txt, fuzz/seeds/fuzz_ddl_parse/seed_phase64_quoted_fqn.txt, fuzz/seeds/fuzz_ddl_parse/seed_phase64_mixed_quoting.txt, tests/quoted_idents_regression.rs, fuzz/fuzz_targets/fuzz_ddl_parse.rs</files>
  <read_first>
    - fuzz/fuzz_targets/fuzz_ddl_parse.rs (entire file — currently ~30 LOC)
    - `ls fuzz/seeds/fuzz_ddl_parse/` (the TRACKED seed bank — seed_basic.txt, seed_facts.txt, seed_homoglyph.txt, etc. — these are committed to git)
    - `cat fuzz/seeds/fuzz_ddl_parse/seed_basic.txt` (one-line plain-text payload format)
    - .gitignore lines 30–45 — confirms `fuzz/corpus/` IS gitignored but `fuzz/seeds/` is NOT
    - tests/vector_reference_test.rs (workspace integration test style template — `use semantic_views::...; #[test] fn ...`)
    - tests/parse_proptest.rs (parse-test style; shows how to call `validate_and_rewrite` from an integration test)
    - .planning/phases/64-fix-quoted-identifier-handling/64-RESEARCH.md §Validation Architecture (Wave 0 Gaps; specifies the three seeds: `"v"`, `"db"."s"."v"`, `db."s".v`)
  </read_first>
  <behavior>
    Two-part deliverable: (1) tracked seed files in the right directory, (2) a regression integration test that drives the same inputs through the public crate API so they're exercised by every `cargo test` run, not just by future fuzzing sessions.

    PART A — Three tracked seed files in `fuzz/seeds/fuzz_ddl_parse/`:

      (Naming follows the existing convention — `seed_<descriptor>.txt`. The directory is checked into git; `fuzz/corpus/` is the gitignored runtime cache that's bootstrapped from `fuzz/seeds/` on each run.)

      - fuzz/seeds/fuzz_ddl_parse/seed_phase64_quoted_bare.txt:
          CREATE SEMANTIC VIEW "memory"."main"."orders_sv" AS TABLES (o AS orders PRIMARY KEY (id)) DIMENSIONS (o.r AS o.r) METRICS (o.t AS SUM(o.a))

      - fuzz/seeds/fuzz_ddl_parse/seed_phase64_quoted_fqn.txt:
          CREATE OR REPLACE SEMANTIC VIEW "db"."s"."v" AS TABLES (o AS orders PRIMARY KEY (id)) DIMENSIONS (o.r AS o.r) METRICS (o.t AS SUM(o.a))

      - fuzz/seeds/fuzz_ddl_parse/seed_phase64_mixed_quoting.txt:
          CREATE SEMANTIC VIEW db."s".v AS TABLES (o AS orders PRIMARY KEY (id)) DIMENSIONS (o.r AS o.r) METRICS (o.t AS SUM(o.a))

      Each file contains ONE line of payload (no trailing newline needed; libfuzzer reads bytes). The "memory.main.orders_sv" payload in the first seed deliberately matches the substring the regression test greps for, so the acceptance check works against either path (seed file OR regression test source).

    PART B — Workspace integration test `tests/quoted_idents_regression.rs`:

      Rust integration test at the workspace root (NOT under `fuzz/`). Drives the three quoted-identifier inputs through the public crate entry-points so they're permanently exercised by `cargo test`. This survives the gitignored fuzz corpus and gives us a true regression guard.

      Shape:

      ```rust
      //! Phase 64 regression test — quoted identifier handling.
      //!
      //! These inputs were the bug-report reproductions for Phase 64.
      //! They must never regress: validate_and_rewrite() must accept each one,
      //! return rewritten SQL where the stored view name is the bare unquoted
      //! last part of the identifier ("memory"."main"."orders_sv" → orders_sv).
      //!
      //! Companion seed files: fuzz/seeds/fuzz_ddl_parse/seed_phase64_*.txt.

      use semantic_views::parse::validate_and_rewrite;

      const QUOTED_BARE_NAME: &str = "\
          CREATE SEMANTIC VIEW \"memory\".\"main\".\"orders_sv\" AS \
            TABLES (o AS orders PRIMARY KEY (id)) \
            DIMENSIONS (o.r AS o.r) \
            METRICS (o.t AS SUM(o.a))";

      const QUOTED_FQN: &str = "\
          CREATE OR REPLACE SEMANTIC VIEW \"db\".\"s\".\"v\" AS \
            TABLES (o AS orders PRIMARY KEY (id)) \
            DIMENSIONS (o.r AS o.r) \
            METRICS (o.t AS SUM(o.a))";

      const MIXED_QUOTING: &str = "\
          CREATE SEMANTIC VIEW db.\"s\".v AS \
            TABLES (o AS orders PRIMARY KEY (id)) \
            DIMENSIONS (o.r AS o.r) \
            METRICS (o.t AS SUM(o.a))";

      #[test]
      fn fully_quoted_fqn_normalises_to_bare_name() {
          let sql = validate_and_rewrite(QUOTED_BARE_NAME).expect("parse").expect("rewrite");
          // The rewritten SQL embeds the bare view name as a SQL string literal.
          // The exact shape is "...INSERT INTO ..._definitions VALUES ('orders_sv', ...)".
          assert!(
              sql.contains("'orders_sv'"),
              "rewritten SQL does not embed bare 'orders_sv': {sql}"
          );
          // It must NOT embed the quoted form.
          assert!(
              !sql.contains("'\"memory\".\"main\".\"orders_sv\"'"),
              "rewritten SQL still embeds quoted FQN: {sql}"
          );
          // Sanity: starts with the parser_override prefix.
          assert!(sql.starts_with("SELECT * FROM "), "unexpected SQL shape: {sql}");
      }

      #[test]
      fn quoted_fqn_short_parts_normalise_to_bare_name() {
          let sql = validate_and_rewrite(QUOTED_FQN).expect("parse").expect("rewrite");
          assert!(sql.contains("'v'"), "expected bare 'v' in rewritten SQL: {sql}");
          assert!(
              !sql.contains("'\"db\".\"s\".\"v\"'"),
              "rewritten SQL still embeds quoted FQN: {sql}"
          );
      }

      #[test]
      fn mixed_quoting_normalises_to_bare_name() {
          let sql = validate_and_rewrite(MIXED_QUOTING).expect("parse").expect("rewrite");
          assert!(sql.contains("'v'"), "expected bare 'v' in rewritten SQL: {sql}");
      }
      ```

      Notes:
      - If `validate_and_rewrite` is not `pub` at the crate root, add `pub use parse::validate_and_rewrite;` to `src/lib.rs` (it likely already is — the fuzz target imports it as `use semantic_views::parse::{detect_semantic_view_ddl, validate_and_rewrite, ...}`).
      - The "embedded bare name in the rewrite output" property is the same invariant the integration sqllogictest checks; this test gives us a faster (≤ 100 ms) signal.
      - If a string literal in the rewritten SQL differs from the exact shape `'orders_sv'` (e.g. `''orders_sv''` for escaping, or sanitised differently), iterate the assertion to match — read the actual rewrite output once and snap to it. The MOST IMPORTANT assertion is the NEGATIVE one ("does not still contain the quoted form").

    PART C — Doc-comment header in `fuzz/fuzz_targets/fuzz_ddl_parse.rs`:

      Add at the top of the file (after the `#![no_main]` line, before the `use`):

      ```rust
      // Phase 64 seed inputs covering quoted-identifier forms live in:
      //   fuzz/seeds/fuzz_ddl_parse/seed_phase64_quoted_bare.txt
      //   fuzz/seeds/fuzz_ddl_parse/seed_phase64_quoted_fqn.txt
      //   fuzz/seeds/fuzz_ddl_parse/seed_phase64_mixed_quoting.txt
      // The same inputs are asserted as permanent regression tests in
      // tests/quoted_idents_regression.rs (workspace-level cargo test).
      ```

      That single comment block is the only change to the .rs file.

    Success criteria:
    - `cargo test --test quoted_idents_regression` passes (3 tests).
    - `cargo build --manifest-path fuzz/Cargo.toml --release` succeeds (or `just fuzz-build` if the recipe exists — check `justfile`).
    - All three seed files exist in the TRACKED `fuzz/seeds/fuzz_ddl_parse/` directory.
  </behavior>
  <action>
    1. Verify the seed directory + .gitignore situation:
       ```
       ls fuzz/seeds/fuzz_ddl_parse/ | head
       grep -n "fuzz/corpus\|fuzz/seeds" .gitignore
       ```
       Confirm `fuzz/corpus/` IS ignored (line 36) and `fuzz/seeds/` is NOT.

    2. Use the Write tool to create the three seed files in `fuzz/seeds/fuzz_ddl_parse/`:
       - `seed_phase64_quoted_bare.txt` — payload from <behavior> Part A line 1
       - `seed_phase64_quoted_fqn.txt` — payload from <behavior> Part A line 2
       - `seed_phase64_mixed_quoting.txt` — payload from <behavior> Part A line 3

       Each file: ONE line of payload, no trailing newline preferred (libfuzzer reads bytes — newline-or-not is harmless but follow the existing seed file convention).

    3. Create `tests/quoted_idents_regression.rs` with the three #[test] functions described in <behavior> Part B. Use the Write tool.

    4. Run the regression test:
       ```
       cargo test --test quoted_idents_regression 2>&1 | tail -20
       ```
       All three tests must pass. If the rewrite-output assertions need shape adjustment (e.g. exact quoting of the bare name in the embedded string literal), read the actual rewrite output (insert a `println!("{}", sql)` temporarily, run once, remove the print) and adjust the `assert!` to the actual shape — keep the negative assertion intact.

    5. Append the doc-comment header to `fuzz/fuzz_targets/fuzz_ddl_parse.rs` per <behavior> Part C (single Edit, not a rewrite).

    6. Verify the fuzz target still builds:
       ```
       cargo build --manifest-path fuzz/Cargo.toml --release 2>&1 | tail -10
       ```
       (Or `just fuzz-build` if it exists — `grep -n fuzz justfile`.)

    7. Optional smoke run (skip if `cargo fuzz` is not installed locally):
       ```
       cargo fuzz run fuzz_ddl_parse -- -max_total_time=3
       ```
  </action>
  <verify>
    <automated>cargo test --test quoted_idents_regression 2>&1 | tail -10 && ls fuzz/seeds/fuzz_ddl_parse/seed_phase64_*.txt | wc -l</automated>
  </verify>
  <acceptance_criteria>
    - `ls fuzz/seeds/fuzz_ddl_parse/seed_phase64_*.txt | wc -l` returns exactly 3.
    - `grep -l "memory.*main.*orders_sv" tests/ 2>/dev/null || grep -l "memory.*main.*orders_sv" fuzz/fuzz_targets/fuzz_ddl_parse.rs` returns at least one match — proves the regression coverage path exists somewhere git-tracked, regardless of which file ends up holding the canonical reference. (In practice both will match: `tests/quoted_idents_regression.rs` AND the doc-comment in the fuzz target.)
    - `grep -l "memory.*main.*orders_sv" tests/quoted_idents_regression.rs` returns one match.
    - `grep -n "Phase 64 seed inputs" fuzz/fuzz_targets/fuzz_ddl_parse.rs` returns one match.
    - `cargo test --test quoted_idents_regression` exits 0 with at least 3 passing tests.
    - `cargo build --manifest-path fuzz/Cargo.toml --release` exits 0.
    - `git status --porcelain fuzz/seeds/fuzz_ddl_parse/seed_phase64_*.txt` shows the three files as new (untracked or staged) — proving the gitignore does NOT exclude them.
  </acceptance_criteria>
  <done>
    Three tracked fuzz seeds in fuzz/seeds/fuzz_ddl_parse/; workspace integration test at tests/quoted_idents_regression.rs locks the regression in for every cargo test run; fuzz target carries a doc-comment pointer and still builds.
  </done>
</task>

<task type="auto">
  <name>Task 3: Update CHANGELOG, REQUIREMENTS, ROADMAP, and TECH-DEBT</name>
  <files>CHANGELOG.md, .planning/REQUIREMENTS.md, .planning/ROADMAP.md, TECH-DEBT.md</files>
  <read_first>
    - CHANGELOG.md (lines 1–25 — the existing [0.9.0] section header + existing Added/Known limitations subheadings; we APPEND a `### Fixed` subheading)
    - .planning/REQUIREMENTS.md (entire file — see Read-Only LOAD subsection format; mirror it for Quoted Identifier Handling; also note line 75 `- v1 requirements: 14 total` — must become `- v1 requirements: 21 total`)
    - .planning/ROADMAP.md (lines 230–270 — Phase 64 entry; the `**Requirements**: QID-01..QID-07` line is already pre-populated; verify, restore if reverted)
    - TECH-DEBT.md (last entry numbered 23 — append entry 24 for the body-parser TABLES-clause limitation)
    - .planning/phases/64-fix-quoted-identifier-handling/64-RESEARCH.md §Pitfall 5 (the deferred limitation text)
    - ./CLAUDE.md (Milestone Completion / CHANGELOG format rules — Keep a Changelog 1.1.0; only the standard subheadings; this is a [0.9.0] addition, NOT a new version section)
  </read_first>
  <behavior>
    A. CHANGELOG.md — add a `### Fixed` subheading under the existing `## [0.9.0]` section (CURRENTLY only has `### Added` and `### Known limitations`). Insert BETWEEN `### Added` and `### Known limitations`:

      ### Fixed

      - **Quoted identifier handling in `CREATE / DROP / ALTER / DESCRIBE / SHOW COLUMNS SEMANTIC VIEW`.** All five DDL forms now accept any combination of quoted, partially-quoted, and unquoted fully-qualified names — e.g. `CREATE OR REPLACE SEMANTIC VIEW "memory"."main"."orders_sv" AS ...`, `CREATE SEMANTIC VIEW main."orders_sv" AS ...`, and `CREATE SEMANTIC VIEW orders_sv AS ...` all store the same bare key, and `FROM semantic_view('orders_sv', ...)` resolves them uniformly. `ALTER ... RENAME TO` normalises both the source name and the new-name target. Error messages reference the unquoted bare name. Previously, a quoted FQN was stored verbatim (quotes and all) as the lookup key, which made any subsequent `semantic_view('orders_sv', ...)` call return "view does not exist".
      - **Triple-quoted identifiers in expanded SQL.** When a `TABLES (o AS "memory"."main"."orders" ...)` clause used a quoted source-table reference, the expansion path re-quoted each part producing strings like `"""memory"""."""main"""."""orders"""` in the generated `FROM` clause. The expansion now operates on parsed identifier parts and emits exactly one pair of quotes per part regardless of input shape, restoring `EXPLAIN SEMANTIC VIEW` legibility and (in the rare case the source-table reference had embedded special chars) correctness of the generated SQL.

    B. .planning/REQUIREMENTS.md — add a new subsection after the existing "Release" subsection (after REL-01 line). Mirror the format of the Read-Only LOAD subsection:

      ### Quoted Identifier Handling

      - [ ] **QID-01**: `CREATE [OR REPLACE | IF NOT EXISTS] SEMANTIC VIEW` accepts a fully-quoted fully-qualified name (e.g. `"memory"."main"."orders_sv"`). The catalog row is stored under the bare unquoted last part.
      - [ ] **QID-02**: The same DDL forms accept partial-quoting variants (`db.schema."view"`, `"db".schema.view`, `"view"`); all normalise to the bare last-part key.
      - [ ] **QID-03**: `DROP`, `ALTER`, `DESCRIBE`, and `SHOW COLUMNS IN SEMANTIC VIEW` accept the same quoting forms; the runtime `semantic_view('"name"', ...)` positional argument is also normalised. `ALTER ... RENAME TO` normalises BOTH the source and the new-name target.
      - [ ] **QID-04**: The expansion pipeline emits exactly one pair of quotes per identifier part for `TABLES (...)` source-table references, regardless of whether the source-table reference is quoted, partially quoted, or bare. No `"""triple"""` strings appear in expanded SQL.
      - [ ] **QID-05**: `GET_DDL('SEMANTIC_VIEW', 'name')` round-trip is stable: a copy-paste of the output is an executable `CREATE OR REPLACE SEMANTIC VIEW <bare_name> AS ...` that produces an equivalent view.
      - [ ] **QID-06**: Error messages for "view does not exist" and "view already exists" reference the unquoted bare name, never the quoted source string.
      - [ ] **QID-07**: `src/ident.rs` ships with unit-test coverage of bare / quoted / mixed / `""`-escaped / dot-inside-quotes / whitespace-inside-quotes / error inputs, plus a round-trip proptest (`parse(emit(parse(x))) == parse(x)`). `test/sql/phase64_quoted_idents.test` integration fixture covers QID-01..06 end-to-end.

      Then add seven traceability rows to the matrix at the bottom of the file:

      | QID-01 | Phase 64 | Complete |
      | QID-02 | Phase 64 | Complete |
      | QID-03 | Phase 64 | Complete |
      | QID-04 | Phase 64 | Complete |
      | QID-05 | Phase 64 | Complete |
      | QID-06 | Phase 64 | Complete |
      | QID-07 | Phase 64 | Complete |

      Update the "Coverage" line (around line 75): change `- v1 requirements: 14 total` to `- v1 requirements: 21 total`. There is exactly ONE occurrence of `v1 requirements: 14 total` in the file at the time of writing — verify with `grep -n "v1 requirements: 14 total" .planning/REQUIREMENTS.md` (must return 1 match) BEFORE the edit, then `grep -n "v1 requirements: 21 total" .planning/REQUIREMENTS.md` (must return 1 match) AFTER. Also update any "all mapped" counterpart line consistently (read the surrounding 10 lines once to see the full counter format and update each number that referenced 14).

      Note: the checkboxes start UNCHECKED in the requirements file — the gsd-verify step will check them. Use `[ ]` (empty box) here; do NOT pre-check.

    C. .planning/ROADMAP.md — find the Phase 64 entry (around line 236). The line `**Requirements**: QID-01, QID-02, QID-03, QID-04, QID-05, QID-06, QID-07` should already be pre-populated by the planner. Verify by running `grep -n 'QID-01.*QID-07\|Requirements.*: QID-01' .planning/ROADMAP.md` — must return at least one match. If the entry still shows `TBD`, restore it to:
       - **Requirements**: QID-01, QID-02, QID-03, QID-04, QID-05, QID-06, QID-07
       - **Plans:** 4 plans
       - Plans list:
         - [ ] 64-01-PLAN.md — Identifier normalisation core (src/ident.rs)
         - [ ] 64-02-PLAN.md — Wire normalisation into parse.rs capture sites + runtime arg
         - [ ] 64-03-PLAN.md — Fix expansion re-quoting + qualify-and-quote heuristic
         - [ ] 64-04-PLAN.md — Tests, fuzz seeds, docs, requirements, tech-debt

    D. TECH-DEBT.md — append a new entry 24 after the existing entry 23. Format matches existing entries:

      ### 24. ❓ Body parser's TABLES clause splits on whitespace inside quoted source-table names

      - **Origin:** v0.9.0 Phase 64 RESEARCH §Pitfall 5; surfaced as a known limitation during the quoted-identifier fix.
      - **Decision:** `parse_single_table_entry` in `src/body_parser.rs:660` uses a whitespace-based tokenizer to peel off the alias, the `AS`, and the source-table name from each entry in the `TABLES (...)` clause. If a source-table name has whitespace INSIDE a quoted part (e.g. `TABLES (o AS "my db"."schema"."t" PRIMARY KEY (id))`), the tokenizer truncates mid-name and the parse fails. The quoted-identifier fix in Phase 64 tightens identifier handling at every other capture site but leaves this body-parser path on the legacy whitespace tokenizer.
      - **Why deferred:** The bug-report reproduction (`"memory"."main"."orders_sv"` as the VIEW name) doesn't exercise this path. Whitespace inside quoted source-table parts is vanishingly rare — physical-table names almost never contain spaces, and even when they do (`"sales 2024"`), the user has the option of writing them as `"sales_2024"` or aliasing them outside the semantic-views layer. Fixing this requires `src/body_parser.rs` to adopt the `src/ident.rs` `find_identifier_end` helper at multiple parse points, which is non-trivial body-parser surgery for a vanishingly rare case.
      - **Action if a user hits this:** Provide an alias in DuckDB itself (e.g. `CREATE VIEW orders_clean AS SELECT * FROM "my db"."schema"."t"`) and reference the alias in the semantic view's `TABLES` clause. If the case becomes common, port `find_identifier_end` into `src/body_parser.rs::parse_single_table_entry` and surrounding helpers.

    E. STATE.md — do NOT edit. The gsd-verify or gsd-execute-phase orchestrator owns that file. (Listed here as a reminder.)
  </behavior>
  <action>
    1. CHANGELOG.md: open the file, locate the existing `## [0.9.0]` section (around line 14). Add a `### Fixed` subheading between the existing `### Added` block and `### Known limitations` block, with the two bullets described in <behavior> A. Preserve the surrounding Markdown style exactly.

    2. .planning/REQUIREMENTS.md:
       a. Read the full file once to capture the existing Coverage counter format (around line 74-80; the file currently has `- v1 requirements: 14 total` on line 75).
       b. Append the Quoted Identifier Handling subsection (under the Release subsection) with QID-01..QID-07 entries.
       c. Add the seven traceability rows to the matrix at the bottom.
       d. Bump the counter line: `v1 requirements: 14 total` → `v1 requirements: 21 total`. Update any matching "all mapped" counterpart that referenced 14.
       e. Verify post-edit: `grep -n "v1 requirements: 21 total" .planning/REQUIREMENTS.md` returns exactly one match; `grep -n "v1 requirements: 14 total" .planning/REQUIREMENTS.md` returns zero matches.

    3. .planning/ROADMAP.md: verify the Phase 64 entry's Requirements / Plans / Plans-list lines (the planner pre-populated them). If anything is missing or reverted, restore per <behavior> C.

    4. TECH-DEBT.md: append entry 24 after the existing entry 23 (preserve the existing "Date / Milestone / Audit report" footer at the very bottom of the file — insert BEFORE that footer block).

    5. After all four files are edited, run `just test-all` (the full quality gate per CLAUDE.md):

       Command: `just test-all 2>&1 | tail -30`

       This runs cargo test (unit + proptest + doctests + integration tests including tests/quoted_idents_regression.rs), just test-sql (sqllogictest including phase64_quoted_idents.test), and just test-ducklake-ci. ALL must be green.

    6. Run `just ci`:

       Command: `just ci 2>&1 | tail -30`

       This adds clippy pedantic + fmt + cargo-deny + fuzz target compilation. ALL must be green.

       If clippy surfaces new lints (likely since we added src/ident.rs and modified hot files), fix them in-place. Do not allow regressions.
  </action>
  <verify>
    <automated>just test-all 2>&1 | tail -20 && echo --- && just ci 2>&1 | tail -20</automated>
  </verify>
  <acceptance_criteria>
    - `grep -n "### Fixed" CHANGELOG.md` returns a match within the [0.9.0] section (verify by reading 5 lines of context).
    - `grep -nE "Quoted identifier handling|Triple-quoted identifiers" CHANGELOG.md` returns 2 matches.
    - `grep -cE "QID-0[1-7]" .planning/REQUIREMENTS.md` returns at least 14 (7 in the requirements list + 7 in the traceability matrix).
    - `grep -n "v1 requirements: 21" .planning/REQUIREMENTS.md` returns exactly one match.
    - `grep -n "v1 requirements: 14" .planning/REQUIREMENTS.md` returns zero matches.
    - `grep -n "Requirements\*\*: QID-01" .planning/ROADMAP.md` returns one match.
    - `grep -n "64-01-PLAN.md" .planning/ROADMAP.md` returns at least one match (the plan list update).
    - `grep -n "^### 24\\." TECH-DEBT.md` returns one match.
    - `grep -n "TABLES clause splits on whitespace" TECH-DEBT.md` returns one match.
    - `just test-all` exits 0.
    - `just ci` exits 0.
  </acceptance_criteria>
  <done>
    CHANGELOG documents the fix, REQUIREMENTS gains QID-01..07 with traceability and the 21/21 coverage bump, ROADMAP Phase 64 entry points at the IDs and plan list, TECH-DEBT records the deferred body-parser limitation. `just test-all` and `just ci` both green.
  </done>
</task>

</tasks>

<verification>
- Phase 64 quality gate: `just test-all` exits 0.
- Pre-push gate: `just ci` exits 0.
- `test/sql/phase64_quoted_idents.test` registered in `test/sql/TEST_LIST` and passing.
- `fuzz/seeds/fuzz_ddl_parse/seed_phase64_*.txt` present (3 files, tracked in git — NOT in the gitignored fuzz/corpus/).
- `tests/quoted_idents_regression.rs` present, exercised by `cargo test --test quoted_idents_regression` (3 passing tests).
- `fuzz/fuzz_targets/fuzz_ddl_parse.rs` carries a doc-comment header pointing at the seed files + regression test.
- CHANGELOG `[0.9.0] Fixed` subheading lists both the primary and the triple-quoting bullets.
- REQUIREMENTS.md gains QID-01..07 + 7 traceability rows; Coverage line bumped 14 → 21.
- ROADMAP Phase 64 `Requirements:` line lists QID-01..07; Plans list points at 64-01..04.
- TECH-DEBT.md entry 24 documents the deferred body-parser TABLES-clause limitation.
</verification>

<success_criteria>
- End-to-end acceptance: a user running `CREATE OR REPLACE SEMANTIC VIEW "memory"."main"."orders_sv" AS ...` followed by `FROM semantic_view('orders_sv', ...)` gets results, not "view does not exist".
- End-to-end acceptance: `EXPLAIN SEMANTIC VIEW` output for a quoted-FQN TABLES clause contains no triple-quoted substrings.
- Quality gate: `just test-all` green (includes the new tests/quoted_idents_regression.rs and the new sqllogictest fixture).
- Pre-push gate: `just ci` green (clippy pedantic + fmt + cargo-deny + fuzz target compile).
- Phase 64 ships as part of v0.9.0 (no new tag; the [Unreleased] section of CHANGELOG remains "_No unreleased changes yet._" — Phase 64's bullets land directly under the existing [0.9.0] - 2026-05-15 header).
- Maintainer-handoff requirements (squash-merge, tag v0.9.0) remain out of scope per Phase 63 Plan 04 SUMMARY.
</success_criteria>

<output>
After completion, create `.planning/phases/64-fix-quoted-identifier-handling/64-04-SUMMARY.md` recording:

- The final passing test counts: cargo test --lib (unit + proptest), cargo test --test quoted_idents_regression (regression integration), just test-sql (sqllogictest fixture count), just test-ducklake-ci, just ci (clippy + fmt + cargo-deny + fuzz-build).
- Confirmation that fuzz seeds went into the tracked `fuzz/seeds/fuzz_ddl_parse/` directory (NOT the gitignored fuzz/corpus/).
- Any error-message wording adjustments made to the sqllogictest fixture during iteration (so future researchers know what the actual error text is).
- Confirmation that CHANGELOG, REQUIREMENTS (incl. 14 → 21 coverage bump), ROADMAP, and TECH-DEBT updates landed.
- A flag note for the maintainer: v0.9.0 ready to squash-merge to main and tag (Phase 63 Plan 04 already handled the version bump; Phase 64 ships under the same version).
</output>
