Skip to content

quarto-core + xtask: extension-subtree infrastructure (julia epic Steps 3 + 4-infra) - #717

Merged
gordonwoodhull merged 4 commits into
mainfrom
feature/extension-subtree-infra
Sep 24, 2026
Merged

gordonwoodhull merged 4 commits into
mainfrom
feature/extension-subtree-infra

Conversation

@gordonwoodhull

@gordonwoodhull gordonwoodhull commented Sep 24, 2026 •

Copy link
Copy Markdown
Member

What this is

This PR ports Quarto 1's extension-subtree machinery to q2 and proves it end-to-end — without vendoring any real engine. It is the infrastructure half of Step 4 of the julia-engine epic, deliberately split out so it never waited on external PRs. The original gate on the julia-specific remainder (a PumasAI-hosted branch, PumasAI/quarto-julia-engine#15) is gone as of 2026-09-24: the epic's Step 2c pivoted to using the gordonwoodhull/quarto-julia-engine fork as q2's upstream of record until the Q1 schema change (quarto-dev/quarto-cli#14936) ships in a stable release. What still gates vendoring julia is only q2-internal sign-off (epic Q8/Q9). Once cleared, bundling the Julia engine is one row in a table and one include_dir! registration — the runbook is included here (claude-notes/instructions/extension-subtrees.md).

