# iter embedding-abi-m1.1 — Embedding ABI M1: linkable artifact + scalar C entrypoint

**Date:** 2026-05-18
**Started from:** b0bd7efeaff137af22bb12f5e751260804e770c3
(Tasks 1–3 committed at 818177d; Tasks 4–7 re-dispatched from 818177d)
**Status:** DONE — all 7 tasks complete. Tasks 1–3 committed
(818177d); Tasks 4–7 implemented on the corrected plan and sit
unstaged in the working tree. The E2E coherent-stop proof
(`embed_e2e.rs`: `cc` link + run, `s == 25`, exit 0) is GREEN.
**Tasks completed:** 7 of 7 (Tasks 1–3 DONE + committed; Task 4
initially BLOCKED on a Boss plan-defect — fixture module≠stem —
resolved by Boss plan Repair [Design-decision 6] + re-dispatch;
Tasks 4–7 then completed cleanly on the corrected plan)

## Summary

The Embedding-ABI M1 iter is complete across all 5 pipeline
layers. Tasks 1–3 landed in the first dispatch and were committed
(818177d) as a verified known-good subset: the CLI build-path
`MissingEntryMain` baseline pin (Task 1), the additive
`FnDef.export: Option<String>` schema field threaded across the
whole workspace via compile-driven enumeration with a hash-stability
golden pin (Task 2), and the Form-A `(export "<sym>")` surface
modifier with full parse/print/grammar lockstep and a byte-identical
round-trip (Task 3).

