Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
4 changes: 3 additions & 1 deletion .braid/snapshot.jsonl

Large diffs are not rendered by default.

17 changes: 17 additions & 0 deletions .config/nextest.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,23 @@
# watcher. Limited to one in-flight process at a time.
quarto-preview-fs-watcher = { max-threads = 1 }

# Julia-engine e2e tests share ONE ambient transport file
# (`~/Library/Caches/quarto/julia/julia_transport.txt`): each test uses
# `setup_julia_project()` (temp project dir, but NOT a temp HOME), so
# concurrent `daemon: false` runs each boot their own QNR server and
# overwrite each other's transport entry — a client then reads a stale
# entry and fails the socket handshake with "Incorrect HMAC digest" at
# `isopen`. Observed 2026-07-03 (bd-h4rhohhy P3 verification): rotating
# victims (j1/j2 on one run, j3/j4 on a clean-machine rerun) confirm an
# intra-suite race, not any single bad test. Serializing the suite is
# the config-level fix; full hermetic isolation for the j-tests is
# tracked separately (they belong to the julia-validation plan).
julia-shared-transport = { max-threads = 1 }

[[profile.default.overrides]]
filter = "package(quarto-preview) & binary(integration) & test(/^(staleness|eager_capture|boot)::/)"
test-group = "quarto-preview-fs-watcher"

[[profile.default.overrides]]
filter = "package(quarto-core) & binary(integration) & test(/^julia_engine_e2e::/)"
test-group = "julia-shared-transport"
20 changes: 20 additions & 0 deletions .github/workflows/test-suite.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ concurrency:

env:
PANDOC_VERSION: "3.8.3"
# Deno version provisioned for the QUARTO_CI assertion test in ts_process.rs.
# Must provide `Deno.Command`/`Deno.stdin.isTerminal()` (available since Deno 1.40+).
DENO_VERSION: "2.9.0"

jobs:
test-suite:
Expand Down Expand Up @@ -119,6 +122,20 @@ jobs:
if: runner.os == 'macOS'
run: brew install minisign

# Deno — required for ts_process.rs engine tests and the deno_available_when_quarto_ci
# assertion. Plain install (curl + $GITHUB_PATH on Linux; brew on macOS) avoids
# /opt/hostedtoolcache/ so the "Free disk space" invariant below is not broken.
- name: Set up Deno (Linux)
if: runner.os == 'Linux'
shell: bash
run: |
curl -fsSL https://deno.land/install.sh | sh -s -- v${DENO_VERSION}
echo "$HOME/.deno/bin" >> "$GITHUB_PATH"

- name: Set up Deno (macOS)
if: runner.os == 'macOS'
run: brew install deno

# Free disk space on Linux runners (14 GB SSD is tight for Rust monorepo).
# `remove_tool_cache: true` is safe — no step in this job uses /opt/hostedtoolcache/
# (no setup-node, setup-python, etc.). See claude-notes/2026-04-28-ci-disk-space-and-profile-ci.md.
Expand Down Expand Up @@ -179,6 +196,9 @@ jobs:
run: cargo nextest run --tests --cargo-profile ci
env:
RUSTFLAGS: "-D warnings"
# Turns the silent Deno-skip in deno_available_when_quarto_ci into a hard
# failure if the "Set up Deno" step above regresses.
QUARTO_CI: "1"

wasm-tests:
name: WASM Tests
Expand Down
52 changes: 52 additions & 0 deletions .github/workflows/ts-test-suite.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ concurrency:

env:
PANDOC_VERSION: "3.8.3"
# Deno version for engine-host-deno vitest + deno-test + bundle freshness gate.
DENO_VERSION: "2.9.0"

jobs:
test-suite:
Expand Down Expand Up @@ -117,6 +119,14 @@ jobs:
shell: bash
run: npm ci

# Deno — needed for engine-host-deno deno-test and the freshness gate.
# denoland/setup-deno is safe here: this workflow has no "Free disk space"
# step (and no remove_tool_cache invariant to preserve).
- name: Set up Deno
uses: denoland/setup-deno@v2
with:
deno-version: ${{ env.DENO_VERSION }}

# WASM build for hub-client (must happen before TypeScript build)
- name: Set up Clang (Linux)
if: runner.os == 'Linux'
Expand Down Expand Up @@ -145,3 +155,45 @@ jobs:
run: |
cd hub-client
npm run test:ci

