This experiment loads two real local TypeScript plugins from the sibling sandbox plugin directory: a transformer adds a reading-statistics badge to article content, and an emitter writes a deterministic published-content inventory. The badge measures selected prose rather than every displayed token; the public inventory includes unlisted content and is not a privacy boundary.
The following contiguous excerpt is copied from /home/loca/dev/quartz-sandbox/sandbox/engine/quartz.config.yaml, lines 321–332:
- source: ../plugins/sandbox-reading-stats
enabled: true
order: 900
options:
wordsPerMinute: 220
locale: en
- source: ../plugins/sandbox-manifest
enabled: true
order: 900
options:
fileName: sandbox-manifest.json
pretty: trueThe ../plugins/ paths resolve relative to the isolated engine working directory, placing ownership under /home/loca/dev/quartz-sandbox/sandbox/plugins/ rather than requiring shared-engine edits or an engine-side symlink. The receipt sandbox-plugin.json records these kinds and defaults:
sandbox-reading-stats:kind: "transformer"; defaultswordsPerMinute: 220,locale: "en". Its recorded behavior is Unicode-aware prose counting and rounded reading minutes in a server-rendered aside, with code, math, script, and style excluded. Empty pages produce 0 words and 0 minutes; repeated application is idempotent.sandbox-manifest:kind: "emitter"; defaultsfileName: "sandbox-manifest.json",pretty: true. It emitsschemaVersion: 1with sorted slug, route, title, and tags entries, encoded routes, safe basename JSON output, and destination-symlink refusal.
What the rendered output proves
The corresponding live article is https://quartz.loca.zone/experiments/local-sandbox-plugins.html, and the public artifact is https://quartz.loca.zone/sandbox-manifest.json. Evidence below is from prior captures of dist-1789511778, not commands run while authoring this page. The receipts are /home/loca/dev/quartz-sandbox/evidence/2026-09-15-quartz-migration-f1fd35c3/sandbox-experiments.json and sandbox-plugin.json in that same directory; their browser and served-artifact captures used temporary loopback servers.
The experiment receipt’s rendered_proof.routes records bytes: 36497 and markers reading_stats: 1, reading_words: "275", reading_minutes: "2". The plugin receipt’s built_effect records found: true, count: 1, and this exact article badge:
<aside class="sandbox-reading-stats" data-sandbox-reading-stats="v1" data-words="275" data-minutes="2" aria-label="Reading statistics">275 words · 2 min read · Sandbox reading stats</aside>The plugin receipt’s wider built_output_scan enumerated html_files: 47 and html_files_with_badges: 21. This is not a one-badge-per-document claim: the native canvas fixture contains seven badge occurrences and the transclusion output contains two. Native fixture output is separate from the explanatory article. DOM presence establishes a rendered badge, not an interaction test or a prediction of an individual reader’s completion time.
For the deterministic manifest:
artifacts["sandbox-manifest.json"]recordsbytes: 7782,schemaVersion: 1,scope: "published-content",entries: 44,unique_routes: 44,sorted_by_slug: true, andall_entries_have_emitted_html: true.- The plugin receipt’s
emitter_artifactindependently recordsvalid: true,entries: 44,unique_slugs: 44,every_entry_has_html: true,served_status: 200, andserved_bytes_match: true. - Its SHA-256 is
a2fc0f6e57b96b3eb7ad65efd9b58624a9bc149302310ac151e7ef8fee19b0df. The digest covers only the completesandbox-manifest.jsonbytes, not the entire dist tree. - The scope is all post-filter content supplied to emitters, including generated virtual folder/tag pages appended by Quartz. Redirect-only artifacts and arbitrary output files are excluded.
includes_alias_redirect: falseandincludes_unlisted_demo: truemake those boundaries concrete.
Unlisted is not private: the experiment receipt records unlisted_demo_direct_route_exists: true, while that fixture is absent from the ordinary content index, sitemap, and RSS. The manifest still inventories it. A publication filter must remove content before emission if it must not enter this public inventory; hiding a discovery link alone does not provide confidentiality.
The recorded build has install_plugins_exit: 0 and build_exit: 0. Its plugin_loader_notes says both local plugins were linked and loaded, with warnings about missing optional dist/index.d.ts files; those warnings did not prevent the recorded final build or either plugin effect. This is a description of that capture, not a newly observed build failure.
The documentation author’s independent public-route check on 2026-09-16 fetched https://quartz.loca.zone/experiments/local-sandbox-plugins with HTTP 200. Fresh-profile Chrome with a 15-second virtual-time budget exited 0, returned title Local Sandbox Plugins — Quartz Sandbox, and contained exactly one element carrying data-sandbox-reading-stats, with data-words="275" and data-minutes="2". Its DOM was 31,293 bytes, SHA-256 484e5ae3f5d2f9336f2ddd7638ef6721420916be450e703eb9f95b91ef2ef07c. The manifest was also freshly fetched with HTTP 200: 7,782 bytes, 44 entries, scope: "published-content", the unlisted fixture included, and the same manifest-only SHA-256 quoted above. These fresh checks corroborate the badge and inventory, not every client-side interaction.
Safe for the shared engine?
Recorded experiment classification: safe_for_shared_engine: false.
Classified experiment reason, verbatim:
The plugins are private to the sandbox and resolve from a path absent in the shared checkout, avoiding a shared dependency or namespace.
Both plugin-specific verdicts in sandbox-plugin.json are also safe_for_shared_engine: false.
For sandbox-reading-stats, the reason is verbatim:
Targets the isolated v5 npm-plugin API and Node 22; compatibility with the older shared checkout and its prose-exclusion/English-label policy has not been assessed. This verdict never authorizes installation.
For sandbox-manifest, the reason is verbatim:
Not assessed against the older shared engine/plugin API or metadata disclosure policy. This intentionally public inventory includes unlisted content unless a publication filter removes it, and must not be installed without review and authorization.
These are recorded assessments, not deployment authority. Even a true verdict does NOT authorize a shared-engine change: report any such requirement as a BLOCKER, not an action. Do not install these sandbox plugins into the shared engine serving 51 wikis on the strength of these examples.
Try it in the sandbox
The rebuild command is provided for an authorized sandbox operator; it was not run while authoring this page:
cd /home/loca/dev/quartz-sandbox/sandbox && ./build.shThe actual wrapper /home/loca/dev/quartz-sandbox/sandbox/build.sh pins the isolated engine to commit 3dff48b5df6d84c9544a5ae19c8f2cbb01dc44e5 and refuses an engine outside the project. A commit mismatch fails unless the explicit SANDBOX_ALLOW_DRIFT=1 override is used; that override does not remove the isolation fence. A nonblocking .build.lock serializes wrapper builds. It runs install-plugins BEFORE bootstrap-cli.mjs build, using --directory with /home/loca/dev/quartz-sandbox/sandbox/vault by default and --output with a fresh timestamped /home/loca/dev/quartz-sandbox/sandbox/dist-<epoch> directory. It checks for index.html before atomically swapping current. The origin sees the new release on the next request without a restart, as the prior sandbox-runtime.json hot-swap receipt demonstrated.
Recorded runtime invocation, reference only:
PATH=/usr/bin:/bin PORT=51947 SANDBOX_ROOT=/home/loca/dev/quartz-sandbox/sandbox/current NODE_ENV=production /usr/bin/node /home/loca/dev/quartz-sandbox/sandbox/serve.mjsThe production service already owns port 51947. Do NOT start a second process or restart the service. The runtime receipt describes an earlier supervised proof process; it is not an instruction to recreate that process beside production.