Task 4 was BLOCKED in the first dispatch on a Boss plan-defect
(fixtures declared `module ≠ file-stem`; the loader hard-enforces
`module == file-stem` at `workspace.rs:438`). The Boss Repaired the
plan (Design-decision 6: fixture `(module …)` == file stem; the
C symbol stays `backtest_step` via `(export …)`, demonstrating spec
Decision 2's mangling-decoupling) and re-dispatched Tasks 4–7. On
the corrected plan all four landed cleanly: the check-side
scalar-only + effect-free export gate with two new `CheckError`
variants (`export-non-scalar-signature` / `export-has-effects`, the
clause-3 discriminator *in code*, Task 4); the codegen
`Target::StaticLib` mode suppressing `@main`/`MissingEntryMain` and
emitting one external `@<sym>` forwarder per export to
`@ail_<module>_<fn>` (Task 5); the CLI `ail build --emit=staticlib`
two-archive emit (`libembed_backtest_step.a` + `libailang_rt.a`,
Task 6); and the C-host E2E coherent-stop proof — `cc` links both
archives, calls `backtest_step(0,3)=9`, `backtest_step(9,4)=25`,
the `s == 25` assertion holds, exit 0 (Task 7), plus the DESIGN.md
§"Embedding ABI (M1)" current-state subsection. Full workspace
green (0 failures); `roundtrip_cli` green; `compile_check.py` /
`cross_lang.py` exit 0; `check.py` fires only the tracked P2
`*.bump_s` / `*.max_us` known-noise envelope (plan Step 6: audit-
adjudicated, not a plan-level gate — the default-executable
codegen/runtime path is byte-unchanged). No clean-core dependency
added (Invariant 1).

## Per-task notes

- iter embedding-abi-m1.1.1: CLI build-path `MissingEntryMain`
  baseline pin. Created `examples/embed_noentry_baseline.ail`
  (main-free module) + `crates/ail/tests/embed_missing_main_baseline.rs`
  (triple-assertion: non-zero exit + verbatim `has no \`main\` def`
  Display substring [codegen lib.rs:129] + absent binary). GREEN on
  write; not-a-no-op confirmed; `roundtrip_cli` green with the new
  fixture. DONE.
- iter embedding-abi-m1.1.2: additive `FnDef.export` schema field +
  workspace-wide threading. Added the field to `FnDef` after `doc`
  (verbatim plan doc-comment, `#[serde(default,
  skip_serializing_if = "Option::is_none")]`). Compile-driven
  enumeration via `cargo build --workspace --all-targets
  --keep-going` over two dependency-ordered waves threaded
  `export: None` into ~85 `FnDef`/`AstFnDef` literals across 18
  files incl. all named blind-spot sites (codegen lib.rs:3320
  `missing_entry_main_is_error`, design_schema_drift.rs:278,
  spec_drift.rs:256, hash_pin.rs, the in-source `#[cfg(test)]
  mod tests` literals, the `mono.rs` `AstFnDef` alias, the
  `parse.rs`/`print.rs` literals). `cargo build --workspace`
  exits 0 (planner self-review item 7 gate met). DESIGN.md
  fn-JSON `"export"` line + drift test green (Recon Q2 confirmed:
  no nested-struct-key anchor forced). Hash pin green (golden
  `b4662aa70839f60b`). Full workspace test gate: 611/0.
  DONE_WITH_CONCERNS.
- iter embedding-abi-m1.1.3: Form-A `(export "<sym>")` surface
  modifier. `parse_export` helper (faithful `parse_doc` mirror);
  `parse_fn` threaded (`let mut export`, `Some("export")` arm with
  duplicate-clause guard, unknown-attr string updated, `Ok(FnDef{…})`
  uses parsed `export`); `write_fn_def` export emission block (doc →
  export → suppress → type order); form_a.md production +
  `(export STRING)?` + prose paragraph. RED verified
  (`unknown fn attribute \`export\``), then GREEN: round-trip gate
  byte-identical, surface crate 72/0, ailang-core 111/0. DONE.
- iter embedding-abi-m1.1.4 (FIRST dispatch — BLOCKED, audit
  trail): wrote the five plan fixtures + the verbatim gate pin; ran
  it → RED for the WRONG reason (a `ModuleNameMismatch` load
  failure, not the absence of the gate). BLOCKED — see Blocked
  detail + Boss resolution. No gate production code written; the
  WIP fixtures + RED test were discarded by the Boss for the
  re-dispatch.
- iter embedding-abi-m1.1.4 (RE-dispatch — DONE): check-side
  export-signature gate on the corrected plan (Design-decision 6,
  module==stem). Recreated the five fixtures (module == file stem;
  `embed_export_float_ok.ail` uses `(app * a b)` — no `*.` head)
  + the verbatim gate pin `crates/ailang-check/tests/embed_export_gate.rs`.
  RED verified for the RIGHT reason (fixtures load cleanly, 4
  `*_rejected` fail because no gate exists, 2 positives already
  pass). Added two `CheckError` Error variants
  (`ExportNonScalarSignature(String,&'static str,String)` /
  `ExportHasEffects(String,Vec<String>)`) after `ConstHasEffects`,
  their two `code()` arms, and the gate in `check_fn` after the
  param/ret well-formedness checks (gate order params→ret→effects,
  first-violation-wins, unconditional on `f.export.is_some()`).
  GREEN 6/6; combined fixture fails signature-first. Round-trip +
  full workspace green. DONE.
- iter embedding-abi-m1.1.5: codegen `Target::StaticLib`. Added the
  `Target` enum next to `AllocStrategy`; threaded `target` through
  `lower_workspace_inner`; `lower_workspace_with_alloc` /
  `lower_workspace` pin `Target::Executable`; new public
  `lower_workspace_staticlib_with_alloc`. Gated the
  `MissingEntryMain` shape-check + the `@main` trampoline behind
  `Target::Executable`; the `StaticLib` arm emits one external
  `define {cret} @<sym>(...)` per `(export …)` fn forwarding to
  `@ail_<module>_<fn>` via the new `fn_scalar_sig` / `llvm_scalar`
  helpers. RED→GREEN IR-shape pin (no `@main`, has `@backtest_step`,
  forwards to `@ail_embed_backtest_step_step`; exe target still
  `MissingEntryMain`). In-source `missing_entry_main_is_error` pin
  byte-unchanged; codegen crate 0 failures. DONE_WITH_CONCERNS
  (the `emit_ir`-routes-via-`lower_workspace` plan note).
- iter embedding-abi-m1.1.6: CLI `ail build --emit=staticlib`.
  Added the `--emit` clap field (`value_parser = ["exe",
  "staticlib"]`, default `exe`) after `alloc`; branched
  `Cmd::Build` (`emit == "staticlib"` → `build_staticlib`); added
  `build_staticlib` (verbatim `build_to:2239–2294` shared prefix +
  `has_export` guard + `lower_workspace_staticlib_with_alloc` +
  two-archive `clang -c`/`ar rcs` tail) and the new shared `run_cmd`
  helper (confirmed no pre-existing equivalent). RED→GREEN CLI pin
  (`libembed_backtest_step.a` + `libailang_rt.a` produced;
  export-less module rejected with the zero-export error).
  Default-exe regression (floats E2E + Task-1 baseline) green.
  DONE.
- iter embedding-abi-m1.1.7: E2E host harness + DESIGN.md
  §"Embedding ABI (M1)". Wrote `crates/ail/tests/embed/host.c`
  (spec headline host, verbatim) + `crates/ail/tests/embed_e2e.rs`.
  E2E GREEN — `cc` links `libembed_backtest_step.a` +
  `libailang_rt.a`, runs the host, `backtest_step(0,3)=9` /
  `backtest_step(9,4)=25`, `s == 25` assertion holds, exit 0 (the
  milestone coherent stop). Added the DESIGN.md §"Embedding ABI
  (M1)" subsection (verbatim prose, at `##` level for consistency
  with §"Mangling scheme"). Drift tests 8/8 + 8/8; full workspace
  0 failures; bench trio per plan Step 6. DONE_WITH_CONCERNS (the
  dead-`Path`-import correction + the `###`-vs-`##` heading-level
  resolution).