# engine-host-deno: vitest suite (105+ Node-side harness tests)
- name: Run engine-host-deno tests (vitest)
shell: bash
run: npm run test -w @quarto/engine-host-deno

# engine-host-deno: deno-native test (the one leg that requires Deno)
- name: Run engine-host-deno deno test
shell: bash
run: deno test --allow-all ts-packages/quarto-engine-host-deno/src/deno-host.deno-test.ts

# plan1a.6 Phase 2 (Deno dial-back) — seam #8: connectControl round-trip
# over a REAL loopback socket (Deno.listen({ port: 0 }) in-test); no mock
# Deno.Conn. CI-only tier (cannot be a vitest test — the module references
# Deno.*). `--sloppy-imports` lets deno's type-checker resolve
# @quarto/types' `.js` internal specifiers pulled in transitively via
# ./types.ts (same reason the wire-parity step below needs it).
- name: Run engine-host-deno control-transport deno test
shell: bash
run: deno test --allow-all --sloppy-imports ts-packages/quarto-engine-host-deno/src/control-transport.deno-test.ts

# T-Gate-parity: TS↔Rust wire-dual parity (Plan 2 Phase B gate). Reads the
# Rust-serialized fixture (crates/quarto-core/tests/fixtures/ts_wire_parity.json,
# produced by the regen-gated #[test] in ts_protocol.rs) and set-equates each
# instance's keys against a KEYS list pinned to the TS wire type via
# `satisfies`/`_Exhaustive`. `--sloppy-imports` lets deno's type-checker
# resolve @quarto/types' `.js` internal specifiers (so the compile guards
# are checked here, not just the runtime set-equality). @quarto/types is
# mapped in the repo-root deno.jsonc.
- name: Run engine-host-deno wire-parity deno test
shell: bash
run: deno test --allow-all --sloppy-imports ts-packages/quarto-engine-host-deno/src/wire-parity.deno-test.ts

# Bundle freshness gate: rebuild engine-host-deno.js from source and assert
# the committed bytes are unchanged. Fails if someone edited TS but forgot
# to run `npm run bundle`. build-info.json is gitignored (volatile builtAt);
# the diff targets only the bundle itself.
- name: Check engine-host-deno bundle freshness
shell: bash
run: |
npm run bundle -w @quarto/engine-host-deno
git diff --exit-code -- ts-packages/quarto-engine-host-deno/dist/engine-host-deno.js
15 changes: 15 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,14 @@ node_modules/
**/.quarto/hub/hub.json
ts-packages/*/dist/
ts-packages/*/dist-bundle/
# Exception: the engine-host-deno bundle is embedded into the q2 binary via
# include_str! (plan1a-host "Bundle embedding"), so the single bundle file must
# be tracked even though it lives under an otherwise-ignored dist/. A committed
# placeholder lets fresh clones compile; Plan 1b overwrites it with the real
# esbuild output (same path, new bytes). Other dist/ artifacts stay ignored.
!ts-packages/quarto-engine-host-deno/dist/
ts-packages/quarto-engine-host-deno/dist/*
!ts-packages/quarto-engine-host-deno/dist/engine-host-deno.js
ts-packages/*/*.tsbuildinfo
q2-demos/*/dist/
crates/wasm-quarto-hub-client/pkg/
Expand Down Expand Up @@ -57,3 +65,10 @@ CLAUDE.local.md

# local-prod mode data directory
.local-prod-data/

# Hermetically-regenerated TS engine extension bundles (built at test time
# via crate::engine_fixture_build; see plan1c3 Task 6). legacy-python's
# committed stub is unaffected — a gitignore entry cannot untrack an
# already-tracked file.
crates/quarto-core/tests/fixtures/extensions/*/dist/
resources/extension-build/deno.lock
106 changes: 106 additions & 0 deletions .superpowers/sdd/1c-task-1-report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
# Task 1 Report — Engine-contribution data types + static-claim → LanguageClaim conversion

## Status

DONE

## Files Changed

- `crates/quarto-core/src/extension/types.rs` — sole file modified

## What Was Added

### New types
- `EngineContribution` enum (`External { path, name, claims, file_extensions, claims_files }` + `Reorder { name }`)
- `StaticLanguageClaim` struct (`kind`, `priority`, `when_class`)
- `ClaimKind` enum (`Primary`, `Interop`, `Fallback`)

