Skip to content

Unit 6d: let, list*, the list library, and the shared puzzle harness - #65

Merged
EvanWinget merged 12 commits into
mainfrom
unit-6d-forms
Aug 24, 2026
Merged

Unit 6d: let, list*, the list library, and the shared puzzle harness#65
EvanWinget merged 12 commits into
mainfrom
unit-6d-forms

Conversation

@EvanWinget

Copy link
Copy Markdown
Owner

What changed

Unit 6d of the execution plan, on the evidence of authoring observations 1, 3, 4, and 8 in docs/puzzles/singleton.md: the let and list* compiler forms, the completed list library, and a shared puzzle test harness. The two landed puzzles are not rewritten, so no puzzle bytes, mod hashes, or puzzle vectors move, and the drift guards prove it.

  • let binds in parallel, matching the modern Chialisp dialect, and compiles as the hand-written naming helpers do: the bound names become parameters of the body, applied once in place, the environment rebuilt as the enclosing one with the bound values consed in front of the arguments. The function tree keeps path 2, so calls and recursion inside a let body need nothing new, and the cost is one apply plus one cons per binding. Enclosing bindings re-root under the rebuilt tail: path atoms rewrite arithmetically, and pair-valued inline substitutions re-apply against the restored environment, keeping call-by-name.
  • list* conses its items onto its last argument, the tail, a purely syntactic expansion. Chialisp has no such form, recorded in the language reference's deviations.
  • append, reverse (with reverse-onto), sum, and last join length and nth in puzzles/lib/list.blib. map, filter, and member are declined for lack of production evidence. Pruning keeps the growth free for existing callers, pinned.
  • python/tests/puzzle_support.py owns the puzzle-agnostic harness both puzzle test files duplicated, roughly 190 preamble lines each: builders, the outpoint-bound signer, instance derivation, run wrappers, the spec-rule root reimplementation, and the vector-agreement audits.

Authorization

Tooling, not consensus: docs/execution-plan.md unit 6d and the language reference commit. No spec/ change. The reserved-word growth is a deliberate compatibility break for source or symbol files naming let or list*, pinned by the dispatch and symbol-loader tests, as units 6 and 6c recorded.

Reading order

  1. docs: let and list* join the language reference (the semantics, first)
  2. compiler: list* conses its items onto its tail
  3. compiler: let compiles as the one-shot naming helper (the path rewrite in _let is the one subtle piece, its two invariants stated in the code)
  4. vectors: pin the unit 6d compiled representatives
  5. lib: the list library completes
  6. tests: the shared puzzle harness (pure movement plus parameterization)
  7. docs: unit 6d flips to landed

Verify independently

.venv/bin/pytest python/tests
.venv/bin/python tools/run_vectors.py
.venv/bin/python tools/diff_clvm.py --count 10000 --seed 62
ci/lint/lint.sh
# byte identity of the landed puzzles:
.venv/bin/bitlisp-compile -T puzzles/vault/vault.bl -I puzzles/lib -I puzzles/vault
.venv/bin/bitlisp-compile -T puzzles/vault/triggered.bl -I puzzles/lib -I puzzles/vault
.venv/bin/bitlisp-compile -T puzzles/singleton/singleton.bl -I puzzles/lib -I puzzles/singleton
.venv/bin/bitlisp-compile -T puzzles/singleton/owner-inner.bl -I puzzles/lib -I puzzles/singleton

The four hashes must equal the pinned literals in the two puzzle test files, and git diff main -- vectors/vm/vault-programs.json vectors/vm/singleton-programs.json vectors/validation/ must be empty.

Unit 6d in docs/execution-plan.md, on the evidence of authoring
observations 1 and 4 in docs/puzzles/singleton.md. let is parallel,
matching the modern Chialisp dialect, and compiles as the naming
helpers it replaces are written by hand: the bound names become
parameters and the body is applied once, in place, with the bound
values consed in front of the arguments so the function tree stays
at path 2. list* conses its items onto its last argument, the tail,
a purely syntactic expansion recorded as a divergence because no
Chialisp dialect has the form.

Reserved-word growth is a deliberate compatibility break for source
that used either spelling as a name, pinned by the symbol-loader
test when the implementation lands.
Unit 6d in docs/execution-plan.md, authoring observation 4 in
docs/puzzles/singleton.md: both landed puzzles build condition
lists as chained c calls because list cannot end in a tail. The
form is the list fold seeded with the compiled last argument
instead of nil, purely syntactic, and a lone tail compiles bare on
the single-operand assert precedent. (list*) is a compile error.

