Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
bc40e4d
docs(plans): provenance-epic revisions to plans 3-8
gordonwoodhull May 20, 2026
39ba9b7
docs(plans): tighten Plan 3 scope + propagate to plans 6/7/7a
gordonwoodhull May 20, 2026
d634e42
docs(plan-3): incorporate review decisions on hashing, drive modes, f…
gordonwoodhull May 21, 2026
bd7cc18
docs(plan-3): reuse existing test helpers; pin decisions; long-lived …
gordonwoodhull May 21, 2026
96975b6
plan-3 phase 1: meta-hash + divergence localization
gordonwoodhull May 21, 2026
6d02d6a
plan-3 phase 2: idempotence test crate scaffolding
gordonwoodhull May 21, 2026
9a5ac23
plan-3 phase 3: 11 carry-forward fixtures
gordonwoodhull May 21, 2026
33f9d13
chore: refresh lockfiles after npm install + wasm build
gordonwoodhull May 21, 2026
19c9d97
plan-3 phase 4a: 9 gap-closure doc fixtures + 1 in queue
gordonwoodhull May 21, 2026
d2879ed
plan-3 phase 4b: include-in-header + resource-image fixtures
gordonwoodhull May 21, 2026
9ec134b
plan-3 phase 4c: website-chrome, website-links, website-listing
gordonwoodhull May 21, 2026
8f05fa6
plan-3 phase 4d: attribution fixture
gordonwoodhull May 21, 2026
20b7840
plan-3 phase 6: idempotence-contract.md + cross-links
gordonwoodhull May 21, 2026
fa43b50
plan-3 phase 7: final verification + queue state recorded
gordonwoodhull May 21, 2026
ea170e9
plan-3: check off the per-fixture coverage-gaps inventory
gordonwoodhull May 21, 2026
c874d6b
bd-rz2we: split vfs_root into write-root + url-root in ResourceResolv…
gordonwoodhull May 21, 2026
09bb3c3
docs(plans): Plan 4 implementation-ready + cross-plan `from` rename
gordonwoodhull May 21, 2026
2a33d19
docs(plans-4-and-5): annotate bd-3odjm as Plan-5-owned baseline failure
gordonwoodhull May 21, 2026
4c46576
docs(plans): propagate SmallVec macro to plans 5 & 6 code samples
gordonwoodhull May 21, 2026
9a04fc3
docs(plans): bump SmallVec capacity to 2 and fold in research
gordonwoodhull May 21, 2026
5bea4d0
docs(plans): correct SmallVec cap=2 memory delta (~40 bytes, not 16)
gordonwoodhull May 22, 2026
e120d7f
docs(plan-4): consolidate file-id walkers + close open questions
gordonwoodhull May 22, 2026
1099b5a
docs(plan-5): review pass — checklist, TS shape, scope cleanups
gordonwoodhull May 22, 2026
d92472c
Merge feature/provenance-plan-5: review pass on Plan 5 wire format
gordonwoodhull May 22, 2026
7ddd43b
plan-4: implement SourceInfo Generated + Anchor types
gordonwoodhull May 22, 2026
c7cdb01
docs(plan-4): record implementation surprises
gordonwoodhull May 22, 2026
3c089d1
docs(plan-5): review-2 pass — phase reorder, strict readers, TS rename
gordonwoodhull May 22, 2026
eb06c4c
docs(plan-5): sharpen Phase 0 + carry Plan-4 implementation learnings
gordonwoodhull May 22, 2026
e60145f
docs(plan-5): resolve open questions from pre-impl audit
gordonwoodhull May 22, 2026
d7b8450
fix(pampa): close bd-3odjm with code-3 dual-shape + code-4 readers (P…
gordonwoodhull May 22, 2026
3074b6e
feat(pampa): emit Generated as JSON wire code 4 (Plan 5 Phases 3+4, a…
gordonwoodhull May 22, 2026
cf6b1a0
feat(preview-renderer): consume code-4 Generated wire format (Plan 5 …
gordonwoodhull May 22, 2026
03feae8
docs(plan-6): review pass — close open questions, expand scope, fix P…
gordonwoodhull May 22, 2026
cc1b60e
docs(plans 6-8): post-review followups — cleanup open questions, cros…
gordonwoodhull May 22, 2026
89d8278
Merge review/provenance-plan-6: Plan-6 review pass
gordonwoodhull May 22, 2026
e909c83
plan-6 phase 0: add Inline/Block::source_info_mut accessors
gordonwoodhull May 22, 2026
b270c51
plan-6 audit: enumerate sites + document AttrSourceInfo invariant
gordonwoodhull May 22, 2026
6423220
plan-6: shortcode stamper + dispatch funnel + error/literal call sites
gordonwoodhull May 22, 2026
91f4bc1
plan-6: synthesizer transforms emit Generated provenance
gordonwoodhull May 22, 2026
085f262
plan-6 tests: per-transform shape + shortcode + Lua enrichment
gordonwoodhull May 22, 2026
2f27448
plan-6: verify pass + WASM Cargo.lock update + plan checklist closed
gordonwoodhull May 22, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ proc-macro2 = { version = "1.0.106", features = ["span-locations"] }
schemars = "1.2.1"
serde = { version = "1.0.228", features = ["derive"] }
serde_json = "1.0.149"
smallvec = { version = "1.13", features = ["serde"] }
serde_yaml = "0.9"
thiserror = "2.0"
toml = "0.9.11"
Expand Down
149 changes: 149 additions & 0 deletions claude-notes/instructions/idempotence-contract.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
# The q2-preview idempotence contract

A note for transform / filter authors. Read this before adding a new
Rust transform to `build_q2_preview_transform_pipeline`, a new stage
to `build_q2_preview_pipeline_stages`, or a new built-in Lua filter
under `resources/extensions/`.

The contract is enforced by the CI gate at
`crates/quarto-core/tests/idempotence.rs`, which is the Phase-3
deliverable of the provenance epic. The full design lives in
`claude-notes/plans/2026-05-04-q2-preview-plan-3-builtin-filter-idempotence.md`.

## What the contract says

Running the q2-preview pipeline twice on the same input must produce
the same structural AST: identical `blocks` hash and identical `meta`
hash with `meta.rendered.*` excluded.

"Same input" means the same byte sequences for the same file layout —
but **not** necessarily the same absolute paths. Each idempotence
fixture runs both pipeline invocations inside a fresh `TempDir`, so
the project root differs across runs while the content is identical.
A transform that captures the absolute project root into the AST will
fail the gate.

## What the hash includes and excludes

Defined by `compute_blocks_hash_fresh` /
`compute_meta_hash_fresh_excluding_rendered` in
`crates/quarto-ast-reconcile/src/hash.rs`.

Included:

- All block / inline structure (type, text, attributes, children).
- All meta tree structure: scalars by `Yaml` payload; `Map` entries
in **insertion order** (no sort); `Array` entries in order;
`merge_op` on every `ConfigValue`.
- `PandocInlines` / `PandocBlocks` payloads inside meta values,
recursed via the existing block/inline hashers.

Excluded:

- `SourceInfo` on every block, inline, and `ConfigValue`.
- `key_source` on every `ConfigMapEntry`.
- Top-level `meta.rendered.*` — chrome transforms, `IncludeResolveStage`,
the favicon transform, and Bootstrap/clipboard injection populate
HTML/text strings under `rendered.*` that may legitimately vary in
trivial whitespace or attribute ordering; HTML-shape canonicalization
is a different concern.

Source-info is excluded by design so Plan 4's source-info churn
doesn't break the contract.

## What this means in practice

A new transform / stage / filter must:

### 1. Not depend on undefined-iteration-order state

If you populate a `Map` value in `meta` from a `HashMap`, the
iteration order is undefined and two runs will produce different
hashes. The gate uses insertion-order map hashing precisely to catch
this — sorting would silently mask it.

Use `Vec<(key, value)>`, `BTreeMap`, or `LinkedHashMap` and append
in a deterministic order.

### 2. Not capture process-local state into the AST

No timestamps, no PIDs, no random IDs, no absolute paths derived
from the project root, no `temp_dir()` output. If you need to refer
to a file, emit a path relative to the project root.

Source-info is the only legitimate place absolute paths live, and
the hash excludes source-info by design.

### 3. Use fresh Lua state per pipeline run (Lua filters / shortcodes)

The shortcode resolver and per-filter Lua engine are constructed
fresh inside their respective transforms; do not stash global state
on `_G` and expect it to survive between runs. If you need a cache,
key it by the *filter* identity, not the *pipeline run* — and clear
it on `Lua` construction.

### 4. Not execute engine cells

CI doesn't run Jupyter / Knitr. Fixtures use only fenced code blocks
(`` ```python `` etc.) — AST nodes, not executed. If your transform's
behavior is conditional on engine-execution side effects, the gate
cannot exercise it.

## Adding a fixture when you add a new transform

Every new transform / filter must come with at least one fixture
that exercises its happy path. Add it to
`crates/quarto-core/tests/idempotence.rs`:

- Trivial single-page fixture: use the `doc_fixture(name, content)`
helper. Writes `index.qmd` to a fresh `TempDir` and runs both
`DriveMode::SingleFile` and `DriveMode::ProjectOrchestrator`.
- Multi-file fixture (sibling files, includes, image resources):
write an inline `setup` closure that writes everything into the
fresh `TempDir`. Same dual-mode run.
- Website-chrome / link / listing fixture: use
`modes: ORCHESTRATOR_ONLY`. Chrome transforms need a populated
`ProjectIndex`, which only the orchestrator pass-1 builds.
- Attribution exercise: set `attribution_json: Some(...)` with a
deterministic transport-shape JSON; `PreBuiltAttributionProvider`
is installed on the `RenderContext` automatically. Do not use
`GitBlameProvider` here — it depends on actual git history.

See `crates/quarto-core/tests/fixtures/idempotence/README.md` for
the per-fixture rules (no engine cells, no absolute paths, mode
mapping).

## If your new fixture fails on first run

Two possibilities:

1. **Your transform really is non-deterministic.** Trace the
`DivergencePoint` the panic message hands you (block index, or
meta key path) and fix the underlying state — usually a
`HashMap` iteration, a `SystemTime::now()`, or an absolute path
stuffed somewhere it shouldn't be.

2. **The hasher is wrong.** Vanishingly unlikely with FxHasher,
but if you've ruled out (1), file a bug against
`quarto-ast-reconcile`.

Per the plan's long-lived-integration-branch policy, **do not
`#[ignore]` the failing test** without explicit user approval.
Failing fixtures are the triage backlog; the integration branch
(`feature/provenance`) is allowed to be red while the queue is
drained.

## Related

- `claude-notes/plans/2026-05-04-q2-preview-plan-3-builtin-filter-idempotence.md` —
the plan that introduced this gate, with the design rationale.
- `claude-notes/plans/2026-05-04-q2-preview-plan-7a-user-filter-idempotence.md` —
the runtime counterpart: per-user-filter idempotence detection at
render time, with `idempotent: false` opt-out. The contract this
file describes is the CI-time half for built-ins; Plan 7a is the
runtime half for user filters.
- `crates/quarto-ast-reconcile/src/hash.rs` — the hash implementations
and unit tests.
- `crates/quarto-core/tests/idempotence.rs` — the gate.
- `crates/quarto-core/tests/fixtures/idempotence/README.md` — the
fixture-format rules.
Loading