### New field on `Contributes`
```rust
pub engines: Vec<EngineContribution>,
```
All three existing `Contributes { .. }` literals already used `..Default::default()`, so no manual updates were needed:
- `crates/quarto-core/src/filter_resolve.rs:488`
- `crates/quarto-core/src/transforms/shortcode_resolve.rs:2048`
- `crates/quarto-core/src/stage/stages/metadata_merge.rs:1630`

### New functions
- `static_claim_to_language_claim(claim, first_class) -> LanguageClaim`
- `lookup_static_claim(claims, language, first_class) -> LanguageClaim`

### `LanguageClaim` derives
Already had `#[derive(Debug, Clone, Copy, PartialEq, Eq)]` at `engine/mod.rs:104` — no changes needed.

## TDD Sequence

**RED** — wrote tests with two stubs:
- `static_claim_to_language_claim`: converted correctly but ignored `when_class` (always converted)
- `lookup_static_claim`: always returned `Primary(1)` ignoring map contents

Ran `cargo nextest run -p quarto-core -E 'test(extension::types::tests::static_claim) or test(extension::types::tests::lookup)'`:
- 5 PASS (positive conversion cases — stubs handled those correctly)
- 4 FAIL (the required RED cases):
- `static_claim_when_class_mismatch_returns_none`: got `Primary(1)`, expected `None`
- `static_claim_when_class_mismatch_no_first_class_returns_none`: got `Primary(1)`, expected `None`
- `lookup_absent_language_returns_none`: got `Primary(1)`, expected `None`
- `lookup_present_mismatched_when_class_returns_none`: got `Primary(1)`, expected `None`

**GREEN** — replaced stubs with correct implementations:

`static_claim_to_language_claim`: added `when_class` guard before the `match`:
```rust
if let Some(ref required) = claim.when_class {
if first_class != Some(required.as_str()) {
return LanguageClaim::None;
}
}
```

`lookup_static_claim`: proper absent-check + delegation:
```rust
match claims.get(language) {
None => crate::engine::LanguageClaim::None,
Some(claim) => static_claim_to_language_claim(claim, first_class),
}
```

## Test Results

### New tests (15 total in `extension::types::tests`)
```
cargo nextest run -p quarto-core -E 'test(extension::types)'
Summary [0.062s] 15 tests run: 15 passed, 2556 skipped
```

Tests added (9 new, 6 pre-existing):
1. `static_claim_primary_no_when_class_default_priority` — `Primary(1)` default ✓
2. `static_claim_primary_no_when_class_explicit_priority` — `Primary(5)` explicit ✓
3. `static_claim_interop_and_fallback_default_priority` — `Interop(0)`, `Fallback(0)` ✓
4. `static_claim_when_class_match_converts` — `"marimo"` == `"marimo"` → `Primary(1)` ✓
5. `static_claim_when_class_mismatch_returns_none` — `"marimo"` != `"python"` → `None` ✓ (P1-14 binding)
6. `static_claim_when_class_mismatch_no_first_class_returns_none` — `"marimo"` != `None` → `None` ✓ (P1-14 binding)
7. `lookup_absent_language_returns_none` — absent key → `None` ✓
8. `lookup_present_matching_when_class_converts` — present + match → converts ✓
9. `lookup_present_mismatched_when_class_returns_none` — present + mismatch → `None` ✓

Pre-existing tests also updated:
- `test_contributes_default`: added `assert!(c.engines.is_empty())` ✓

### Broader regression check
```
cargo nextest run -p quarto-core -E 'test(extension::) or test(engine::)'
Summary [6.699s] 460 tests run: 460 passed, 2111 skipped
```

### Build verification
```
cargo build -p quarto-core
Finished `dev` profile [optimized + debuginfo] target(s) in 2.78s
```
No warnings, no errors. All three existing `Contributes` literals compile correctly via `..Default::default()`.

## Notes

- No `serde` derives added (Task 2 owns YAML parsing).
- No changes to `parse_contributes`, `TsEngine`, or resolution code.
- `LanguageClaim` needed no derive additions.
99 changes: 99 additions & 0 deletions .superpowers/sdd/1c-task-11-report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
# Task 11 Report — P2-12 + P2-13

## Summary

Both P2-12 and P2-13 are implemented, tested GREEN, and all 2595 quarto-core
tests pass. Clippy reports zero warnings.

---

## P2-13 (implemented earlier in this session)

### What changed

