iter design-md-rolesplit.tidy (DONE 7/7): resolve milestone-close audit drift

Gate-first TDD: widened design_index_pin.rs clause-3 to a hand-rolled
FAITHFUL Sweep-1 superset (case-sensitive digit-anchored line anchors
+ Sweep-1's ^[^/]* path-excluded date + the audit-named
decision-record phrases, case-insensitive); no regex dep; the blanket
iter-detector rejected as unworkable. Sentence-level strip of
faithfully-migrated history/decision-record prose out of 5 contract
files (the audit's 3 spot-checked + roundtrip-invariant.md +
data-model.md the exhaustive scan found) into the decision-record
journal, each replaced by its present-tense contract equivalent;
float-semantics.md stale 'see Str ABI below' -> str-abi.md;
architect_sweeps honesty sweeps re-scoped to design/contracts only
(models/ is the narrative tier) + ailang-architect.md lockstep.
Invariant: clause-3 GREEN => Sweep-1 clean in contracts/.

The prior dispatch correctly BLOCKED on a real plan defect (iso_date
lacked Sweep-1's path-exclusion, over-firing on legit
docs/specs/2026-.. citations); per the two+-defects-in-one-iteration
discipline the audit Resolution mechanism+scope were corrected
upstream in lockstep (f2cdd67) before re-dispatch, not patched a
third time.

