Consolidates the three-lane review (core K01-K110, hosts J*/C*/JS*/P*/S*,
conformance F1-F15) into plans/sx-review/:
- PLAN.md — 15 workstreams, phased execution, full per-finding coverage
ledger (every ~213 finding-instances mapped to a workstream + status)
- RULINGS.md — 40 draft normative rulings (Phase-0 gate)
- core.md / hosts.md / conformance.md — the lane evidence files
dc7aa709 quick-wins batch marked DONE in the ledger; K01 (guard re-raise
hang), S1 (live HTTP crash), K03 (shift-k), and W14 (test gate) flagged as
the highest-value open work.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
26 KiB
26 KiB
SX RULINGS — normative decisions on every ambiguity surfaced by the 2026-07-03 review
DRAFT for ratification. Each ruling: STATUS PROPOSED → flip to RATIFIED / REJECTED /
AMENDED: <text>. Once ratified, this file moves to spec/RULINGS.md and becomes the
authority the conformance batteries pin against. Evidence citations: core.md finding names,
hosts.md J/C/JS/P/S codes, conformance.md F codes.
Default posture used for recommendations (override per-ruling as you see fit):
- Prefer an ERROR over any silent behavior (silent drop/no-op/misparse caused the worst findings).
- Prefer R7RS/standard semantics where churn is low; prefer current-behavior-plus-documentation where churn is high and behavior is defensible.
- Every ruling lands with conformance rows that run on BOTH production kernels (native + WASM).
Companion decisions (not language rulings, restated for context):
- D1 host lineup — recommended: kernel family (native OCaml + WASM) are the only evaluator targets; hosts/javascript and hosts/python standalone retired; shared/sx/parser.py shrunk to a wire-subset with a parity suite. Rulings below marked [D1] simplify to kernel-only if ratified.
- D3 gate — recommended: native corpus green (hs-upstream skip-listed) + same corpus on WASM + cross-kernel differential battery + CEK-vs-JIT differential (when JIT on) + sx_ref.ml regen diff.
A. Bindings & scope
R1. set! on an unbound name
- Current: silently creates a root binding (tested intent, test-scope.sx:196) — but BOTH spec docs say error (eval-rules.sx:112, special-forms.sx:141), and under JIT it writes a different global table than the interpreter (split brain).
- RECOMMENDATION: ERROR ("set!: is not bound — use define"). Typo'd set! is a bug-hider; the docs already promise this. Flip test-scope.sx:196; sweep the corpus for reliance (expected small — the idiom is define-then-set!). Either way the JIT/interpreter split MUST die.
- Churn: low-medium. Findings: core set!-unbound; hosts J-globals split. STATUS: PROPOSED
R2. The ~60 special-form/HO names (map, filter, bind, match, do, case, ->, …)
- Current:
define/let/defmacroof these names is silently accepted but ignored in call position (CEK); the VM honors them (J8) — worst of both worlds. - RECOMMENDATION: reserved words —
define/let/set!/defmacroof any dispatch-table name is a load-time ERROR. Publish the list in spec. Align the VM. (Full lexical honoring is more Schemely but taxes every list-head dispatch and rescues little real code.) - Churn: low (error surfaces existing dead definitions). Findings: core unshadowable-names; J8. STATUS: PROPOSED
R3. let semantics
- Current: sequential (
let*), body = implicit begin, on BOTH engines (tested intent). CLAUDE.md island rules claim the opposite (describes a dead evaluator). - RECOMMENDATION: ratify current behavior:
let≡let*; body sequences. Fix CLAUDE.md. Document (or forbid) the observed letrec-ish quirk that binding-init lambdas capture the shared frame ((let ((f (fn () a)) (a 5)) (f))→ 5). - Churn: zero (docs only). Findings: core let-docs; hosts handoff let-sequential. STATUS: PROPOSED
R4. letrec
- Current: parallel (all inits evaluated, then bound); read-before-init yields nil silently; PLUS two outright bugs (names injected into foreign lambdas' closures = global contamination; named-let loop name leaks into and clobbers the enclosing frame).
- RECOMMENDATION: letrec* semantics (sequential init) with ERROR on read-before-init (pre-bind to an "uninitialized" sentinel that faults on read). Named-let binds its loop name in a fresh frame, invisible after the form. The closure-injection and frame-leak are bugs to fix regardless of ruling.
- Churn: low. Findings: core letrec-parallel/-injection/named-let. STATUS: PROPOSED
R5. Component &key conventions
- Current:
:flag falseis coerced to nil (indistinguishable from omitted); trailing keyword with no value silently binds nil;&keyon plain fn/defmacro silently misbinds. - RECOMMENDATION:
falseis a legal &key value (bind via has-key, not(or …)); trailing keyword without a value = ERROR;&keyin fn/defmacro either implemented identically to components or ERROR at definition (recommend: implement — one binding path for all three). - Churn: low. Findings: core &key-false / trailing-kw / defmacro-&key. STATUS: PROPOSED
B. Errors & conditions
R6. Handler installation semantics
- Current: a handler runs with ITSELF still installed → any raise/error inside a guard clause or handler-bind handler loops forever (crit 1; WASM-verified).
- RECOMMENDATION: R7RS/CL semantics — handlers run with the OUTER handler set; guard clause bodies evaluate after the escape (the no-match auto-reraise path already does this correctly — make it the only path).
- Churn: zero for correct code (only un-hangs broken cases). Findings: crit 1, guard family. STATUS: PROPOSED
R7. What is catchable
- Current: only guest
(raise …)reaches guard; host primitive errors, undefined-symbol, arity errors, and strict type errors all blow through every handler. - RECOMMENDATION: everything is a condition. Host/primitive/strict/undefined-symbol errors are raised as structured condition dicts ({:type :message :op …}) through the same channel guard sees. Reserve a non-catchable class only for kernel panics.
- Churn: low-medium (code that "relied" on uncatchability is unlikely). Findings: core host-errors-uncatchable, strict-uncatchable; enables sane server error pages. STATUS: PROPOSED
R8. raise-continuable / signal-condition
- RECOMMENDATION: ratify R7RS: handler's value returns to the signal site (the current whole-program-result behavior is crit 2's frame-key bug, not a semantic choice). STATUS: PROPOSED
C. Special forms
R9. cond grammar — kill the dual-mode heuristic
- Current: flat pairs documented; undocumented Scheme clause mode auto-detected iff every arg is a 2-element list → silent side-effect drops, mode flips, wrong values (core cond-ambiguity).
- RECOMMENDATION: flat pairs only:
(cond t1 r1 t2 r2 … :else d). Multi-expression results use explicit(do …). Support arrow as a flat triplet => receiver. A clause-shaped arg list as a test position is just evaluated — no mode detection ever. Migrate the cond-arrow suite (test-r7rs.sx:135-145) and any clause-mode usage (sweep needed). - Churn: medium (sweep + migrate clause-mode call sites). Findings: core cond-ambiguity, guard-multi-expr (inherits). STATUS: PROPOSED
R10. case
- RECOMMENDATION: ratify the flat evaluated-datums form and document it (vals ARE evaluated,
first-match, structural
=);:else/elselegal ONLY in final position (else ERROR); Scheme datum-list clause syntax → clear parse-time ERROR ("use flat pairs"). Keyword/string punning follows R21 and gets documented. - Churn: low. Findings: core case-else-position / case-dialect. STATUS: PROPOSED
R11. do
- Current:
dois a begin-alias EXCEPT when its first form's head is a list — then it's a Scheme do-loop → IIFE misparse. - RECOMMENDATION:
do= begin alias, always. Scheme do-loop moves to a distinct name (do-loop) or is dropped (named let covers it). Kills the heuristic. - Churn: low (sweep for real do-loop usage; expected rare). Findings: core do-IIFE. STATUS: PROPOSED
R12. Quasiquote
- RECOMMENDATION, four sub-rulings:
a.
unquote-splicingbecomes an alias ofsplice-unquote(one-line; kills the silent zero-splice trap; rename the misleadingly-named tests). b. Implement standard depth tracking (nested quasiquote raises quote depth;,,xworks). Hosts agree current shallow behavior is consistent-but-nonstandard — fix at spec level. c. Quasiquote traverses dict literals ({:k ,v}works). d. Splicing a non-list and malformed splice arity → ERROR. - Churn: low (b is the only subtle one). Findings: core qq-longhand/-depth/-dicts/-splice-nonlist. STATUS: PROPOSED
R13. Threading -> / ->>
- RECOMMENDATION: (a) steps evaluate in CEK frames (bug: guard/IO broken through threading);
(b) a lambda literal as a step = expand-time ERROR; (c) keyword step sugar:
(-> x :k :j)≡(-> x (get :k) (get :j))— cheap, expected, kills theNot callable: niltrap; (d) remove the dead|>dispatch branch (parser rejects|anyway); (e) fix reduce-seeding via R15. - Churn: low. Findings: core threading-nested-CEK/-lambda-literal/|>-dead/keywords-as-getters. STATUS: PROPOSED
R14. match
- RECOMMENDATION:
(pattern (when cond))guard clauses either implemented or ERROR — never silently read as a structural pattern (current). Recommend: implement (small, high value). Document let-match as dict-destructuring-only with a clear error for list patterns. - Churn: low. Findings: core match-guards. STATUS: PROPOSED
D. Calling convention
R15. Higher-order forms
- RECOMMENDATION, six sub-rulings:
a. Arg-order swap happens ONLY when exactly one argument is callable (components count as
callable); both-callable or neither → ERROR "map: cannot determine function/collection".
b.
(reduce f coll)(2-arg) = Clojure-style fold (first element as init, empty coll → error unless f has identity? keep simple: empty → ERROR);(reduce init f coll)and threaded(-> init (reduce f coll))work via the one-callable rule in (a). c. Data-first with extra args = ERROR (today silently dropped). d. Multi-collection map coerces every collection with seq-to-list (strings/vectors), zips to shortest (already); map over a dict iterates(k v)pairs. e. HO names are first-class:mapetc. in value position resolve to real closures so(define f map)/(apply map …)work. f. Zero/one-arg HO calls = arity ERROR (today silently()). Also fix the O(n²) accumulation (implementation, not semantics). - Churn: medium — (a) changes behavior for ambiguous calls, sweep needed. Findings: core reduce-2arg / reduce-swap / swap-drops-args / HO-not-first-class / ho-cryptic-errors / multi-coll / zero-arg; J7 (VM parity). STATUS: PROPOSED
R16. apply
- Current: native never spreads; WASM spreads 2-arg; test runner has a third behavior (three-way divergence, F-3 + core corrected finding).
- RECOMMENDATION: R7RS:
(apply f a b … rest-list)spreads, leading args prepended. All surfaces align; strict checks fire through apply (R25). - Churn: low (today it mostly errors). STATUS: PROPOSED
R17. Arity checking (too-few args)
- Current: missing params silently nil-fill (this is load-bearing: 1-arg
(assert x)works only via nil-fill); too-many errors. - RECOMMENDATION: ERROR on too-few as well, with
&optional/&key/&restas the explicit mechanisms. Sweep required (harnessassert, any nil-fill reliance). If the sweep turns up heavy reliance, fallback position: keep nil-fill but document it loudly and make strict mode error. Primary recommendation stands: error. - Churn: high — flagged as the riskiest ruling; do the sweep before ratifying. Findings: core strict-too-few / harness-assert nit. STATUS: PROPOSED
E. Keywords, equality, types
R18 (=R21 referenced above). Keywords
- RECOMMENDATION: ratify current model — keywords self-evaluate to their string name; keyword-ness
exists only in unevaluated AST. Consequences made explicit:
(keyword-name :k)needs a quote;"keyword"is REMOVED from the strict type system; case/dict punning documented. NOT callable (R13c covers the getter idiom). - Churn: zero (docs + removing a dead type branch). STATUS: PROPOSED
R19. Equality
- RECOMMENDATION (low-churn variant, chosen deliberately over full R7RS split):
a.
=stays deep structural equality (alias equal?) — ubiquitous in the corpus; add the missing Char arm (today(= #\a #\a)→ false) and any other missing type arms; document that(= 1 1.0)→ true (numeric value equality inside =). b. Add realeqv?(identity + exact numeric/char equality) andeq?(alias identical?) as kernel primitives — they are spec-declared today but implemented NOWHERE. c. Comparisons< > <= >=become n-ary chained (R7RS);=stays 2+-ary deep. d. If content-addressing ever needs exactness-distinguishing equality, that'seqv?, not=. - Churn: low. Findings: core eq?/eqv?-missing, =-binary, char-equality; P5. STATUS: PROPOSED
R20. Strict typing
- RECOMMENDATION: (a) checks move to the continue-with-call/vm_call chokepoints → HO callbacks,
apply, components, => receivers all covered; (b) unknown type name at declaration = ERROR;
(c)
"component"becomes a real type branch;"keyword"removed (R18); (d)(:as type)param annotations become the declaration channel (deprecate the name-keyed global dict, which is trivially evaded and inherited by shadowers); (e) strict errors are catchable conditions (R7); (f) set-prim-param-types! merges and validates; (g) return types: explicitly out of scope now. - Churn: low-medium. Findings: core strict-* family (8 findings). STATUS: PROPOSED
F. Numbers
R21. Integer model & overflow
- Current: native = int63 with overflow-promote-to-float on + and * but silent WRAP on expt; WASM = 32-bit silent wrap (F-1 — production browsers!); JS bundle = float64.
- RECOMMENDATION: spec defines SX integers as exact within ±2^53 (the portable range);
arithmetic that exceeds the host's exact range promotes to float (never wraps) —
exptincluded. WASM must be fixed to match (js_of_ocaml int64/boxed or explicit overflow checks) — hosts lane feasibility-checks the mechanism; silent 32-bit wrap is a bug under any ruling. Values beyond 2^53 must not be trusted exact across the wire. - Churn: low at spec level; WASM fix is real hosts work. Findings: F-1, P4, core expt. STATUS: PROPOSED
R22. Division & zero
- RECOMMENDATION: integer
/,mod,quotient,remainderby zero = catchable SX condition (today: raw OCaml Division_by_zero for mod/quotient, silentinffor /); float ops keep IEEE (inf/nan)./doc fixed: returns int when exact, float otherwise (current behavior ratified). - Churn: low. Findings: core div-by-zero, /-doc. STATUS: PROPOSED
R23. Float text & wire
- RECOMMENDATION: shortest-round-trip printing everywhere (native
%g6-sig-digit printing is a wire-corruption bug — P1);inf/-inf/nanare THE wire tokens on all hosts (P10);roundstays half-away-from-zero, documented (R7RS banker's rejected: churn without benefit);inexact->exactrounding behavior kept + documented;str 1.0→ keep"1"but canonical/wire serializers must preserve the float/int distinction (1.0serializes as1.0). - Churn: low. Findings: P1, P10, core round/float-rendering; canonical CID determinism. STATUS: PROPOSED
R24. Rationals
- RECOMMENDATION:
string->numberparses"1/2";(/ 1 3)stays float (rationals remain opt-in via make-rational) — documented; radix arg restored by fixing the r7rs.sx shadow (C2). - Churn: low. STATUS: PROPOSED
G. Strings
R25. Unit semantics
- Current: native counts UTF-8 bytes (substring can split codepoints → invalid UTF-8); JS counts UTF-16 units; constructors are codepoint-aware. Project style mandates UTF-8 text everywhere.
- RECOMMENDATION: codepoint semantics for length/substring/index/ref at the spec level; kernel implements UTF-8-aware ops. Accept the perf cost (or add byte-* variants for hot paths later).
- Churn: medium (kernel work + any code relying on byte counts). Findings: core UTF-8 family, P6. STATUS: PROPOSED
R26. Case mapping
- RECOMMENDATION: kernel
upcase/downcase/upper/lowerare ASCII-only, documented (full Unicode case tables deferred; JS's full-Unicode behavior dies with D1). Aliases exist on all surfaces (P11). - Churn: zero. STATUS: PROPOSED
R27. split and escapes
- RECOMMENDATION:
split= literal substring separator, keeps empties, empty separator → chars (ratifies native; pin with the multi-char test that history shows is needed). String escape table is normative:\n \t \r \\ \" \uXXXX(validated: 4 hex digits, scalar value, else ERROR); unknown escape = parse ERROR (kills the native-keeps-backslash vs guest-drops-it silent divergence, C25 direction fight). - Churn: low. Findings: core split note, \u family, unknown-escape divergence; C25. STATUS: PROPOSED
H. Collections, nil, dicts
R28. nil vs empty list
- Current: distinct values in the reader/serializer;
(cons 1 nil)→(1)on native (nil-as- empty in constructors); read ops inconsistent (first nil→ nil butreverse nil→ error). - RECOMMENDATION: nil and
()remain distinct values; collection READ ops uniformly nil-pun (treat nil as empty: first/rest/nth/last/reverse/len/empty? all accept nil); constructors keep nil-as-empty seeding (cons/append onto nil).nil?≠empty?preserved. - Churn: low (only un-errors cases). Findings: core nil-tolerance; P7/P8 arms. STATUS: PROPOSED
R29. Dict ordering
- RECOMMENDATION: insertion order preserved — iteration, keys/vals, and serialization (OCaml Hashtbl replaced with an insertion-indexed structure; keys-reversed bug dies). CANONICAL form always sorts keys independently (already true in the CBOR/CID layer). Duplicate literal keys: last-wins, documented.
- EMPIRICAL NOTE (quick-wins batch, 2026-07-03): an interim sorted-keys change broke 4 render
tests — attr emission order flows through dict_keys and the tests PIN source-order attributes
(
widthbeforeheightetc.). So the current reverse-ish order is load-bearing for render; any change here must land together with the render-attr ordering contract. Reverted; do not change keys order except via this ruling. - Churn: medium (kernel dict rework) but pays across wire/golden/cache findings C27/P9/core-keys. STATUS: PROPOSED
R30. Small-primitive contract fixes (spec already says; hosts violate)
- RECOMMENDATION: ratify the spec text and fix:
contains?on dicts = key check;mergeskips nil;intonative on the kernel;sorttakes an optional comparator, compares int/float numerically, stable;getreturns a STORED nil (default only when key absent);zip-pairs= sliding window per spec (kernel currently chunks);(max)/(min)zero-arg = ERROR. - Churn: low each. Findings: core contains?/sort/keys; P2/P3/P8/P12; JS
getarm. STATUS: PROPOSED
R31. append! and mutation
- Current: silently no-ops on ANY derived list (map/filter/rest/reverse output) — worst silent- data-loss finding in the primitives sweep.
- RECOMMENDATION:
append!ERRORS on non-mutable lists immediately (honest), and is deprecated in favor of persistentappend+ a real mutable vector/buffer for accumulator idioms. Sweep the corpus (it's a known accumulator idiom in loops). - Churn: medium (idiom sweep). Findings: core append!. STATUS: PROPOSED
I. Parser & wire
R32. One token grammar
- RECOMMENDATION: publish the normative ident/number classifier in spec/parser.sx and make every
surface bind THE SAME table (today: four divergent tables → same source, different ASTs).
Specific token rulings: maximal-munch then classify (
1+,a,bare symbols — ratifies native); hex/binary/octal#x/#o/#b-style and0x10accepted, documented;inf/nan/-infare number literals (reserved, not idents);1eand other malformed numbers = parse ERROR (never nil); unicode identifiers allowed (UTF-8 letters — the docs mandate UTF-8 text; native reader extends its charset);$/|NOT ident chars;.IS a valid symbol (ratifies native; JS4 dies with D1);#t/#f= boolean literals on all surfaces. - Churn: medium (native reader charset + guest table sync). Findings: core parser-divergence family; C1b (unicode symbol kills server — fixed by charset + C1 try-wrap); JS4. STATUS: PROPOSED
R33. Reader extensibility & comments
- RECOMMENDATION: implement the
#namereader-macro registry on the kernel (spec documents it; only JS has it today) — small, and sx-pub extensibility wants it.#;datum comment valid before)and at EOF (standard).#|…|stays a RAW STRING (documented loudly as not-a-block- comment); no block comments. - Churn: low. Findings: core reader-macro/datum-comment/raw-string. STATUS: PROPOSED
R34. Dict literals & serializer round-trip
- RECOMMENDATION: dict literal keys must be keyword/string/symbol — anything else is a parse ERROR
on every parser (guest currently stringifies
{1 2}silently); odd form count gets a "dict needs key-value pairs" error. Serializer: dict keys escaped/round-trippable (today unparseable output for non-ident keys — also a CID hazard); chars serialize by codepoint (#\éreadable back once R25 lands); PROPERTY TEST:parse(serialize(x)) = xfor the full value lattice, run on both kernels. - Churn: low. Findings: core serializer-dict-keys / multibyte-chars / dict-edges. STATUS: PROPOSED
R35. Canonical form & CIDs (sx-pub-critical)
- RECOMMENDATION: the native CBOR/CID path is normative (key-sorted, verified native==WASM, F-3). The canonical TEXT form is defined as: sorted keys, shortest-round-trip floats with preserved int/float distinction, fully-escaped strings, and is a fixed point (canonical(parse(canonical(x))) = canonical(x)) — property-tested cross-kernel. spec/canonical.sx either becomes a tested mirror of the native path (fix its runner-only helpers) or is deleted; two silently-diverging implementations is the one unacceptable state.
- Churn: low-medium. Findings: core canonical family; F-3; P9/C27 (via R29). STATUS: PROPOSED
R40. Primitive naming & small-default unification (answers the hosts handoff list)
- RECOMMENDATION: one canonical name registry in spec/primitives.sx; per-host aliases die (with
D1 most of these resolve to "make it native on the kernel"):
json-encode/json-parseare KERNEL primitives (not IO-bridge helpers — today unavailable sandboxed);regex-*is the canonical family name;parse/sx-parse—sx-parsecanonical,parsealias documented; 1-arg(range n)= 0..n-1 (ratifies native);parse-int/string->numberon failure → nil (ratifies native, never 0);formatand the stdlib move for real (the primitives.sx header claims a stdlib migration that never happened — make the header true or revert it) and spec/stdlib.sx loads in production (todayformatis unresolved on the server). - Churn: low. Findings: F-9 naming splits, P7 arms, core spec-drift / stdlib-header. STATUS: PROPOSED
J. Render contracts
R36. Attribute contract (all four adapters)
- RECOMMENDATION: one contract, HTML-mode's as base: boolean-registry attrs — false/nil omit,
anything else emits bare name (SX truthiness, documented footgun stands); non-boolean attrs —
value stringified INCLUDING
"true"/"false"(DOM adapter aligns — C19/core, found by both lanes); attribute NAMES validated[A-Za-z_:][A-Za-z0-9_:.-]*else ERROR (kills spread-dict injection); nil attr value omits the attribute. - Churn: low. Findings: core attr-name-injection / bool-footguns / dom-html-parity; C19. STATUS: PROPOSED
R37. Raw-text elements & voids
- RECOMMENDATION:
<script>/<style>children are NOT entity-escaped; instead the renderer ERRORS if content contains</script/</style(raw! unchanged as the explicit bypass) — HTML- correct and injection-safe, and stops corrupting legitimate inline JS/CSS. Void-element registry completed (area/base/embed/param/track added to HTML_TAGS); children passed to a void element = ERROR (today silently dropped). Component render gets a depth limit (default 512) with a clear error.is-render-expr?either wired in (html:/custom elements) or deleted. - Churn: low. Findings: core script-style / void-elements / recursive-component / is-render-expr. STATUS: PROPOSED
R38. aser wire format
- RECOMMENDATION: list-valued keyword args serialize quoted (
:items (quote (…))) so the wire form re-evaluates to the same value; contract documented: components unexpanded, control flow evaluated, all VALUES must round-trip through parse+eval. Property-test aser output re-evaluation. - Churn: low. Findings: core aser-list-kwargs; S14 (deep-nesting parity — add the depth-2 test). STATUS: PROPOSED
K. Signals (spec-level semantics)
R39. Reactive semantics
- RECOMMENDATION: (a) change detection by
=(deep equality — needs R19's Char arm etc.), not physical identity — kills the every-reset-notifies behavior; (b)batchis unwind-safe (depth decremented on any exit — today one throw wedges all reactivity forever); (c) notify is glitch-free: two-phase (mark dirty → recompute in topological order) so diamonds recompute once; (d) dispose-computed actually unsubscribes (bug); (e) effect cleanup cleared after invocation (bug). Ratify as the documented reactive contract with tests (today: zero coverage of these). - Churn: low-medium (topological notify is the real work). Findings: core signals family (5). STATUS: PROPOSED
Ratification checklist
- Flip each STATUS; AMENDED rulings get their text edited in place.
- High-churn rulings needing a pre-ratification sweep: R17 (arity), R9 (cond clause-mode usage), R31 (append! idiom), R15a (ambiguous HO calls).
- On ratification: move to spec/RULINGS.md; every ruling becomes (a) a conformance battery row (native + WASM), (b) a fix ticket if behavior changes, (c) a docs line in CLAUDE.md's rewrite.
- Rulings deliberately NOT made here (need your call, no strong recommendation):
- Whether rationals should ever be the default result of exact
/(R24 keeps float). - Whether to pursue full-Unicode case mapping (R26 defers).
- Whether
do-loop(R11) is worth keeping at all vs deleting Scheme do entirely. - JIT re-enable timeline (J1–J8 are preconditions, not rulings).
- Whether rationals should ever be the default result of exact