`partition_cells` in `crates/quarto-core/src/engine/jupyter/text_execute.rs`
gained a `multi_engine: bool` third parameter. When `false` (single-engine
sequence), owned-but-unrunnable cells are passed through unexecuted rather than
raising `NoHandlerForLanguage`. When `true` (multi-engine), the existing loud
error fires.

`ExecutionContext` gained `multi_engine: bool` (default `false`) and
`with_multi_engine(bool)`. `engine_execution.rs` computes `let multi_engine =
to_run.len() > 1` before the `into_iter()` move and passes it via
`.with_multi_engine(multi_engine)`.

### Tests (P2-13)

| Test | File | Result |
|------|------|--------|
| `test_partition_cells_owned_unrunnable_fails_loudly` | `text_execute.rs` | GREEN (updated to `multi_engine=true`) |
| `test_partition_cells_single_engine_owned_unrunnable_passthrough` | `text_execute.rs` | GREEN (new, `multi_engine=false` → Ok) |
| `test_partition_cells_cede` | `text_execute.rs` | GREEN (updated to pass `false`) |
| `test_partition_cells_execute` | `text_execute.rs` | GREEN (updated to pass `false`) |
| `test_partition_cells_mixed` | `text_execute.rs` | GREEN (updated to pass `false`) |

---

## P2-12 — Registered owning engine unavailable → loud error

### What changed

`get_engine_with_fallback` in `engine_execution.rs` return type changed from
`Arc<dyn ExecutionEngine>` to `Result<Arc<dyn ExecutionEngine>, PipelineError>`.

New behaviour matrix:

| Registered? | `is_available()` | In `spliced_engines`? | Result |
|-------------|------------------|----------------------|--------|
| Yes | true | — | `Ok(engine)` |
| Yes | false | No | **`Err(PipelineError::stage_error(...))`** ← P2-12 |
| Yes | false | Yes | `Ok(markdown)` silently (capture replay) |
| No | — | No | `Ok(markdown)` + warning |
| No | — | Yes | `Ok(markdown)` silently |

`run()` now propagates the error via `?` at the call site.

### Blast-radius analysis

One test asserted the now-obsolete silent-fallback contract:

| Test | File | What changed | Why |
|------|------|-------------|-----|
| `q2_preview_without_capture_still_warns_unavailable_engine` | `pipeline.rs` | Renamed to `q2_preview_without_capture_errors_unavailable_engine`; assertion changed from "Ok + `not available` warning" → "Err + engine name in message" | Was asserting the old silent-fallback behaviour P2-12 intentionally removes |

Three unit tests call `get_engine_with_fallback` directly (all test the
UNREGISTERED path, which still returns `Ok`):

| Test | Change | Why |
|------|--------|-----|
| `test_engine_fallback_with_unavailable_engine` | Added `.expect()` | Return type changed to `Result` |
| `test_spliced_engine_suppresses_fallback_warning` | Added `.expect()` | Return type changed to `Result` |
| `test_unspliced_engine_still_warns_when_sibling_spliced` | Added `.expect()` (both calls) | Return type changed to `Result` |

These tests still exercise unregistered engines (else branch), which return
`Ok(markdown)` both before and after P2-12 — their semantics did not change.

### New P2-12 tests

| Test | File | RED → GREEN |
|------|------|-------------|
| `test_p2_12_owning_engine_unavailable_fails_loudly` | `engine_execution.rs` | RED (result.is_ok(), expected Err) → GREEN |
| `test_p2_12_spliced_unavailable_engine_still_silent` | `engine_execution.rs` | Was already PASS (spliced path unchanged); GREEN from start |

### Vacuity check

Vacuity revert described in test comment: removing the `is_available()` gate
(falling back to markdown for ALL registered engines) causes
`test_p2_12_owning_engine_unavailable_fails_loudly` to fail with "got Ok (old
silent-fallback behaviour)". Confirmed at RED run before implementation.

---

## Test counts

- Tests added: 2 (P2-12: `test_p2_12_owning_engine_unavailable_fails_loudly`,
`test_p2_12_spliced_unavailable_engine_still_silent`)
- Tests modified: 1 renamed + 1 assertion changed (`q2_preview_without_capture_*`)
+ 4 `.expect()` additions + 5 P2-13 arg updates
- Total quarto-core: **2595 passed, 0 failed**
- Clippy: **0 warnings**
Loading
Loading