convention: counter-prefix file naming across docs/specs/, docs/plans/, design/contracts/, design/models/

All 176 files in the four accumulating directories now use a
zero-padded 4-digit counter prefix that reflects creation order
(`NNNN-slug.md`). The counter is assigned per directory in strict
git-log creation order; ties broken alphabetically by original name.
The old `YYYY-MM-DD-` prefix on docs/specs/ and docs/plans/ files is
dropped — the date is recoverable from git log and the counter
carries the ordering.

A file's counter is stable for the life of the file: never reassigned,
never reused, never compacted. Deleted files retire their counter;
subsequent files do not fill the gap. This is the property that lets
cross-references stay literal — refs use the full filename including
the counter (`design/contracts/0007-honesty-rule.md`) so they grep
cleanly and resolve directly without a glob step.

313 cross-references updated across .md/.rs/.toml/.c/.json files
(test pins, include_str! paths, design-INDEX entries, baseline notes,
runtime C comments, inter-contract markdown links incl. bare basename
and `../models/foo.md` forms).

CLAUDE.md gets a new "File-naming convention" section spelling out
the rule and rationale. skills/brainstorm/SKILL.md and
skills/planner/SKILL.md updated so new spec/plan creation produces
counter-prefixed names from the start.

The full test suite (cargo test --workspace) passes.
This commit is contained in:
2026-05-28 13:31:31 +02:00
parent 7b8596cef0
commit 832375f2ac
227 changed files with 785 additions and 751 deletions
+25 -25
View File
@@ -36,7 +36,7 @@ evolving in lockstep with the language:
- **Surface forms** (`crates/ailang-surface`, `crates/ailang-prose`):
the LLM-facing renderings of a module. `ailang-surface`
is the lossless Form-A printer/parser — the canonical
[authoring surface](contracts/authoring-surface.md), with a
[authoring surface](contracts/0001-authoring-surface.md), with a
round-trip property `parse ∘ print = id` gating every release. `ailang-prose`
is the lossy Form-B projection — human-readable prose for review and
edit, with no parser; re-integration goes through the
@@ -78,34 +78,34 @@ is the default.
## Contracts
| id | consumer / lifetime | ratifying-test | link |
|----|---------------------|----------------|------|
| feature-acceptance | brainstorm-gate / stable | skills/brainstorm/SKILL.md | design/contracts/feature-acceptance.md |
| authoring-surface | LLM author / stable | crates/ailang-surface/tests/round_trip.rs | design/contracts/authoring-surface.md |
| roundtrip-invariant | every release / stable | crates/ailang-surface/tests/round_trip.rs | design/contracts/roundtrip-invariant.md |
| language-constraints | LLM author / stable | crates/ailang-check/src/uniqueness.rs (in-source mod tests) | design/contracts/language-constraints.md |
| memory-model | LLM author / stable | crates/ailang-check/src/uniqueness.rs (in-source mod tests) | design/contracts/memory-model.md |
| data-model | LLM author / stable | crates/ailang-core/tests/design_schema_drift.rs | design/contracts/data-model.md |
| feature-acceptance | brainstorm-gate / stable | skills/brainstorm/SKILL.md | design/contracts/0004-feature-acceptance.md |
| authoring-surface | LLM author / stable | crates/ailang-surface/tests/round_trip.rs | design/contracts/0001-authoring-surface.md |
| roundtrip-invariant | every release / stable | crates/ailang-surface/tests/round_trip.rs | design/contracts/0009-roundtrip-invariant.md |
| language-constraints | LLM author / stable | crates/ailang-check/src/uniqueness.rs (in-source mod tests) | design/contracts/0015-language-constraints.md |
| memory-model | LLM author / stable | crates/ailang-check/src/uniqueness.rs (in-source mod tests) | design/contracts/0008-memory-model.md |
| data-model | LLM author / stable | crates/ailang-core/tests/design_schema_drift.rs | design/contracts/0002-data-model.md |
| mangling | codegen / stable | crates/ail/tests/eq_ord_e2e.rs | crates/ailang-codegen/src/lib.rs //! |
| env-construction | codegen / stable | crates/ailang-check/tests/duplicate_ctor_pin.rs | crates/ailang-codegen/src/lib.rs //! |
| qualified-xref | codegen / stable | crates/ail/tests/codegen_import_map_fallback_pin.rs | crates/ail/src/main.rs //! |
| frozen-value-layout | embedding ABI / one-way-frozen| crates/ailang-codegen/tests/embed_record_layout_pin.rs | design/contracts/frozen-value-layout.md + runtime/rc.c §layout |
| float-semantics | LLM author / stable | crates/ail/tests/eq_float_noinstance.rs | design/contracts/float-semantics.md |
| typeclasses | LLM author / stable | crates/ail/tests/show_no_instance_e2e.rs | design/contracts/typeclasses.md |
| method-dispatch | LLM author / stable | crates/ail/tests/show_no_instance_e2e.rs | design/contracts/method-dispatch.md |
| prelude-classes | LLM author / stable | crates/ail/tests/show_no_instance_e2e.rs | design/contracts/prelude-classes.md |
| str-abi | runtime ABI / stable | crates/ail/tests/e2e.rs (Str path) | design/contracts/str-abi.md + runtime/str.c §heap-Str |
| tail-calls | codegen / stable | crates/ailang-check/src/lib.rs (in-source tail_call_in_non_tail_position_is_rejected) | design/contracts/tail-calls.md |
| honesty-rule | architect+grounding / stable | crates/ailang-core/tests/docs_honesty_pin.rs | design/contracts/honesty-rule.md |
| embedding-abi | embedding host / stable | crates/ailang-codegen/tests/embed_record_layout_pin.rs | design/contracts/embedding-abi.md |
| scope-boundaries | architect+author / stable | crates/ailang-core/tests/effect_doc_honesty_pin.rs | design/contracts/scope-boundaries.md |
| verification | architect / stable | bench/architect_sweeps.sh | design/contracts/verification.md |
| frozen-value-layout | embedding ABI / one-way-frozen| crates/ailang-codegen/tests/embed_record_layout_pin.rs | design/contracts/0006-frozen-value-layout.md + runtime/rc.c §layout |
| float-semantics | LLM author / stable | crates/ail/tests/eq_float_noinstance.rs | design/contracts/0005-float-semantics.md |
| typeclasses | LLM author / stable | crates/ail/tests/show_no_instance_e2e.rs | design/contracts/0013-typeclasses.md |
| method-dispatch | LLM author / stable | crates/ail/tests/show_no_instance_e2e.rs | design/contracts/0016-method-dispatch.md |
| prelude-classes | LLM author / stable | crates/ail/tests/show_no_instance_e2e.rs | design/contracts/0017-prelude-classes.md |
| str-abi | runtime ABI / stable | crates/ail/tests/e2e.rs (Str path) | design/contracts/0011-str-abi.md + runtime/str.c §heap-Str |
| tail-calls | codegen / stable | crates/ailang-check/src/lib.rs (in-source tail_call_in_non_tail_position_is_rejected) | design/contracts/0012-tail-calls.md |
| honesty-rule | architect+grounding / stable | crates/ailang-core/tests/docs_honesty_pin.rs | design/contracts/0007-honesty-rule.md |
| embedding-abi | embedding host / stable | crates/ailang-codegen/tests/embed_record_layout_pin.rs | design/contracts/0003-embedding-abi.md |
| scope-boundaries | architect+author / stable | crates/ailang-core/tests/effect_doc_honesty_pin.rs | design/contracts/0010-scope-boundaries.md |
| verification | architect / stable | bench/architect_sweeps.sh | design/contracts/0014-verification.md |
## Models
| id | consumer / lifetime | link |
|----|---------------------|------|
| rc-uniqueness | onboarding / evolves | design/models/rc-uniqueness.md |
| typeclasses | onboarding / evolves | design/models/typeclasses.md |
| effects | onboarding / evolves | design/models/effects.md |
| authoring-surface | onboarding / evolves | design/models/authoring-surface.md |
| prose-projection | onboarding / evolves | design/models/prose-projection.md |
| pipeline | onboarding / evolves | design/models/pipeline.md |
| kernel-extensions | onboarding / evolves (design accepted 2026-05-28; impl in progress) | design/models/kernel-extensions.md |
| rc-uniqueness | onboarding / evolves | design/models/0004-rc-uniqueness.md |
| typeclasses | onboarding / evolves | design/models/0005-typeclasses.md |
| effects | onboarding / evolves | design/models/0002-effects.md |
| authoring-surface | onboarding / evolves | design/models/0001-authoring-surface.md |
| prose-projection | onboarding / evolves | design/models/0006-prose-projection.md |
| pipeline | onboarding / evolves | design/models/0003-pipeline.md |
| kernel-extensions | onboarding / evolves (design accepted 2026-05-28; impl in progress) | design/models/0007-kernel-extensions.md |
@@ -18,7 +18,7 @@ the same time, leaving only diagnostic helpers (`type_to_string`,
The textual surface is **not** a replacement for the JSON-AST. It is
one projection among potentially many. Concretely:
- The [JSON-AST](data-model.md) keeps its role as the canonical, hashable, content-
- The [JSON-AST](0002-data-model.md) keeps its role as the canonical, hashable, content-
addressed representation of a module. All hashing, content-
addressing, cross-module references, and typecheck/codegen input
flow through the JSON-AST. **No new hashable form is introduced.**
@@ -55,7 +55,7 @@ the AST and remain projection-agnostic.
shape. The full bijection between `.ail.json` and `.ail` (both
directions, BLAKE3-stable hashing, Float-bits-hex encoding,
workspace-CI enforcement points) is anchored as the top-level
[Roundtrip Invariant](roundtrip-invariant.md) — this constraint
[Roundtrip Invariant](0009-roundtrip-invariant.md) — this constraint
records that the surface-design choice must satisfy that
invariant; the invariant itself lives at top level because the
property is load-bearing on the language identity, not on this
@@ -28,10 +28,10 @@ contract is what makes growing the schema cheap.
are real surface forms. Class and type cross-module references
(canonical-form rule, qualified `<module>.<Class>` /
`<module>.<TypeName>`) follow the scoping rule in
[memory model](memory-model.md); the `class`/`instance` schema
[memory model](0008-memory-model.md); the `class`/`instance` schema
narrative — defaults, superclasses, diagnostics — lives in
[typeclasses](typeclasses.md). Exported `fn` defs interact with
[embedding ABI](embedding-abi.md).
[typeclasses](0013-typeclasses.md). Exported `fn` defs interact with
[embedding ABI](0003-embedding-abi.md).
```jsonc
// fn (the unit that gets a content hash)
@@ -65,7 +65,7 @@ narrative — defaults, superclasses, diagnostics — lives in
"drop-iterative": true // opt-in; omitted when false (hash-stable when omitted)
}
// class (typeclass declaration; narrative in contracts/typeclasses.md)
// class (typeclass declaration; narrative in contracts/0013-typeclasses.md)
{ "kind": "class",
"name": "<id>", // class name (e.g. "Show")
"param": "<id>", // single class parameter, kind *
@@ -80,7 +80,7 @@ narrative — defaults, superclasses, diagnostics — lives in
"doc": "<optional string>"
}
// instance (typeclass instance; narrative in contracts/typeclasses.md)
// instance (typeclass instance; narrative in contracts/0013-typeclasses.md)
{ "kind": "instance",
"class": "<id>", // class being instantiated; canonical form (bare for same-module, "<module>.<Class>" for cross-module)
"type": Type, // concrete type expression (never the class param)
@@ -165,7 +165,7 @@ narrative — defaults, superclasses, diagnostics — lives in
// non-`recur` branch. Strictly additive (no `skip_serializing_if`;
// pre-existing fixtures hash bit-identically — none carry the tag).
// No totality claim — an infinite loop is legal. See
// `docs/specs/2026-05-17-loop-recur.md`.
// `docs/specs/0034-loop-recur.md`.
{ "t": "loop",
"binders": [ { "name": "<id>", "type": Type, "init": Term }, ... ],
"body": Term }
@@ -179,7 +179,7 @@ narrative — defaults, superclasses, diagnostics — lives in
```
In the MVP, `do` is only a direct call to a built-in effect op (no
handler); the effect system is described in [effects](../models/effects.md).
handler); the effect system is described in [effects](../models/0002-effects.md).
A `lam` term constructs an anonymous function value; free
variables of its body are captured from the enclosing scope.
@@ -188,7 +188,7 @@ ordinary local scope plus a positional `loop_stack`, and codegen
lowers them as entry-block allocas. Capturing a `loop` binder into a
lambda body is rejected at typecheck via
`CheckError::LoopBinderCapturedByLambda`. See
`docs/specs/2026-05-17-loop-recur.md`.
`docs/specs/0034-loop-recur.md`.
**`Literal`**:
@@ -215,7 +215,7 @@ Patterns are linear: each pattern variable may appear at most once.
The `Type::Con.name` canonical-form rule (bare for same-module /
primitives, qualified `<module>.<TypeName>` for cross-module) lives
in [memory model](memory-model.md); `Type::Fn`'s parameter-mode
in [memory model](0008-memory-model.md); `Type::Fn`'s parameter-mode
metadata is defined and gated there as well.
```jsonc
@@ -228,7 +228,7 @@ metadata is defined and gated there as well.
// in the typechecker (unify, occurs, apply) keeps working.
// `paramModes` omitted when every entry is "implicit"; `retMode`
// omitted when "implicit" (hash-stable when omitted). Full mode
// contract lives in contracts/memory-model.md.
// contract lives in contracts/0008-memory-model.md.
{ "k": "fn",
"params": [Type...],
"paramModes": [ParamMode...],
@@ -239,7 +239,7 @@ metadata is defined and gated there as well.
{ "k": "var", "name": "<id>" }
// Top-level polymorphism only. `constraints` carries class
// constraints (narrative in contracts/typeclasses.md); omitted when
// constraints (narrative in contracts/0013-typeclasses.md); omitted when
// empty (hash-stable when omitted).
{ "k": "forall",
"vars": ["<id>"...],
@@ -248,7 +248,7 @@ metadata is defined and gated there as well.
```
**`ParamMode`** (full contract in
[memory model](memory-model.md)):
[memory model](0008-memory-model.md)):
```
"implicit" — unannotated / back-compat. Treated as `own` by the typechecker.
@@ -260,8 +260,8 @@ metadata is defined and gated there as well.
unannotated fixtures continue to serialize without the mode wrapper and keep their
canonical-JSON hash. The full mode contract (codegen consequences,
the over-strict-mode lint, the `Suppress` mechanism) lives in
[memory model](memory-model.md); the four language-design
[memory model](0008-memory-model.md); the four language-design
preconditions that make RC sound live in
[language constraints](language-constraints.md).
[language constraints](0015-language-constraints.md).
Ratified by: `crates/ailang-core/tests/design_schema_drift.rs`.
@@ -44,12 +44,12 @@ The staticlib swarm artefact is **RC-only**:
stub is leak-only and not swarm-safe; `--alloc=gc` no longer
exists as a CLI value — see the Boehm-retirement iter). The
value/record
layout is **frozen as of M3** (see [Frozen value layout](frozen-value-layout.md)); the
layout is **frozen as of M3** (see [Frozen value layout](0006-frozen-value-layout.md)); the
ctx-threaded C signature is the M2 shape.
Export parameters are written **bare**: a scalar type carries no
`own`/`borrow` mode (a single-constructor record export parameter,
by contrast, carries `own`/`borrow` — see [memory model](memory-model.md)
by contrast, carries `own`/`borrow` — see [memory model](0008-memory-model.md)
for the full ownership contract; the
frozen value layout below specifies how that contract lands at the C ABI). The
canonical M1 export shape:
@@ -30,7 +30,7 @@ A proposed feature ships only if all three hold:
question is whether the construct re-opens a class of mistakes
(iterated-mutable-state reasoning, silently-unbounded recursion,
reference cycles) that a foundational invariant — see
[language constraints](language-constraints.md) — closes. If it does,
[language constraints](0015-language-constraints.md) — closes. If it does,
it is cut even when 1 and 2 hold — *or* it must be reshaped until
the bug class is structurally impossible rather than merely
discouraged. A documentation note is not a reshape; the
@@ -44,7 +44,7 @@ A proposed feature ships only if all three hold:
built without a documented-unenforced precondition*, pass all
three — and the fact that the 2026-05 attempt could not (it
forced a silent-divergence precondition; see
`docs/specs/2026-05-16-iteration-discipline-revert.md`) is itself
`docs/specs/0032-iteration-discipline-revert.md`) is itself
the bug-class-reintroduction discriminator working as intended.
This is the positive complement to the CLAUDE.md rule that
@@ -12,7 +12,7 @@ no `f32` variant. The runtime / codegen contract:
fmul / fdiv double`, `fneg double`. Float comparison goes through
the named prelude fns `float_eq` / `float_ne` / `float_lt` /
`float_le` / `float_gt` / `float_ge` (Float has no Eq/Ord instance
— see [Prelude (built-in) classes](prelude-classes.md)); each fn
— see [Prelude (built-in) classes](0017-prelude-classes.md)); each fn
lowers to a single `fcmp` instruction (`oeq` / `une` / `olt` /
`ole` / `ogt` / `oge` respectively) via the codegen intercept
`try_emit_primitive_instance_body`, with the `alwaysinline`
@@ -73,7 +73,7 @@ no `f32` variant. The runtime / codegen contract:
The same libc-`%g` rendering applies to `show 1.5` / `show nan` /
`show inf` via `instance Show Float` (which calls `float_to_str`
internally — see [Prelude (built-in) classes](prelude-classes.md) for the Show ship).
internally — see [Prelude (built-in) classes](0017-prelude-classes.md) for the Show ship).
The NaN-spelling caveat above is observable via `do print x` for
Float-typed `x`; the rendering is libc-version-dependent and
target-libc-specific. AILang does NOT canonicalise Float textual
@@ -91,15 +91,15 @@ use case appears.
**Form-A serialisation:** Float literals carry the IEEE-754
bit pattern as a 16-character lowercase hex string in the canonical
JSON: `{"kind":"float","bits":"<16-hex>"}` (see
[Data model](data-model.md) for the literal schema). Routing through the
[Data model](0002-data-model.md) for the literal schema). Routing through the
JSON *string* path (not `serde_json::Number`) preserves bit
stability across `serde_json` versions and lets NaN / ±Inf
round-trip through Form-A — JSON numbers cannot represent them.
This bits-hex encoding is what keeps Floats inside the
[Roundtrip Invariant](roundtrip-invariant.md).
[Roundtrip Invariant](0009-roundtrip-invariant.md).
**Pattern matching:** `Pattern::Lit` on `Literal::Float` (see
[Data model](data-model.md) for the pattern schema) is hard-
[Data model](0002-data-model.md) for the pattern schema) is hard-
rejected at typecheck (`CheckError::FloatPatternNotAllowed`). IEEE
semantics make Float patterns semantically dubious — NaN never
matches via `float_eq` (its `fcmp oeq` lowering is `false` for
@@ -109,7 +109,7 @@ Use the explicit comparison fns (`float_lt`, `float_gt`, ...) 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](str-abi.md)
a fresh heap-Str slab at the call site (see [Str ABI](0011-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.
@@ -4,7 +4,7 @@
For a single-constructor `data T` whose `n` fields are each `Int`
or `Float`, a value of `T` crossing the
[embedding C boundary](embedding-abi.md) is a
[embedding C boundary](0003-embedding-abi.md) is a
bare payload pointer `p`:
| bytes | content |
@@ -29,7 +29,7 @@ offsets above. A raw `malloc` is a contract violation:
header at `p - 8` initialised to `1`.
**Ownership follows the declared mode** (the
[Mode metadata is load-bearing for codegen](memory-model.md)
[Mode metadata is load-bearing for codegen](0008-memory-model.md)
contract, as the C ABI): a `(own (con
T))` parameter transfers ownership in — the kernel consumes it
(Iter-B drop-at-return); the host MUST NOT touch or `dec` it after
@@ -3,16 +3,16 @@
The four language-design constraints that make RC sound without a
cycle-collector backstop (strict evaluation, no recursive value
bindings, no shared mutable refs, acyclic ADTs) live in
[language constraints](language-constraints.md); this file covers
[language constraints](0015-language-constraints.md); this file covers
the schema additions, advisory diagnostics, and codegen contract
that build on them.
## Schema additions
**Parameter modes on `Type::Fn`** (see [Data model](data-model.md)
**Parameter modes on `Type::Fn`** (see [Data model](0002-data-model.md)
for the schema-level definition of `Type::Fn`).
The form-A surface (see [authoring surface](authoring-surface.md))
The form-A surface (see [authoring surface](0001-authoring-surface.md))
for fn signatures gains mode wrappers:
```
@@ -22,7 +22,7 @@ for fn signatures gains mode wrappers:
Internally, this is *not* a new `Type` variant. Modes are
metadata on `Type::Fn``paramModes` and `retMode` fields run
parallel to `params` and `ret` (see [Data model](data-model.md) for the JSON
parallel to `params` and `ret` (see [Data model](0002-data-model.md) for the JSON
schema). The substantive reasons for per-position metadata over a
`Type::Borrow` / `Type::Own` variant approach:
@@ -33,7 +33,7 @@ schema). The substantive reasons for per-position metadata over a
`(con List (borrow Int))` — syntactically possible, semantically
meaningless (you cannot separately own/borrow a list element
from the list it lives in). The
[canonical-schema principle](data-model.md) is "schema = data,
[canonical-schema principle](0002-data-model.md) is "schema = data,
schema permits exactly what is meaningful"; per-position
metadata is the option that holds that line.
- **Compositional clarity.** A `Type` value's identity should
@@ -85,7 +85,7 @@ Three schema fields carry class references in this form:
`TypeDef.name`).
Method dispatch is type-driven (see
[Method dispatch](method-dispatch.md)):
[Method dispatch](0016-method-dispatch.md)):
synth resolves a `Term::Var { name: "show" }` by consulting
the workspace's method-to-candidate-class index, filtering by
argument type (concrete) or by declared constraint (rigid-var), and
@@ -150,7 +150,7 @@ until the fixture intentionally adopts the feature.
**`FnDef.suppress`.** The `suppress` field on `FnDef` carries a
list of advisory-diagnostic suppress entries; each entry has a
`code` (the diagnostic being suppressed) and a `because` (a
mandatory non-empty reason). See [Data model](data-model.md) for
mandatory non-empty reason). See [Data model](0002-data-model.md) for
the canonical schema.
Form-A surface: `(suppress (code "...") (because "..."))` clause
@@ -4,8 +4,8 @@
Every well-formed AILang module has a canonical `.ail.json`
representation (the hashable, content-addressed
[JSON-AST](data-model.md)) and a hand-authored `.ail` representation
(Form A, the [authoring projection](authoring-surface.md)). The
[JSON-AST](0002-data-model.md)) and a hand-authored `.ail` representation
(Form A, the [authoring projection](0001-authoring-surface.md)). The
JSON-AST is derived from `.ail` in-process by consumers that need
it; the round-trip is now the *property* of that derivation, not
the byte-level agreement of two on-disk forms.
@@ -47,7 +47,7 @@ approximation. The canonical encoding is
`{"kind":"float","bits":"<16-lowercase-hex>"}` and the surface
emits the same bits-hex string. NaN, ±Inf, signed zero, and subnormals all
round-trip exactly because the JSON-number path is bypassed (see
[Float semantics](float-semantics.md), "Form-A serialisation").
[Float semantics](0005-float-semantics.md), "Form-A serialisation").
Floats are not an exception to the invariant — the bits-hex
encoding is the mechanism that keeps them *inside* it.
@@ -85,7 +85,7 @@ never by relaxing the test.
### Why this is anchored at top level
The invariant is a property of the language identity, not of any
one surface-design choice. The [authoring surface](authoring-surface.md)
one surface-design choice. The [authoring surface](0001-authoring-surface.md)
introduces the `.ail` surface and lists round-trip-as-property as
one of its constraints, but the property is load-bearing for every
downstream concern that treats the two forms as exchangeable:
@@ -5,12 +5,12 @@
Snapshot of the current boundary.
- No effect handlers — only the built-in `IO` op (`io/print_str`);
the effect system is described in [effects](../models/effects.md).
the effect system is described in [effects](../models/0002-effects.md).
`Diverge` is a reserved effect name with no op and no codegen.
- No refinements / SMT escalation.
- No HM inference inside bodies. Top-level def types are explicit;
polymorphism is opt-in via `Type::Forall { vars, body }` (see
[Data model](data-model.md)). Inside a body, lambdas check
[Data model](0002-data-model.md)). Inside 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
@@ -20,11 +20,11 @@ Snapshot of the current boundary.
- No recursive `let` for non-fn values. Plain `let x = … in …` only
sees `x` inside the body, not inside its own RHS — recursive value
bindings would break the
[acyclicity invariant](language-constraints.md). Recursive
[acyclicity invariant](0015-language-constraints.md). Recursive
*fn* bindings are supported via `Term::LetRec` (`{ "t": "letrec",
... }`); the desugar pass lifts most occurrences to a synthetic
top-level fn, with `lift_letrecs` finishing the residue after
typecheck (see [pipeline](../models/pipeline.md)).
typecheck (see [pipeline](../models/0003-pipeline.md)).
- No visibility rules in imports. Every top-level def of an imported module
is reachable; there is no `pub` / `priv`.
@@ -34,7 +34,7 @@ What **is** supported (and used as the smoke test for the pipeline):
- `if`, `let`, function calls, recursion.
- Effects on function signatures, with `do op(args)` for direct effect
ops (`io/print_str`). The polymorphic `print` (see
[prelude classes](prelude-classes.md)) is the canonical output
[prelude classes](0017-prelude-classes.md)) is the canonical output
path for non-Str values.
- **Builtins.** Arithmetic operators (`+`, `-`, `*`, `/`) of type
`forall a. (a, a) -> a` (codegen-restricted to `{Int, Float}`);
@@ -47,7 +47,7 @@ What **is** supported (and used as the smoke test for the pipeline):
`float_to_int_truncate : (Float) -> Int` (saturating, NaN → 0),
`float_to_str : (Float) -> Str`,
`int_to_str : (Int) -> Str` (both allocate a heap-Str slab at
call time and return it with `ret_mode: Own`; see [Str ABI](str-abi.md) for
call time and return it with `ret_mode: Own`; see [Str ABI](0011-str-abi.md) for
the dual heap-/static-Str realisation); inspection
`is_nan : (Float) -> Bool` (LLVM
`fcmp uno`); Float bit-pattern constants `nan : Float`,
@@ -69,7 +69,7 @@ What **is** supported (and used as the smoke test for the pipeline):
ctor; every primitive instance body carries the
`alwaysinline` attribute so the call folds at every use site.
Float has no Eq / Ord instance (see
[Float semantics](float-semantics.md)); explicit comparison
[Float semantics](0005-float-semantics.md)); explicit comparison
is via the named fns `float_eq` / `float_ne` / `float_lt` /
`float_le` / `float_gt` / `float_ge`, each lowering to a
single `fcmp` with the matching predicate. `float_ne` uses
@@ -92,7 +92,7 @@ What **is** supported (and used as the smoke test for the pipeline):
`Pattern::Lit` (top-level or sub-) to a `Term::If` on `eq` (the
class method `prelude.Eq.eq`) before typecheck/codegen — see
[desugar](../../crates/ailang-core/src/desugar.rs) and
[Pipeline](../models/pipeline.md).
[Pipeline](../models/0003-pipeline.md).
- Literal patterns at top level and inside Ctor sub-patterns (via desugar).
`(pat-lit 0)` and `(pat-ctor Cons (pat-lit 0) _)` both
parse and lower; the rewrite is to `Term::If { cond = (eq sv lit) }`,
@@ -100,7 +100,7 @@ What **is** supported (and used as the smoke test for the pipeline):
primitive Eq instance is authorable. The primitive instances
cover `Int` / `Bool` / `Str` / `Unit`; `Float` lit-patterns are
hard-rejected at typecheck (`CheckError::FloatPatternNotAllowed`,
see [float-semantics](float-semantics.md)). `(pat-lit "hi")` over
see [float-semantics](0005-float-semantics.md)). `(pat-lit "hi")` over
a `Str` scrutinee is exercised by `examples/eq_demo.ail.json`.
- **Imports + qualified cross-module references** via dotted names.
Extends to **types and constructors**: a foreign
@@ -109,7 +109,7 @@ What **is** supported (and used as the smoke test for the pipeline):
inside that scrutinee. Std-library demos (`examples/std_*_demo.ail.json`)
exercise this end-to-end.
- **AI-authoring text surface, form (A)** (see
[authoring surface](authoring-surface.md)). The `ailang-surface`
[authoring surface](0001-authoring-surface.md)). The `ailang-surface`
crate parses `.ail` form-A text into a canonical
`ailang-core::ast::Module` and prints any module back as form-A
text. `ail render` and `ail describe` use it as the
@@ -117,7 +117,7 @@ What **is** supported (and used as the smoke test for the pipeline):
identity (text → AST → JSON → AST → text) is gated by
`ailang-surface/tests/round_trip.rs` over every shipped fixture.
- **Memory management via reference counting + uniqueness inference**
(see [RC + uniqueness](../models/rc-uniqueness.md)), with
(see [RC + uniqueness](../models/0004-rc-uniqueness.md)), with
**per-fn arena via stack `alloca` for non-escaping allocations**
layered on top. Every ADT box, lambda env, and closure pair
allocates either via `@ailang_rc_alloc` (escaping; RC-managed
@@ -125,7 +125,7 @@ What **is** supported (and used as the smoke test for the pipeline):
`alloca` (non-escaping; freed at fn return). The decision is
made by an escape-analysis pre-pass over the fn body — see the
"Per-fn arena via stack `alloca`" subsection of
[RC + uniqueness](../models/rc-uniqueness.md). The per-fn-arena
[RC + uniqueness](../models/0004-rc-uniqueness.md). The per-fn-arena
path is exercised end-to-end by
`examples/escape_local_demo.ail.json`.
- **First-class function references.** A top-level fn name (or
@@ -13,14 +13,14 @@ and backed by a `runtime/str.c` C helper.
- `int_to_str : (borrow Int) -> Str` — decimal rendering
of an `Int`. Backs `Show Int` in the
[prelude](prelude-classes.md).
[prelude](0017-prelude-classes.md).
- `bool_to_str : (borrow Bool) -> Str``"true"`/`"false"`.
Backs `Show Bool` in the [prelude](prelude-classes.md).
Backs `Show Bool` in the [prelude](0017-prelude-classes.md).
- `float_to_str : (borrow Float) -> Str`
type-installed; codegen is reserved and not yet shipped.
- `str_clone : (borrow Str) -> Str` — allocates a fresh
heap-Str copy of the input's bytes. Backs `Show Str` in the
[prelude](prelude-classes.md).
[prelude](0017-prelude-classes.md).
- `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
@@ -34,7 +34,7 @@ instance body that wants to combine fragments.
Primitive output for `Str` values goes through `io/print_str`
directly; values of other primitive types route through the
polymorphic `print` helper (see [prelude classes](prelude-classes.md)),
polymorphic `print` helper (see [prelude classes](0017-prelude-classes.md)),
which feeds the heap-Str result of `show x` into `io/print_str`.
Equality on `Str` dispatches via `prelude.Eq.eq` (Str instance,
@@ -70,7 +70,7 @@ implicit trailing newline (Gitea #29; the earlier `@puts` lowering
appended a newline per call).
The heap-Str realisation participates in standard RC (see
[memory model](memory-model.md)): the
[memory model](0008-memory-model.md)): the
`rc_header` slot eight bytes before the `len` field is managed
by `ailang_rc_alloc` / `ailang_rc_inc` / `ailang_rc_dec` exactly
like any other RC-allocated cell. The static-Str realisation has
@@ -13,7 +13,7 @@ Solution: explicit, verified tail calls.
- **AST.** `Term::App { fn, args, tail: bool }` and
`Term::Do { op, args, tail: bool }` (see
[Data model](data-model.md)) gain a `tail` flag,
[Data model](0002-data-model.md)) gain a `tail` flag,
serde-defaulting to `false` so existing fixtures load with
`tail: false` and their hashes stay bit-identical.
- **Typecheck.** A new pass `verify_tail_positions(fn_body)`
@@ -39,7 +39,7 @@ Solution: explicit, verified tail calls.
`tail: true` set on the resulting term. EBNF gains 2 lines;
total production count goes from ~28 to ~30, still inside
the constraint-1 budget (see
[authoring surface](authoring-surface.md)).
[authoring surface](0001-authoring-surface.md)).
**What this does NOT promise.** Per the tail-call survey of existing fixtures,
many existing recursive calls are *not* in tail position
@@ -2,16 +2,16 @@
The schema, registry, and diagnostic contract for the typeclass
layer. The resolution/monomorphisation whitepaper lives at
[models/typeclasses](../models/typeclasses.md); the call-site
lookup rule at [method dispatch](method-dispatch.md); the built-in
classes the prelude ships at [prelude classes](prelude-classes.md).
[models/typeclasses](../models/0005-typeclasses.md); the call-site
lookup rule at [method dispatch](0016-method-dispatch.md); the built-in
classes the prelude ships at [prelude classes](0017-prelude-classes.md).
## Form-A schema (the JSON authoring surface)
Three additive schema extensions; no existing module becomes invalid.
The canonical schema for `ClassDef`, `InstanceDef`, and the
`FnDef.type` constraints field lives in
[Data model](data-model.md); this section is the typeclass-layer
[Data model](0002-data-model.md); this section is the typeclass-layer
narrative.
**`ClassDef`** — top-level definition kind, declares a class:
@@ -75,7 +75,7 @@ exactly as for any free function.
## Cross-module references in synthesised bodies
The unified mono pass (see
[models/typeclasses](../models/typeclasses.md) for the
[models/typeclasses](../models/0005-typeclasses.md) for the
resolution/monomorphisation algorithm) synthesises mono symbols for
polymorphic free fns and class-method instances in the symbol's
owner module — e.g. `print__Int` lives in `prelude` (because `print`
@@ -84,7 +84,7 @@ is defined in the prelude), but a user-ADT call site
whose body references `show_user_adt.show__IntBox` (the user
instance lives in the user-defining module per the
orphan-free-coherence rule documented in
[models/typeclasses](../models/typeclasses.md)). The synthesised
[models/typeclasses](../models/0005-typeclasses.md)). The synthesised
body crosses a module boundary the source template did not.
Three invariants make this work:
@@ -218,7 +218,7 @@ wording is fixed; the categories and their triggers are:
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](method-dispatch.md).
[Method dispatch](0016-method-dispatch.md).
**Class-schema diagnostics** (validation of class declarations):
@@ -259,6 +259,6 @@ There is no `AmbiguousInstance` diagnostic at the registry level —
coherence (`DuplicateInstance` at registry build, W2) makes
per-`(class, type)` ambiguity structurally impossible. Cross-class
method ambiguity is a separate concern resolved at the call site
via `AmbiguousMethodResolution` (see [Method dispatch](method-dispatch.md)).
via `AmbiguousMethodResolution` (see [Method dispatch](0016-method-dispatch.md)).
Ratified by: `crates/ail/tests/show_no_instance_e2e.rs`.
@@ -5,7 +5,7 @@
1. **Snapshot tests** for the pretty-printer and IR emit. The diff makes
regressions visible immediately.
2. **Property tests** for the JSON ↔ pretty-print
[roundtrip](roundtrip-invariant.md).
[roundtrip](0009-roundtrip-invariant.md).
3. **End-to-end tests** for `examples/` with expected program output.
4. **Hash stability**: a test ensures the same def always produces the same
hash.
@@ -5,11 +5,11 @@
The four constraints below are necessary preconditions for RC to
be sound and complete *without* a cycle-collector backstop. They
are not new — AILang already satisfies all four — but the
[memory model](memory-model.md) makes them load-bearing rather
[memory model](0008-memory-model.md) makes them load-bearing rather
than incidental:
1. **Strict evaluation.** Every `Term::App` (see
[Data model](data-model.md)) argument is fully
[Data model](0002-data-model.md)) argument is fully
evaluated before the call. No laziness, no thunks. (Already
true.)
2. **No recursive value bindings.** `(let x EXPR ...)` evaluates
@@ -31,11 +31,11 @@ proposal proves the cycle is collectible by an extension (e.g.
linear ownership).
These constraints are the precondition for the
[memory model](memory-model.md): a sound RC implementation without
[memory model](0008-memory-model.md): a sound RC implementation without
a cycle-collector backstop requires the reference graph to be a
DAG, which constraint 4 establishes from 13. They are also the
load-bearing structural reason behind several rejections under the
[feature-acceptance](feature-acceptance.md) bug-class-reintroduction
[feature-acceptance](0004-feature-acceptance.md) bug-class-reintroduction
discriminator (notably iterated-mutable-state reasoning).
Ratified by: `crates/ailang-check/src/uniqueness.rs` (in-source mod tests).
@@ -3,8 +3,8 @@
## Method dispatch
The class-method resolution rule that pairs with the
[typeclasses](typeclasses.md) contract; the `Show`/`Eq`/`Ord` instances
the rule dispatches against live in [prelude classes](prelude-classes.md).
[typeclasses](0013-typeclasses.md) contract; the `Show`/`Eq`/`Ord` instances
the rule dispatches against live in [prelude classes](0017-prelude-classes.md).
Dispatch is two-mode:
@@ -12,7 +12,7 @@ Dispatch is two-mode:
constraint set): the constraint names the class via the qualified
`Constraint.class` field (canonical form: bare for same-module,
`<module>.<Class>` for cross-module; see
[memory model](memory-model.md) for the canonical-form rule).
[memory model](0008-memory-model.md) for the canonical-form rule).
Synth's residual carries the class name directly;
constraint-discharge at fn-body-end matches against the workspace
registry by `(class, type_hash)` key.
@@ -3,7 +3,7 @@
## Prelude (built-in) classes
The prelude ships the `Ordering` ADT, the `Eq` and `Ord`
[classes](typeclasses.md), primitive `Eq Int/Bool/Str/Unit` and
[classes](0013-typeclasses.md), primitive `Eq Int/Bool/Str/Unit` and
`Ord Int/Bool/Str` instances, and the five polymorphic free-fn
helpers `ne`/`lt`/`le`/`gt`/`ge`. The primitive `Eq` / `Ord`
instance bodies are placeholder lambdas in `examples/prelude.ail`;
@@ -35,12 +35,12 @@ maintenance; the symmetric extensions are mechanical when the
first such workload appears.
Float has neither `Eq` nor `Ord` instance per [Float semantics](
float-semantics.md); a polymorphic helper invoked at Float fires
0005-float-semantics.md); a polymorphic helper invoked at Float fires
`NoInstance` at typecheck with a Float-aware diagnostic that names
the explicit `float_eq` / `float_lt` alternatives and
cross-references this section. The lookup machinery that turns a
`show x` call site into the right monomorphic instance is
documented in [method dispatch](method-dispatch.md).
documented in [method dispatch](0016-method-dispatch.md).
Float comparison is a separate surface: the six monomorphic prelude
fns `float_eq` / `float_ne` / `float_lt` / `float_le` / `float_gt`
@@ -57,15 +57,15 @@ 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](float-semantics.md). Each
NaN-spelling caveat in [Float semantics](0005-float-semantics.md). 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`; see [Str ABI](str-abi.md) for the
`str_clone`, `float_to_str`; see [Str ABI](0011-str-abi.md) for the
heap-Str primitives); 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 the Str carve-out in
[Str ABI](str-abi.md)). The let-binder is
[Str ABI](0011-str-abi.md)). The let-binder is
structurally pinned by `crates/ail/tests/print_mono_body_shape.rs`.
Routing through `print` is the path for non-`Str` primitives;
`io/print_str` is the only built-in direct-output effect-op.
@@ -63,7 +63,7 @@ Bool literals (`true`, `false`) and unit (`(lit-unit)`) are
disambiguated by parser context, not by lex. Comparison and
equality are class methods (`eq` / `compare`) and named fns
(`float_eq` / `float_lt` / etc.), not operators — see
[Prelude classes](../contracts/prelude-classes.md).
[Prelude classes](../contracts/0017-prelude-classes.md).
Every AST node form has a unique head keyword (`module`, `data`,
`fn`, `forall`, `fn-type`, `con`, `var`, `app`, `lam`, `match`,
@@ -144,10 +144,10 @@ params, `<>` for type args). Formalising it crisply is harder than
designing a uniform form from scratch.
The chosen form is (A), shipped as the `ailang-surface` crate (see
[authoring surface](../contracts/authoring-surface.md)); the
[authoring surface](../contracts/0001-authoring-surface.md)); the
canonical JSON-AST it round-trips against is documented in
[Data model](../contracts/data-model.md), and the round-trip
property itself in [Roundtrip Invariant](../contracts/roundtrip-invariant.md).
[Data model](../contracts/0002-data-model.md), and the round-trip
property itself in [Roundtrip Invariant](../contracts/0009-roundtrip-invariant.md).
For the prose projection used for human review (Form B), see
[prose projection](prose-projection.md).
[prose projection](0006-prose-projection.md).
@@ -7,15 +7,15 @@ function type: `(Int) -> Int ![IO]`. The effect set is a
flat, unordered, closed set of effect names, unified by set-equality —
there is no effect row variable; a signature lists exactly the effects
its body may perform. The schema-level effect-set field on `Type::Fn`
is documented in [Data model](../contracts/data-model.md); what is
is documented in [Data model](../contracts/0002-data-model.md); what is
currently in scope vs. excluded lives in
[scope boundaries](../contracts/scope-boundaries.md).
[scope boundaries](../contracts/0010-scope-boundaries.md).
In the MVP only the effect `IO` is wired up (its sole op is `io/print_str`).
`Diverge` (for non-termination) is reserved as an effect name but is
unimplemented — no op, no codegen, no checker injection — in the same
sense the refinements layer is reserved as a future extension without
present-day surface (see
[scope boundaries](../contracts/scope-boundaries.md)).
[scope boundaries](../contracts/0010-scope-boundaries.md)).
This is the most important LLM property: when I read a function, I can trust
its signature without reading the body.
@@ -18,7 +18,7 @@
Two allocator backends share the same MIR. `--alloc=rc` is the
canonical backend committed to in the
[memory model](../contracts/memory-model.md) and the CLI default;
[memory model](../contracts/0008-memory-model.md) and the CLI default;
the typechecker enforces `(own)` / `(borrow)` modes, codegen emits
`ailang_rc_inc` / `_dec` calls at the points dictated by linearity,
and `Term::Clone` / `Term::ReuseAs` materialise into actual rc-bumps
@@ -73,8 +73,8 @@ ail run <module> — build + execute (tempdir), passthro
```
The text projections the CLI moves between are documented in
[authoring surface](../contracts/authoring-surface.md) (Form-A,
round-trippable) and [prose projection](prose-projection.md)
[authoring surface](../contracts/0001-authoring-surface.md) (Form-A,
round-trippable) and [prose projection](0006-prose-projection.md)
(Form-B, lossy, no parser); `ail build --emit=staticlib` produces
the layout fixed in [embedding ABI](../contracts/embedding-abi.md)
plus [frozen value layout](../contracts/frozen-value-layout.md).
the layout fixed in [embedding ABI](../contracts/0003-embedding-abi.md)
plus [frozen value layout](../contracts/0006-frozen-value-layout.md).
@@ -73,10 +73,10 @@ RC's costs are bounded and analysable per program point; the
canonical position is "RC + inference" sharpened with the five
LLM-author mechanisms below. A corpus committed to one memory
model is expensive to switch — the commitment lives in the
contracts ([memory-model](../contracts/memory-model.md),
[language-constraints](../contracts/language-constraints.md)).
contracts ([memory-model](../contracts/0008-memory-model.md),
[language-constraints](../contracts/0015-language-constraints.md)).
**Choice.** AILang's canonical [memory model](../contracts/memory-model.md)
**Choice.** AILang's canonical [memory model](../contracts/0008-memory-model.md)
is reference counting with static uniqueness inference **and
explicit LLM-author annotations on fn signatures**, in the lineage
of Lean 4 / Roc / Koka. The RC pipeline tracks the bump-allocator
@@ -110,7 +110,7 @@ baseline records its rc/bump ratio as the `rc_over_bump`
reference value with ±15% tolerance) and are excluded from the
linear/tree 1.3× regression gate; the closure-chain corpus has
its own ±15% band until a slab/pool optimisation ships. The
[memory model](../contracts/memory-model.md)'s RC commitment is
[memory model](../contracts/0008-memory-model.md)'s RC commitment is
unchanged; what is scoped is the *quantitative* regression band,
not the choice of memory model.
@@ -166,7 +166,7 @@ guess.
**(2) Linear-by-default consumption with explicit `(clone X)`**
(the `Term::Clone` schema entry lives in
[Data model](../contracts/data-model.md)).
[Data model](../contracts/0002-data-model.md)).
In bodies, every binder is consumed by exactly one `own`-mode
use. If the LLM writes:
@@ -241,7 +241,7 @@ compiles, but what the compiler tells the author when it doesn't.
## Inference algorithm
Post-typecheck, post-`lift_letrecs` (see [pipeline](pipeline.md)),
Post-typecheck, post-`lift_letrecs` (see [pipeline](0003-pipeline.md)),
pre-codegen pass over the elaborated module. For each `Term` node that produces or binds a
boxed value, the pass computes a uniqueness flag:
@@ -1,17 +1,17 @@
# Typeclasses — resolution and monomorphisation whitepaper
The schema-and-diagnostics contract for typeclasses lives in
[typeclasses](../contracts/typeclasses.md); the call-site lookup
rule in [method dispatch](../contracts/method-dispatch.md); the
[typeclasses](../contracts/0013-typeclasses.md); the call-site lookup
rule in [method dispatch](../contracts/0016-method-dispatch.md); the
built-in classes shipped in the prelude in
[prelude classes](../contracts/prelude-classes.md). This file
[prelude classes](../contracts/0017-prelude-classes.md). This file
covers the design choices and the resolution / monomorphisation
algorithm.
## The design — Haskell-lite, monomorphised, coherent
**The design pass for typeclasses. Codified after the
[Feature-acceptance criterion](../contracts/feature-acceptance.md)
[Feature-acceptance criterion](../contracts/0004-feature-acceptance.md)
was committed; the criterion is the explicit basis for the choices
below.**
@@ -56,7 +56,7 @@ types) is one an LLM author does not unprompted produce.
**Constraint collection (per function body).** During typechecking
of a body, each method call generates a residual constraint of shape
`<Class> <Type>` (the schema for these lives in
[Data model](../contracts/data-model.md)) where `<Type>` may still
[Data model](../contracts/0002-data-model.md)) where `<Type>` may still
contain type variables.
After local typechecking, residual constraints are checked against
the function's declared constraints (modulo α-conversion and modulo
@@ -163,4 +163,4 @@ for instance, because a constraint remains unresolved at the entry
point — is a static error, not a runtime one. This is the
LLVM-friendly form and is consistent with the performance
commitment of the
[RC + uniqueness memory model](../contracts/memory-model.md).
[RC + uniqueness memory model](../contracts/0008-memory-model.md).
@@ -6,7 +6,7 @@ AILang ships a
second textual projection of the AST: `ailang-prose`, a one-way
projection from `Module → human-readable text`. It is **not** an
authoring surface; it is the "display" projection that the
[authoring surface](../contracts/authoring-surface.md)'s
[authoring surface](../contracts/0001-authoring-surface.md)'s
architectural pin explicitly anticipated:
> *"Future projections are explicitly anticipated: a visual /
@@ -36,15 +36,15 @@ human readers. Form (B) inverts the trade-offs:
lambdas, the `tail` flag — all preserved verbatim.
Critically, **form (B) has no parser**. Form (A) is round-trippable
by construction (see [roundtrip invariant](../contracts/roundtrip-invariant.md));
by construction (see [roundtrip invariant](../contracts/0009-roundtrip-invariant.md));
form (B) deliberately is not. Re-integrating prose edits requires an
external LLM mediator, not a compiler pass — the prompt template
`ail merge-prose` composes the six-step cycle.
Form (B) does not weaken any
[authoring surface](../contracts/authoring-surface.md) invariant:
[authoring surface](../contracts/0001-authoring-surface.md) invariant:
- The [JSON-AST](../contracts/data-model.md) remains the only
- The [JSON-AST](../contracts/0002-data-model.md) remains the only
hashable artefact. Prose is not hashed, not content-addressed,
not load-bearing for any cross-module reference.
- Form (A) remains the canonical authoring surface. Foreign LLMs
@@ -58,7 +58,7 @@ Form (B) does not weaken any
The CLI gains `ail prose <m.ail.json>` (the deterministic
projection) and `ail merge-prose <m.ail.json> <edited.prose.txt>`
(the mediator-prompt composer); both are listed under
[pipeline](pipeline.md).
[pipeline](0003-pipeline.md).
## Form-A spec embedding
@@ -8,7 +8,7 @@ whole; the per-milestone specs in `docs/specs/` carry the
implementation-level detail. As each milestone closes, sections of
this whitepaper transition from forward-looking design to
present-state description, per the
[honesty-rule](../contracts/honesty-rule.md).
[honesty-rule](../contracts/0007-honesty-rule.md).
## The problem
@@ -232,7 +232,7 @@ kernel-tier modules (the stub, future Series, future Matrix)
become auto-injected through the same path.
Class-method dispatch (a separate mechanism — see
[method dispatch](../contracts/method-dispatch.md)) is
[method dispatch](../contracts/0016-method-dispatch.md)) is
orthogonal: the dispatch is about how method calls are
resolved; the auto-import is about which names are in scope.
@@ -516,7 +516,7 @@ of off-by-one and warmup-handling bugs (clause 2); the bounded
push-only mutation surface — gated by the `Series` effect — does
not reintroduce the iterated-mutable-state bug class that the
`mut`/`var`/`assign` removal addressed (clause 3 — see
`docs/specs/2026-05-28-kernel-extension-mechanics.md`'s clause-3
`docs/specs/0052-kernel-extension-mechanics.md`'s clause-3
discussion).
The same three-clause check applies to *each future kernel
@@ -578,7 +578,7 @@ here so the spec can quote the position:
- **Classes / method dispatch.** Orthogonal. Type-scoped
namespacing accesses *top-level defs* in a type's home module;
class-method dispatch (see [method dispatch](../contracts/method-dispatch.md))
class-method dispatch (see [method dispatch](../contracts/0016-method-dispatch.md))
resolves *class method calls* by type-driven instance lookup.
Both can coexist on the same type — e.g. `Series` could
someday have a `Show` instance (class method `show` dispatches
@@ -598,7 +598,7 @@ here so the spec can quote the position:
- **Heap-Str ABI.** The Series runtime borrows the heap-Str
RC-pointer-to-opaque-struct pattern (see
[str-abi](../contracts/str-abi.md)). Same pattern: opaque
[str-abi](../contracts/0011-str-abi.md)). Same pattern: opaque
`ptr` from the compiler's view; dedicated `_new`, `_drop`,
and operation symbols in C; RC header in the struct prefix.
@@ -623,14 +623,14 @@ above is the only commitment.
## Cross-references
- **Feature-acceptance gate** applied to each kernel extension:
[feature-acceptance](../contracts/feature-acceptance.md).
[feature-acceptance](../contracts/0004-feature-acceptance.md).
- **Method dispatch** (orthogonal to type-scoped namespacing):
[method-dispatch](../contracts/method-dispatch.md).
[method-dispatch](../contracts/0016-method-dispatch.md).
- **Honesty rule** (this whitepaper's STATUS contract):
[honesty-rule](../contracts/honesty-rule.md).
[honesty-rule](../contracts/0007-honesty-rule.md).
- **Reference design** for Series storage layout:
`/home/brummel/dev/RustAst/src/ast/rtl/series/` (external Rust
project; not in this repo).
- **Per-milestone specs:** `docs/specs/2026-05-28-kernel-extension-mechanics.md`
- **Per-milestone specs:** `docs/specs/0052-kernel-extension-mechanics.md`
(prep milestone); future `docs/specs/YYYY-MM-DD-series.md`
(Series milestone, written after prep closes).