Skip to content

Keep Deno KV caches per V8 release branch - #15180

Merged
cderv merged 4 commits into
mainfrom
fix/deno-kv-cache-compat
Oct 23, 2026
Merged

cderv merged 4 commits into
mainfrom
fix/deno-kv-cache-compat

Conversation

@cderv

@cderv cderv commented Oct 23, 2026

Copy link
Copy Markdown
Member

Deno 2.9 writes V8 serializer format 16 where 2.7 writes 15, and an older V8 rejects newer formats. Once a Quarto on Deno 2.9 touches the shared sass.kv or a project's .quarto/project-cache/deno-kv-file, every older Quarto on the machine fails to render with RangeError: could not deserialize value until the cache is deleted. The reverse direction is fine, 2.9 reads 2.7 data. This has to land before the Deno bump in #15157 (#15152), and in any 1.10.x backport of it.

Released Quarto cannot be fixed on its read side, so the new Quarto must never write a store that older versions open. Both caches now live in a v8-<major>.<minor> subdirectory, taken from Deno.version.v8, holding the KV file and its data files. A separate index in the same directory would let two versions overwrite each other's <hash>.css or content files. Untagged stores are left untouched, so older versions keep using them.

Alternatives considered: treating a deserialize error as a cache miss only helps versions that already contain the change, and tagging by Deno version instead of V8 branch would invalidate the cache on every patch bump. Old tagged directories are not pruned. This is an interim measure until the cache moves off Deno KV in the runtime migration.

Project cache clear()

clear() passed each index entry's value to join() as if it were a hash, but disk entries store {hash, type} and immediate entries store {value, type}, so any populated cache threw Path must be a string. Its only caller is init() when the stored cache version is unknown, which is exactly the cache written by a newer Quarto, so it is reachable from this change's scenario. It now removes the hash file only for disk entries, with safeRemoveSync since entries with identical content share one file.

Checklist

I have (if applicable):

  • referenced the GitHub issue this PR closes
  • updated the appropriate changelog in the PR
  • ensured the present test suite passes
  • added new tests
  • created a separate documentation PR in Quarto's website repo and linked it to this PR
AI-assisted PR
  • AI tool used: Claude Code
  • Codebase grounding: local clone
  • Human review: I have reviewed, tested, and verified the AI-generated content before submitting.

Note: autonomous AI agents submitting PRs without human oversight are not permitted — see the Code of Conduct.

Related to #15157, #15152

cderv added 4 commits October 23, 2026 12:17
Deno 2.9 writes V8 serializer format 16 (2.7 writes 15), and an older V8
rejects newer formats, so a Quarto on Deno 2.9 poisons the shared sass.kv
for every older Quarto on the machine (RangeError: could not deserialize
value). Open the store and write the compiled CSS under v8-<major>.<minor>
inside the requested cache dir; the untagged files are never touched, so
older versions keep working. The registry stays keyed by the requested
path, so cleanup passes that key explicitly (#14594, #13955).
Same cause as the Sass cache: Deno 2.9 writes V8 serializer format 16 and
an older V8 rejects it, so a shared .quarto/project-cache/deno-kv-file
written by a newer Quarto breaks every older Quarto on the project. Put the
index and the content-addressed files under project-cache/v8-<major>.<minor>.
The untagged files stay untouched for older versions.
The new cache tests write and read legacy sentinel files and open a KV store to prove the untagged stores stay untouched.
clear() passed each index entry's value to join() as if it were a hash string, but disk entries store {hash, type} and immediate entries store {value, type}. join() threw "Path must be a string", so any populated cache made clear() fail.

The only caller is init() when the stored cache version is unknown, i.e. a cache written by a newer Quarto. That is the cross-version case this branch is about: an older Quarto would crash there instead of clearing the cache. The bug dates from the cache's introduction in 60294d0.

clear() now removes the hash file only for disk entries, and drops immediate entries from the index alone. It uses safeRemoveSync because entries with identical content share one hash file, so the second removal finds it gone. That replaces a raw Deno call, so the src count in the runtime boundary baseline drops by one.
@posit-snyk-bot

posit-snyk-bot commented Oct 23, 2026 •

Copy link
Copy Markdown
Collaborator

✅ 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.

@cderv
cderv merged commit 714c426 into main Oct 23, 2026
51 of 52 checks passed
@cderv
cderv deleted the fix/deno-kv-cache-compat branch October 23, 2026 12:20
cderv added a commit that referenced this pull request Oct 23, 2026
#15180 merged without a changelog entry.
cderv added a commit that referenced this pull request Oct 23, 2026
#15183)

The PR-mode runtime boundary check compared the merge result only with
the base branch's baseline, so a PR that had already run
--update-baseline --accept-increase still got a warning for each new
call and the maintainer block. See #15180, where six new Deno calls
were accepted in the PR's baseline and still reported as pending.

- Read the working-tree baseline in PR mode. An increase the PR raised
  there, and that the merge result does not exceed, is a notice listed
  under "Accepted in this PR".
- Keep the warnings when the base baseline has room the PR did not
  take, or when the merge result is now above the accepted count.
- Leave errors for Deno APIs that src/deno_ral replaces unchanged.

Related to #15153
cderv added a commit that referenced this pull request Oct 23, 2026
…absorb

Two cache changes landed or are landing ahead of the plans that assume
they own that code. #15180 keeps Deno KV stores in a v8-<branch>
directory and exports v8Branch from platform.ts, because Deno 2.9 writes
V8 values Deno 2.7 cannot read. The #15119 fix creates src/deno_ral/kv.ts
first, with damage classification that PR 18 has to keep.

The plans stay unedited until their step 0, so the findings go in as
"Open decisions with an owner" rows naming PRs 18, 19, 26, 27b and 30.
The store-name convention and the platform.ts/kv.ts vocabulary state the
per-V8-branch rule now, since every later plan reads them.

docs: runtime migration, keep PR 18's list() change and move the KV damage tests to PR 30

Dropping PR 18 Task 2 Step 2 whole would also drop the list({ prefix: [] })
to list([]) change clear() needs once the index is a KeyValueStore, so the
step now shrinks to that change and keeps the clear() body #15180 shipped.

The PR 26 row asked for known-failures entries for the Deno KV damage
tests, but its node-unit job runs only tests/unit/ral/ and rejects an
entry for a file that did not run. Those tests first meet Node in PR 32's
full suite, after PR 30 removes Deno KV, so PR 30 replaces or deletes
them instead.
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