iter form-a-scalar-param-mode-carveout: scalar-param mode carve-out (docs-honesty tidy)

Resolves the M1-fieldtest [friction] + [spec_gap]#1 shared root:
form_a.md stated an unconditional 'every (fn ...) param needs an
(own/borrow) mode' rule in 4 places, contradicting shipped checker
behaviour (scalars take and require bare (con Int); a mode on a
scalar trips body-pointing use-after-consume/consume-while-borrowed).
All 4 sites rewritten symmetric to the existing return-type carve-out;
DESIGN.md gains the bare-scalar export-param rule + a corpus-grounded
Form-A snippet; RED-first anti-regrowth pin via FORM_A_SPEC + norm().
Zero language/checker/codegen change. Plan 4c266a6.
This commit is contained in:
2026-05-18 15:51:34 +02:00
parent 4c266a64b4
commit 7d7f04e1a4
6 changed files with 169 additions and 11 deletions
+17
View File
@@ -2284,6 +2284,23 @@ context parameter that M2 threads through every exported call will
change this C signature; do not treat the M1 signature as frozen.
The value/record layout freeze is M3.
Export parameters are written **bare**: a scalar type carries no
`own`/`borrow` mode (modes apply only to heap-shaped types, which the
scalar-only rule above forbids at an export boundary anyway). The
canonical M1 export shape:
```
(fn step
(export "backtest_step")
(type
(fn-type
(params (con Int) (con Int))
(ret (con Int))))
(params state sample)
(body
(app + state (app * sample sample))))
```
## Data model
The on-disk JSON-AST is what the toolchain hashes, typechecks, and
@@ -0,0 +1,73 @@
# iter form-a-scalar-param-mode-carveout — form_a.md scalar-parameter mode carve-out (docs-honesty tidy)
**Date:** 2026-05-18
**Started from:** 4c266a64b478c6a6106958d577faea5ac30dfa3e
**Status:** DONE
**Tasks completed:** 4 of 4
## Summary
Docs-honesty tidy in the `docs-honesty-lint` class resolving the
fieldtest `[friction]` + `[spec_gap]#1` from
`docs/specs/2026-05-18-fieldtest-embedding-abi-m1.md`:
`crates/ailang-core/specs/form_a.md` and `docs/DESIGN.md` §"Embedding
ABI (M1)" stated an unconditional "every `(fn ...)` parameter MUST
carry a mode" rule that contradicts the shipped checker behaviour
(scalar `Int`/`Bool`/`Unit`/`Str` params take — and require — no
mode, mirroring the existing return-type carve-out). RED-first: the
new pin `form_a_scalar_param_carveout_present_and_old_rule_absent`
was written asserting the post-edit state and failed correctly
(Task 1), then the four form_a.md sites + one DESIGN.md insertion
turned it green (Tasks 23). Zero language/checker/codegen change;
exactly three files touched. Workspace suite 622 → 623 (delta = +1,
the new pin), zero failures.
## Per-task notes
- iter form-a-scalar-param-mode-carveout.1: RED pin —
`use ailang_core::FORM_A_SPEC;` added + new `#[test] fn
form_a_scalar_param_carveout_present_and_old_rule_absent`
(4 ABSENT + 4 PRESENT form_a + 1 PRESENT DESIGN assertions);
failed at the first ABSENT check (site 3) as designed; 4
pre-existing pins unaffected (4 passed / 1 failed).
- iter form-a-scalar-param-mode-carveout.2: form_a.md four sites —
site 1 `### Function` prose, site 2 grammar-block comment, site 3
Schema-invariants item 1 (+ checker-behaviour rationale), site 4
Pitfalls bullet split into heap-shaped-forget + inverse
scalar-over-wrap. Pin then failed only on the DESIGN.md assertion;
spec_drift all 8 green (not a lockstep partner, confirmed).
- iter form-a-scalar-param-mode-carveout.3: DESIGN.md §"Embedding
ABI (M1)" — inserted the bare-scalar export-param rule + the
corpus-grounded `step` Form-A snippet (byte-identical to
`examples/embed_backtest_step.ail` lines 3-11). Pin fully green;
docs_honesty_pin 5 passed / 0 failed.
- iter form-a-scalar-param-mode-carveout.4: regression gate —
`cargo test -p ailang-core` 112 passed / 0 failed; workspace
TOTAL_PASSED=623 (baseline 622, +1 expected) / 0 failed; zero
src-code diff under ailang-check / ailang-codegen /
ailang-core/src / ailang-surface.
## Concerns
(none)
## Known debt
(none — site 5 form_a.md:521-524 already-correct few-shot annotation
left verbatim by design; the carve-out text deliberately reuses its
heap-shaped/primitive vocabulary so the document is internally
consistent end to end.)
## Blocked detail
(none — DONE)
## Files touched
- `crates/ailang-core/specs/form_a.md` (4 carve-out sites)
- `docs/DESIGN.md` (1 §"Embedding ABI (M1)" insertion)
- `crates/ailang-core/tests/docs_honesty_pin.rs` (1 `use` + 1 new `#[test]`)
## Stats
bench/orchestrator-stats/2026-05-18-iter-form-a-scalar-param-mode-carveout.json
+1
View File
@@ -96,3 +96,4 @@
- 2026-05-18 — iter embedding-abi-m1.1 (Embedding-ABI M1, DONE across 2 dispatches + a Boss plan Repair): a compiled AILang scalar `fn` is now callable in-process from a C/Rust host. Five pipeline layers shipped: additive `FnDef.export: Option<String>` (serde `default`+`skip_serializing_if`, modelled on `FnDef.doc`, ~85-site compile-driven thread incl. all in-source `#[cfg(test)]`/drift/hash literals, hash-stable golden `b4662aa70839f60b`); Form-A `(export "<sym>")` modifier (parse/print/`form_a.md` lockstep, byte-identical round-trip); check-side scalar-only+effect-free export gate — two new `CheckError` Error variants `export-non-scalar-signature`/`export-has-effects`, params→ret→effects first-violation-wins, unconditional on `f.export.is_some()` (the feature-acceptance clause-3 discriminator *in code*); codegen `Target::StaticLib` (suppresses `@main`+`MissingEntryMain`, emits one external `@<sym>` forwarder per export to `@ail_<module>_<fn>`); CLI `ail build --emit=staticlib` (`build_staticlib`: `libembed_backtest_step.a` program-only + separate `libailang_rt.a`). Coherent stop PROVEN: `embed_e2e.rs` `cc`-links both archives, `backtest_step(0,3)=9`/`(9,4)=25`, `s==25`, exit 0. Task 1 (fixed-first CLI `MissingEntryMain` baseline pin) green before any staticlib code. Process: Task 4 BLOCKED on the first dispatch — a Boss plan-defect (gate/headline fixtures declared `module ≠ file-stem`; the loader hard-enforces `module==stem` at `workspace.rs:438`, so `ail check` panicked on load before the gate). Boss verified, chose **Option 2 over the orchestrator-recommended Option 1** (keep descriptive `embed_*` filenames, rename fixture *modules* to their stems — the C symbol `backtest_step` stays via `(export …)`, *demonstrating* spec Decision 2's mangling-decoupling rather than violating it; archive→`libembed_backtest_step.a`, internal→`@ail_embed_backtest_step_step`, `host.c` unchanged), Repaired the plan (Design-decision 6 + Task-4 module==stem + Task-5/6/7 dependent strings + folded the no-`*.`-Float-head and `content_hash_fn``def_hash` concerns), committed Tasks 13 as a verified known-good partial (`818177d`), re-dispatched [4,7]. Independent Boss verification post-completion: E2E 1/1, gate 6/6, lowering 2/2, CLI 2/2, full `cargo test --workspace` 0 failures, `roundtrip_cli`+`design_schema_drift`+`spec_drift` green, Invariant 1 holds (no new dep in `ailang-core`/`ailang-codegen`/`runtime`). 3 behaviour-neutral plan pseudo-vs-reality concerns recorded (`emit_ir` routes via `lower_workspace`; dead `Path` import dropped; `##` vs `###` heading). Bench: `compile_check.py`/`cross_lang.py` exit 0; `check.py` exit 1 = tracked-P2 `*.bump_s`/`*.max_us` known-noise (default-exe codegen/runtime byte-unchanged — audit-adjudicated, not a plan gate). spec `docs/specs/2026-05-18-embedding-abi-m1.md` (grounding-check PASS) → plan `docs/plans/embedding-abi-m1.1.md` (Repaired) → commits `818177d` (partial 13) + this completion. Milestone-close `audit` then `fieldtest` (surface-touching — `(export …)` is new authoring surface) are the next pipeline steps. → 2026-05-18-iter-embedding-abi-m1.1.md
- 2026-05-18 — audit embedding-abi-m1 (milestone close, CLEAN — carry-on, NO ratify): scope `064599e..e406d07`. Architect `clean` — Invariant 1 (clean core) holds semantically (no finance/`data-server` vocab/logic/dep in `ailang-core`/`ailang-codegen`/`runtime`; `backtest_step` is an opaque author string through generic `FnDef.export`; AILang↔host meeting point correctly M5-deferred), DESIGN.md honesty satisfied (§"Embedding ABI (M1)" + fn-JSON `"export"` + field rustdoc present-tense; "provisional until M3"/"M2 changes the C sig" correctly *labelled* reserved-unimplemented), lockstep intact (`FnDef`↔fn-JSON↔drift/spec-drift carry the field; `@<sym>` forwarder additive raw-IR *beside* unchanged `@ail_<module>_<fn>` mangling — not through `lower_app`; schema exhaustively-constructed, no `_=>`/`..Default`; in-source `missing_entry_main_is_error` byte-unchanged; `_adapter`/`_clos` untouched), Design-decision 6 confirmed correct (fixture module==stem differs from the spec's *illustrative* `(module backtest)`/`(module bad)` by the loader invariant + spec Decision 2 — the fixtures *demonstrate* the C-symbol decoupling, not violate the spec). One `[low]` — ~85 `export: None,` lines brace-column-indented (rustfmt-divergent) — **orchestrator-adjudicated carry-on on evidence** (not pending, not ignored): `cargo fmt --all --check` shows **1137 fmt-divergent locations tree-wide predating M1**, so rustfmt is demonstrably not a project convention and the architect's "cargo fmt would churn later" premise is moot; selectively aligning 85/1137 would impose an absent standard (noise, not tidy). Bench: `compile_check.py` 0/24 + `cross_lang.py` 0/25 EXIT 0; `check.py` EXIT 1, 7 firings (`*.bump_s` trio +1214%; `explicit_at_rc`/`implicit_at_rc` `p99_9`/`max_us` +36124%). Bencher causal exoneration **decisive**: 21/21 bench binaries `cmp`-byte-identical `064599e``e406d07` (`bench_list_sum_bump` sha `dce18c288904c9f0` both) — M1's `Target::Executable` default leaves every bench fixture's emitted machine code unchanged ⇒ zero causal mechanism; RC-latency tails reproduce *on the byte-identical oracle itself* (3× run-to-run swing, median/p99 steady = `-n 5` single-sample jitter); fresh re-run collapsed 6/7 firings to ok. Both families map (confirmed) onto the two pre-existing roadmap-P2 tracked items (~4th zero-runtime-change milestone they fire+collapse on). **EXONERATED, NO baseline ratify** (Iron Law forbids ratifying noise against a zero-default-path-byte iter; spec forecloses a zero-codegen baseline bump); the two P2 bench-harness-recalibration items stay orchestrator-owned, NOT bundled (bencher recommendation honoured). M1 milestone audit **CLEAN**; `fieldtest` (surface-touching — `(export "<sym>")` is new authoring surface) is the next pipeline step before close → 2026-05-18-audit-embedding-abi-m1.md
- 2026-05-18 — fieldtest embedding-abi-m1 (milestone CLOSE, surface-touch — thesis substantiated, 3 non-blocking follow-ups routed): post-audit downstream-LLM-author field test of M1's `(export "<sym>")` + `ail build --emit=staticlib` + scalar/effect-free gate, DESIGN.md + public examples only (never compiler source). 4 fresh programs: Int fixed-point EMA `(State,Sample)->State` fold + Float leaky integrator (both author→`--emit=staticlib`→C-host link→typed scalar return, `s==67`/`s==1.875`, exit 0 — **clause-1 confirmed first-try**), IntList-param export (correctly rejected `export-non-scalar-signature`), `!IO` scalar-clean export (correctly rejected `export-has-effects`) — **clause-3 confirmed, both diagnostics precise+self-correcting**. **0 bugs**; the M1 capability + gate are sound and the milestone thesis ("an LLM author makes a scalar fold callable") is empirically substantiated. 3 working + 1 friction + 2 spec_gap. The friction + spec_gap#1 share a root: `crates/ailang-core/specs/form_a.md` "Schema invariants" item 1 + Pitfalls state an *unconditional* "every fn param MUST be `(own/borrow T)`" but scalars require **bare** `(con Int)` (`(own (con Int))``use-after-consume`, `(borrow)``consume-while-borrowed`); the doc the LLM reads first misdirects M1's headline task into a body-pointing linearity diagnostic — a **pre-existing form_a.md docs-honesty defect M1 made acute, not introduced** (the positive examples only succeeded because the fieldtester imitated the public `embed_*` corpus over the form_a.md prose rule). spec_gap#2: no public `emit-ir --emit=staticlib` though Decision 5 makes kernel-IR readability load-bearing. Boss independently re-verified via the public CLI (positive E2E builds both archives; both rejections fire the documented codes). Orchestrator routing (own judgement, not the fieldtester's recommended-action column): friction+spec_gap#1 → ONE P1 `[todo]` docs-honesty tidy (form_a.md scalar-param carve-out symmetric with the existing return-type carve-out + DESIGN.md mirror; behaviour settled → tidy, no brainstorm; ahead of M2 because it undermines the clause-1 ergonomics of the surface M2M5 extend); spec_gap#2 → P1 `[feature]` add `--emit=staticlib` to `ail emit-ir` (restore the affordance, NOT narrow DESIGN.md). None are bugs (no debug); none M1-blocking. **embedding-abi-m1 fully ratified and CLOSED**: spec (grounding-check PASS) + plan (Boss-Repaired) + iter (`818177d` partial+Repair / `e406d07` DONE) + audit `425c4eb` CLEAN + fieldtest thesis-substantiated; roadmap P1 `[~]``[x]`, 2 follow-ups added P1-ahead-of-M2. Next *milestone* is Embedding ABI — M2 (no spec) = a `/boss` new-milestone bounce-back (not auto-started); the 2 P1 follow-ups are autonomous-eligible tidies/feature but the session is context-deep — surfaced to the user for the session-shape/next-item call. → 2026-05-18-fieldtest-embedding-abi-m1.md
- 2026-05-18 — iter form-a-scalar-param-mode-carveout (M1-fieldtest follow-up #1, docs-honesty tidy in the docs-honesty-lint class, DONE): resolved the M1 fieldtest `[friction]` + `[spec_gap]#1` shared root. `crates/ailang-core/specs/form_a.md` stated an *unconditional* "every `(fn ...)` param MUST carry an `(own/borrow)` mode" rule in four places (`### Function` prose L97, grammar-block comment L230, "Schema invariants" item 1 L347, Pitfalls bullet L382) that contradicts shipped checker behaviour — scalar `Int`/`Bool`/`Unit`/`Str` params take **and require** bare `(con Int)` (a mode on a scalar makes the linearity pass hold the primitive to linear discipline → body-pointing `use-after-consume`/`consume-while-borrowed`, the exact fieldtest misdirection). All four sites rewritten symmetric to the pre-existing return-type carve-out; site 5 (L521 few-shot annotation) already correct, left verbatim by design (carve-out reuses its heap-shaped/primitive vocabulary for end-to-end internal consistency). `docs/DESIGN.md` §"Embedding ABI (M1)" gained the bare-scalar export-param rule + a corpus-grounded `step` Form-A snippet (byte-identical to `examples/embed_backtest_step.ail:3-11`). RED-first anti-regrowth pin `form_a_scalar_param_carveout_present_and_old_rule_absent` in `crates/ailang-core/tests/docs_honesty_pin.rs` (reads the canonical `ailang_core::FORM_A_SPEC` `include_str!` const — single-source, compile-checked — + `read("docs/DESIGN.md")`; `norm()` whitespace-collapse structurally discharges the grep/contains line-wrap family; 4 ABSENT + 4 PRESENT form_a + 1 PRESENT DESIGN). Boss scope calls: 4 form_a sites not 2 (internal-consistency, on the merits — a tidy whose thesis is "consistent current-state mirror" cannot fix some and leave others contradicting); pin idiom = `FORM_A_SPEC` const (semantic single-source, not effort). `spec_drift.rs` recon-confirmed NOT a lockstep partner (anchors are short keyword tokens; `(own`/`(borrow` survive an additive carve-out). Zero language/checker/codegen change by construction; independent Boss verification: docs_honesty_pin 5/0, spec_drift 8/8, ailang-core 112/0, workspace 622→623 (+1 = the pin), zero `crates/**/src/**` diff. spec carrier `docs/specs/2026-05-18-fieldtest-embedding-abi-m1.md` + roadmap P1 entry → plan `docs/plans/form-a-scalar-param-mode-carveout.md` (`4c266a6`). Pure docs+pin tidy — no audit/fieldtest gate (zero authoring-behaviour change; the pin IS the regression coverage). → 2026-05-18-iter-form-a-scalar-param-mode-carveout.md