Chialisp has no such form, recorded as a divergence in the
language reference. The reserved-word growth is a deliberate
compatibility break, pinned by the dispatch test and the
symbol-loader test.
Unit 6d in docs/execution-plan.md, authoring observation 1 in
docs/puzzles/singleton.md: naming a value once is the dominant
authoring cost, and both landed puzzles hand-wrote helpers whose
only purpose is a binding. The bound names become parameters of
the body, applied once in place: the environment is rebuilt as the
enclosing one with the bound values consed in front of the
arguments, so the function tree keeps path 2, calls and recursion
inside the body work unchanged, and the cost is one apply plus one
cons per binding, pinned against the inline substitution it beats.

Bindings are parallel, matching the modern Chialisp dialect, and a
sibling reference is an unknown name, pinned. Enclosing bindings
re-root under the rebuilt environment's tail: path atoms rewrite
arithmetically, and a pair-valued inline substitution re-applies
against the restored environment, keeping call-by-name, pinned by
the used-twice and unused tests. Programs without let compile
byte-identically, which the existing codegen pins and both puzzle
suites' mod-hash drift guards prove.
Three cases in the vm suite: let through the function tree with an
outer name read through the rebuilt environment's tail, nested let
shadowing its own binding, and list* consing onto an inherited
tail. Generated by bitlisp-compile and verified against the
reference VM by tools/run_vectors.py.
Unit 6d in docs/execution-plan.md, authoring observation 3 in
docs/puzzles/singleton.md: append, reverse with its reverse-onto
accumulator, sum, and last join length and nth, each a generic
first-order recursion the puzzles otherwise respell. map, filter,
and member are declined for lack of production evidence: without
function values there is no generic map, Chialisp's position too.

Unreached declarations prune, so growth is free for existing
callers, pinned by the length-only bytes recorded before the
helpers joined. Each helper is tested against Python's own fold
under Hypothesis, with the empty and improper-tail edges pinned.
Unit 6d in docs/execution-plan.md, authoring observation 8 in
docs/puzzles/singleton.md: both puzzle test files defined the same
transaction and input builders, outpoint-bound signer, curried
instance derivation, run wrappers, and vector-agreement loops.
puzzle_support.py now owns the puzzle-agnostic pieces, a plain
module beside support.py under the same no-conftest convention,
and both files keep only what the puzzle owns: sources, mod
hashes, lifecycle, and case tables.

Pure movement plus parameterization. fee_input takes its amount
explicitly at every call site, the input builder is one
taproot_input with the vault's argument order, the vault's
spec-rule root reimplementation moves whole (the third puzzle
wants it), and the run wrappers accept text or nodes, folding the
two files' variants. Test-only: the mod-hash drift guards and the
vector-agreement closures in both files pass unchanged, so no
puzzle bytes and no vectors moved.
let, list*, the completed list library, and the shared puzzle
harness are in, with the puzzles byte-unmoved as the unit
required.
…pin, harness rename repairs

Self-review of the branch. The classic unnested spelling
(let (A 1) body) now errors as 'a let binding takes a name and a
value' instead of complaining about the argument list, with the
row pinned. The let pin gains a two-binding case showing the
second name one rest step deeper and the old environment behind
both consed values. The harness extraction had renamed
test_unknown_path_raises through an over-broad substitution, and
one vault comment still said the spec-rule reimplementation lived
'above' after it moved to puzzle_support: both repaired.
The library inverse of int_to_atom, the same reading everywhere,
replacing a raw unsigned int.from_bytes that agreed with it only
because path atoms are always positive.
…cons fold

From the multi-agent review. A quote-headed binding evaluates the
same under any environment, so it now crosses a let unwrapped
instead of paying a restore-apply per reference, pinned by a
disassembly test. The rewrite's argument-side invariant is
enforced with a loud AssertionError instead of resting on a
docstring, malformed-binding errors carry the offending entry's
offset instead of the let keyword's, the two list forms share one
_cons_onto fold instead of maintaining parallel copies, and the
dict that becomes the body's bindings is named bound_paths for
what it holds.
From the multi-agent review: the vault drift guard checked only
program bytes, so a pinned solution could drift from source
silently while the singleton file audited both. Every case now
carries its constructed solution. The three failing trigger cases
pin the captured no-revault signature reused across rewritten
amounts, the replay an observer could actually attempt, which
their paths reject on the amount guards before any signature
runs.
From the multi-agent review. A top-level let binding only
literals, in a program with no function tree, emits the curried
shape, so curry.md's collision note now names both shapes. let
and list* get their glossary rows beside the other authoring
forms, per the same-PR rule.
@EvanWinget
EvanWinget merged commit 0df6315 into main Aug 24, 2026
2 checks passed
@EvanWinget
EvanWinget deleted the unit-6d-forms branch August 24, 2026 14:52
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.

1 participant