Boss-verified independently: cargo test --workspace 646/0,
design_index_pin 4/4 (clause-3 RED->GREEN), architect_sweeps.sh exit
0 'All five sweeps clean' (acceptance criterion 9 met), acceptance
grep CLEAN, 3 docs_honesty_pin pinned runs each exactly 1 contiguous
match. Zero spec/quality re-loops. FINAL design-md-rolesplit
iteration — milestone functionally complete, audited, drift-resolved,
hard gate enforces the honesty spirit.
This commit is contained in:
2026-05-19 13:44:37 +02:00
parent f2cdd67e69
commit f683f1aec8
13 changed files with 317 additions and 89 deletions
+2 -2
View File
@@ -146,7 +146,7 @@ are real surface forms.
// lowers as in-place rewrite under `--alloc=rc`.
{ "t": "reuse-as", "source": Term, "body": Term }
// loop-recur iter 1: strict iteration block. `binders` declares
// loop: strict iteration block. `binders` declares
// one or more loop parameters (name, type, init), evaluated in
// order on loop entry; `body` is in scope of all binders. The
// loop's value is `body`'s value on the iteration that exits via a
@@ -158,7 +158,7 @@ are real surface forms.
"binders": [ { "name": "<id>", "type": Type, "init": Term }, ... ],
"body": Term }
// loop-recur iter 1: re-enter the lexically innermost enclosing
// recur: re-enter the lexically innermost enclosing
// `loop`, rebinding its binders positionally to `args`. Transfers
// control (no fall-through); valid only in tail position of its
// enclosing loop (enforced at typecheck, `recur-not-in-tail-position`).
+2 -2
View File
@@ -97,8 +97,8 @@ LLM-author wants. Use ordering operators (`<`, `>`, ...) and
`float_to_str` (Float → Str) and `int_to_str` (Int → Str) are
fully wired through checker, codegen, and runtime. Both allocate
a fresh heap-Str slab at the call site (see "Str ABI" below for
the dual realisation) and carry `ret_mode: Own` so the let-binder
a fresh heap-Str slab at the call site (see design/contracts/str-abi.md
for the dual realisation) and carry `ret_mode: Own` so the let-binder
for the call result is RC-tracked and the slab is freed at scope
close.
+1 -2
View File
@@ -69,8 +69,7 @@ inherit the gate automatically):
Pins drift internal tests cannot see.
- `crates/ailang-core/tests/schema_coverage.rs::every_ast_variant_is_observed_in_the_fixture_corpus`
— every variant of `Def`, `Term`, `Pattern`, `Literal`, `Type`,
and `ParamMode` appears in at least one `.ail` fixture (corpus
flipped from `.ail.json` to `.ail` at iter form-a.1). New AST
and `ParamMode` appears in at least one `.ail` fixture. New AST
variants fail compile until the visitor and corpus are extended
in lockstep.
- `crates/ailang-core/tests/carve_out_inventory.rs::examples_ail_json_inventory_matches_carve_outs`
+4 -8
View File
@@ -2,8 +2,7 @@
## What is not (yet) supported
Snapshot of the current boundary. Items move out of this list
as iterations land; the JOURNAL records when.
Snapshot of the current boundary.
- No effect handlers — only the built-in `IO` op (`io/print_str`).
`Diverge` is a reserved effect name with no op and no codegen.
@@ -13,8 +12,7 @@ as iterations land; the JOURNAL records when.
a body, lambdas check monomorphically against their declared type.
- Polymorphic fns must be **directly called** at the use site.
Passing a polymorphic fn as a value (`let f = id in f(42)`) is
not yet supported — it would need one closure-pair global per
instantiation, deferred.
not yet supported.
- No higher-rank polymorphism. Passing a polymorphic fn to another
polymorphic fn (`apply(id, 42)`) is not supported.
- No recursive `let` for non-fn values. Plain `let x = … in …` only
@@ -32,10 +30,8 @@ What **is** supported (and used as the smoke test for the pipeline):
- Int, Bool, Unit, **Str**, **Float** as primitive types.
- `if`, `let`, function calls, recursion.
- Effects on function signatures, with `do op(args)` for direct effect
ops (`io/print_str`). The per-type print ops
`io/print_int`, `io/print_bool`, `io/print_float` were retired in
iter rpe.1; the polymorphic `print` (§"Polymorphic print") is the
canonical output path for non-Str values.
ops (`io/print_str`). The polymorphic `print` (§"Polymorphic print")
is the canonical output path for non-Str values.
- **Builtins.** Arithmetic operators (`+`, `-`, `*`, `/`) of type
`forall a. (a, a) -> a` (codegen-restricted to `{Int, Float}`);
`%` of type `(Int, Int) -> Int` (Int-only — `fmod` semantics for
+12 -16
View File
@@ -11,16 +11,16 @@ and return an owned `Str`. Each is registered as a builtin in
`crates/ailang-codegen/src/lib.rs::lower_app` to a `call ptr @ailang_<name>`,
and backed by a `runtime/str.c` C helper.
- `int_to_str : (borrow Int) -> Str` (iter 24.1) — decimal rendering
- `int_to_str : (borrow Int) -> Str` — decimal rendering
of an `Int`. Backs `Show Int` in the prelude.
- `bool_to_str : (borrow Bool) -> Str` (iter 24.1)`"true"`/`"false"`.
- `bool_to_str : (borrow Bool) -> Str``"true"`/`"false"`.
Backs `Show Bool` in the prelude.
- `float_to_str : (borrow Float) -> Str` (iter 24.1)
- `float_to_str : (borrow Float) -> Str`
type-installed; codegen is reserved and not yet shipped.
- `str_clone : (borrow Str) -> Str` (iter 24.1) — allocates a fresh
- `str_clone : (borrow Str) -> Str` — allocates a fresh
heap-Str copy of the input's bytes. Backs `Show Str` in the prelude.
- `str_concat : (borrow Str, borrow Str) -> Str` (iter str-concat,
2026-05-13) — combines two `Str` values into a single owned `Str`.
- `str_concat : (borrow Str, borrow Str) -> Str` — combines two `Str`
values into a single owned `Str`.
General-purpose; commonly used in Show bodies for labelled output
(`(app str_concat "label=" (app int_to_str x))`).
@@ -35,16 +35,12 @@ directly; values of other primitive types route through the
polymorphic `print` helper (§"Polymorphic print"), which feeds
the heap-Str result of `show x` into `io/print_str`.
`==`, `<`, `<=`, `>`, `>=` REMAIN primitive operators (unchanged
from the original draft). Class methods are accessed by name (`eq x y`,
`lt x y`, …), not via these operators. Routing operators through
classes is deliberately deferred — it would require migrating every
existing fixture and would re-baseline the bench corpus, which is a
new-baseline decision rather than an iter detail.
`==`, `<`, `<=`, `>`, `>=` REMAIN primitive operators. Class methods
are accessed by name (`eq x y`, `lt x y`, …), not via these
operators.
`Num` is NOT in milestone 22. Arithmetic operators (`+`, `-`, `*`,
`/`) stay primitive and per-type. Class-based numeric overloading
would invoke literal-defaulting which axis-7 already excluded.
Arithmetic operators (`+`, `-`, `*`, `/`) stay primitive and
per-type.
**Str ABI.** A `Str` is a pointer to a structure with `i64 len` at
offset 0 followed by `len` bytes plus a trailing `NUL` at offset 8.
@@ -71,7 +67,7 @@ the previous global in `.rodata` and reading them is undefined.
**The static-Str non-RC invariant is enforced at codegen.** Two
mechanisms keep static-Str pointers out of `ailang_rc_dec` along
every shipping execution path: (1) the non-escape lowering pass
(iter 18b) and the move-tracking partial-drop logic (iter 18d.3)
and the move-tracking partial-drop logic
prevent let-binders or pattern-binders for static-Str literals
from reaching scope-close drop emission; (2) the `Type::Con { name: "Str" }`
carve-outs in `field_drop_call` and in the `Term::App` arm of
+22 -42
View File
@@ -74,8 +74,7 @@ user-ADT call site `print (MkIntBox 7)` causes synthesis of
user-defining module per Decision 11 coherence). The synthesised body
crosses a module boundary the source template did not.
Three invariants make this work, all installed in milestone 24's iter
24.3 and worth keeping load-bearing:
Three invariants make this work:
1. **`MonoTarget::FreeFn::type_args` carries canonical types
post-collection.** At every site where `subst.apply(m)` produces a
@@ -179,11 +178,10 @@ wording is fixed; the categories and their triggers are:
- `OverridingNonExistentMethod` — instance specifies a method not in
the class.
(`MethodNameCollision` was retired at iter mq.3. Cross-class method
sharing is now structurally legal; ambiguity surfaces at the call
site via `AmbiguousMethodResolution` or, for class-fn name overlap,
via the `class-method-shadowed-by-fn` warning. See §"Method
dispatch" below.)
Cross-class method sharing is structurally legal; ambiguity surfaces
at the call site via `AmbiguousMethodResolution` or, for class-fn
name overlap, via the `class-method-shadowed-by-fn` warning. See
§"Method dispatch" below.
**Class-schema diagnostics** (validation of class declarations):
@@ -196,8 +194,7 @@ A class param appearing in applied position (e.g., `f a` where `f`
is the class param) is rejected earlier by the canonical-form
validator as `BareCrossModuleTypeRef``f` is a bare,
non-primitive name not declared as a `TypeDef` in the owning
module — so the dedicated `KindMismatch` diagnostic was retired
at iter ctt.3.
module.
**Typecheck diagnostics** (per function body):
@@ -276,40 +273,23 @@ warning surfaces the shadow.
## Prelude (built-in) classes
Milestone 22 ships **no built-in Prelude classes**.
An earlier draft committed to a fixed Prelude (Show / Eq / Ord on the
primitives), but the implementation work to wire `int_to_str` as a
heap-allocated-string runtime primitive proved substantively
separable from the typeclass machinery itself, and the LLM-utility
case for primitive `Show` is weak (LLM-natural form is `int_to_str
x`, not `show x` through a single-instance class). The user-class
end-to-end path is the milestone's typeclass acceptance gate
(the user-defined-class fixture: `class Foo a` + `data IntBox` +
`instance Foo IntBox`); see `docs/specs/2026-05-09-22-typeclasses.md`
"Amendments" for the substantive rationale.
The prelude ships the `Ordering` ADT, the `Eq` and `Ord` classes,
primitive `Eq Int/Bool/Str` and `Ord Int/Bool/Str` instances, and
the five polymorphic free-fn helpers `ne`/`lt`/`le`/`gt`/`ge`. Float
has neither `Eq` nor `Ord` instance per §"Float semantics"; a
polymorphic helper invoked at Float fires `NoInstance` at typecheck
with a Float-aware diagnostic cross-referencing this section.
Milestone 23 amends the above: the prelude now ships the `Ordering`
ADT, the `Eq` and `Ord` classes, primitive `Eq Int/Bool/Str` and
`Ord Int/Bool/Str` instances, and the five polymorphic free-fn helpers
`ne` / `lt` / `le` / `gt` / `ge`. Operator routing through `Eq`/`Ord`
and `print`-rewire remain out of scope per their original substantive
reasons (bench rebaseline, milestone-24 dependency on the post-mq
dispatcher); `Show` itself ships in milestone 24. Float has neither
`Eq` nor `Ord` instance per §"Float semantics"; a polymorphic helper
invoked at Float fires `NoInstance` at typecheck with a Float-aware
diagnostic cross-referencing this section.
Milestone 24 amends the above further: the prelude ships `class Show
a where show : (a borrow) -> Str` and primitive `Show Int`,
`Show Bool`, `Show Str`, `Show Float` instances (iter 24.2). Float
**is** included in Show (unlike Eq/Ord) — IEEE-754 makes structural
equality and total ordering semantically dubious, but textual
representation of a Float is well-defined modulo the NaN-spelling
caveat in §"Float semantics". Each `Show <T>` instance body is a
single-application lambda invoking the corresponding runtime primitive
(`int_to_str`, `bool_to_str`, `str_clone`, `float_to_str`); no codegen
intercept is required. The polymorphic helper `print : forall a. Show
a => a -> () !IO` shipped in iter 24.3 with body
The prelude ships `class Show a where show : (a borrow) -> Str` and
primitive `Show Int`, `Show Bool`, `Show Str`, `Show Float`
instances. Float **is** included in Show (unlike Eq/Ord) — IEEE-754
makes structural equality and total ordering semantically dubious,
but textual representation of a Float is well-defined modulo the
NaN-spelling caveat in §"Float semantics". Each `Show <T>` instance
body is a single-application lambda invoking the corresponding
runtime primitive (`int_to_str`, `bool_to_str`, `str_clone`,
`float_to_str`); no codegen intercept is required. The polymorphic
helper `print : forall a. Show a => a -> () !IO` has body
`\x -> let s = show x in do io/print_str s` (explicit let-binder for
heap-Str RC discipline per eob.1 Str carve-out). The let-binder is
structurally pinned by `crates/ail/tests/print_mono_body_shape.rs`.