# 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 (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 to `base(n)` and `never_runs(n)`, both bodies `return n`, with `main` printing `base(5)` and `never_runs(5)`. - `targets04.c` — the two wrap targets, which cannot be written in El (that is claim 5): `twice_and_sum` returns `body(env) + body(env)`; `never_invoke` ignores the body and returns `999`. 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 0 - `git branch -a --list '*wraps-body*'` → **no output**, exit 0 - `git for-each-ref | grep -i body` → **no matching ref of any kind** - `git log --all --oneline --grep=repeats_body` and `--grep=repeats-body` → the *only* match is `2bed848` itself, 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 `2bed848` produce different output. - Two consecutive runs of the **same** compiler at the **parent** `4f7568b` also 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 at `7d01608` (`0053`, 6.297ms). No failures at either commit. - Stage2 build: `BUILD_OK`, generated `elc-cli.el` C = **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/` contains `bench`, `calendar`, `html_sanitizer`, `native`, `runtime`, `suite`, `swarm`, `text`, `time`. - codegen.el: `4f7568b` 4953 → `2bed848` 5084 → `7d01608` 5084 (`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.tsv` is 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.