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
@@ -0,0 +1,12 @@
{
"iter_id": "form-a-scalar-param-mode-carveout",
"date": "2026-05-18",
"mode": "standard",
"outcome": "DONE",
"tasks_total": 4,
"tasks_completed": 4,
"reloops_per_task": { "1": 0, "2": 0, "3": 0, "4": 0 },
"review_loops_spec": 0,
"review_loops_quality": 0,
"blocked_reason": null
}
+29 -11
View File
@@ -94,8 +94,10 @@ The `because` MUST be a non-empty justification — empty / whitespace-
only `because` is itself an error (`empty-suppress-reason`).
`type` is a `(fn-type ...)` (possibly wrapped in `(forall ...)` for
polymorphic defs). All parameters of a `(fn ...)` MUST carry a mode
annotation — see *Modes* below.
polymorphic defs). A *heap-shaped* parameter or return of a
`(fn ...)` MUST carry a mode annotation; a scalar (non-heap-shaped:
`(con Int)`, `(con Bool)`, `(con Unit)`, `(con Str)`) takes **no**
mode and is written bare — see *Modes* below.
`params` is a list of bare names that bind the parameters in `body`.
The list length must match the number of params in `type`.
@@ -227,7 +229,7 @@ TYVAR-NAME ; type variable (e.g. `a`, `T`)
annotation:
```
TYPE ; implicit mode (DO NOT USE in new (fn ...) defs)
TYPE ; no mode — REQUIRED for scalars (Int/Bool/Unit/Str), rejected for heap-shaped
(own TYPE) ; caller transfers ownership; callee consumes
(borrow TYPE) ; caller retains ownership; callee must not consume
```
@@ -344,12 +346,20 @@ The parser will accept syntactically valid Form-A that violates these;
the typechecker will not. Producing Form-A that obeys them yields
checked code on the first try.
1. **Mode annotations on every `(fn ...)` parameter.** Every type in
the `(params ...)` clause of a `(fn ...)` definition's
`(fn-type ...)` MUST be wrapped in `(own T)` or `(borrow T)`. The
return type MUST also carry a mode whenever the type is heap-shaped
1. **Mode annotations on every heap-shaped `(fn ...)` parameter.** A
parameter type in the `(params ...)` clause of a `(fn ...)`
definition's `(fn-type ...)`, and the return type, MUST be wrapped
in `(own T)` or `(borrow T)` whenever the type is heap-shaped
(i.e. anything other than `(con Int)`, `(con Bool)`, `(con Unit)`,
`(con Str)`). Implicit mode on a `(fn ...)` def is rejected.
`(con Str)`). A scalar (non-heap-shaped) parameter or return takes
**no** mode: write it bare, e.g. `(con Int)`. Scalars are
primitive value types, not linear resources; putting a mode on a
scalar makes the checker hold the primitive to linear discipline
(single use under `own`, no use while a `borrow` is live), so an
ordinary scalar reused in the body (`sample * sample`) trips a
body-pointing `use-after-consume` / `consume-while-borrowed`
rather than a mode error. Implicit (omitted) mode on a
*heap-shaped* `(fn ...)` parameter or return is rejected.
2. **Constructors via `term-ctor`.** `Cons(1, Nil)` becomes
`(term-ctor List Cons 1 (term-ctor List Nil))`, never
`(app Cons 1 (app Nil))`.
@@ -378,9 +388,17 @@ significantly.
it is interpreted as a type variable. So `(fn-type (params Int) ...)`
parses as "function with one type-variable parameter named Int",
which is almost certainly not what was meant.
- **Forgetting mode annotations.** `(fn-type (params (con List)) ...)`
is accepted by the parser but rejected by the checker. Wrap every
`(fn ...)` parameter in `(own ...)` or `(borrow ...)`.
- **Forgetting mode annotations on a heap-shaped parameter.**
`(fn-type (params (con List)) ...)` is accepted by the parser but
rejected by the checker. Wrap every *heap-shaped* `(fn ...)`
parameter in `(own ...)` or `(borrow ...)`.
- **Putting a mode on a scalar parameter.** The inverse trap:
`(fn-type (params (own (con Int))) ...)` is wrong — scalars
(`Int`/`Bool`/`Unit`/`Str`) take no mode, write them bare
`(con Int)`. A mode on a scalar makes the checker treat the
primitive as a linear resource, so an ordinary reuse trips a
body-pointing `use-after-consume` / `consume-while-borrowed`
instead of pointing at the signature.
- **Using `app` for constructors.** Constructors are NOT first-class
functions. `(app Cons 1 Nil)` is interpreted as "apply variable
`Cons` to ...", which then fails because `Cons` is not a fn.
@@ -9,6 +9,7 @@
//! docs' hard line-wrap. Companion to `effect_doc_honesty_pin.rs` and
//! to Sweep 5 of `bench/architect_sweeps.sh`.
use ailang_core::FORM_A_SPEC;
use std::fs;
fn read(rel: &str) -> String {
@@ -98,3 +99,39 @@ fn prose_roundtrip_md_has_no_wunschdenken() {
assert!(p.contains("the lowest-common-denominator cycle and always works with any client"),
"PROSE_ROUNDTRIP.md must close present-tense on the static-prompt path");
}
#[test]
fn form_a_scalar_param_carveout_present_and_old_rule_absent() {
// form_a.md is read via the canonical include_str! const, not a
// re-derived path: single source of truth, compile-checked, no
// stale-path bug. norm() collapses the docs' hard line-wrap so
// every asserted substring below is the single-spaced form
// (planner Step-5 item 6: line-wrap is structurally discharged,
// the substrings need not be contiguous in the .md source).
let f = norm(FORM_A_SPEC);
let d = norm(&read("docs/DESIGN.md"));
// --- ABSENT: the four old unconditional phrasings must be gone ---
assert!(!f.contains("Mode annotations on every `(fn ...)` parameter."),
"form_a.md item 1 must no longer head with the unconditional 'every (fn ...) parameter' rule (site 3)");
assert!(!f.contains("All parameters of a `(fn ...)` MUST carry a mode annotation"),
"form_a.md ### Function prose must no longer state the unconditional all-params rule (site 1)");
assert!(!f.contains("(DO NOT USE in new (fn ...) defs)"),
"form_a.md grammar block must no longer blanket-forbid the bare (implicit-mode) form (site 2)");
assert!(!f.contains("Wrap every `(fn ...)` parameter in `(own ...)` or `(borrow ...)`."),
"form_a.md Pitfalls bullet must no longer state the unconditional 'wrap every parameter' imperative (site 4)");
// --- PRESENT: the scalar-parameter carve-out, the four sites ---
assert!(f.contains("A scalar (non-heap-shaped) parameter or return takes **no** mode: write it bare, e.g. `(con Int)`."),
"form_a.md item 1 must state the scalar-parameter carve-out symmetrically with the return-type carve-out (site 3)");
assert!(f.contains("a scalar (non-heap-shaped: `(con Int)`, `(con Bool)`, `(con Unit)`, `(con Str)`) takes **no** mode and is written bare"),
"form_a.md ### Function prose must carry the scalar carve-out (site 1)");
assert!(f.contains("; no mode — REQUIRED for scalars (Int/Bool/Unit/Str), rejected for heap-shaped"),
"form_a.md grammar block comment must state the bare form is required for scalars (site 2)");
assert!(f.contains("Putting a mode on a scalar parameter."),
"form_a.md Pitfalls must carry the inverse pitfall — over-wrapping a scalar (site 4)");
// --- PRESENT: the DESIGN.md §\"Embedding ABI (M1)\" mirror ---
assert!(d.contains("Export parameters are written **bare**: a scalar type carries no `own`/`borrow` mode"),
"DESIGN.md §Embedding ABI (M1) must mirror the canonical bare-scalar export-param rule");
}
+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