The generated C, amalgams, vendored runtime pins, and compiled binaries from the Claude Code era are removed from the worktree. The El sources survive; this tree is now source-only for the first-principles rebuild. Per Principal direction 2026-08-19.
15 KiB
CLAIMS — cycle 04, c has no closure syntax
Source doc: docs/v1/experiments/cycles/04-c-has-no-closure-syntax.md
Pinned commits
| Cycle commit (the EXPERIMENT) | 2bed8483f75ebc76eefac69567b01d2da70d5db9 |
| Committed | 2026-08-17 08:08:37 -0500 |
| Subject | EXPERIMENT: hand the construct the body as a real closure |
| Parent (the "before" state) | 4f7568b07f61a81fed1db5b8274813032ea3b16e |
| Land commit | 7d01608a9dd3f7d4f6f476f567504d3ec341e7af |
| Committed | 2026-08-17 08:12:16 -0500 |
| Subject | land wraps_body: a construct controls invocation |
| Land parents | 4f7568b (first) + 2bed848 (second) — an ordinary merge |
| Re-run method | detached worktrees /tmp/rerun-v1-04a at 2bed848 and /tmp/rerun-v1-04b at 7d01608, both trees clean |
| Artifact provenance | every artifact pinned to 2bed8483f75e or 7d01608a9dd3, tree clean (see MANIFEST.tsv) |
| Machine | Wills-MacBook-Pro, Darwin 25.5.0 arm64, Apple clang 21.0.0 (see RUN.env) |
git diff 2bed848 7d01608 is empty (0050): the land commit's tree is
byte-for-byte the experiment's tree. Everything measured at 2bed848 therefore
also holds at 7d01608; the 04b captures confirm it independently.
The only file the cycle touches is lang/el-compiler/src/codegen.el
(+134 / −3, net +131). Line count 4f7568b = 4953 → 2bed848 = 5084
(0050), consistent with the net delta.
Verdicts
| # | Claim (verbatim from the record) | Artifact | Commit | Verdict |
|---|---|---|---|---|
| 1 | P1 "env struct + thunk taking void* — TRUE" | 0008, 0009 |
2bed848 |
REPRODUCED — emitted C contains struct __env_base { el_val_t n; };, static el_val_t __thunk_base(void* __v), and extern el_val_t twice_and_sum(el_val_t, el_val_t, el_val_t(*)(void*), void*); |
| 2 | P2 "fails to compile: struct redefinition — FALSE" | 0015, 0010, 0011 |
2bed848 |
REPRODUCED — no redefinition failure. The env structs are file-scope and uniquely named per fn (__env_base, __env_never_runs; four distinct tags in the 4-fn probe). A 2-wrap and a 4-wrap translation unit each compile and link clean. |
| 3 | P2 reason: "C allows the inner declaration to shadow" | 0018, 0015 |
2bed848 |
NOT-CAPTURED as an account of this emitter — the C language fact is confirmed independently (0018: a block-scope struct E shadows a file-scope struct E, cc -std=c11 -Wall -Wextra exit 0, prints outer=3 inner=7). But the emitter at 2bed848 emits no inner/block-scope struct declaration at all, so nothing shadows anything. The intermediate emitter that would have produced the shadowed declaration exists in no commit and cannot be measured. |
| 4 | P2 real defect: "a wrap with no exit construct emitted (EL_STR("f"), EL_STR(""), __r); — a call to an empty target … Fixed." |
0016, 0017 |
2bed848, 4f7568b |
REPRODUCED (defect absent, fix present) — grep for EL_STR("") in the emitted C of a wrap-with-no-exit program returns exit 1 (absent). Mechanism confirmed: at the parent 4f7568b the exit line is emitted unconditionally (codegen.el:3360); at 2bed848 it is guarded by if !str_eq(exit_target, "") (codegen.el:3458) and has_exit is widened to !str_eq(exit_target, "") || has_wrap (3405), exactly as the record describes. |
| 5 | P3 "compiles when the target is declared in El — FALSE" | 0013, 0014 |
2bed848 |
REPRODUCED — elc accepts the El extern fn, then cc fails with 3 errors: conflicting types for 'twice_and_sum', and two incompatible pointer to integer conversion (passing el_val_t (void *) and struct __env_base * to parameters of type long long). cc_rc=1. |
| 6 | P3 "El has ONE type, el_val_t = int64_t" | 0006, 0057 |
2bed848 |
REPRODUCED — typedef int64_t el_val_t; at lang/runtime/el_runtime.h:50, el_seed.h:32, el_native_target.h:26. Erasure confirmed: fn mixed(a: Int, b: String, c: Bool) -> Int emits el_val_t mixed(el_val_t a, el_val_t b, el_val_t c). |
| 7 | P3 "codegen DEFINES the wrap calling convention, so codegen emits the extern declaration" | 0008, 0009, 0017 |
2bed848 |
REPRODUCED — codegen.el:3442 emits the extern el_val_t <target>(el_val_t, el_val_t, el_val_t(*)(void*), void*); line; it appears in the emitted C at line 22/38. |
| 8 | P4 "target controls invocation, 0..N times — TRUE" | 0011, 0012, 0010 |
2bed848 |
REPRODUCED — one El source, four wrap targets differing only in how many times they call the thunk: 0 → 999, 1 → 5, 2 → 10, 3 → 15. |
| 9 | P5 "existing @manager output byte-identical — TRUE" | 0019, 0020, 0028, 0038 |
4f7568b vs 2bed848 |
REPRODUCED — the parent compiler was rebuilt from a git archive 4f7568b extraction (0019) and the same inputs compiled with both emitters. All 3 files in the tree that apply @manager/@accessor (engram/src/server.el 1 application, tools/api-reshape/route_proof.el 7, tools/api-reshape/surface.el 8) emit byte-identical C. Treewide: 378 .el files → 368 identical, 1 byte-differing, 9 failing under both emitters. The 1 differing file is explained below and is not caused by this cycle. |
| 10 | P6 "compiler fixpoint holds — TRUE" | 0051, 0054 |
2bed848, 7d01608 |
REPRODUCED — gen2 == gen3 at both commits; at these commits gen1 == gen2 as well, so all three generations share sha256 91b3de5b5c95cc659fb4d78aa821627c9a5c325c14b461852efff7db3f68a64b. |
| 11 | P7 "emitting the convention makes it compile — TRUE" | 0010, 0055 |
2bed848, 7d01608 |
REPRODUCED — COMPILE_AND_LINK_OK at both commits, against a C translation unit supplying the two wrap targets. |
| 12 | MEASURED "base(5) wrapped by a target that invokes the body twice and sums -> 10" | 0007, 0010, 0055 |
2bed848, 7d01608 |
REPRODUCED — via a RECONSTRUCTED probe (see below) — observed base(5) = 10 |
| 13 | MEASURED "never_runs(5) wrapped by a target that never invokes it -> 999" | 0007, 0010, 0055 |
2bed848, 7d01608 |
REPRODUCED — via a RECONSTRUCTED probe (see below) — observed never_runs(5) = 999 |
| 14 | "This supersedes the repeats_body experiment on experiment/repeats-body" | 0049 |
all refs | DIVERGED — no such ref exists. See below. |
| 15 | 7d01608: "Proven on experiment/wraps-body (2bed848)" |
0049 |
all refs | DIVERGED — no such ref exists. The commit 2bed848 is present and reachable; the branch name is not. |
| 16 | "Every C++ lambda, every Go closure, every Rust closure compiles to a struct of captured values plus a function pointer" | 0008, 0009 |
— | NOT-CAPTURED — the trailing half, "which is what is emitted here", is claim 1 and is REPRODUCED. The claim about C++/Go/Rust codegen is about three other toolchains, none of which is in this tree; it was not measured then and is out of scope for this record. |
| 17 | "It would also have crippled the JS backend, which has closures natively, for a limit that applies only to the C one" | 0058 |
2bed848 |
NOT-CAPTURED (counterfactual) — and the premise is weaker than stated: lang/el-compiler/src/codegen-js.el (1381 lines) implements no construct machinery whatsoever at 2bed848. It contains no reference to wraps_body, injects_at_entry, guards_at_entry, injects_at_exit, thunks or env structs; the only decorator it knows is @async (line 1119). There is no wrap emission in the JS backend to cripple, either way. |
Counts: 13 REPRODUCED · 2 DIVERGED · 0 NOT-REPRODUCIBLE · 2 NOT-CAPTURED (17 assertions).
The MEASURED numbers were produced by a RECONSTRUCTED probe
State plainly: no test, fixture, or harness in the tree at 2bed848 exercises
wraps_body. git grep wraps_body outside codegen.el returns exit 1;
lang/tests/integration/ does not exist at either pinned commit (0056); and
2bed848 changes exactly one file, codegen.el, adding no test. The probe the
commit message describes was never committed.
The probe was therefore reconstructed from what the record states, and it is
an INPUT, not evidence. Its full text is in the record at 0007:
w04.el— two declared wrap constructs (@decorator("wraps_body", "twice_and_sum")and@decorator("wraps_body", "never_invoke")) applied tobase(n)andnever_runs(n), both bodiesreturn n, withmainprintingbase(5)andnever_runs(5).targets04.c— the two wrap targets, which cannot be written in El (that is claim 5):twice_and_sumreturnsbody(env) + body(env);never_invokeignores the body and returns999.
The artifact is the captured output of running it at the pinned commits
(0010 at 2bed848, 0055 at 7d01608), which reads:
COMPILE_AND_LINK_OK
base(5) = 10
never_runs(5) = 999
The emitted C is byte-identical between 2bed848 and 7d01608 (0055).
Reconstruction risk, stated honestly: the record fixes the inputs (5), the
target semantics ("invokes the body twice and sums", "never invokes it") and the
outputs (10, 999), and the 999 sentinel is only reachable from the target,
so it is not a value the body could have produced. What the record does not
fix is the target's names, which are free choices here. The numbers were
observed, not asserted.
Divergence detail — claims 14 and 15, the two branch references
Both pinned records point at branches that are not in the clone (0049):
git branch -a --list '*repeats-body*'→ no output, exit 0git branch -a --list '*wraps-body*'→ no output, exit 0git for-each-ref | grep -i body→ no matching ref of any kindgit log --all --oneline --grep=repeats_bodyand--grep=repeats-body→ the only match is2bed848itself, i.e. the sentence making the claim- 249 branches exist in the clone; none of them is either name.
Consequences, and they are not symmetrical:
- Claim 15 (
7d01608: "Proven on experiment/wraps-body (2bed848)") is a cosmetic divergence. The branch is gone but the commit it names survives, is reachable, and everything it asserts re-measures. Only the branch label is wrong. - Claim 14 ("This supersedes the repeats_body experiment on
experiment/repeats-body, which was built around the mistaken limit") is a
substantive one. The superseded work is unrecoverable: no branch, no tag,
no commit anywhere in the object graph mentions
repeats_body. The record's central rhetorical move — that a strictly weaker "decide whether to repeat" design existed, was built, and was replaced — rests entirely on a dangling reference. That the weaker design is weaker is separately demonstrable (claim 8 shows 0 and 3 invocations, neither expressible as "repeat or not"), but the existence and content of the superseded experiment cannot be checked from this repository.
Supporting finding — the one treewide byte-difference is pre-existing nondeterminism
The treewide P5 sweep (0028) found exactly one file whose emitted C differs
between the parent and the commit: arbor/vessels/arbor-parse/src/main.el.
It is not a regression from this cycle (0038):
- The file applies zero decorators (
grep -c '^@'= 0), so no construct machinery — wrap or otherwise — is reachable in it. - Two consecutive runs of the same compiler at
2bed848produce different output. - Two consecutive runs of the same compiler at the parent
4f7568balso produce different output.
The difference is always the same three lines, where a string literal is emitted
as a large decimal number that changes run to run (e.g.
EL_STR("40345588256") vs EL_STR("30447047200") at line 325, and similarly at
338 and 392) — an address, or an address-derived value, leaking into the emitted
literal. This is a real pre-existing emitter defect worth its own investigation,
but it predates cycle 04 and does not touch claim 9.
The 9 files that fail under both emitters (identical exit codes, 0028) are
likewise unchanged by the cycle: arbor-core/src/main.el,
arbor-render/src/main.el, elp/src/vocabulary-{de,fr,it,pt,ro}.el,
ui/examples/native-hello/src/{App,main}.el.
Measurements the record makes no claim about, recorded as observed
The cycle doc states no test count, no fixpoint detail and no timing. Observed:
- Native compiler tests: 98 tests, 98 passed, 0 failed, 243 assertions at
2bed848(0004, 5.660ms) and the same 98/98/0/243 at7d01608(0053, 6.297ms). No failures at either commit. - Stage2 build:
BUILD_OK, generatedelc-cli.elC = 11494 lines at both commits (0003,0052). lang/tests/integration/does not exist at either pinned commit (0056), so no integration harness could be run.lang/tests/containsbench,calendar,html_sanitizer,native,runtime,suite,swarm,text,time.- codegen.el:
4f7568b4953 →2bed8485084 →7d016085084 (0050).
Measurement defects in this re-run, recorded rather than removed
Defect 1 — superseded fixpoint command (recipe defect, not this cycle's).
Artifact 0005-selfhost-fixpoint-04a.out was captured with the recipe's original
command, which compares gen1 (the committed dist/platform/elc's output)
against gen2. That is the wrong check: gen1 may legitimately differ from gen2
whenever codegen's output changes, and reporting it as a break would be a
fabricated divergence. The corrected three-generation check
(gen2 == gen3) is 0051 at 2bed848 and 0054 at 7d01608. 0005 is
retained, not deleted. It happens to agree — at these commits gen1 == gen2 too —
but it should not be cited as the fixpoint evidence.
Defect 2 — multi-line commands corrupted MANIFEST.tsv line structure.
Four captures (0020, 0028, 0038, and the shell text of 0019) were invoked
with bash -c scripts containing literal newlines. capture.sh writes the
command verbatim as the last TSV field, so those newlines became extra physical
lines in MANIFEST.tsv. Two visible consequences:
- the artifact counter, which is derived from the manifest's line count, jumped
(
0020→0028→0038→0049), so the artifact numbers in this cycle are not contiguous. No artifact is missing; the gaps are counter skips. MANIFEST.tsvis no longer strictly one physical line per artifact.
verify-manifest.sh is unaffected — the continuation lines parse with an empty
artifact field and are skipped — and the cycle verifies 0 altered, 0 missing
with every row clean. The manifest was not edited to tidy this up; editing
it is precisely the defect this record exists to control for. Later captures use
single-line commands.
Reproduction
git worktree add /tmp/rerun-v1-04a 2bed848
git worktree add /tmp/rerun-v1-04b 7d01608
then the commands in MANIFEST.tsv, and
bash tools/evidence/verify-manifest.sh docs/v1/experiments/evidence/cycles.
The parent compiler used for claim 9 is built by 0019 from a
git archive 4f7568b extraction into /tmp/p04parent; no worktree at the parent
is required.