## Concerns

- iter embedding-abi-m1.1.2: the plan's RED-pin symbol
  `ailang_core::hash::content_hash_fn(&FnDef)` does not exist. The
  real entry point is `def_hash(&Def) -> String` (the one
  `crates/ailang-core/tests/hash_pin.rs` uses). Resolved by
  mirroring `hash_pin.rs` exactly (wrap the `FnDef` in
  `Def::Fn(...)`, call `def_hash`) — this is explicitly authorised
  by the plan's own Step-1 NOTE ("the implementer mirrors the exact
  hashing entry point that `hash_pin.rs` uses if the name differs").
  Property and shape unchanged; recorded because the plan's literal
  symbol was wrong (planner pseudo-vs-reality class — the
  `feedback_specs_need_concrete_code` / `feedback_plan_pseudo_vs_reality`
  family).
- iter embedding-abi-m1.1.4: the plan fixture `embed_export_float_ok.ail`
  uses `(app *. a b)`. There is no `*.` Float-multiply head in
  AILang — Float arithmetic uses the same typeclass-generic head as
  Int (`examples/floats.ail` / `examples/mut_sum_floats.ail` both
  use `(app + …)`; codegen lowers to `fadd/fmul double` by type).
  Per the plan NOTE ("if the Float-multiply surface differs, use the
  same head that example uses"), the fixture as written uses
  `(app * a b)`. The fixture's role ("scalar+pure Float export
  passes the gate") is preserved. Recorded; not yet exercised
  (Task 4 BLOCKED before the gate ran).
- INFRA (not iter-authored): `docs/roadmap.md` became dirty in the
  working tree during this run (a ~98-line P2 "DESIGN.md → design/
  role-split" milestone proposal, mtime mid-session). Phase 0's
  `git status --porcelain` was empty (clean start). Nothing in this
  iter writes `roadmap.md`; it was modified by an external/concurrent
  writer. It is path-disjoint from every iter file. Left untouched
  (roadmap.md is Boss-owned per CLAUDE.md; main HEAD sacrosanct).
  The Boss must decide independently whether to keep or discard it;
  it is NOT part of this iter's commit. (RESOLVED at Boss
  re-dispatch: committed separately as Boss roadmap maintenance —
  see Boss resolution. Not part of the Tasks 4–7 working tree.)
- iter embedding-abi-m1.1.5 (plan pseudo-vs-reality, behaviour-
  neutral): plan Task-5 Step 3(b) third bullet says "`emit_ir`
  (line 197): its internal `lower_workspace_inner` call passes
  `Target::Executable`". Reality: `emit_ir` does NOT call
  `lower_workspace_inner` directly — it calls `lower_workspace`
  (`codegen lib.rs:214`), which now passes
  `lower_workspace_inner(ws, AllocStrategy::Gc, Target::Executable)`.
  The plan's *intent* (the in-source `missing_entry_main_is_error`
  pin, which calls `emit_ir`, stays byte-unchanged) is delivered
  transitively and verified GREEN at Task-5 Step 7. Recorded
  because the plan's literal call-chain description was inaccurate
  (planner pseudo-vs-reality family); no behavioural deviation.
- iter embedding-abi-m1.1.7 (plan-verbatim transcription defect,
  corrected): the plan's verbatim Task-7 `embed_e2e.rs` imports
  `std::path::{Path, PathBuf}` but the test body uses only
  `PathBuf` — a transcribed unused import that raises
  `unused_imports` on every build. Implementer-mindset judgement:
  a verbatim copy that introduces a compiler warning is not the
  plan's intent; reduced to `use std::path::PathBuf;`. Test
  behaviour byte-identical (E2E still GREEN). Recorded as a
  deviation from plan-literal text in service of plan intent.
- iter embedding-abi-m1.1.7 (plan code-block vs plan prose, design
  consistency): plan Task-7 Step 4's code block shows the new
  section as `### Embedding ABI (M1)`, but the same step's prose
  instruction says "at the same heading level as §"Mangling
  scheme"" — §"Mangling scheme" is `## ` (level-2) in DESIGN.md.
  Used `## Embedding ABI (M1)` for structural consistency with the
  surrounding §"Mangling scheme" / §"Data model" level-2 sections,
  following the plan's explicit prose directive over its
  illustrative code-block `#`-count. Section content byte-verbatim.
- iter embedding-abi-m1.1 (cross-checks, plan-confirming, no
  deviation): the six named implementer-judgement cross-checks were
  executed. (a) `Type::Con` has exactly `{name: String, args:
  Vec<Type>}` — no mode field; the plan's `matches!` guard is
  correct. (b) `declared_effs` is already `Vec<String>` (per
  `lib.rs` `declared_effs.into_iter().collect()`), so the plan's
  conditional `.into_iter().collect()` conversion did NOT apply —
  `.clone()` matches `ExportHasEffects(_, Vec<String>)` exactly.
  (c) `synth::llvm_type` lowers `Int`→`"i64"`, `Float`→`"double"`
  (`synth.rs:17,21`) — the plan's `llvm_scalar` is an exact mirror
  for the 2-scalar M1 subset; forwarder `call` types match
  `@ail_<module>_<fn>`'s emitted signature (also proven by the
  Task-7 E2E real `cc` link + run). (d) no pre-existing `run_cmd`
  helper in `main.rs` (only inline `Command::new` blocks) — adding
  it was correct per the plan. (e) `ailang_core::Def` is crate-root
  re-exported (`ailang-core lib.rs:76`) — `ailang_core::Def::Fn`
  valid in `main.rs`. (f) `ailang_surface::load_workspace` +
  `ailang_check::check_workspace` + `Diagnostic.code: String` are
  the exact `main.rs:580–581` public symbols. All confirmations of
  plan assumptions, not contradictions.

## Known debt

- None blocking. The M1 ABI is deliberately scalar-only and
  provisional until M3 (no `Bool`/`Str`/ADT/record marshalling, no
  per-thread context lifecycle API); this is reserved-unimplemented
  scope, labelled in DESIGN.md §"Embedding ABI (M1)", not debt.
- `check.py` fires the tracked P2 `*.bump_s` / `*.max_us`
  known-noise envelope (3 regressed / 2 improved beyond tolerance /
  58 stable; e.g. `throughput.bench_list_sum.bump_s` +13.95%,
  `latency.explicit_at_rc.max_us` +60.61%). This iter touched only
  check-side gating, a codegen `Target` enum (every existing path
  pinned `Executable`), and CLI dispatch — none on the runtime hot
  path these metrics measure; the default-executable codegen/runtime
  path is byte-unchanged (Task-5 Step 7/8 + Task-6 Step 6 confirm).
  Per plan Step 6 this is milestone-close-`audit`-adjudicated, NOT
  a plan-level gate. `compile_check.py` / `cross_lang.py` exit 0.
- `bench/architect_sweeps.sh` exits 1 both with and without the
  DESIGN.md edit (pre-existing advisory residue, established
  carry-on adjudicated at milestone-close `audit`); the new
  §"Embedding ABI (M1)" introduced no new Wunschdenken / post-
  mortem phrasing (the "provisional until M3" / "M2 will change"
  clauses are labelled reserved-unimplemented, which the DESIGN.md
  honesty rule explicitly permits).

## Blocked detail

> **RESOLVED (2026-05-18, re-dispatch).** This block was a
> first-dispatch event; it was resolved by the Boss plan Repair
> (see "## Boss resolution" below — Design-decision 6, Option 2,
> module==stem) and the Tasks 4–7 re-dispatch completed all four
> tasks cleanly. The detail below is **preserved verbatim as the
> iter's audit trail** and is no longer an open block. Iter status
> is DONE.

**Task:** 4 (check-side export-signature gate)
**Reason:** spec-ambiguous (a spec-internal contradiction that
rippling-binds Tasks 4–7, surfaced as `unclear` per the per-task
sub-status table → BLOCKED to Boss).

**Worker says:** The plan's Task-4 gate pin
(`crates/ailang-check/tests/embed_export_gate.rs`) is verbatim and
correct Rust; it calls `ailang_surface::load_workspace(&path)` on
each fixture (the exact call shape `crates/ail/src/main.rs:580–581`
uses). `load_workspace` →
`ailang_core::workspace::load_modules_with` →
`module_name_from_path` (`workspace.rs:1566`) hard-enforces
`in-file module name == file stem`, returning
`WorkspaceLoadError::ModuleNameMismatch` (`workspace.rs:182`)
otherwise. The plan's verbatim fixtures deliberately violate this:

  - `examples/embed_backtest_step.ail` has `(module backtest …)` —
    spec-mandated, because the entry module name `backtest` is what
    produces `libbacktest.a` (Task 6), `@ail_backtest_step` and the
    exported C symbol `backtest_step` (Tasks 5/7, the spec headline
    host `host.c` declares `extern int64_t backtest_step(...)`).
    Stem is `embed_backtest_step` ⇒ `ModuleNameMismatch`.
  - `embed_export_str_param_rejected.ail` → module `bad_str_param`;
    `embed_export_io_rejected.ail` → `bad_io`;
    `embed_export_effectful_rejected.ail` → `bad`;
    `embed_export_adt_ret_rejected.ail` → `bad_adt_ret`;
    `embed_export_float_ok.ail` → `embed_float_ok`. All
    stem-mismatched ⇒ every `check_codes()` call panics on load
    before the gate is ever consulted.

These three plan facts cannot all hold simultaneously: (a) fixture
files named `examples/embed_*` (every Task 4/5/6/7 path cites these
exact names), (b) the spec-headline module/C-symbol naming
(`backtest` → `libbacktest.a` / `backtest_step`), (c) a
`load_workspace`-based check-side gate pin. `roundtrip_cli` (Task 1/3)
sidesteps this only because it drives `ail parse` (single-file, no
workspace stem-match). The contradiction is therefore invisible until
the first `load_workspace`-based fixture test — exactly Task 4.

This is not one of the six named implementer-judgement cross-checks
(Float `*.` head, public-symbol confirmation, `Type::Con` field set,
`declared_effs` element type, `run_cmd` pre-existence, `llvm_scalar`
callee-ABI). It is a structural spec/plan defect requiring a
Boss/spec decision among at least:

  1. Rename each fixture **file** to its module stem
     (`examples/backtest.ail`, `examples/bad_str_param.ail`, …) and
     update every Task 4/5/6/7 path citation accordingly (preserves
     the spec headline module/C-symbol naming; ripples ~12 plan
     path references + the Task-6/7 `libbacktest.a` math still holds
     since module stays `backtest`).
  2. Rename each fixture **module** to its filename stem
     (`(module embed_backtest_step …)`, …) and re-derive the spec's
     C-symbol / `lib<entry>.a` story from `embed_backtest_step`
     (changes the spec headline; `libembed_backtest_step.a`,
     `@ail_embed_backtest_step_step` — almost certainly not what the
     spec wants).
  3. Change the Task-4 gate pin to a loader that does not
     stem-enforce (e.g. parse the single file directly), which
     diverges from the plan's verbatim `load_workspace`-based test
     and from how `ail check` actually loads (`main.rs:580`).

Option 1 is the most spec-faithful (it keeps the headline module
`backtest` and only moves files), but it is a cross-task plan edit
(≥12 path citations) and therefore the planner's / Boss's call, not
an inline implementer substitution. I did NOT silently pick one
(reverted my own speculative fixture-module renames so the Boss sees
the contradiction unobscured); no gate production code was written.

**Suggested next step:** Boss adopts Option 1 — re-issue the plan
for Tasks 4–7 with fixture files renamed to their module stems
(`examples/backtest.ail` etc.) and the ~12 path citations updated;
keep module `backtest` so the spec's `libbacktest.a` /
`backtest_step` headline is preserved — then re-dispatch this iter
with `task_range: [4, 7]` (Tasks 1–3 are clean and can be committed
now or carried).

## Boss resolution (2026-05-18)

The BLOCK was verified accurate against source:
`crates/ailang-core/src/workspace.rs:438` (`entry_module.name !=
module_name_from_path(entry_path)` → `ModuleNameMismatch`, no
exception) — module name MUST equal file stem. The defect is a
**Boss plan-write error**: the plan's headline + gate fixtures
declared `(module backtest)` / `(module bad)` / … in `embed_*.ail`
files, malformed under that invariant. The orchestrator correctly
stopped at the Iron Law instead of guessing. Recon maps file
structure, not fixture-content invariants; the Boss source-
verification focused on code-edit anchors, not cross-checking each
fixture's module name against its filename against the loader —
this is the `feedback_specs_need_concrete_code` /
`feedback_plan_pseudo_vs_reality` family (the fixture as written
would not load), now also a fixture-well-formedness lesson.

**Decision: Option 2, overriding the orchestrator-recommended
Option 1.** (Agent recommendations are not directives — own
judgement formed first.) Keep the descriptive `embed_*` filenames;
rename the fixture *modules* to their file stems
(`embed_backtest_step`, `embed_export_str_param_rejected`, …).
Rationale from semantics, not effort:

- The spec's load-bearing contract (Decision 2, spec lines
  52–59 / 167–169) is that the exported C symbol is *author-chosen
  and decoupled from `ail_<module>` mangling* —
  `(export "backtest_step")` is the contract; `host.c` asserts on
  `backtest_step`, which is invariant under module rename. So
  spec-faithfulness does **not** discriminate between the options;
  `libbacktest.a` / `@ail_backtest_step` are *illustrative*
  (build-output filename + internal mangling — the latter is
  exactly what the spec decouples from), not frozen-ABI contracts.
  The orchestrator's "Option 1 most spec-faithful" over-weighted
  the illustrative `libbacktest.a` string.
- `examples/` is a flat shared corpus auto-enrolled in whole-corpus
  tests (`roundtrip_cli`); Option 1's `examples/bad.ail` /
  `backtest.ail` / `bad_io.ail` pollute it with collision-prone,
  non-self-describing names. Option 2 keeps the self-describing
  grouped `embed_*` set — a real maintainability argument in a
  ~100-file flat dir read by future LLM authors.
- Option 2 (module = stem, C symbol stays `backtest_step` via the
  `(export …)` string) is a *concrete demonstration of the spec's
  own Decision-2 decoupling thesis* — the first fixture proving
  module-name ≠ C-symbol — not a violation of it. The spec's
  `(module backtest)` / `(module bad)` blocks are shape-
  illustrative (planner contract: spec owns shape, not bytes).

Consequence: entry module `embed_backtest_step` ⇒ archive
`libembed_backtest_step.a`, internal symbol
`@ail_embed_backtest_step_step`; the external C symbol
`backtest_step` and `host.c` are UNCHANGED (the actual coherent-
stop contract). Architect-at-close note recorded in the plan
(Design-decision 6): a fixture module name differing from the
spec's illustrative block is expected under module==stem +
Decision 2, not drift.