It also carries an independent q2 bug fix (epic Step 3 / finding F5): static engine-claim lookup was case-sensitive while dynamic claiming was not, so a ```{Julia} cell claimed dynamically but missed its own static declaration.

The three pieces

1. cargo xtask pull-extension-subtree — a port of Q1's hidden pull-git-subtree dev command (src/command/dev-call/pull-git-subtree/cmd.ts). Maintains vendored extension repos as git subtrees under resources/extension-subtrees/: finds the last split via the git-subtree-dir: trailer, subtree add --squash on first run, no-op when there's nothing new, subtree pull --squash otherwise. The SUBTREES table ships empty — adding the julia row is the gated Step 4 remainder. Test-only seams (QUARTO_SUBTREE_ROOT, --table <json>) let the tests run real git against fixture remotes in tempdirs; this is xtask's first test module that shells out, and it's hermetic.

2. Multi-root builtin discovery. discover_extensions / discover_project_extensions now take &[&Path] builtin roots instead of Option<&Path>; subtree payloads are scanned after the regular builtin dir and before user extensions, so last-match-wins still lets a user install override a bundled extension. builtin_extension_subtree_roots() resolves roots from (a) the QUARTO_EXTENSION_SUBTREES_DIR dev/test override, (b) per-subtree embedded ResourceBundle payloads, (c) the WASM VFS. Along the way this fixes a real inconsistency: discover_extensions_only hardcoded None for builtins on WASM — WASM builds got no builtin extensions at all. That split is gone; native and WASM share one path.

3. End-to-end proof with a fake extension. synth-echo (a fixture shaped like a vendored repo, claiming a synthsub language) is discovered via the subtree-root mechanism with zero _extensions/ install and executed by the real Deno engine host through render_to_file — the same entry quarto render uses. The fake lives under tests/fixtures/ and never ships: release binaries embed a placeholder README, and nothing is registered in EXTENSION_SUBTREE_PAYLOADS yet.

Design decisions reviewers should know about

  • List of builtin roots, not one merged bundle (mirrors Q1's separate extension-subtrees root). Each future subtree gets its own include_dir! static so we embed only its _extensions/ payload (~68K for julia), never the whole vendored repo (~14M of tests and CI config).
  • No filterBundledSubtreeEngines analogue needed. Q1 strips bundled engines from pandoc metadata because it resolves engine objects into it; q2's extension-contributed engines are registry-only and never enter writer metadata. This is asserted by test (the engine's own name must not appear in rendered HTML), not merely surveyed.
  • The one deliberate deviation from Q1's script: after subtree add, the port returns instead of falling through to a same-invocation pull check — an apparent quirk in the JS, not reproduced.

Test story

TDD throughout; every phase gated on clippy -D warnings + per-crate nextest, with full cargo xtask verify (including hub-client build/tests) green at each boundary. Workspace totals at the final gate: 14485 passed, 0 failed, 200 skipped — +9 tests over the branch point, exactly the ones this PR adds:

  • 4 xtask tests driving real git subtree against fixture remotes (add, no-op, pull, unknown-name, all-rows)
  • 3 discovery/roots unit tests (multi-root ordering, user-overrides-subtree, env-override honored)
  • 2 e2e tests (zero-install discovery; real Deno execution + the registry-only assertion)
  • 3 case-sensitivity tests (RED before the F5 fix, GREEN after)

Full details, including the empirical git subtree behaviors the tests encode, are in the plan.

Explicitly out of scope

  • Vendoring the actual julia engine — will subtree from the q2-static-declarations branch of gordonwoodhull/quarto-julia-engine (epic Step 2c pivoted to the fork on 2026-09-24); gated only on the 14M git-history sign-off (epic Q8) and the no-Julia UX decision (Q9). Planned as a stacked PR on this one.
  • Fixture convergence (hand-maintained julia fixture vs. bundled copy) — epic Step 5.
  • UX on a machine without Julia installed (epic Q9).

Port of Q1's hidden `pull-git-subtree` dev command: fetches a subtree's
remote and reconciles it into the repo via `git subtree add --squash`
(no prior split) or `git subtree pull --squash` (new upstream commits),
tracked via the `git-subtree-dir`/`git-subtree-split` trailers `git
subtree` writes into its squash commit. SUBTREES starts empty; the
first real row (julia) lands in the epic's Step 4.

Covers the plan's five scenarios via inline unit tests against a real
git fixture remote/consumer pair: add, no-op on immediate re-run, pull
on a new upstream commit, unknown-name error listing available names,
and omitted/`all` processing every table row. Also smoke-tested through
the real `cargo run -p xtask -- pull-extension-subtree` binary.

Plan: claude-notes/plans/2026-09-23-extension-subtree-infrastructure.md
Adds `builtin_extension_subtree_roots` + `all_builtin_extension_roots` to
extension/mod.rs (env override -> per-subtree ResourceBundles, empty until
a real subtree is vendored -> WASM VFS path), and pluralizes
discover_extensions/discover_project_extensions from a single
Option<&Path> builtin dir to &[&Path], scanned in order before user
extensions. Updates all three production call sites and fixes a latent
bug along the way: discover_extensions_only hardcoded no built-ins on
WASM while stage/context.rs and ProjectConfig::parse_config correctly
used builtin_extensions_path — both now go through the same
all_builtin_extension_roots helper.

Adds the WASM parallel (populate_extension_subtrees in
wasm-quarto-hub-client, mirroring populate_builtin_extensions) and the
resources/extension-subtrees/README.md placeholder both include_dir!
call sites need to compile (git doesn't track empty directories).

crates/wasm-quarto-hub-client/Cargo.lock picked up a large but
legitimate diff from resolving that crate's dependencies fresh for the
first time in this worktree; kept as-is.

Plan: claude-notes/plans/2026-09-23-extension-subtree-infrastructure.md
Adds the synth-echo fake extension fixture (tests/fixtures/extension-subtrees/synth-echo/,
shaped like a whole vendored repo with the extension nested at
_extensions/synth-echo/) and two integration tests proving the Phase 1-2
infrastructure works end-to-end:

- discovers_synth_echo_via_subtree_root_env_var_with_zero_extensions_install:
  discovery via QUARTO_EXTENSION_SUBTREES_DIR + all_builtin_extension_roots,
  with no project _extensions/ install.
- e2e_single_file_render_executes_synthsub_via_subtree_root: a single-file
  render (no _quarto.yml) of a {synthsub} cell through the real Deno engine
  host, exercising discover_extensions_and_build_registry -- the other
  caller of discover_extensions_only, closing the loop on Phase 2's
  WASM-branch fix. Also verifies D4 (no Q1-style engine-name leak into
  rendered metadata).

Both tests passed against the existing Phase 1-2 implementation with no
further code changes needed -- this phase is the proof, not new work.
HERMETIC_FIXTURES in engine_fixture_build.rs gained one name
("synth-echo"); ensure_bundle/build_bundle needed no path-aware variant,
since they already take an explicit ext_dir with no assumption about
where it lives.

Adds claude-notes/instructions/extension-subtrees.md, the runbook for
registering a real subtree (one SUBTREES row, one include_dir! payload
static) that the julia-engine epic step will follow.

Plan: claude-notes/plans/2026-09-23-extension-subtree-infrastructure.md
(all three phases complete)
…Step 3, F5)

Dynamic claiming lowercases the language (claimsLanguage: (language) =>
language.toLowerCase() === "julia"), but the static path never normalized
either side: parse_claims_map inserted claim keys verbatim and
lookup_static_claim did an exact claims.get(language). A {Julia} cell
therefore claimed dynamically yet silently missed the static/zero-load
resolution path that claims: exists to provide.

Fix (epic option 1, mirroring normalize_ext's file-extensions precedent):
- parse_claims_map lowercases claim keys at parse time, in both the
  map-entry and top-level list-shorthand forms
- lookup_static_claim lowercases the language before lookup

TDD: three new tests RED before the fix, GREEN after —
parse_claims_map_lowercases_language_keys,
parse_claims_map_lowercases_list_shorthand_language (read.rs),
lookup_static_claim_is_case_insensitive (types.rs).

Gates: clippy -p quarto-core --all-targets -D warnings clean; cargo fmt
clean; cargo nextest run --workspace 14488 passed / 200 skipped (baseline
14485 + the 3 new tests, 0 failed); cargo xtask verify --skip-hub-build
--skip-hub-tests all steps passed. Epic plan updated (Step 3 DONE +
Developments entry).
@posit-snyk-bot

posit-snyk-bot commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor

✅ Snyk checks have passed. No issues have been found so far.

Status Scan Engine Critical High Medium Low Total (0)
✅ Open Source Security 0 0 0 0 0 issues
✅ Licenses 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

@gordonwoodhull
gordonwoodhull merged commit bd2d96e into main Sep 24, 2026
10 checks passed
@gordonwoodhull
gordonwoodhull deleted the feature/extension-subtree-infra branch September 24, 2026 11:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants