Rename src/jolt -> stdlib (the runtime-loaded layer; jolt-core stays the seed-baked layer) and update the loader / emit-image / doc paths. Drop dead code: the spike/ experiments, the duplicate clojuredocs-export.edn (json moves to tools/), the Janet-era jolt.http binding, and the orphaned persistent_vector.clj whose ns/path didn't even match. Strip porting residue from comments and docstrings across host/chez, jolt-core, stdlib, tests, and docs: internal issue ids, "Phase N" markers, and the "vs Janet" historical exposition, leaving present-tense descriptions and the real JVM-Clojure semantic contrasts. Same pass over the corpus suite labels. The seed is unchanged (docstrings/comments aren't emitted), so the self-host fixpoint and corpus are untouched. Port tools/spec_coverage.py off the dead janet probe to bin/joltc and regenerate coverage.md; drop the dead :host/janet rule from certify.clj and regenerate the conformance profile. Add docs/host-interop.md (the JVM shims and how to register your own host class from a library) and a writing-style note in CLAUDE.md. Stabilize the four racy concurrency corpus cases (future-cancel and agent send/send-off): give the future a sleeping body and the agent a slow action, so cancel reliably catches an in-flight future and deref reliably reads the pre-update snapshot. They certify deterministically now, so drop their :flaky allowlist entries and the orphaned legend.
3.8 KiB
RFC 0002 — Reader-Conditional Feature Set
- Status: Accepted (implemented; measured)
- Created: 2026-06-10
- Spec:
docs/spec/02-reader.md§2.3 S18
Summary
jolt's reader-conditional feature set is #{:jolt :default}, matched in
clause order (the first clause whose key the platform satisfies wins).
A loading context may opt a foreign, clj-targeted library into :clj
compatibility via reader-features-set! (or process-wide via the
JOLT_FEATURES environment variable). jolt does not satisfy :clj by
default.
Background
#?(:clj … :cljs … :default …) selects a branch by platform feature at read
time. Until now jolt satisfied :clj — a compatibility shortcut inheriting
the JVM branches of .cljc files, on the theory that the :clj branch is
usually the "main" implementation. Each dialect chooses its own policy:
ClojureScript satisfies only :cljs; jank uses :jank; babashka includes
:clj because it genuinely is JVM-Clojure-compatible to a deep degree.
Two defects forced the decision:
- jolt is not JVM-compatible where it matters for
:cljbranches: they contain interop (java.util.*,deftypeover JVM classes) and encode JVM-specific expectations in tests (e.g.parse-uuid's reference permissiveness), both of which jolt fails. - The old implementation also matched by key priority (
:cljfirst, then:default) rather than clause order —#?(:default 5 :clj 6)read as6, diverging from Clojure on all platforms.
Decision and evidence
Measured A/B over the cross-dialect clojure-test-suite (identical tree, 2026-06-10):
| Feature set | Assertions reached | Pass | Fail | Error | Clean files |
|---|---|---|---|---|---|
clj, default (old) |
4967 | 4324 | 524 | 119 | 78 |
jolt, default (new) |
5069 | 4470 | 518 | 81 | 86 |
The portable convention reads more of the suite (:default branches were
being shadowed by :clj ones jolt can't satisfy) and improves every metric:
+146 passes, −38 errors, +8 clean files. The :clj shortcut was a net
liability, not a compatibility win.
The opposing case — loading real-world clj-targeted libraries — is real:
SCI's .cljc sources select their implementation via #?(:clj …)/:cljs
with no :jolt branches, and fail to load under the portable set. That is a
property of the loading context, not of the platform: the resolution is
per-context opt-in, exactly how the SCI bootstrap now loads
((reader-features-set! ["jolt" "clj" "default"])).
Specification (normative, mirrored in spec §2.3 S18)
- The platform feature set is implementation-defined and MUST be
documented. jolt's is
#{:jolt :default}. - Matching MUST be by clause order: the first clause whose key is in the
feature set wins.
:defaultmatches on every platform.#?(:default 5 :clj 6)is5everywhere. - An unmatched conditional reads as nothing (no form); an unmatched
#?@(…)splices nothing. - Implementations SHOULD provide a per-loading-context override so foreign
libraries written for other dialects can be read under a compatibility
set; using it is a deliberate, scoped decision (jolt:
reader-features-set!/JOLT_FEATURES).
Consequences
- Suite baselines re-measured and raised:
baseline-pass4324 → 4470,baseline-clean-files78 → 86. - Reader tests assert the portable set + clause-order semantics, plus one
opt-in round-trip through
reader-features-set!. - Loading clj-ecosystem libraries via deps requires deciding their feature set; the deps loader currently inherits the process default — a future refinement is per-dependency feature configuration (filed with the deps work).
.cljcauthors targeting jolt can write:joltbranches and rely on:defaultfallbacks.