diff --git a/.gitignore b/.gitignore
index c2b63ea..bee26d6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,3 +4,6 @@ peripheral/.consent.json
peripheral/.resume.json
peripheral/.engram/
peripheral/organ
+
+# Claude Code session state
+.claude/
diff --git a/design/completing-el.html b/design/completing-el.html
new file mode 100644
index 0000000..9b321dc
--- /dev/null
+++ b/design/completing-el.html
@@ -0,0 +1,153 @@
+
+
+
+ Completing El
+ A working surface. Nothing here is settled, and none of the code is assumed right — El is self-hosting, so all of it can change and be rebuilt.
+ Whiteboard v0 · no sacred cows · not a plan, not a task list
+
+
+
01What we established
+
+
El is a concept-oriented language — the first, and intended as the last, because every other family is oriented toward a representation of a concept rather than the concept. Procedures, objects, functions, predicates are the shapes concepts get flattened into. Once the primitive is the concept, there is no further rung.
+
+
Everything here is El. The engram is an El program, the soul is El, elp is El, ingest is El. Which gives the load-bearing consequence:
+
+
A concept with no home in El does not disappear. It becomes C, or it becomes a convention.
+
+
Both are measurable, and both were measured. As C: 20,504 lines of el_runtime.c — 2.3× the entire self-hosting language it serves (9,089 lines), ~47% of it engram code that has its own six sibling files. As convention, from language.md §18.0 — "these are not four problems, they are one absence, four times":
+
+
+
+
The last row is the strongest evidence available about what this class of convention is worth: it failed at 100% of its sites.
+
+
02The decomposition axis
+
+
Not by file, module, or subsystem. By faculty.
+
+
Every defect fought in the last day resolves to a faculty rather than a bug, and each one leaked out of El into something else — into C, into a Swift binary, into a shell script with a curl timeout, into a convention nobody performs.
+
+
+
+
Stated plainly: it cannot take in, cannot remember, cannot perceive, cannot reason, and barely speaks. These were filed as tickets against a repository. They are faculties of the thing the repository is.
+
+
03The ordering principle
+
+
El's compiler is written in El. Every concept the language gains, the compiler can then be written in — so the tool improves the tool, and the fixpoint (stage2 ≡ stage3, byte-identical) makes each turn provable rather than hopeful. The verifier answers in 2.9s.
+
+
Which means the ordering criterion is not size of payoff:
+
+
Order by leverage on the next iteration. Which concept, added to El, most increases the ability to add the following one?
+
+
In a recursive system that dominates immediate value — a small early gain that compounds beats a large one that doesn't. It also bounds itself correctly: unbounded in depth, bounded in rate, because nothing lands that the compiler and the fixpoint have not passed.
+
+
04Open — for the whiteboard
+
+
What does a declaration bind to?If cat names a region rather than a struct — one that shifts and completes against the engram and the neighbouring code — then what is written at the declaration site, and what is resolved at use? This is the centre of the whole thing and it is not specified anywhere yet.
+
+
Is "the type checker" a type checker at all?§2.3 records annotations as parsed and skipped, and every codegen hazard is downstream of that — + dispatching on AST node kind, == lowering to str_eq unless both operand names are in an int-name set. But if a declaration names a region, checking is asking whether the geometry supports the use. That is grounding, not unification. Naming this wrong builds the wrong thing.
+
+
Is the faculty list above right?Seven were derived from what broke. Derived-from-failure is a biased sample — it finds what is loud, not what is missing. What faculty is absent entirely and therefore never failed?
+
+
Which concept has the highest leverage on the next turn?Candidates so far: the prologue/epilogue seam (§19.3 names it as the prerequisite and its stated blocker has expired — it would collapse 62 + 10 convention sites); protocol/impl (the absence that produced five ingest functions); and the resolution question above. These are not equal and the criterion in §03 should decide it, not preference.
+
+
What is the seam that makes cognition non-optional?"Use the ops" is itself a convention — present in context every turn, enforced by nothing, and it failed at ~100% of sites in a full session. A stronger instruction is still a convention. What makes reasoning-outside-Neuron fail, the way @manager makes dharma_emit outside the boundary a compile error rather than a lint?
+
+
+
+
+
+
diff --git a/docs/architecture/capabilities.md b/docs/architecture/capabilities.md
new file mode 100644
index 0000000..6de9f7a
--- /dev/null
+++ b/docs/architecture/capabilities.md
@@ -0,0 +1,142 @@
+# El — Capabilities
+
+**What the language can do, stated as capabilities rather than as code.**
+
+This list is the unit of analysis. Each entry gets one question — *prove this
+cannot be done with pure geometry* — and the answer determines whether it stays a
+capability of the language or collapses into the manifold.
+
+Draft, 2026-08-17. Ordered roughly from most-likely-geometry to most-likely-code.
+
+**Status after measurement.** The list was audited against the implementation
+the same day. 28 entries collapsed to 19 geometry + 3 code: serialization, text
+encoding, network and emission are all *projection onto a basis* (row 18) —
+the convention is the basis, never the act. Storage collapsed because
+persistence has no caller. Concurrency collapsed because coordination is the
+price of forgetting, not a capability. A fourth proof form was added,
+**adversarial exactness**, and form 1 stopped being a valid verdict.
+
+**The table answers CAN only.** SHOULD and COST resolve per *site*, not per
+capability — `is_digit` and `is_letter` are one capability with opposite
+answers, and comparison spans three cost tiers. See the notes below.
+
+---
+
+## The list
+
+| # | Capability | What it means | Verdict |
+|---|---|---|---|
+| 1 | **Comparison** | is this the same as that; is this greater | zero distance / sign of a displacement |
+| 2 | **Ordering** | arrange by a criterion | position along an axis |
+| 3 | **Containment** | is this inside that; does this contain that | region membership |
+| 4 | **Correspondence** | where does this occur in that; how much of this is in that | a match-strength field over a span |
+| 5 | **Segmentation** | divide a whole into parts | boundaries at measured discontinuity |
+| 6 | **Composition** | join parts into a whole | adjacency; one position with parts |
+| 7 | **Classification** | what kind of thing is this | which region does it land in |
+| 8 | **Naming / binding** | attach a name to a thing and find it again | an edge; retrieval is projection |
+| 9 | **Collection** | many things held together, indexed, counted | a set of positions; cardinality; projection onto the i-th |
+| 10 | **Iteration** | do something for each of many | traversal |
+| 11 | **Arithmetic** | quantity, magnitude, combination | displacement algebra on a line |
+| 12 | **Time** | when; how long; how often | a 1-D affine space — instants are points, durations displacements, rhythms phases on a circle |
+| 13 | **Identity** | which one is this; are these two the same one | coincidence of position |
+| 14 | **Selection / dispatch** | choose which behaviour applies | nearest region |
+| 15 | **Transformation** | produce a thing from a thing | change of basis |
+| 16 | **Grounding** | how well is this supported | the weight on an edge. Has no caller |
+| 17 | **Learning** | get better at something | standing changing over time |
+| 18 | **Projection** | render meaning onto a surface | change of basis onto a surface basis |
+| 19 | **Transduction** | take a signal in | change of basis from a sensor basis |
+| ~~20~~ | ~~Serialization~~ | **collapsed → 18.** The format is a basis; projecting onto it is the act | — |
+| ~~21~~ | ~~Text encoding~~ | **collapsed → 18.** An encoding is a basis | — |
+| ~~22~~ | ~~Storage~~ | **collapsed.** No save — persistence has no caller. Durability survives at one site inside the engram | — |
+| ~~23~~ | ~~Network~~ | **split.** Wire format → 18; socket → 24 | — |
+| 24 | **Process / OS** | syscalls; the one-way boundary. Where monotonicity stops | CODE, form 2 |
+| ~~25~~ | ~~Concurrency~~ | **collapsed.** Monotone state needs no coordination; coordination is the price of forgetting | — |
+| 26 | **Memory substrate** | what holds the positions | CODE, form 3 |
+| 27 | **Concealment** | meaning made unreadable without a key. *Renamed*: "secrecy" covered one of three things and got the other two backwards — a hash is public, a signature exists to be read. Integrity and authenticity are **grounding under adversarial conditions** (row 16); only concealment stands alone | CODE, form 4 |
+| ~~28~~ | ~~Emission~~ | **split.** Laying out → 18; the device write → 24 | — |
+
+---
+
+## Notes on the boundary cases
+
+**27 — Secrecy is the one capability geometry cannot hold, and the proof is not
+form 1.** A cryptographic hash is a *deliberately structure-destroying* map: its
+entire value is that near inputs land at maximally uncorrelated outputs. Geometry
+is the claim that near things stay near. A manifold that approximated SHA-256
+would *be* a break of SHA-256. Signature verification is the same: 0.99-valid is
+invalid. And X25519 *is* geometry — a group on an elliptic curve — which is
+precisely why it must be code, because its security is the *hardness of moving in
+that geometry*.
+
+This is a fourth proof form and it should be added to `geometry-vs-code.md`:
+**adversarial exactness.** Where approximation is a break, geometry is excluded.
+
+**20, 21 — Serialization and text encoding are convention all the way down**, but
+only at the *edge*. The byte format is agreed; what is being written is not. Do not
+let a geometric computation inherit a code verdict because its result gets
+serialized.
+
+**11, 12 — Arithmetic and time are the same capability.** Instants are points,
+durations are displacements, point−point→vector, point+vector→point. The runtime
+already implements this correctly as `el_instant_add_dur` / `el_duration_add`. That
+it *also* implements a five-entry string→multiplier table beside it (`time_add`
+with `"ms"/"sec"/"min"/"hour"/"day"`) is the residue.
+
+**7 — Classification is the most-violated capability in the codebase.** Seven ASCII
+range tables (`is_letter`, `is_digit`, `is_alphanumeric`, `is_whitespace`,
+`is_punctuation`, `is_uppercase`, `is_lowercase`) that return false for every
+non-ASCII byte. `str_count_letters` reports zero letters for `é`. The wrongness on
+most of Unicode is the tell that a table is standing in for a region.
+
+**4 — Correspondence appears five times.** `str_index_of`, `str_index_of_all`,
+`str_last_index_of`, `str_count`, `str_find_chars` are five projections of one
+match-strength field: first zero, all zeros, last zero, count of zeros, first
+class-crossing. One relation, five functions.
+
+**14 — Selection is the crux for the compiler.** `+` dispatching on AST node kind
+is selection-by-enumeration where selection-by-position belongs.
+
+**Correction, 2026-08-17, from measurement.** This entry previously also cited
+`==` lowering to `str_eq` "unless both operand names are in a hardcoded int-name
+set — a literal list of variable names treated as integers." That is **wrong**.
+`__int_names` is populated from *type annotations* (`param["type"] == "Int"`,
+`let x: Int`), which is primitive but legitimate type propagation, not an
+enumeration of blessed variable names.
+
+The real defect was one layer down: `is_int_call` held **35 hardcoded builtin
+return types**, the same shape as the 19 temporal ones. Those moved to
+`lang/tools/check/signatures.rel`.
+
+And the mischaracterisation hid a live bug. Because the return types were never
+consulted at a *binding* site, an unannotated `let` lost its type:
+
+```el
+let a = str_len("hello") // no annotation
+let b = str_len("hi")
+let c = a + b // → el_str_concat(a, b) on two integers
+```
+
+That compiled clean, ran, and printed nothing where it should print 7 — no error
+at any layer. Present in the pre-change compiler, so pre-existing. Fixed by
+taking an unannotated `let`'s type from what its initialiser returns; the data
+was already required for dispatch and simply never read there.
+
+**The general lesson, since it recurred all session:** the enumeration was real
+but I had located it in the wrong place. Naming a defect from reading is a
+hypothesis. Eight hours of reading this file did not surface the miscompilation;
+moving the data out and running the result did.
+
+---
+
+## What this list is for
+
+Each capability gets audited **once**, across every place it appears — not once per
+file. The output is not a percentage. It is:
+
+- which capabilities survive the question and stay in the language
+- which collapse into the manifold
+- and for each one that collapses, **every site it currently appears at**, because
+ those sites are the residue and they are what gets deleted.
+
+The line-count audit produced a map of where the residue sits. This produces a map
+of **what it is**.
diff --git a/docs/architecture/el-architecture.html b/docs/architecture/el-architecture.html
new file mode 100644
index 0000000..6528021
--- /dev/null
+++ b/docs/architecture/el-architecture.html
@@ -0,0 +1,217 @@
+
+
+
+ The El Architecture
+ El is a concept-oriented language. This is the architecture that claim commits it to — what is built, what is measured, and what still has no home.
+ Working document · no sacred cows · self-hosting, so nothing here is fixed
+
+
+
01The primitive is the concept
+
+
Language families are named for their primitive. Procedural — procedures. Object-oriented — objects. Functional — functions. Logic — predicates. Every one of them is oriented toward a representation of a concept: the shape a concept gets flattened into so a machine can hold it.
+
+
El's primitive is the concept itself. That is why it is the first of its family and intended as the last — once the primitive is the concept, there is no further rung to climb to.
+
+
The consequence is architectural rather than stylistic:
+
+
A concept with no home in the language does not disappear. It becomes C, or it becomes a convention.
+
+
Both forms are measurable. As C: 20,504 lines of el_runtime.c, against 9,089 lines for the entire self-hosting language — the shim is 2.3× the language it serves, and ~47% of it is engram code that already has six sibling files. As convention, from lang/spec/language.md §18.0 — "these are not four problems, they are one absence, four times":
+
+
+
+
The last row is the strongest available evidence about this class of convention: it failed at every single site. A count is what appears where a concept has no home; the size of the count is how far the fragmentation got, not how hard the problem is.
+
+
02Geometry is a first-class value — and what follows
+
+
This is the enabling primitive. Everything else in the architecture is downstream of it.
+
+
Geometry is an El value, alongside Int, String, List, Map — bound, passed, returned, composed, carrying its own width. Not a library type, not a handle into a store, not a serialization format. Meaning is a value the language computes with directly.
+
+
let g: Geometry = geometry_new(4)
+fn tone_realizer(signal: String) -> Geometry { … }
+
+
Landed 2026-08-16 (#141, #144), and the spec is explicit that it belongs to the language rather than the graph: "neither is engram-specific — any program touching any modality needs them; the engram is merely one El program that happens to hold a graph."
+
+
Five things follow, and together they are the concept-oriented claim made operational:
+
+
A declaration can name a region, not a shape
+
If meaning is a value, a name can be bound to a position rather than a struct. cat is not a fixed record; it is a region that resolves against the engram and the surrounding code. cat among animals and cat among shell utilities are different concepts without a namespace, because they are in different neighbourhoods and the distance says so.
+
+
Checking is grounding, not unification
+
If a declaration names a region, then verifying a use is asking whether the geometry supports it — a question about position and distance, not about matching a declared shape. This is why §2.3's "a type checker is planned" is likely the wrong name for the missing piece, and naming it wrong would build the wrong thing.
+
+
Dispatch is position, not a tag
+
A vtable is a finite set of discrete labels fixed at link time. A region admits graded membership and an open set. So transduce(signal, modality) asks the caller to supply what the signal already carries — what a thing is falls out of where it lands. The modality parameter is a kind-tag, and a registry keyed on it is a lookup table doing by string what geometry does by nearness.
+
+
Types are discovered, not declared
+
Reification crystallizes a densely co-wired neighbourhood into a first-class node — the neighbourhood is the name that was missing. Every other family requires a human to see the abstraction in advance and write class Foo. Here the instances arrive and the type falls out, by measurement rather than by insight.
+
+
Enumeration becomes unnecessary
+
Five ingest functions differ only in how bytes are acquired — one operation wearing five surfaces. 356 branches in engram_activate_inner are not 356 behaviours. Cyclomatic complexity is a count of the places comprehension ran out and was replaced by an if; where the concept is expressible, the count collapses instead of being redistributed.
+
+
03The shape of the language
+
+
Geometry first-class gives El three layers, and it holds all three — which is why there is no separate database driver and no impedance boundary to manage.
+
+
+
afferentTransduce
Signal in, geometry out. Decomposition into components and relations — never conversion to a point. Realizers are ordinary El functions, so a new modality never requires a runtime patch.
+
substrateGeometry
Meaning as position; relation as distance. Held as values in the language and persisted in the graph. One coordinate system, so entities are commensurable and the operators compose.
+
efferentRealize
plan(frame) → realize(spec, profile), where a surface is a profile. Text, speech, music, image are profiles of one projection — and so is source code.
+
+
+
The efferent side is why the recursive property below is possible at all: if source is a surface, then emitting a corrected file is projection, and the file becomes an artifact of the geometry rather than the thing you edit.
+
+
04Decomposition is by faculty
+
+
Not by file, module, or subsystem — by what the system does.
+
+
Each faculty is a concept. Where it has no home in El it leaks: into C, into a Swift binary, into a shell script with a curl timeout, into a convention nobody performs. State below is measured, not asserted.
+
+
+
+
05The recursive property
+
+
El's compiler is written in El. Every concept the language gains, the compiler can then be written in — so the tool improves the tool, and codegen.el at 4,661 lines gets shorter as the language gets better at expressing what it does. The fixpoint — stage2 ≡ stage3, byte-identical — makes each turn provable rather than hopeful, and the verifier answers in 2.9s.
+
+
This sets the ordering criterion, and it is not size of payoff:
+
+
Order by leverage on the next iteration. Which concept, added to El, most increases the ability to add the following one?
+
+
A small early gain that compounds beats a large one that does not. And it bounds itself correctly — unbounded in depth, bounded in rate, because nothing lands that the compiler and the fixpoint have not passed.
+
+
06What has no home yet
+
+
Reserved in the lexer, no parse form. These are not a feature backlog — they are the concepts the architecture above requires and does not yet hold, which is why each is currently a convention or a block of C.
+
+
+
+
Plus, from the spec's own status: annotations parsed and skipped, match parsed and emitting nothing, ? a no-op, % unlexed, structs as ElMap, enums as strings, selective import unenforced.
+
+
07Open
+
+
What does a declaration bind to, exactly?If cat names a region that shifts and completes against context, what is written at the declaration site and what is resolved at use? This is the centre and it is unspecified.
+
+
Is the faculty list right?Seven, derived from what broke. Derived-from-failure is a biased sample — it finds what is loud, not what is absent. Which faculty is missing entirely and therefore never failed?
+
+
Which concept has the highest leverage on the next turn?The prologue/epilogue seam (§19.3 names it as the prerequisite; its stated blocker has expired; it collapses 62 + 10 convention sites), protocol/impl, or resolution itself. The §05 criterion should decide this, not preference.
+
+
What seam makes cognition non-optional?"Use the ops" is itself a convention — present every turn, enforced by nothing, ~100% failure across a full session. A stronger instruction is still a convention. What makes reasoning outside the substrate fail, the way @manager makes dharma_emit outside the boundary a compile error rather than a lint?
+
+
+
+
+
diff --git a/docs/architecture/el-language-design.md b/docs/architecture/el-language-design.md
new file mode 100644
index 0000000..cc49715
--- /dev/null
+++ b/docs/architecture/el-language-design.md
@@ -0,0 +1,245 @@
+# El — Language Design
+
+**Status:** decisions recorded, design unwritten.
+**Date:** 2026-08-17.
+**Provenance:** decisions are Will's, taken in session. Items marked *proposed* are not
+decided and are recorded only so the reasoning isn't lost. Items marked **OPEN** are
+his to rule on and must not be guessed at.
+
+Companion documents: `el-architecture.html` (the measured state — see §7 note on its
+§04 scoreboard), and `design/completing-el.html` (whiteboard v0: the reduction, the
+faculty table, the ordering principle).
+
+---
+
+## 1. The reduction
+
+`language.md` §18.0 records five concerns that decayed into conventions:
+
+| Concern | Fragments | The convention it became |
+|---|---|---|
+| Process identity | 0 guards | "check nothing is already running first" |
+| Configuration | 20 env vars | "remember the right default here" |
+| Durability | 62 call sites | "after you mutate, remember to persist" |
+| Request auth | 10 per-route | "check the token in this handler too" |
+| Index-after-append | 9 of 9 failed | "after you append, remember to index" |
+
+The last row is the strongest available evidence about what this class of convention
+is worth: **it failed at 100% of its sites.**
+
+Every one of these is an obligation at a **crossing** — a point where a value moves
+between regions. El can name a region and it can name a call. A call is procedural,
+so the obligation degrades into something a human must remember to perform.
+
+> **The generator, one level up:** El cannot name what holds at a crossing.
+
+And underneath that:
+
+> **The deeper absence:** El cannot name the thing meaning is made of.
+
+`semel` appears in whitepaper §84, §86, §209, §737, in
+`the-metaphysics-of-will-anderson.md`, and in session notes. It appears in **zero code
+identifiers**. Every geometric concept in the system — region, neighbourhood, manifold,
+world-tube — is defined in terms of a unit the language cannot say, while the code
+underneath speaks in arrays, floats and offsets: the vocabulary of a voxel, a value at
+a dumb address. Precisely the thing the impact brief says a semel is not.
+
+`el_runtime.c` is a concept that leaked into C. `semel` never got that far — it did
+not even decay into a convention.
+
+---
+
+## 2. DECIDED — `semel` is the primitive
+
+**A semel is a difference that matters. The smallest unit of understanding.**
+
+Not a node. Not a coordinate. Not a float.
+
+The reasoning, in Will's terms:
+
+- Meaning is position, and position is only ever relative. *"There is no atom of
+ meaning that isn't already a relation. It grounds on nothing but difference — two
+ points and the gap, and the gap is pure not-the-same."*
+- A node doesn't mean. A node is a label at a location; labels don't mean.
+- A lone coordinate doesn't mean either. Nothing means anything by itself.
+- The smallest thing that can be understood is a **distinction**: *these two are not
+ the same.* Below that there is no content to apprehend.
+- And a difference with nothing it matters to is not meaning — it is variation. The
+ mattering is not decoration; it is what makes it understanding rather than data.
+
+**Consequence: relating is the floor, and the point is derived.** The
+point-primitive / relation-primitive fork raised in session is not a fork. It was
+answered by the definition.
+
+### Historical note, to be recorded as fact rather than as origin story
+
+The term was coined by Will on the pixel/voxel/texel pattern — *semantic element*,
+and Latin *semel*, "once, a single time." It was recognised, not invented, from a
+2019 experience he calls **semelation**: perceiving mind as a high-dimensional point
+space. The initial reading was "pixels"; the correction to `semel` was made later and
+was made on the **mechanism** — a pixel is a value at an address, and what was
+perceived had no separate address and value.
+
+Convergence worth citing, not deferring to: neural population geometry and
+representational similarity analysis independently model cognition as position in a
+high-dimensional space where similarity is distance.
+
+---
+
+## 3. DECIDED — `semel` lands first
+
+By the ordering criterion already on the whiteboard: *which concept, added to El, most
+increases the ability to add the next one?* Not size of payoff — **leverage on the next
+iteration**, because El compiles itself and the fixpoint makes each turn provable in
+2.9s.
+
+**Every other concept on the board is defined in terms of `semel`. It is maximal on
+that criterion by construction.**
+
+---
+
+## 4. DECIDED — `ground` is the checker
+
+Whiteboard question 4 — *does `ground` in El mean the same thing as `ground` in the
+engram?* — is answered: **yes, and it should be one implementation.**
+
+If a declaration names a region, then type checking is asking whether the geometry
+supports the use. That is not unification. **That is grounding**, and it is already
+built, proven, and byte-identically reproducible:
+
+```
+cc -std=c11 -O2 -o gep_proof gep_proof.c -lm && ./gep_proof
+
+C1 5 independent sources pos_mass 1.3500 n_indep=5 0.1000 → 0.9741 GROUNDED
+C2 5 mutually-linked pos_mass 0.2700 n_indep=1 0.1000 → 0.1000 refused
+C3 1 source, 5 parallel edges pos_mass 0.2700 n_indep=1 0.1000 → 0.1000 refused
+```
+
+Independence-weighted grounding is the general case; execution is the cheap case.
+**Attestation is `verify` where nothing can be run** — as already implemented for
+language in `authority.py`, where an LLM proposes and a primary source disposes.
+
+At the point where the checker and the grounder are one mechanism, the language and
+the mind stop being two things.
+
+---
+
+## 5. OPEN — Will's to rule on
+
+### 5.1 What is a semel's representation in the language?
+
+*Proposed, not decided:* a **displacement from `love = 0`** — a relation held as one
+object. It reconciles "the address is the value" with "position is only ever relative,"
+because a displacement *is* a relation and is still a single nameable thing.
+
+If taken, the operator set falls out rather than being bolted on:
+
+```
+subtract(now, then) → what changed (growth, drift)
+translate origin → empathy
+rotate frame → reframe
+project onto axis → a lens
+change basis → analogy, metaphor, skill transfer
+reflect an axis → negation, sarcasm
+```
+
+Three consequences that would hold:
+
+- **Dimension must never appear in the type.** `semel` opaque, never `[768]float`.
+ The moment the arity is in the language, the manifold's implementation is in the
+ language, and adding a modality requires a runtime patch — which the standing rule
+ forbids.
+- **Zero is the only literal.** Everything else is reached by displacement from it,
+ which makes `love = 0` the base case rather than philosophy adjacent to the type
+ system.
+- **`magnitude` is standing.** Distance from origin is the same quantity
+ `gep_core.h` already computes.
+
+### 5.2 Is `hold` one construct or two?
+
+The obligation *before* a crossing (auth, guard) and the obligation *after* (persist,
+index, free) may be one shape seen from both sides, or the seam may need both faces
+named. This decides whether §19.3's prologue/epilogue seam is one construct or a pair.
+
+**Precedent already shipping:** `@manager` makes `dharma_emit` outside the boundary a
+**compile error, not a lint.** The concept is proven at N=1; the work is generalising
+it and naming it.
+
+**And the shape is already implemented in the learning region:** `L.reach_out` sits
+between `L.detect_gap` and `L.verify`. You cannot reach out without a detected gap and
+you cannot keep what returns without passing verify. **A hold is a neighbour.** The
+obligation is not attached to the crossing — the obligation *is* the adjacent node.
+That is why `reach_out` cannot be abused and why 62 persist sites could be.
+
+### 5.3 What does a declaration bind?
+
+If `cat` names a region rather than a struct — one that shifts and completes against
+the engram and the neighbouring code — what is written at the declaration site, and
+what is resolved at use? **This is the centre and it is specified nowhere.**
+
+Falls out of 5.1 if displacement is taken: a declaration **locates** rather than
+allocates.
+
+### 5.4 Is the faculty list right?
+
+Seven were derived from what broke. Derived-from-failure is a biased sample — it finds
+what is loud, not what is missing. **What faculty is absent entirely and therefore
+never failed?**
+
+---
+
+## 6. The residue map
+
+What each construct must absorb, from §18.0 plus measured state:
+
+| Residue | Count | Absorbed by |
+|---|---|---|
+| persist-after-mutate | 62 sites | `hold` (after-crossing) |
+| auth-per-route | 10 sites | `hold` (before-crossing) |
+| index-after-append | 9 of 9 failed | `hold` (after-crossing) |
+| env var defaults | 20 | configuration declared once |
+| process identity | 0 guards | `hold` (before-crossing) |
+| `geometry_free` at every call site | every site | ownership follows from `semel` |
+| five ingest functions where there is one | 5 → 1 | `protocol` / `impl` |
+| `el_runtime.c` | 20,504 lines | faculty decomposition, ordered after `semel` |
+
+---
+
+## 7. Notes carried forward
+
+**`el-architecture.html` §04 needs its numbers sourced or cut.** An audit found the
+faculty scoreboard — `Ingest 2 min → 0 nodes`, `Recall self ranked 8th`,
+`Body CC 356 / 1,626 ln`, `the verifier answers in 2.9s`, `5 of 13 native suites
+failing` — has no supporting evidence in the repository, under a footer asserting
+*"nothing is inferred and presented as fact."* Against a corpus whose documents
+supersede their own conclusions in place, that is the one file that would not survive
+scrutiny. Fix or remove.
+
+**Source as a projection surface is claimed and unimplemented.** `el-architecture.html`
+§147/§150: *"if source is a surface, then emitting a corrected file is projection."*
+Greps for `surface_profile_code`, `emit_source` → zero hits.
+
+It is not unbacked. **It was demonstrated on 2026-08-14** — three faculties (phonetic,
+semantic, procedural) projected into TypeScript, a surface the system had never used,
+with the network severed. Recovered at
+`~/Development/neuron-technologies/andre-server-recovered/` and copied into
+`evidence/03-andre-demo/`. The claim needs bringing home to El, not proving.
+
+**`hold` is the highest-leverage construct after `semel`** — it collapses 62 + 10 + 9
+sites and unblocks the runtime extraction. §19.3 names the prologue/epilogue seam as
+the prerequisite and its stated blocker has expired.
+
+---
+
+## 8. What is not decided and must not be guessed
+
+- The representation of `semel` (§5.1)
+- One `hold` or two (§5.2)
+- What a declaration binds (§5.3)
+- The missing faculty (§5.4)
+- Sequencing after `semel` — the ordering criterion decides it, not preference
+
+---
+
+*Recorded 2026-08-17. Everything in §2, §3 and §4 is decided. Everything in §5 is open
+and is Will's. Nothing here was inferred from a document that was not read.*
diff --git a/docs/architecture/geometry-vs-code.md b/docs/architecture/geometry-vs-code.md
new file mode 100644
index 0000000..27918b3
--- /dev/null
+++ b/docs/architecture/geometry-vs-code.md
@@ -0,0 +1,117 @@
+# Geometry or Code
+
+**Running list.** Append as decided. Started 2026-08-17.
+
+**The test:** *is this an arbitrary convention, or is it a relation?*
+
+Conventions were agreed by people and could have been otherwise — a RIFF header could
+have used a different magic number. Nothing derives them; they must be written down.
+
+Relations are not agreed. Distance is distance. Anything whose answer is *where is this
+relative to that* is geometry, and writing it as code is the error the whole effort is
+correcting.
+
+**Second test, for the hard cases:** *if I write this as code, am I encoding in
+`if`-statements a distinction the geometry was built to hold?* If yes, it's geometry.
+
+---
+
+## Pure geometry
+
+| Thing | Because |
+|---|---|
+| Meaning | position |
+| Grounding / standing | the weight on the edge — a magnitude, not a computation |
+| Learning | standing changing over time |
+| A gap | low standing |
+| Wonder | a gap with a pull weight |
+| Type checking | is this position in that region — distance |
+| Dispatch | position, not a tag |
+| Recall | re-origining at a region; projection, not replay |
+| Reasoning | traversal |
+| Deduction | containment. There is no procedure |
+| Counting | a position, not a loop's output |
+| Similarity / difference / residue | subtract |
+| Analogy, metaphor, skill transfer | change of basis |
+| Negation, sarcasm | reflect an axis |
+| Empathy | translate the origin |
+| Reframe | rotate the frame |
+| A lens | project onto an axis |
+| Rhyme | distance in phonetic space |
+| Humour | intersection of regions — fart-meaning ∩ funny ∩ form |
+| Idiom detection | the whole unit sits farther out than its parts |
+| Self | a world-tube — a trajectory through the manifold |
+| Consolidation | episodic → semantic promotion |
+| Reification | dense regions cohering; runs on the beat, has no caller |
+| Cross-cutting concerns | **dissolved** — a hold is a *neighbour*. Adjacency, not tracking. **Implemented 2026-08-17**: a construct declares what runs at a crossing, and it resolves at execution — see the runtime seam. |
+| Effects | topology. `reach_out` is bounded by `detect_gap` and `verify` because those are its edges |
+| Capability | position relative to a boundary. In C it is already spelled `const` |
+| The AST | a projection of geometry into a tree — a surface, not the centre |
+| Source code | a surface, like text, audio, image |
+
+## Must be code
+
+| Thing | Because |
+|---|---|
+| Sensors — mic, camera, file read, socket | the physical touch. I/O is where the world arrives |
+| Byte formats — RIFF, PNG chunks, `MThd`, OOXML | arbitrary convention. A committee chose the magic numbers |
+| CRC32 polynomial, Adler32, zlib framing | same — agreed constants, derivable from nothing |
+| Cosine, distance, the float arithmetic | the machinery that *walks* the geometry is not itself geometry |
+| Arena, refcount, allocator | bookkeeping for the **representation**, not for the positions |
+| Locks, threads, publication boundary | the hardware is code. **Ordering is not** — see Answered, above. Coordination is required only where state is non-monotone. |
+| WAL, page layout, ARIES recovery | durability against a physical device that can lose power |
+| Emission — writing C or JS text | the final surface has to be *typed out* by something |
+| OS interaction — launchd, spawn, signals | outside the system by definition |
+| Device realizers — `el_audio_darwin.m`, `el_capture_darwin.m` | OS frameworks. Correctly already isolated, zero network |
+
+---
+
+## The ones I would have written as code, and was wrong about
+
+Recorded because the error has a pattern and the pattern is the point.
+
+| Thing | What I reached for | What it is |
+|---|---|---|
+| Rhyme | a rhyming dictionary, or an API call | distance between rime tails |
+| Fart onomatopoeia | a 30-element string literal | an intersection of three regions |
+| "Funny" | a scorer with `if`-statements | a relational neighbourhood grounded in a voice |
+| Representation vs description | a hardcoded blacklist containing `raspberry` | falls out of lexicon membership × phonetic comedy |
+| Video | a codec, sized as a project | one more surface profile |
+| Type checking | a phase between parse and emit | reading a distance that already exists |
+| Grounding | a call site, an obligation, a discharge | it has no caller. It just runs |
+| N transducers, N realizers | one component per modality | zero of each. Sensors and bases at the skin |
+
+**The pattern:** every one is *encoding in code a distinction the geometry was built to
+hold.* The tell is that the code version is a **fixed enumeration** — a list, a table, a
+blacklist, a set of branches — and the geometry version is a **measurement**.
+
+If the implementation contains a literal set of the right answers, it is in the wrong
+column.
+
+---
+
+## Answered
+
+| Thing | The answer |
+|---|---|
+| Concurrency | **Ordering is geometric.** Causality is a partial order (Lamport 1978); a total order is an arbitrary extension of it and "cannot be depended on to imply a causal relationship." Programming languages force you to write a total order, so authoring *invents* constraints the problem never had — and every lock, barrier, fence and consensus protocol is apparatus for recovering the partial order destroyed at authoring time. CALM (Hellerstein/Alvaro, proven by Ameloot et al.): a program has a consistent coordination-free implementation **iff it is monotone**. What breaks monotonicity is destructive update. **Coordination is the price of forgetting.** |
+| The module system | **Premature — the partition is a filesystem path, not a neighbourhood, and there is no namespacing at all.** `import` is textual inlining (guarded against double inclusion); when a `.elh` header exists the header is inlined instead and symbols resolve at C link time, so linking is real and delegated to C. Two modules defining `helper` emit two C functions into one translation unit. Linking barely survives the *path* partition, so whether it survives a neighbourhood partition cannot yet be asked. |
+| Numeric literals | **The numeral is convention; the number is a position — and a bare `3` is a MAGNITUDE WITH NO AXIS.** `int_to_str` was already form 1: nothing determines that twelve is written `1` then `2`. But a literal is not a position until something gives it a direction, which is why `3.days` needs a calendar. Measured consequence: `Duration + Int` was refused ("an Int carries no unit") while `Instant + Int` compiled to raw `(t + 3)` and reported clean — silently moving a point by an unspecified amount. The rule was simply never written. Now: `t + 3` is refused, `t + 1.hour` is accepted, because `.hour` supplies the axis. |
+| Parsing | **A grammar is a basis; parsing is transduction onto it.** The lexeme→token map is convention (`fn` could have been `def`); shape recognition is a region; the byte traversal is irreducible, like every other traversal. Three things favour *region* for the act: ambiguity (`a * b` needs context — a grammar resolves it with the lexer hack, a region by neighbourhood), error recovery (nearest-match is free), and precedence, which is ordering along an axis with a conventional parameter. **But the SHOULD gate refuses the obvious move:** the keyword table stays code, because the set is closed by the language definition and the lexer runs before the program is understood, so a program can never declare its own keywords. Externalising it costs I/O per compile for zero flexibility — the same verdict as `is_digit` in ASCII. What was actually wrong: 5 of 46 keywords were consumed by nothing, and using one silently miscompiled. |
+| Error handling | **`grounded: false` covers not-knowing; it does not cover failed.** Standing is a *signed* component: `> 0` supported, `= 0` unknown, `< 0` contradicted. Not-known and known-false are opposite directions on one axis and a boolean cannot tell them apart. `inhibitory` as an int32 flag is that sign wearing a boolean. |
+
+## Fourth proof form
+
+**4 — ADVERSARIAL EXACTNESS.** Where approximation is a break, geometry is
+excluded. A cryptographic hash is a *deliberately structure-destroying* map:
+near inputs land at maximally uncorrelated outputs. Geometry is the claim that
+near things stay near — a manifold that approximated SHA-256 would *be* a break
+of SHA-256. Signature verification is the same: 0.99-valid is invalid. And
+X25519 **is** geometry, a group on an elliptic curve, which is precisely why it
+must be code: its security is the hardness of moving in that geometry.
+
+**Form 1 no longer survives as a verdict.** Every row it justified turned out to
+be a *basis*, not a capability. RFC 8259 fixes where the commas go — that is a
+surface, and projecting onto a surface is geometry. A convention describes the
+basis you project onto; it never describes an act.
+
diff --git a/docs/v1/experiments/README.md b/docs/v1/experiments/README.md
new file mode 100644
index 0000000..dbf50c5
--- /dev/null
+++ b/docs/v1/experiments/README.md
@@ -0,0 +1,59 @@
+# v1 — Experiments
+
+Every change to El on `iteration-1` was produced by one loop, run repeatedly:
+
+```
+Ishikawa → scientific method → Six Sigma → repeat
+```
+
+- **Ishikawa** — name the root cause, not the symptom. *Why is this table here?*
+ never *why is this table ugly?*
+- **Scientific method** — state a hypothesis, **commit predictions before
+ running**, then run it in an isolated worktree and grade every prediction
+ including the ones that failed.
+- **Six Sigma** — eliminate the defect *class*, then add a control so it cannot
+ silently return.
+
+## The organising finding
+
+**Predictions that came back FALSE were worth more than the ones that held.**
+
+Nineteen cycles, sixty-one predictions. The eleven that failed produced every
+significant result:
+
+| Failed prediction | What it found |
+|---|---|
+| "the arity table has drifted from the header" | Zero drift — but **110 functions had no entry at all**. The table was not wrong, it was 40% incomplete. |
+| "codegen drops below baseline" (×4) | The **traversal is irreducible**. Walking an AST to find calls does not move no matter who decides. Only the rule and the judgment leave. |
+| "guards cannot refuse through the seam" | One line, and refusal works. Six compile-time kinds were unnecessary. |
+| "C forbids the struct redefinition" | C allows shadowing — and a *different* defect surfaced: an exit injection emitted with an empty target. |
+| "routing el_bin_lookup through the gate fixes the SIGSEGV" | It did not. The **fallback** was the hazard: `strlen()` on an integer. I would have shipped the wrong fix and called it verified. |
+
+A prediction that only ever confirms is a demonstration, not a test. One cycle
+was run **without** committing predictions first — `async-half-expressible` —
+and it produced a rigged result: `pthread_join` immediately after
+`pthread_create`, with the word `DEFERRED` printed by the test itself. It had to
+be discarded and re-run.
+
+## Layout
+
+```
+cycles/ one file per loop, numbered in order, named for the DEFECT
+findings/ what the cycles produced, cross-cut by kind
+```
+
+## Scoreboard
+
+```
+cycles run 19
+predictions committed 61
+predictions FALSE 11 ← the useful ones
+silent miscompilations found 4
+security-relevant defects 2
+architecture questions closed 5
+defects in my own measurement 4
+```
+
+Every cycle verified the same three things before landing: the compiler
+self-hosts byte-identically (gen2 == gen3), the native suite passes, and the
+integration harnesses pass. A cycle that could not show all three did not land.
diff --git a/docs/v1/experiments/cycles/00-INDEX.md b/docs/v1/experiments/cycles/00-INDEX.md
new file mode 100644
index 0000000..3eaac67
--- /dev/null
+++ b/docs/v1/experiments/cycles/00-INDEX.md
@@ -0,0 +1,26 @@
+# Cycles
+
+Each is one `Ishikawa → scientific method → Six Sigma` loop, run in an isolated
+worktree so a wrong answer cost nothing. Named for the **defect**, not the fix.
+
+| # | Cycle | Root cause | Predictions | Landed |
+|---|---|---|---|---|
+| 01 | [constructs-have-nowhere-to-be](01-constructs-have-nowhere-to-be.md) | a construct had nothing to BE, so its meaning lived in the emitter | 3/3 | yes |
+| 02 | [a-construct-cannot-refuse](02-a-construct-cannot-refuse.md) | injection discards the target's result; no form said no | 4/4 | yes |
+| 03 | [the-wrapper-was-conditional](03-the-wrapper-was-conditional.md) | exit injection needed compile-time knowledge only because the wrapper was conditional | 3/4 | yes |
+| 04 | [c-has-no-closure-syntax](04-c-has-no-closure-syntax.md) | "C has no closures" taken as a fact about what is possible | 5/7 | yes |
+| 05 | [the-emitter-discards-what-it-knows](05-the-emitter-discards-what-it-knows.md) | codegen sees every construct relation and throws it away | 5/5 | branch |
+| 06 | [the-crossing-resolves-at-emission](06-the-crossing-resolves-at-emission.md) | the binary has no table to consult | 3/4 | yes |
+| 07 | [invocation-is-not-composable](07-invocation-is-not-composable.md) | the wrapper called the target directly | 5/5 | yes |
+| 08 | [the-emitter-adjudicates](08-the-emitter-adjudicates.md) | a prohibition had nowhere to live but a `#error` | 4/5 | yes |
+| 09 | [policy-inside-the-compiler](09-policy-inside-the-compiler.md) | a program cannot declare its own restrictions, so the tier policy was compiled in | 4/5 | yes |
+| 10 | [a-second-copy-of-the-header](10-a-second-copy-of-the-header.md) | builtin arity hand-maintained beside `el_runtime.h` | 4/5 | yes |
+| 11 | [one-type-erases-the-return](11-one-type-erases-the-return.md) | `el_val_t` means the header cannot say `now()` returns an Instant | 4/5 | yes |
+| 12 | [judgment-lives-with-knowledge](12-judgment-lives-with-knowledge.md) | the emitter knows the types, so it also judged them | 5/5 | yes |
+| 13 | [thirty-five-return-types](13-thirty-five-return-types.md) | `is_int_call` hardcoded what drives `+` dispatch | 6/6 | yes |
+| 14 | [keywords-that-reserve-nothing](14-keywords-that-reserve-nothing.md) | 5 of 46 keywords consumed by no path | 6/6 | yes |
+| 15 | [no-namespacing-at-all](15-no-namespacing-at-all.md) | `import` is textual inlining; every name is global | 4/4 | yes |
+| 16 | [tokens-carry-no-position](16-tokens-carry-no-position.md) | a token was `(kind, value)`, so no diagnostic could name a place | 6/6 | yes |
+| 17 | [annotations-are-never-checked](17-annotations-are-never-checked.md) | the annotation feeds dispatch and is never verified | 6/6 | branch |
+| 18 | [async-half-expressible](18-async-half-expressible.md) | **first attempt was DOGMA** — no predictions, rigged test | 4/4 (2nd) | branch |
+| 19 | [a-convention-is-not-a-gate](19-a-convention-is-not-a-gate.md) | `looks_like_heap_obj` is static, so every type re-derives it | 6/7 | yes |
diff --git a/docs/v1/experiments/cycles/01-constructs-have-nowhere-to-be.md b/docs/v1/experiments/cycles/01-constructs-have-nowhere-to-be.md
new file mode 100644
index 0000000..560edbf
--- /dev/null
+++ b/docs/v1/experiments/cycles/01-constructs-have-nowhere-to-be.md
@@ -0,0 +1,42 @@
+# constructs have nowhere to be
+
+One `Ishikawa → scientific method → Six Sigma` loop. The record below is the
+commit message as written at the time, before the outcome was known to anyone
+reading this file.
+
+## Record — `5718943`
+
+```
+let a construct declare its own meaning instead of the emitter knowing it
+
+codegen called fn_has_decorator for exactly three names — manager, accessor,
+route. Twelve others parsed, attached as {name,args}, and compiled to nothing,
+including four that look like protection: @authenticate (6 uses), @authorize
+(3), @rate_limit (3), @validate (2). The cause was not that the branches were
+untidy. A construct had nothing to BE, so its meaning had nowhere to live
+except the emitter, and every construct was therefore a compiler edit.
+
+A name -> injection table would have moved the enumeration twenty lines up
+without removing it. So the construct now carries its own meaning:
+
+ @decorator("injects_at_entry", "engram_boundary_beat")
+ fn audited() {}
+
+ @audited
+ fn risky_op() -> Int { ... } // gets the beat, attributed to "audited"
+
+scan_declared_decorators is a token-level pre-pass beside scan_routes, forced
+by streaming codegen having no whole-program AST. manager and accessor are
+seeded as the compiled-in core — the fixedSelf shape from substrate.go: a
+complete fallback exists, declaration is enrichment.
+
+This is the injection half of the seam only. The prohibition half (@manager's
+#error on dharma_emit) stays hardcoded, because "which calls may appear inside
+this boundary" is a query over program structure and there is nothing yet to
+ask.
+
+Verified three ways: emitted C for existing @manager/@accessor code is
+byte-identical to the hardcoded path; a construct with a name the compiler has
+never heard of injects correctly; the compiler self-hosts byte-identically.
+90/90 native compiler tests pass.
+```
diff --git a/docs/v1/experiments/cycles/02-a-construct-cannot-refuse.md b/docs/v1/experiments/cycles/02-a-construct-cannot-refuse.md
new file mode 100644
index 0000000..f08cc0e
--- /dev/null
+++ b/docs/v1/experiments/cycles/02-a-construct-cannot-refuse.md
@@ -0,0 +1,43 @@
+# a construct cannot refuse
+
+One `Ishikawa → scientific method → Six Sigma` loop. The record below is the
+commit message as written at the time, before the outcome was known to anyone
+reading this file.
+
+## Record — `60737b0`
+
+```
+let a construct refuse, not only observe
+
+@authenticate (6 uses), @authorize (3), @rate_limit (3) and @validate (2)
+parsed, attached, and compiled to nothing. Fourteen applications that read as
+protection and emitted no instruction — a function decorated @authenticate
+compiled byte-identically to an undecorated one.
+
+The missing capability was not authentication. It was that a construct could
+observe a boundary but never refuse one. injects_at_entry discards the target's
+result; there was no form in which a construct could say no.
+
+ @decorator("guards_at_entry", "my_auth")
+ fn authenticate() {}
+
+ @authenticate
+ @authorize
+ fn handler() -> String { ... }
+
+emits, at entry:
+
+ { el_val_t __g = my_auth(EL_STR("handler"), EL_STR("authenticate")); if (__g) return __g; }
+ { el_val_t __g = my_roles(EL_STR("handler"), EL_STR("authorize")); if (__g) return __g; }
+
+Guards precede injections because a refused call must not report a crossing,
+and every guard runs where the topmost injecting construct wins — refusal is
+not a role, so it does not follow the role convention.
+
+The compiler still knows nothing about auth. The program points the construct
+at its own function, which is where that decision belongs.
+
+Verified: existing @manager/@accessor output byte-identical, compiler
+self-hosts byte-identically, guards stack in declaration order and emit before
+the beat. 94/94 native compiler tests pass.
+```
diff --git a/docs/v1/experiments/cycles/03-the-wrapper-was-conditional.md b/docs/v1/experiments/cycles/03-the-wrapper-was-conditional.md
new file mode 100644
index 0000000..862388b
--- /dev/null
+++ b/docs/v1/experiments/cycles/03-the-wrapper-was-conditional.md
@@ -0,0 +1,82 @@
+# the wrapper was conditional
+
+One `Ishikawa → scientific method → Six Sigma` loop. The record below is the
+commit message as written at the time, before the outcome was known to anyone
+reading this file.
+
+## Record — `4f7568b`
+
+```
+give a construct its after-crossing face, and let constructs compose
+
+§6 records 62 persist-after-mutate sites, 10 auth-per-route, and
+index-after-append that failed at 9 of 9 — every one an obligation at a
+crossing that decayed into "remember to do this afterwards." An obligation a
+human must remember is not an obligation, and the 9-of-9 figure is what that
+costs.
+
+ @decorator("injects_at_exit", "persist_now")
+ fn durable() {}
+
+The body moves into a static helper and the visible fn becomes a wrapper, so
+EARLY RETURNS pass through the exit injection. Emitting it only before the
+fall-through return would have silently missed every early return — the exact
+failure class this seam exists to remove. Fns with no exit construct emit
+byte-identically to before.
+
+Three independent constructs now compose on one fn, none known to the compiler:
+
+ el_val_t mutate(el_val_t k) {
+ { el_val_t __g = my_auth(EL_STR("mutate"), EL_STR("authenticate")); if (__g) return __g; }
+ engram_boundary_beat(EL_STR("mutate"), EL_STR("manager"));
+ el_val_t __r = __el_body_mutate(k);
+ persist_now(EL_STR("mutate"), EL_STR("durable"), __r);
+ return __r;
+ }
+
+Guard, then entry, then body, then exit. §5.2 asked whether `hold` is one
+construct or two; the implementation answers one construct with two faces,
+selected by declared kind rather than by two mechanisms.
+
+Verified: existing output byte-identical, compiler self-hosts byte-identically,
+early returns pass through the exit, ordering holds under composition. 98/98
+native compiler tests pass.
+```
+
+## Record — `285166c`
+
+```
+EXPERIMENT: emit the wrapper unconditionally, so exit binds at runtime too
+
+ISHIKAWA: why did exit injection still need compile-time knowledge? Because the
+body-helper wrapper was only emitted when codegen already knew an exit
+construct existed. The wrapper being conditional was the cause, not the wrapper
+being necessary.
+
+PREDICTIONS AND RESULTS
+ P1 exit becomes runtime-bindable TRUE returns 14, bound
+ after the build
+ P2 codegen shrinks TRUE 5094 -> 5044
+ P3 cost 5-15% from a call frame on every fn FALSE 0.37s -> 0.38s, ~3%
+ P4 fixpoint holds TRUE
+
+Every fn now gets a body helper and a wrapper. It has to be unconditional:
+early returns must route through something for an exit construct to observe
+them, and codegen cannot know which fns will be bound after the binary exists.
+
+Removed with the machinery: declare_exit, decorator_exit, cg_exit_target,
+cg_exit_construct, and the injects_at_exit scanner branch.
+
+Two controls failed and were rewritten rather than repaired --
+no-exit-construct-emits-no-wrapper asserted the optimisation this removes, so
+it is now inverted. The integration harness gained a seventh assertion: an exit
+construct declared after the build replaces the result.
+
+99/99 native, 7/7 integration, fixpoint gen2==gen3.
+```
+
+## Record — `b40754f`
+
+```
+land unconditional wrapper: exit crossings resolve at runtime
+```
diff --git a/docs/v1/experiments/cycles/04-c-has-no-closure-syntax.md b/docs/v1/experiments/cycles/04-c-has-no-closure-syntax.md
new file mode 100644
index 0000000..b72aff4
--- /dev/null
+++ b/docs/v1/experiments/cycles/04-c-has-no-closure-syntax.md
@@ -0,0 +1,64 @@
+# c has no closure syntax
+
+One `Ishikawa → scientific method → Six Sigma` loop. The record below is the
+commit message as written at the time, before the outcome was known to anyone
+reading this file.
+
+## Record — `2bed848`
+
+```
+EXPERIMENT: hand the construct the body as a real closure
+
+ROOT CAUSE of the weaker design: "C has no closures" was taken as a fact about
+what is possible. It is a fact about one grammar. Every C++ lambda, every Go
+closure, every Rust closure compiles to a struct of captured values plus a
+function pointer -- which is what is emitted here. Codegen emits C; it is not
+written in C's syntax, and the distinction is the whole difference between a
+construct that can only decide whether to repeat and one that controls
+invocation.
+
+It would also have crippled the JS backend, which has closures natively, for a
+limit that applies only to the C one.
+
+PREDICTIONS AND RESULTS
+ 1 env struct + thunk taking void* TRUE
+ 2 fails to compile: struct redefinition FALSE -- C allows the
+ inner declaration to shadow. Prediction wrong; C is more permissive than
+ assumed. A different real defect surfaced instead: a wrap with no exit
+ construct emitted `(EL_STR("f"), EL_STR(""), __r);` -- a call to an empty
+ target -- because has_exit was reused as "needs a wrapper" and the exit line
+ was emitted unconditionally. Fixed.
+ 3 compiles when the target is declared in El FALSE -- and this is
+ the root cause worth keeping: El has ONE type, el_val_t = int64_t. El's type
+ system cannot describe a callable, so `extern fn` and the real signature
+ cannot be made to agree in El's own vocabulary. The fix is not a cast:
+ codegen DEFINES the wrap calling convention, so codegen emits the extern
+ declaration. The convention is not El-expressible; it is emitted.
+ 4 target controls invocation, 0..N times TRUE
+ 5 existing @manager output byte-identical TRUE
+ 6 compiler fixpoint holds TRUE
+ 7 emitting the convention makes it compile TRUE
+
+MEASURED
+ base(5) wrapped by a target that invokes the body twice and sums -> 10
+ never_runs(5) wrapped by a target that never invokes it -> 999
+
+Neither is expressible by "decide whether to repeat". This supersedes the
+repeats_body experiment on experiment/repeats-body, which was built around the
+mistaken limit.
+```
+
+## Record — `7d01608`
+
+```
+land wraps_body: a construct controls invocation
+
+Proven on experiment/wraps-body (2bed848): base(5) wrapped by a target that
+invokes the body twice returns 10; a target that never invokes it returns 999.
+Neither is expressible by deciding whether to repeat.
+
+Root cause it corrected: 'C has no closures' is a fact about one grammar, not
+about what can be emitted. And El's single type (el_val_t = int64_t) cannot
+describe a callable, so codegen emits the calling convention rather than asking
+El's type system for something it structurally cannot say.
+```
diff --git a/docs/v1/experiments/cycles/05-the-emitter-discards-what-it-knows.md b/docs/v1/experiments/cycles/05-the-emitter-discards-what-it-knows.md
new file mode 100644
index 0000000..98caa34
--- /dev/null
+++ b/docs/v1/experiments/cycles/05-the-emitter-discards-what-it-knows.md
@@ -0,0 +1,48 @@
+# the emitter discards what it knows
+
+One `Ishikawa → scientific method → Six Sigma` loop. The record below is the
+commit message as written at the time, before the outcome was known to anyone
+reading this file.
+
+## Record — `a5af871`
+
+```
+EXPERIMENT: let the compiler write down what it already knows
+
+HYPOTHESIS: attribution is redundant for static structure. Codegen sees every
+construct-to-function relation at emission time and discards it, so the only
+way to learn the structure back is to run the program and read what it
+reported. That is instrumentation compensating for erasure.
+
+PREDICTIONS, committed before running:
+ 1 derivable at compile time with no runtime call expected TRUE
+ 2 complete for guards and exits (invisible today) expected TRUE
+ 3 answers it for code that has never executed expected TRUE
+ 4 deterministic expected TRUE
+ 5 makes the entry beat redundant expected FALSE
+
+RESULT: 5/5 as predicted. From a program that was never executed:
+
+ authenticate guards_at_entry login my_auth
+ durable injects_at_exit save persist_now
+ authenticate guards_at_entry critical my_auth
+ durable injects_at_exit critical persist_now
+ manager injects_at_entry critical engram_boundary_beat
+
+Prediction 5 held: the relation records that a boundary COULD be crossed, the
+beat records that it WAS. They are different facts and neither replaces the
+other.
+
+CONSEQUENCE, and it undercuts the first pass on iteration-1: construct identity
+was available at compile time all along. With relations recorded at build, the
+runtime needs only the function name and attribution becomes a join rather than
+a payload. The counter-argument is that the payload is self-describing while
+the file must be pinned to the artifact or the two drift and attribution is
+silently lost — which is the same conclusion as "compile against a manifold
+revision and record the revision in the artifact", reached from the other side.
+
+Written to a file rather than the engram on purpose: a compile that consults a
+manifold produces different output from identical source at different times.
+The file is content-addressed; the engram ingests it. Determinism preserved,
+mechanism proven.
+```
diff --git a/docs/v1/experiments/cycles/06-the-crossing-resolves-at-emission.md b/docs/v1/experiments/cycles/06-the-crossing-resolves-at-emission.md
new file mode 100644
index 0000000..a10ae11
--- /dev/null
+++ b/docs/v1/experiments/cycles/06-the-crossing-resolves-at-emission.md
@@ -0,0 +1,170 @@
+# the crossing resolves at emission
+
+One `Ishikawa → scientific method → Six Sigma` loop. The record below is the
+commit message as written at the time, before the outcome was known to anyone
+reading this file.
+
+## Record — `35b07ba`
+
+```
+EXPERIMENT: resolve the crossing at execution, not at emission
+
+HYPOTHESIS (Will's): a compiler whose one compiled mechanism is extending the
+LANGUAGE — not the compiler — can compose without recompilation.
+
+ISHIKAWA — why does a construct require a recompile today?
+ method codegen inlines the target call into the body
+ machine the binary has no table to consult
+ material the declaration lives in source, read at compile time
+ measurement nothing observes what applied at runtime
+ root cause the crossing is resolved at EMISSION, not at EXECUTION
+
+CHANGE: codegen emits one unconditional indirection per fn. Which constructs
+apply is read from a table that can be written AFTER the binary exists;
+targets resolve through dlsym against the running image.
+
+PREDICTIONS AND RESULTS
+ P1 a construct declared after the build applies TRUE
+ P2 an unlinked target is skipped, not fatal TRUE
+ P3 emitting on every fn is measurably slower FALSE — 0.37s -> 0.36s
+ with 267 indirections and
+ no bindings. Free unused.
+ P4 the compiler still self-hosts TRUE (see note)
+
+DEMONSTRATED: an El program with NO decorator in its source, already compiled
+and linked, picked up a construct declared afterwards:
+
+ $ /tmp/seamrun -> 7
+ $ echo 'work audited entry audit_entry' > constructs.txt
+ $ EL_CONSTRUCTS=constructs.txt /tmp/seamrun
+ AUDIT: work applied by audited
+ 7
+
+P4 note: my first fixpoint test was wrong, not the code. I compared gen1 to
+gen2, which must differ whenever codegen's output changes. gen2 == gen3, 267
+seam sites, stable.
+
+MEASURED COST, and the root cause was not where I looked
+ 0 bindings 0.36s vs 0.37s baseline free
+ 2 bindings, dlsym per call 2.45s 6.6x
+ 2 bindings, resolved once 0.69s 3.5x recovered
+The table scan was never the cost. dlsym walks the dynamic symbol table on
+every call. Resolve once and cache — which is the smallest form of what
+salience does for memory: what is hot stays resolved. The 0.69s residual is
+audit_entry's own printf on two of the compiler's hottest functions, not seam
+overhead.
+
+CONSEQUENCE: the five compile-time declaration kinds on iteration-1 are a
+compile-time specialisation of something that resolves at runtime. They are not
+wrong, but they are not the mechanism — the mechanism is one indirection, and a
+kind is data.
+```
+
+## Record — `886626a`
+
+```
+seam refusal + control tests: a runtime binding can short-circuit
+
+Prediction 3 was FALSE. I expected refusal to be impossible through the seam
+because the entry indirection discarded its return. One line:
+
+ { el_val_t __s = el_seam_run(EL_STR(f), 0, 0); if (__s) return __s; }
+
+work() returns 7; bound to a refusing construct AFTER the build it returns 42.
+So three of the five compile-time kinds are runtime-bindable: entry injection,
+exit injection, and refusal. wraps_body needs invocation control and
+prohibits_outside is compile-time by nature.
+
+104/104 native compiler tests pass.
+```
+
+## Record — `28d19da`
+
+```
+strip the compile-time machinery the seam replaces
+
+PREDICTION: codegen.el drops below 4661, its size before any of these passes.
+RESULT: FALSE. 5157 -> 5096. Still +435 over baseline.
+
+ injects_at_entry collapsed into the seam removed
+ guards_at_entry collapsed into the seam removed
+ injects_at_exit needs the body-helper wrapper STRUCTURAL
+ wraps_body needs the closure + wrapper structural
+ prohibits_outside a #error cannot be emitted at runtime
+
+The wrapper is not a consequence of compile-time resolution. Early returns must
+be routed through something no matter when the target is resolved, so exit
+injection was never going to collapse. I predicted it would because I had
+conflated "resolved late" with "emitted less".
+
+What did collapse is entry injection and refusal -- 61 lines of compiler
+replaced by one refusable indirection, with the capability now bindable after
+the binary exists.
+
+8 tests fail, and they are exactly the 8 controls for compile-time entry
+injection and guards. No unrelated breakage: the controls reported precisely
+what moved. They assert emission of something that now happens at runtime, so
+they need rewriting as integration tests -- which the framework does not
+currently support, because runtime binding needs a built binary and an
+environment, not compile_capture.
+
+Verified after the strip: fixpoint gen2==gen3, observation and refusal both
+work through the seam with the compiler knowing nothing about either.
+```
+
+## Record — `8bbb750`
+
+```
+control the claim that cannot be unit tested
+
+The seam's whole claim is that a construct declared AFTER a binary exists
+applies to that already-built program. compile_capture only sees emitted text,
+so it structurally cannot check this: it needs a built binary, a linked target,
+and an environment. Verified by hand until now, which is the standing problem
+this session has been about.
+
+tests/integration/seam_binding.sh builds a probe from El source containing no
+construct at all, links a target that El never references, and asserts:
+
+ ok unbound program is unaffected
+ ok a construct declared AFTER the build applies
+ ok a construct declared after the build can REFUSE
+ ok an unlinked target is skipped, not fatal
+ ok a binding for a different fn does not fire
+ ok two constructs compose on one crossing
+
+ 6 assertions, 6 passed, 0 failed
+
+The eight controls that failed after the strip were replaced, not repaired.
+They asserted compile-time emission of capability that moved to runtime;
+contorting them would have kept an assertion whose subject no longer exists.
+Three took their place, asserting the emitted shape, and the behaviour they
+used to cover is now the integration harness's job -- which is the honest
+division, since the shape and the behaviour are no longer the same fact.
+
+99/99 native compiler tests pass. Fixpoint holds.
+```
+
+## Record — `24f7fb5`
+
+```
+land the runtime seam: resolve the crossing at execution
+
+Five compile-time passes added 491 lines to the thing that was supposed to stop
+growing. The seam is ~55 lines of C and one line of emission, and it does at
+runtime what three of those five kinds did at compile time -- for programs that
+are already built.
+
+ a construct declared AFTER the binary exists applies to it
+ free when unused: 0.36s vs 0.37s baseline across 267 indirections
+ dlsym was the cost, not the table scan; resolve-once recovered 3.5x
+ refusal works, composition works, unlinked targets are skipped not fatal
+
+injects_at_exit and wraps_body do NOT collapse: early returns must route
+through the body-helper wrapper regardless of when the target is resolved. The
+wrapper is structural, which I had wrong. prohibits_outside cannot move at all
+-- a #error has no runtime.
+
+Controls: 99/99 native compiler tests, plus tests/integration/seam_binding.sh
+(6/6) for the claim compile_capture structurally cannot see.
+```
diff --git a/docs/v1/experiments/cycles/07-invocation-is-not-composable.md b/docs/v1/experiments/cycles/07-invocation-is-not-composable.md
new file mode 100644
index 0000000..49ad88a
--- /dev/null
+++ b/docs/v1/experiments/cycles/07-invocation-is-not-composable.md
@@ -0,0 +1,45 @@
+# invocation is not composable
+
+One `Ishikawa → scientific method → Six Sigma` loop. The record below is the
+commit message as written at the time, before the outcome was known to anyone
+reading this file.
+
+## Record — `bc2f26d`
+
+```
+EXPERIMENT: invocation control resolves at runtime
+
+ISHIKAWA: why did wraps_body need compile-time knowledge? Because the wrapper
+called the target directly. If the wrapper calls through the seam instead, the
+seam can call the body itself, and a construct bound after the build decides
+how and whether to invoke it.
+
+PREDICTIONS AND RESULTS
+ P1 wrap becomes runtime-bindable TRUE body x3 -> 21,
+ never invoked -> 111
+ P2 codegen shrinks TRUE 5042 -> 4977
+ P3 cost 5-10% from an indirect call on every fn TRUE 0.36s -> 0.39s, ~8%
+ P4 zero-param fns break on the empty struct TRUE empty struct is a GNU
+ extension, empty init
+ is C23. Fixed with a
+ char field.
+ P5 fixpoint holds TRUE
+
+PROCESS FAILURE worth recording: my first patch silently did not apply because
+I dropped the assert on the string replacement. The build then failed with
+"undeclared identifier __thunk_noargs", which I nearly attributed to the
+empty-struct prediction. The guard that would have caught it existed and I
+removed it -- the same shape as every other defect found tonight.
+
+Removed: declare_wrap, decorator_wrap, cg_wrap_target, cg_wrap_construct,
+params_to_call_args, and the wraps_body scanner branch.
+
+prohibits_outside is now the ONLY construct kind left at compile time, and it
+cannot move: a #error has no runtime.
+```
+
+## Record — `c04d68f`
+
+```
+land runtime invocation control: only prohibition remains compiled
+```
diff --git a/docs/v1/experiments/cycles/08-the-emitter-adjudicates.md b/docs/v1/experiments/cycles/08-the-emitter-adjudicates.md
new file mode 100644
index 0000000..08edf7c
--- /dev/null
+++ b/docs/v1/experiments/cycles/08-the-emitter-adjudicates.md
@@ -0,0 +1,53 @@
+# the emitter adjudicates
+
+One `Ishikawa → scientific method → Six Sigma` loop. The record below is the
+commit message as written at the time, before the outcome was known to anyone
+reading this file.
+
+## Record — `c741cfe`
+
+```
+EXPERIMENT: prohibition becomes a query over emitted relations
+
+I said prohibition could not move because "a #error has no runtime". That
+conflated two separable things: WHEN a violation is detected (build time --
+correct, and unchanged) and WHERE the rule and the checker live (the compiler
+-- assumed).
+
+A prohibition is a containment relation over the call graph. So codegen now
+records what it saw:
+
+ sneaky calls raw_sql
+ allowed calls raw_sql
+ allowed calls @repository
+ repository calls prohibits:raw_sql
+
+and tools/check/prohibitions.sh decides, at build time, outside the compiler.
+
+PREDICTIONS AND RESULTS
+ P1 codegen can emit the call graph it already walks TRUE
+ P2 the check becomes a query outside the compiler TRUE
+ P3 all prohibition decisions leave codegen TRUE zero #errors now
+ P4 violations still caught at build time TRUE exit=1
+ P5 codegen drops below the 4661 baseline FALSE 4962, +301
+
+P5 is the finding. The TRAVERSAL is irreducible -- you must walk the AST to
+find calls, and those ~120 lines do not move no matter who decides. What is not
+irreducible is the rule (which names) or the decision (#error). Those left. I
+predicted the whole 223 lines would go because I had not separated walking from
+adjudicating.
+
+Still compiled, and measured rather than assumed: the capability-tier system
+(cap_check_call, is_self_formation_call, is_dharma_call, is_llm_call,
+cap_record_violation, emit_cap_violations) is 76 lines of the same shape --
+prohibits_WITHIN rather than prohibits_outside, so the checker needs the
+opposite polarity to absorb it.
+
+98/98 native, 4/4 prohibition_query.sh, 7/7 seam_binding.sh, fixpoint ok.
+```
+
+## Record — `60c07ad`
+
+```
+land prohibition-as-query: the emitter records, it no longer adjudicates
+```
diff --git a/docs/v1/experiments/cycles/09-policy-inside-the-compiler.md b/docs/v1/experiments/cycles/09-policy-inside-the-compiler.md
new file mode 100644
index 0000000..cc121ab
--- /dev/null
+++ b/docs/v1/experiments/cycles/09-policy-inside-the-compiler.md
@@ -0,0 +1,49 @@
+# policy inside the compiler
+
+One `Ishikawa → scientific method → Six Sigma` loop. The record below is the
+commit message as written at the time, before the outcome was known to anyone
+reading this file.
+
+## Record — `c2d9596`
+
+```
+EXPERIMENT: the capability tier becomes shipped policy plus a query
+
+Capability differs from prohibits_outside in one way that matters: a utility
+program cannot be trusted to declare its own restrictions, because it would
+declare none. So the policy comes from OUTSIDE the program -- it ships with the
+language as data, editable without a compiler release.
+
+ tools/check/capabilities.rel 18 names that were string literals in codegen
+ tools/check/capabilities.sh the query that decides
+
+PREDICTIONS AND RESULTS
+ P1 codegen emits kind + call graph, drops the 4 name tests TRUE zero #errors
+ P2 the 18 literals become a data file TRUE
+ P3 the checker catches capability violations TRUE exit=1
+ P4 codegen drops ~76 lines TRUE 4963 -> 4881
+ P5 below the 4661 baseline FALSE ~+230
+
+TWO DEFECTS THE HARNESS FOUND THAT READING WOULD NOT HAVE
+
+1. Calls inside main became invisible. cg_fn returns early for main -- C
+ provides its own -- so hooking the recording there left every call in main
+ unrecorded: a blind spot exactly where a program does its work. The old
+ cap_check_call ran from cg_expr and did see main. Moved the recording to
+ cg_expr.
+
+2. Caller attribution was stale. __cg_current_fn kept whatever cg_fn set last,
+ so a violation in main was reported against the previously emitted function.
+ The test still PASSED, because the violation was detected -- only the name
+ was wrong, and a diagnostic naming the wrong fn is worse than none. Fixed at
+ all three main-emission sites; the first patch missed two because the live
+ path is codegen_streaming.
+
+98/98 native, 7/7 + 4/4 + 5/5 integration, fixpoint ok.
+```
+
+## Record — `29f78f9`
+
+```
+land capability-as-policy: eighteen literals become a data file
+```
diff --git a/docs/v1/experiments/cycles/10-a-second-copy-of-the-header.md b/docs/v1/experiments/cycles/10-a-second-copy-of-the-header.md
new file mode 100644
index 0000000..037407f
--- /dev/null
+++ b/docs/v1/experiments/cycles/10-a-second-copy-of-the-header.md
@@ -0,0 +1,54 @@
+# a second copy of the header
+
+One `Ishikawa → scientific method → Six Sigma` loop. The record below is the
+commit message as written at the time, before the outcome was known to anyone
+reading this file.
+
+## Record — `9cc6040`
+
+```
+EXPERIMENT: derive arity from the runtime's own declarations
+
+codegen.el carried builtin_arity(): 344 lines, 300 entries, a hand-maintained
+second copy of el_runtime.h.
+
+PREDICTIONS AND RESULTS
+ P1 the table duplicates the header TRUE 243 shared names
+ P2 they have already drifted FALSE ZERO drift. The
+ duplicate had been
+ maintained correctly.
+ P3 codegen can emit call-arity relations TRUE
+ P4 the check becomes a query against the header TRUE
+ P5 codegen drops to roughly baseline TRUE 4903 -> 4512,
+ 149 BELOW the 4661
+ it started at
+
+P2 being false is the better result: the table was not WRONG, it was
+INCOMPLETE. 110 functions the runtime declares had no entry, so calling them
+with the wrong argument count produced no El-level diagnostic at all. Measured:
+the old compiler reports 0 arity errors for __http_do_map_to_file(1); the query
+reports "takes 5 arguments, called with 1".
+
+Deriving from the header fixes coverage AND makes drift impossible by
+construction. 503 signatures, versus 300 entries maintained by hand.
+
+THREE DEFECTS IN MY OWN CHECKER, each found by running it rather than reading it
+ 1. El names and C names differ -- `println` is `__println`. 60 of 500 decls
+ carry the prefix and codegen owns the mapping; the old table carried both
+ keys. One rule covers all 60.
+ 2. Multi-line declarations parsed as zero params, so the checker reported
+ "takes 0" for a function taking 5. A diagnostic with the wrong number in it
+ is worse than none -- the same shape as the stale caller attribution in the
+ previous pass.
+ 3. Fixing (2) by joining lines dropped 500 signatures to 334, because a
+ declaration preceded by a comment no longer started its record. Comments
+ are stripped first now.
+
+98/98 native, 5/5 arity_query.sh, fixpoint ok.
+```
+
+## Record — `d9e301b`
+
+```
+land arity-from-header: the runtime declares its own surface
+```
diff --git a/docs/v1/experiments/cycles/11-one-type-erases-the-return.md b/docs/v1/experiments/cycles/11-one-type-erases-the-return.md
new file mode 100644
index 0000000..02ae35b
--- /dev/null
+++ b/docs/v1/experiments/cycles/11-one-type-erases-the-return.md
@@ -0,0 +1,52 @@
+# one type erases the return
+
+One `Ishikawa → scientific method → Six Sigma` loop. The record below is the
+commit message as written at the time, before the outcome was known to anyone
+reading this file.
+
+## Record — `d2d89fc`
+
+```
+EXPERIMENT: temporal types as data — and the pass that GREW the compiler
+
+This block is structurally unlike the previous four. It does not only
+adjudicate, it DISPATCHES: Instant + Duration must become el_instant_add_dur,
+LocalDate + Duration must become el_local_date_add_dur. The emitted C depends on
+the type answer, so it cannot move to a post-hoc query. Selecting which call to
+emit is an emitter's actual job.
+
+PREDICTIONS AND RESULTS
+ P1 the block conflates dispatch with adjudication TRUE
+ P2 adjudication can move, dispatch cannot TRUE
+ P3 this pass shrinks codegen far less than the last TRUE, and worse:
+ 4513 -> 4537, it GREW
+ by 24 lines
+ P4 the rules are affine algebra, closed by construction TRUE
+ P5 no type propagation -- name tracking plus a
+ hardcoded list of which builtins return which type TRUE, 19 names
+
+P3 is the honest result and it is not spun: moving 19 names into a data file
+cost more lines than it saved, because a generic loader is larger than the
+enumeration it replaces. The win is not line count. It is that adding a 20th
+temporal builtin is now a one-line edit to signatures.rel instead of a compiler
+change, and that the data is inspectable.
+
+WHY THE HEADER CANNOT SUPPLY THIS, unlike arity: el_runtime.h declares every
+builtin as returning el_val_t, because El has ONE type. That single type is why
+the whole seam is cheap and it is exactly why the C boundary cannot say that
+now() returns an Instant while unix_seconds() returns an Int. The El-level type
+is real and the boundary erases it.
+
+INCOMPLETE, and stated rather than hidden: P2 said adjudication could move to a
+query. It has NOT. Violations still emit TIME_TYPE_ERROR inline from the
+emitter. Only the type DATA moved. Moving the adjudication needs the operand
+types recorded as relations, which is a further pass.
+
+98/98 native, 4/4 temporal_signatures.sh, fixpoint ok.
+```
+
+## Record — `e01e079`
+
+```
+land temporal signatures as data: the type table leaves, the dispatch stays
+```
diff --git a/docs/v1/experiments/cycles/12-judgment-lives-with-knowledge.md b/docs/v1/experiments/cycles/12-judgment-lives-with-knowledge.md
new file mode 100644
index 0000000..409dc16
--- /dev/null
+++ b/docs/v1/experiments/cycles/12-judgment-lives-with-knowledge.md
@@ -0,0 +1,47 @@
+# judgment lives with knowledge
+
+One `Ishikawa → scientific method → Six Sigma` loop. The record below is the
+commit message as written at the time, before the outcome was known to anyone
+reading this file.
+
+## Record — `e8e25a0`
+
+```
+EXPERIMENT: temporal adjudication moves out; the placeholder stays
+
+The previous pass moved the type DATA and left the judgment inline, which I
+stated rather than hid. This finishes it.
+
+PREDICTIONS AND RESULTS
+ P1 codegen can emit operand-type relations TRUE
+ "main calls temporal:instant_plus_instant"
+ P2 the affine rules are a small closed set as data TRUE 6 rules
+ P3 violations still caught at build time TRUE exit=1
+ P4 the reporter leaves codegen TRUE 4538 -> 4507
+ P5 the TIME_TYPE_ERROR placeholder must STAY TRUE
+
+P5 is the boundary of this whole approach. The emitter has to emit SOMETHING
+for an illegal expression -- it cannot emit nothing and it cannot decide what
+the program meant. So the placeholder is irreducible in the same way the AST
+traversal was: what moved is the judgment and the wording, not the fact that
+something must be written.
+
+The rules are affine algebra and the set is closed because there are only two
+kinds of thing. An Instant is a POINT, a Duration is a DISPLACEMENT: add a
+displacement to a point, subtract two points for a displacement, combine
+displacements. Nothing else is meaningful, which is why the enumeration in
+temporal.rel cannot grow the way an allowlist does.
+
+A defect in my own checker, found by running it: the .rel file uses aligned
+columns and my awk assumed a single space, so the message came out with the
+rule key still prefixed. Same class as the multi-line header parse in the arity
+pass -- formatting assumptions that only fail when you look at the output.
+
+98/98 native, 6/6 temporal_query.sh, fixpoint ok.
+```
+
+## Record — `50425f3`
+
+```
+land temporal adjudication as a query: the emitter records, the rules are data
+```
diff --git a/docs/v1/experiments/cycles/13-thirty-five-return-types.md b/docs/v1/experiments/cycles/13-thirty-five-return-types.md
new file mode 100644
index 0000000..3b2669f
--- /dev/null
+++ b/docs/v1/experiments/cycles/13-thirty-five-return-types.md
@@ -0,0 +1,49 @@
+# thirty five return types
+
+One `Ishikawa → scientific method → Six Sigma` loop. The record below is the
+commit message as written at the time, before the outcome was known to anyone
+reading this file.
+
+## Record — `cbef1c1`
+
+```
+EXPERIMENT: Int return types as data — and the bug that fell out
+
+PREDICTIONS AND RESULTS
+ P1 is_int_call's 35 hardcoded names move to data TRUE
+ P2 is_int_name stays -- it is annotation propagation TRUE
+ P3 the dispatch stays -- it is emission TRUE
+ P4 codegen shrinks ~40 lines TRUE 4507 -> 4469
+ P5 the design doc's characterisation is WRONG TRUE
+ P6 the moved data also fixes the bug it exposed TRUE
+
+P5 CORRECTS THE RECORD. el-language-design.md and geometry-vs-code.md both cite
+"== lowering to str_eq unless both operand names are in a hardcoded int-name
+set -- a literal list of variable names treated as integers" as the paradigm
+defect. It is not one. __int_names is populated from TYPE ANNOTATIONS
+(param["type"] == "Int"), which is primitive but legitimate type propagation.
+The actual defect was is_int_call: 35 hardcoded builtin return types, the same
+shape as the temporal 19.
+
+P6 IS A LIVE CORRECTNESS BUG, PRE-EXISTING, NOW FIXED
+
+ let a = str_len("hello") // no annotation
+ let b = str_len("hi")
+ let c = a + b // -> el_str_concat(a, b) on two integers
+
+Verified identical on the pre-change compiler, so not a regression. It compiled
+clean, ran, and printed NOTHING where it should print 7. No error at any layer.
+
+The repair is three lines: an unannotated let takes its type from what the
+initialiser returns. The return types were already required for dispatch and
+were simply never consulted at the binding site. Moving them into data is what
+made the gap visible -- reading the code for eight hours did not.
+
+98/98 native + 2 new, 31/31 integration, fixpoint ok.
+```
+
+## Record — `505e5e7`
+
+```
+land int signatures, and repair a silent miscompilation they exposed
+```
diff --git a/docs/v1/experiments/cycles/14-keywords-that-reserve-nothing.md b/docs/v1/experiments/cycles/14-keywords-that-reserve-nothing.md
new file mode 100644
index 0000000..3d06af4
--- /dev/null
+++ b/docs/v1/experiments/cycles/14-keywords-that-reserve-nothing.md
@@ -0,0 +1,57 @@
+# keywords that reserve nothing
+
+One `Ishikawa → scientific method → Six Sigma` loop. The record below is the
+commit message as written at the time, before the outcome was known to anyone
+reading this file.
+
+## Record — `0143cc4`
+
+```
+ANSWER: is a grammar a convention, or a region?
+
+Both, at different layers -- and it is the same split as serialization: the
+convention is the BASIS, never the ACT.
+
+ lexeme -> token `fn` means function-start because someone said so CONVENTION
+ shape recognition given tokens, which construct is this REGION
+ source -> structure parsing is transduction onto that basis GEOMETRY
+ byte traversal something must read them in order IRREDUCIBLE
+
+Three things push the ACT toward region rather than convention: ambiguity
+(a * b needs context; a grammar resolves it with the lexer hack, a region by
+neighbourhood), error recovery (nearest-region is free), and precedence, which
+is ordering along an axis with a conventional parameter.
+
+AND THE SHOULD GATE SAYS NO TO THE OBVIOUS MOVE
+
+Every other table this session moved to data. This one stays code. The keyword
+set is CLOSED by the language definition -- it does not leak the way an
+allowlist does -- and the lexer runs before the program is understood, so a
+program can never declare its own keywords. Externalising it costs file I/O on
+every compile and buys nothing. Same verdict as is_digit in ASCII.
+
+WHAT WAS ACTUALLY WRONG: five of 46 keywords were consumed by no parser or
+codegen path. sealed, activate, seed, protocol, impl. Each stole an identifier
+from users for nothing.
+
+SECOND SILENT MISCOMPILATION OF THE DAY. Using one did not fail to parse:
+
+ let seed = 42
+ let impl = seed + 1
+
+compiled CLEAN -- zero cc errors -- and printed 0 instead of 44. No diagnostic
+at any layer. Fixed by removing the five.
+
+A DEFECT IN MY OWN MEASUREMENT, caught before it did damage: my first pass
+checked only parser.el and reported `test` as inert too. codegen consumes it at
+4135 for --test mode, and the tree has 408 uses. Removing it would have broken
+every test in the suite. The measurement was re-run across all four consumers.
+
+100/100 native + 2 new, 31/31 integration, fixpoint ok.
+```
+
+## Record — `067dd40`
+
+```
+answer the parsing question: a grammar is a basis, and five keywords reserved nothing
+```
diff --git a/docs/v1/experiments/cycles/15-no-namespacing-at-all.md b/docs/v1/experiments/cycles/15-no-namespacing-at-all.md
new file mode 100644
index 0000000..0923142
--- /dev/null
+++ b/docs/v1/experiments/cycles/15-no-namespacing-at-all.md
@@ -0,0 +1,53 @@
+# no namespacing at all
+
+One `Ishikawa → scientific method → Six Sigma` loop. The record below is the
+commit message as written at the time, before the outcome was known to anyone
+reading this file.
+
+## Record — `79f6cb7`
+
+```
+ANSWER: if the partition is a neighbourhood, does linking survive?
+
+The question is premature, and measuring says why. El's partition is a
+FILESYSTEM PATH, not a neighbourhood, and there is no namespacing at all.
+
+MEASURED
+ import is textual inlining (resolve_imports), guarded against double
+ inclusion by a __elc_imp__: