|
| 1 | +# Handoff: extract the YAML stack (`quarto-yaml` + `quarto-yaml-validation`) |
| 2 | + |
| 3 | +**Strand:** bd-egcyeym9 (final phase of the diagnostics/YAML extraction epic) |
| 4 | +**Date:** 2026-06-29 |
| 5 | +**Audience:** an agent picking this up cold. You should not need to read the |
| 6 | +session transcript — everything needed is here or in the linked docs. |
| 7 | + |
| 8 | +--- |
| 9 | + |
| 10 | +## 1. Goal & motivation |
| 11 | + |
| 12 | +Extract `quarto-yaml` and `quarto-yaml-validation` out of the q2 monorepo into a |
| 13 | +**single new repository `posit-dev/quarto-yaml`, structured as a Rust workspace |
| 14 | +with two crates**, and publish both to crates.io independently. The motivating |
| 15 | +need: **invisible internal Posit consumers of `quarto-yaml-validation`** want a |
| 16 | +standalone crate; and the error codes must follow the cross-package discipline. |
| 17 | + |
| 18 | +This is the **last phase** of the epic. The diagnostics foundation is already |
| 19 | +done and merged: |
| 20 | +- `quarto-source-map 0.1.0` → `posit-dev/quarto-source-map` (crates.io) — PR #348. |
| 21 | +- `quarto-error-reporting 0.1.0` → `posit-dev/quarto-error-reporting` (crates.io, |
| 22 | + catalog-agnostic, `json` feature-gated) — PRs #349 (carve-out) + #350 (cutover). |
| 23 | + |
| 24 | +## 2. Preconditions (verify before starting) |
| 25 | + |
| 26 | +- **PR #350 merged** (q2 consumes `quarto-error-reporting 0.1.0`). `git switch |
| 27 | + main && git pull`. Confirm `crates/quarto-error-reporting/` is **gone** and the |
| 28 | + workspace builds. |
| 29 | +- Both foundation crates are live on crates.io at `0.1.0`. |
| 30 | +- You have (or the user grants) the same setup used in Phases 1/3: GitHub `gh` |
| 31 | + auth with `posit-dev` org rights (SSH key SSO-authorized), and the **user** |
| 32 | + performs every `cargo publish` (crates.io credentials are theirs; publishing is |
| 33 | + irreversible). |
| 34 | + |
| 35 | +## 3. Read these first (the mechanics are already proven) |
| 36 | + |
| 37 | +- **`claude-notes/plans/2026-06-26-extract-error-reporting-foundation.md`** — the |
| 38 | + authoritative playbook. Phase 1 (source-map) is the leaf-extraction template you |
| 39 | + will mirror almost exactly; Phase 3 (error-reporting) is the second run with the |
| 40 | + gotchas. Read the **Risks** and the per-step completion notes. |
| 41 | +- **`claude-notes/designs/cross-package-error-codes.md`** — the error-code |
| 42 | + discipline. Drives the one real design task (§6 below). |
| 43 | +- **`claude-notes/plans/2026-06-26-extract-quarto-yaml-validation-design.md`** — |
| 44 | + the original YAML design doc. Note its top banner: parts about |
| 45 | + `error-reporting-core`/façade are superseded; the YAML-specific substance |
| 46 | + (origin codes, delete `validate-yaml`, the discipline application) still stands. |
| 47 | + |
| 48 | +## 4. Repo structure (DECIDED: one two-crate workspace) |
| 49 | + |
| 50 | +``` |
| 51 | +posit-dev/quarto-yaml/ (new repo) |
| 52 | + Cargo.toml # [workspace] members=["crates/*"]; [workspace.package]; [workspace.dependencies] |
| 53 | + crates/ |
| 54 | + quarto-yaml/ # the parser leaf |
| 55 | + quarto-yaml-validation/ # schema validation; depends on quarto-yaml |
| 56 | + LICENSE README.md .gitignore .gitattributes .github/workflows/ci.yml |
| 57 | +``` |
| 58 | + |
| 59 | +- Use a **workspace** (unlike the single-crate foundation repos). Per-crate |
| 60 | + `Cargo.toml`s inherit `version`/`edition`/`license`/`repository` from |
| 61 | + `[workspace.package]` and pull shared deps from `[workspace.dependencies]`. |
| 62 | +- **`.gitattributes` with `* text=auto eol=lf` from commit 1** — Phase 3's Windows |
| 63 | + CI caught a CRLF bug in committed JSON-vs-generated comparisons; start with LF |
| 64 | + enforced so you never hit it. (`quarto-yaml-validation` ships |
| 65 | + `test-fixtures/` YAML/JSON — same exposure.) |
| 66 | +- **CI** (`.github/workflows/ci.yml`): mirror the foundation repos — matrix |
| 67 | + Linux/macOS/**Windows** on **stable** Rust, `fmt` + `clippy --all-targets -D |
| 68 | + warnings`, `cargo test`. (Both crates need no nightly.) Watch for **stable-clippy |
| 69 | + lints the q2 pinned-nightly tolerates** — Phase 3 hit `items_after_test_module` |
| 70 | + in `macros.rs`; fix in the new repo (q2 deletes its copy at cutover, so the |
| 71 | + standalone becomes the single source — no divergence). |
| 72 | + |
| 73 | +## 5. Dependency & cutover facts (measured 2026-06-29 on main) |
| 74 | + |
| 75 | +**`quarto-yaml`** (the leaf): deps `yaml-rust2`, `serde`, `thiserror`, |
| 76 | +`quarto-source-map`. Its **only** quarto dep is the published source-map → |
| 77 | +becomes `quarto-source-map = "0.1.0"`. Does **not** depend on |
| 78 | +`quarto-error-reporting`. In-tree q2 consumers: **pampa, quarto-config, |
| 79 | +quarto-core, quarto-lsp-core** (+ `validate-yaml`, being deleted). |
| 80 | + |
| 81 | +**`quarto-yaml-validation`**: deps `anyhow`, `thiserror`, `serde`, `serde_json`, |
| 82 | +`yaml-rust2`, `regex`, `quarto-yaml`, `quarto-source-map`, |
| 83 | +`quarto-error-reporting`. The quarto deps become: `quarto-yaml` (intra-workspace), |
| 84 | +`quarto-source-map = "0.1.0"`, `quarto-error-reporting = "0.1.0"`. **Only in-tree |
| 85 | +consumer is `validate-yaml`** (the demo binary). **After deleting `validate-yaml`, |
| 86 | +`quarto-yaml-validation` has ZERO q2 consumers** — q2 does not depend on it at all. |
| 87 | + |
| 88 | +**Consequence for the q2 cutover:** q2 **deletes** `crates/quarto-yaml-validation` |
| 89 | +AND `crates/validate-yaml`, keeps consuming `quarto-yaml` (now published), and |
| 90 | +gains **no** dependency on the published `quarto-yaml-validation`. The latter is |
| 91 | +published purely for the external Posit consumers. |
| 92 | + |
| 93 | +### The WASM gotcha (read carefully — different from Phase 1) |
| 94 | + |
| 95 | +`wasm-quarto-hub-client` is an *excluded standalone workspace*. It does **not** |
| 96 | +directly depend on `quarto-yaml`; it gets it transitively via `pampa`/`quarto-core` |
| 97 | +(path-included). Those crates use `quarto-yaml = { workspace = true }`, which |
| 98 | +resolves against **q2's** `[workspace.dependencies]` (workspace inheritance follows |
| 99 | +the crate's filesystem home — q2 root — even inside the WASM build). So: |
| 100 | +- Convert the **path**-dep consumers (`pampa`, `quarto-config`) to |
| 101 | + `{ workspace = true }`; the `{ workspace = true }` ones (`quarto-core`, |
| 102 | + `quarto-lsp-core`) stay. |
| 103 | +- Set `[workspace.dependencies.quarto-yaml]` → `version = "0.1.0"`. |
| 104 | +- The WASM crate likely needs **no direct `quarto-yaml` dep** (it had a direct |
| 105 | + `quarto-source-map = "0.1.0"` only because it uses source-map directly). **Verify |
| 106 | + with the full `cargo xtask verify`** — the WASM build is the proof; if it can't |
| 107 | + resolve `quarto-yaml`, add a direct `quarto-yaml = "0.1.0"` to the wasm crate (as |
| 108 | + was needed for source-map). |
| 109 | + |
| 110 | +## 6. The one design task: error codes (needs a user decision) |
| 111 | + |
| 112 | +`quarto-yaml-validation/src/error.rs` `ValidationErrorKind::error_code()` currently |
| 113 | +returns **Quarto presentation codes** `Q-1-10`, `Q-1-11`, … These do **not** belong |
| 114 | +in a standalone library (they are q2's namespace, per the discipline). It has |
| 115 | +**no** dependency on an installed catalog (no `get_docs_url`/`install_catalog` |
| 116 | +refs), so the change is localized to `error_code()` + the ~15 `error.rs` tests that |
| 117 | +assert `"Q-1-x"`. |
| 118 | + |
| 119 | +Per `cross-package-error-codes.md`, change `error_code()` to **own, namespaced |
| 120 | +origin codes** — e.g. `yaml-schema/missing-required`, `yaml-schema/type-mismatch`, |
| 121 | +`yaml-schema/invalid-enum`, … (one per `ValidationErrorKind` variant). There is |
| 122 | +**no q2 remap** to build (q2 doesn't consume the crate); the external consumers get |
| 123 | +the origin codes and may remap to their own presentation codes. |
| 124 | + |
| 125 | +> **⚠️ DECISION TO CONFIRM WITH THE USER before implementing.** Changing `Q-1-x` → |
| 126 | +> `yaml-schema/*` is **breaking** for the invisible Posit consumers that currently |
| 127 | +> see `Q-1-x`. Options: |
| 128 | +> - **(A) Origin codes from `0.1.0`** — clean per the discipline; coordinate the |
| 129 | +> break with those consumers. *Recommended* (0.1.0 is a fresh public line; do it |
| 130 | +> right from the start). |
| 131 | +> - **(B) Ship `Q-1-x` as-is in `0.1.0`, defer origin codes to `0.2.0`** — |
| 132 | +> non-breaking now, but ships Quarto codes in a "non-Quarto" crate (violates the |
| 133 | +> discipline) until later. |
| 134 | +> |
| 135 | +> Ask the user which, and (for A) capture the `Q-1-x` → `yaml-schema/*` mapping as |
| 136 | +> a frozen table in the commit message so the lineage is recoverable. |
| 137 | +
|
| 138 | +## 7. Execution checklist |
| 139 | + |
| 140 | +### Phase A — `quarto-yaml` (the leaf; publish first) |
| 141 | +- [ ] Create `/Users/cscheid/repos/github/posit-dev/quarto-yaml/` as a **workspace**; |
| 142 | + copy `crates/quarto-yaml/` → `crates/quarto-yaml/`; add `LICENSE` (from q2 |
| 143 | + root), `.gitignore` (`/target`), `.gitattributes` (`* text=auto eol=lf`), |
| 144 | + `README.md` (write fresh — the crate is a YAML parser with source tracking). |
| 145 | +- [ ] Workspace `Cargo.toml`: `[workspace] members=["crates/*"]`, |
| 146 | + `[workspace.package]` (version `0.1.0`, edition `2024`, license `MIT`, |
| 147 | + `repository = https://github.com/posit-dev/quarto-yaml`, authors), and |
| 148 | + `[workspace.dependencies]` with `quarto-source-map = "0.1.0"` + the shared |
| 149 | + crates.io deps (pin versions to match q2's `[workspace.dependencies]`: |
| 150 | + `yaml-rust2`, `serde`, `thiserror`, …). Drop `[lints] workspace = true` or |
| 151 | + add a `[workspace.lints]` block (q2 has none). |
| 152 | +- [ ] Build + `cargo test` + `cargo clippy --all-targets -- -D warnings` + `cargo |
| 153 | + fmt --check` + `cargo publish --dry-run -p quarto-yaml`. Fix any stable-clippy |
| 154 | + lints (see §4). |
| 155 | +- [ ] External-consumer smoke test (separate crate, path dep, parse a YAML string, |
| 156 | + assert source info) — proves the public API is usable standalone. |
| 157 | +- [ ] `gh repo create posit-dev/quarto-yaml --public --source=. --push` |
| 158 | + (public; mirror Phase 1/3). Confirm CI green on all 3 OSes. |
| 159 | +- [ ] **USER**: `cargo publish -p quarto-yaml` (from the new repo). |
| 160 | + |
| 161 | +### Phase B — `quarto-yaml-validation` (second crate, same repo) |
| 162 | +- [ ] Copy `crates/quarto-yaml-validation/` (incl. `test-fixtures/`) into the |
| 163 | + workspace. Its `quarto-yaml` dep = `{ version = "0.1.0", path = |
| 164 | + "../quarto-yaml" }` (path for local dev, version so `cargo publish` resolves |
| 165 | + the now-published `quarto-yaml`); `quarto-source-map` / `quarto-error-reporting` |
| 166 | + = `"0.1.0"`. |
| 167 | +- [ ] **Apply the error-code change** from §6 (after the user's A/B decision) + |
| 168 | + update the `error.rs` tests. |
| 169 | +- [ ] If yaml-validation tests render diagnostics and asserted `Q-1-x` text, update |
| 170 | + them; with no catalog installed in the standalone repo, diagnostics render |
| 171 | + **code-only** (`EmptyCatalog`) — assert on the origin codes. |
| 172 | +- [ ] Build + test + clippy + `cargo publish --dry-run -p quarto-yaml-validation` + |
| 173 | + smoke test (validate a doc against a schema, assert a `yaml-schema/*` code). |
| 174 | +- [ ] CI green (3 OSes). **USER**: `cargo publish -p quarto-yaml-validation` |
| 175 | + (after `quarto-yaml` is live so the dep resolves). |
| 176 | + |
| 177 | +### Phase C — q2 cutover (one PR, like #348/#350) |
| 178 | +- [ ] Branch `braid/bd-egcyeym9-yaml-cutover` off updated main. |
| 179 | +- [ ] `[workspace.dependencies.quarto-yaml]` `path` → `version = "0.1.0"`. |
| 180 | +- [ ] Convert `quarto-yaml` path-deps (`pampa`, `quarto-config`) → |
| 181 | + `{ workspace = true }`; leave the existing `{ workspace = true }` ones. |
| 182 | +- [ ] **Delete** `crates/quarto-yaml-validation/` and `crates/validate-yaml/` (and |
| 183 | + drop `[workspace.dependencies.quarto-yaml-validation]` from root `Cargo.toml`; |
| 184 | + check for any stray refs to `validate-yaml` in `xtask`/docs/CI). |
| 185 | +- [ ] Delete in-tree `crates/quarto-yaml/`. |
| 186 | +- [ ] `cargo build --workspace` → confirm Cargo.lock resolves `quarto-yaml 0.1.0` |
| 187 | + from the registry. `cargo nextest run --workspace`. **Full `cargo xtask |
| 188 | + verify`** (the WASM leg is the real gate — see §5 WASM gotcha; do NOT pipe it |
| 189 | + through `tail`, which masks the exit code — a Phase 1 lesson). |
| 190 | +- [ ] Update `CLAUDE.md`: move `quarto-yaml` into the "Externalized foundation |
| 191 | + crates" section; remove `quarto-yaml-validation` and `validate-yaml` from the |
| 192 | + binaries/crate lists; the `validate-yaml` line under **Binaries** must go. |
| 193 | +- [ ] Commit, push to `feature/…`, open PR against `main`, watch CI (5 checks), |
| 194 | + report. Merge is the user's call. |
| 195 | + |
| 196 | +## 8. Proven gotchas (from Phases 1 & 3 — don't rediscover them) |
| 197 | + |
| 198 | +- **CRLF on Windows** → `.gitattributes` `* text=auto eol=lf` from the first commit. |
| 199 | +- **Stable clippy stricter than q2's nightly** → fix lints in the new repo |
| 200 | + (`items_after_test_module` etc.); the standalone repo becomes the single source. |
| 201 | +- **WASM workspace resolution** → §5; verify with full `cargo xtask verify`, never |
| 202 | + `--skip-hub-build`. |
| 203 | +- **`| tail` masks `cargo xtask verify`'s real exit code** → run it without a tail |
| 204 | + pipe (or check the file), or use `run_in_background`. |
| 205 | +- **crates.io / GitHub are user/identity-gated and irreversible** → you prep & dry- |
| 206 | + run; the user publishes and (optionally) `cargo owner --add github:posit-dev:<team>`. |
| 207 | + |
| 208 | +## 9. Open items to raise with the user |
| 209 | + |
| 210 | +1. **Error-code policy (§6 A vs B)** — the one blocking decision. |
| 211 | +2. Confirm the repo is `posit-dev/quarto-yaml` (workspace, two crates) — decided |
| 212 | + 2026-06-29, but re-confirm before `gh repo create`. |
| 213 | +3. Reuse the Phase-1 visibility/ownership choices (public repo; personal crates.io |
| 214 | + account now, `cargo owner --add posit-dev` on a weekday) unless told otherwise. |
| 215 | +4. Whether to also relocate the deleted **`CONTRIBUTING-ERRORS.md`** intent / any |
| 216 | + q2-internal YAML docs (low priority). |
0 commit comments