Actions taken: (1) plan `docs/plans/embedding-abi-m1.1.md` fixed —
Design-decision 6 added, Task-4 fixtures → module==stem, Task-5/6/7
dependent strings updated (`libembed_backtest_step.a`,
`@ail_embed_backtest_step_step`; C symbol `@backtest_step`
unchanged), the two iter-1 concerns folded (no `*.` Float head →
`(app * a b)`; `content_hash_fn` → `def_hash(&Def)`); (2) the
already-created headline `examples/embed_backtest_step.ail`
corrected to `(module embed_backtest_step)`; (3) the 5 blocked-
Task-4 fixtures + the RED `embed_export_gate.rs` discarded
(untracked WIP — recreated correctly by the re-dispatch from the
fixed plan); (4) Tasks 1–3 committed as a verified known-good
partial subset (`cargo build --workspace` exit 0, full workspace
green with zero FAILED after the WIP discard, `roundtrip_cli`
green); (5) orchestrator re-dispatched with `task_range: [4, 7]`
on the clean tree + corrected plan. The user's path-disjoint
roadmap addition (P2 "DESIGN.md → `design/` role-split") was
committed separately as Boss roadmap maintenance, not bundled
into this iter.

## Files touched

### Committed at 818177d (Tasks 1–3, first dispatch)

Schema / canonical docs:
- crates/ailang-core/src/ast.rs (FnDef.export field)
- docs/DESIGN.md (fn-JSON "export" line)
- crates/ailang-core/specs/form_a.md (grammar production + prose)

Workspace-wide compile-gate threading (~85 `export: None,` literals):
- crates/ailang-core/src/desugar.rs, pretty.rs
- crates/ailang-core/tests/{design_schema_drift,hash_pin,spec_drift}.rs
- crates/ailang-check/src/{lib,lift,linearity,mono,reuse_shape,suppress_filter,uniqueness}.rs
- crates/ailang-check/tests/workspace.rs
- crates/ailang-codegen/src/lib.rs
- crates/ailang-prose/src/lib.rs

Surface modifier (Task 3):
- crates/ailang-surface/src/parse.rs (parse_export + parse_fn thread)
- crates/ailang-surface/src/print.rs (write_fn_def export block)

New files committed at 818177d:
- examples/embed_noentry_baseline.ail
- examples/embed_backtest_step.ail (corrected to `(module embed_backtest_step)`)
- crates/ail/tests/embed_missing_main_baseline.rs
- crates/ailang-core/tests/embed_export_hash_stable.rs

### Tasks 4–7 re-dispatch working tree (UNCOMMITTED — this hand-off)

Modified (4 source/doc files):
- crates/ailang-check/src/lib.rs (2 CheckError variants + 2 code() arms + check_fn gate; +51)
- crates/ailang-codegen/src/lib.rs (Target enum + threaded target + forwarder loop + fn_scalar_sig/llvm_scalar; +130/-22)
- crates/ail/src/main.rs (--emit clap field + Cmd::Build branch + build_staticlib + run_cmd; +123)
- docs/DESIGN.md (new §"Embedding ABI (M1)" subsection; +24)

New files (untracked, this re-dispatch):
- examples/embed_export_str_param_rejected.ail
- examples/embed_export_adt_ret_rejected.ail
- examples/embed_export_io_rejected.ail
- examples/embed_export_effectful_rejected.ail
- examples/embed_export_float_ok.ail
- crates/ailang-check/tests/embed_export_gate.rs (GREEN 6/6)
- crates/ailang-codegen/tests/embed_staticlib_lowering.rs (GREEN 2/2)
- crates/ail/tests/embed_staticlib_cli.rs (GREEN 2/2)
- crates/ail/tests/embed/host.c
- crates/ail/tests/embed_e2e.rs (GREEN 1/1 — coherent-stop proof)

(`docs/roadmap.md` is NOT in this iter's working tree — the
first-dispatch external modification was Boss-committed separately
as roadmap maintenance, per the Boss resolution.)

## Stats

bench/orchestrator-stats/2026-05-18-iter-embedding-abi-m1.1.json
