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:
@@ -1098,7 +1098,7 @@ fn workspace_error_to_diagnostic(
|
||||
// typeclass-coherence diagnostics emitted from
|
||||
// `workspace::build_registry`. The codes follow the
|
||||
// diagnostic-categories section of
|
||||
// `design/contracts/typeclasses.md`.
|
||||
// `design/contracts/0013-typeclasses.md`.
|
||||
W::OrphanInstance {
|
||||
class,
|
||||
type_repr,
|
||||
@@ -1568,7 +1568,7 @@ fn walk_term(
|
||||
/// Adds the variable bindings introduced by a pattern to `scope` and
|
||||
/// returns the names that were freshly inserted (so the caller can roll
|
||||
/// them back). MVP-restricted: nested ctor patterns only contain
|
||||
/// `Var`/`Wild` — see design/contracts/data-model.md.
|
||||
/// `Var`/`Wild` — see design/contracts/0002-data-model.md.
|
||||
fn bind_pattern(
|
||||
p: &ailang_core::ast::Pattern,
|
||||
scope: &mut std::collections::HashSet<String>,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//! Pin for the iter-24.3 codegen `import_map`-fallback path
|
||||
//! (design/contracts/typeclasses.md, "Cross-module references in
|
||||
//! (design/contracts/0013-typeclasses.md, "Cross-module references in
|
||||
//! synthesised bodies" invariant 2).
|
||||
//!
|
||||
//! Property protected: post-mono synthesised body cross-module
|
||||
|
||||
@@ -2742,7 +2742,7 @@ fn str_clone_drop_balances_rc_stats() {
|
||||
/// input bytes; RC stats account for three heap-Str slabs (one
|
||||
/// from int_to_str, two from str_clone) and three matching frees.
|
||||
/// Pins the "uniform consumer ABI" claim from
|
||||
/// design/contracts/str-abi.md — str_clone only reads len + bytes +
|
||||
/// design/contracts/0011-str-abi.md — str_clone only reads len + bytes +
|
||||
/// NUL, never the rc_header.
|
||||
#[test]
|
||||
fn str_clone_cross_realisation_uniform_abi() {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* Embedding-ABI M3 record round-trip C host (own + borrow via a
|
||||
* compile-time -DBORROW switch). Frozen value layout
|
||||
* (design/contracts/frozen-value-layout.md):
|
||||
* (design/contracts/0006-frozen-value-layout.md):
|
||||
* p - 8 .. p uint64_t refcount header (set to 1 by ailang_rc_alloc)
|
||||
* p + 0 int64_t constructor tag (single ctor → 0)
|
||||
* p + 8 IEEE-754 double field 0 = Float acc
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* only ever push a *scalar* per-tick sample; this host pushes a
|
||||
* single-ctor all-scalar *record* `Tick` as the per-call payload —
|
||||
* the actual minimal data-server binding shape. Frozen value layout
|
||||
* (design/contracts/frozen-value-layout.md):
|
||||
* (design/contracts/0006-frozen-value-layout.md):
|
||||
*
|
||||
* State (24-byte payload):
|
||||
* p - 8 .. p uint64_t refcount header (set to 1 by ailang_rc_alloc)
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
//!
|
||||
//! Property protected: a polymorphic Eq/Ord helper invoked at Float
|
||||
//! fires `no-instance` (since Float has neither Eq nor Ord instance
|
||||
//! per design/contracts/float-semantics.md) AND the diagnostic message
|
||||
//! per design/contracts/0005-float-semantics.md) AND the diagnostic message
|
||||
//! cross-references the canonical float-semantics contract so the
|
||||
//! LLM author immediately learns the partial-Float story rather
|
||||
//! than seeing a bare "no instance" message.
|
||||
@@ -38,7 +38,7 @@ fn eq_at_float_fires_float_aware_noinstance() {
|
||||
// Cross-ref to the float-semantics contract — the LLM author should
|
||||
// be pointed at the canonical explanation of partial Float orderability.
|
||||
assert!(
|
||||
no_inst.message.contains("design/contracts/float-semantics.md"),
|
||||
no_inst.message.contains("design/contracts/0005-float-semantics.md"),
|
||||
"expected NoInstance message to cross-reference the float-semantics contract, got: {:?}",
|
||||
no_inst.message
|
||||
);
|
||||
|
||||
@@ -7,8 +7,8 @@
|
||||
//! invoked as `(app greet)`.
|
||||
//!
|
||||
//! Two independent fieldtest specs flagged the same gap:
|
||||
//! `docs/specs/2026-05-15-fieldtest-mut-local.md` F3 and
|
||||
//! `docs/specs/2026-05-18-fieldtest-loop-recur.md` spec_gap. The
|
||||
//! `docs/specs/0030-fieldtest-mut-local.md` F3 and
|
||||
//! `docs/specs/0035-fieldtest-loop-recur.md` spec_gap. The
|
||||
//! Form-A surface refusing nullary calls had no schema backing —
|
||||
//! `Term::App.args` is `[Term...]` (zero-or-more), so the parser
|
||||
//! guard was an unbacked rule.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//! Pin for the iter-24.3 FreeFnCall constraint-residual-push
|
||||
//! invariant (design/contracts/typeclasses.md, "Cross-module
|
||||
//! invariant (design/contracts/0013-typeclasses.md, "Cross-module
|
||||
//! references in synthesised bodies" invariant 3).
|
||||
//!
|
||||
//! Property protected: bare-name references to polymorphic free fns
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
//!
|
||||
//! Property protected: calling `print` on a function type fires the
|
||||
//! `no-instance` diagnostic with a Show-aware message that
|
||||
//! cross-references design/contracts/typeclasses.md, so the
|
||||
//! cross-references design/contracts/0013-typeclasses.md, so the
|
||||
//! LLM author immediately learns which types ship with built-in Show
|
||||
//! and how to declare their own instance for a user type.
|
||||
|
||||
@@ -40,11 +40,11 @@ fn print_on_fn_type_fires_show_aware_no_instance() {
|
||||
no_inst.message
|
||||
);
|
||||
|
||||
// Must cross-reference design/contracts/typeclasses.md — that's
|
||||
// Must cross-reference design/contracts/0013-typeclasses.md — that's
|
||||
// the canonical contract naming which types ship with Show.
|
||||
assert!(
|
||||
no_inst.message.contains("design/contracts/typeclasses.md"),
|
||||
"expected design/contracts/typeclasses.md cross-reference, got message: {:?}",
|
||||
no_inst.message.contains("design/contracts/0013-typeclasses.md"),
|
||||
"expected design/contracts/0013-typeclasses.md cross-reference, got message: {:?}",
|
||||
no_inst.message
|
||||
);
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
//! - `module-cycle` — workspace loader (Iter 5b, in the CLI path)
|
||||
//! - `module-name-mismatch` — workspace loader (Iter 5b, in the CLI path)
|
||||
//! - `module-hash-mismatch` — workspace loader (Iter 5b, in the CLI path)
|
||||
//! - `tail-call-not-in-tail-position` (see `design/contracts/tail-calls.md`)
|
||||
//! - `tail-call-not-in-tail-position` (see `design/contracts/0012-tail-calls.md`)
|
||||
//! - `use-after-consume` — `ctx`: `{"binder": "<n>"}` (Iter 18c.2);
|
||||
//! carries non-empty [`Diagnostic::suggested_rewrites`] showing how to
|
||||
//! spell the fix in form-A AILang.
|
||||
|
||||
@@ -446,7 +446,7 @@ pub enum CheckError {
|
||||
|
||||
/// An `(export …)` fn has a parameter or return type that is not
|
||||
/// a permitted embedding-ABI type. The embedding ABI is frozen as
|
||||
/// of M3 (design/contracts/embedding-abi.md):
|
||||
/// of M3 (design/contracts/0003-embedding-abi.md):
|
||||
/// `Int`/`Float` or a single-constructor record of those; every
|
||||
/// other shape (multi-ctor sums, `Str`/`List`/nested-record
|
||||
/// fields) is rejected at typecheck (the feature-acceptance
|
||||
@@ -556,7 +556,7 @@ pub enum CheckError {
|
||||
|
||||
/// a `Term::App { tail: true, .. }` or
|
||||
/// `Term::Do { tail: true, .. }` was found in a non-tail position.
|
||||
/// Code: `tail-call-not-in-tail-position`. See `design/contracts/tail-calls.md`.
|
||||
/// Code: `tail-call-not-in-tail-position`. See `design/contracts/0012-tail-calls.md`.
|
||||
#[error("call marked `tail` is not in tail position")]
|
||||
TailCallNotInTailPosition,
|
||||
|
||||
@@ -858,7 +858,7 @@ impl CheckError {
|
||||
}
|
||||
// Float-aware addendum on `NoInstance`. When the
|
||||
// unresolved class is `Eq` or `Ord` AND the type is `Float`,
|
||||
// append a cross-reference to design/contracts/float-semantics.md so
|
||||
// append a cross-reference to design/contracts/0005-float-semantics.md so
|
||||
// the LLM author learns the partial-Float story immediately
|
||||
// rather than seeing a bare "no instance" message. Float has
|
||||
// neither Eq nor Ord by design (partial orderability per
|
||||
@@ -874,7 +874,7 @@ impl CheckError {
|
||||
orderability per IEEE-754); use float_eq / float_lt \
|
||||
(and siblings float_ne / float_le / float_gt / float_ge) \
|
||||
for explicit IEEE-aware comparison. \
|
||||
See design/contracts/float-semantics.md.",
|
||||
See design/contracts/0005-float-semantics.md.",
|
||||
d.message
|
||||
);
|
||||
} else if class == "prelude.Show" {
|
||||
@@ -883,12 +883,12 @@ impl CheckError {
|
||||
// a function type (`print f` where f is bare-fn-typed)
|
||||
// or a user type the LLM-author forgot to give an
|
||||
// instance for. Cross-references
|
||||
// design/contracts/typeclasses.md so the author learns
|
||||
// design/contracts/0013-typeclasses.md so the author learns
|
||||
// which types ship with built-in Show immediately.
|
||||
d.message = format!(
|
||||
"{} — `print` and `show` require a Show instance. \
|
||||
Built-in Show ships for Int, Bool, Str, Float in \
|
||||
the prelude; see design/contracts/typeclasses.md. \
|
||||
the prelude; see design/contracts/0013-typeclasses.md. \
|
||||
User types declare their own \
|
||||
`instance prelude.Show <T>` in the type's defining \
|
||||
module per the typeclass design coherence.",
|
||||
@@ -2643,10 +2643,10 @@ fn constraint_type_matches(declared: &Type, residual: &Type) -> bool {
|
||||
/// verifies that every `Term::App { tail: true, .. }` and
|
||||
/// `Term::Do { tail: true, .. }` actually sits in tail position, per
|
||||
/// the explicit-tail-call contract
|
||||
/// (`design/contracts/tail-calls.md`).
|
||||
/// (`design/contracts/0012-tail-calls.md`).
|
||||
///
|
||||
/// `is_tail` is the tail-context flag for the term currently being
|
||||
/// visited. The propagation rules (from design/contracts/tail-calls.md):
|
||||
/// visited. The propagation rules (from design/contracts/0012-tail-calls.md):
|
||||
///
|
||||
/// - The body of a fn / Lam is visited with `is_tail = true`.
|
||||
/// - `Match { scrutinee, arms }`: the scrutinee is **not** in tail
|
||||
@@ -6031,7 +6031,7 @@ mod tests {
|
||||
/// Floats milestone post-fieldtest B1 (RED): pattern-matching on a
|
||||
/// `Literal::Float` literal must be hard-rejected at typecheck via
|
||||
/// `CheckError::FloatPatternNotAllowed` per
|
||||
/// design/contracts/float-semantics.md — even when the match arm reaches `check` through
|
||||
/// design/contracts/0005-float-semantics.md — even when the match arm reaches `check` through
|
||||
/// the *full* check pipeline (which runs `desugar_module` first).
|
||||
///
|
||||
/// The existing `reject_float_pattern_in_match` test in
|
||||
|
||||
@@ -85,7 +85,7 @@ impl<'a> Emitter<'a> {
|
||||
out.push_str(" %is_null = icmp eq ptr %p, null\n");
|
||||
out.push_str(" br i1 %is_null, label %ret, label %live\n");
|
||||
out.push_str("live:\n");
|
||||
// FROZEN ABI (embedding boundary) — see design/contracts/embedding-abi.md.
|
||||
// FROZEN ABI (embedding boundary) — see design/contracts/0003-embedding-abi.md.
|
||||
out.push_str(" %tag = load i64, ptr %p, align 8\n");
|
||||
|
||||
let n_ctors = td.ctors.len();
|
||||
@@ -111,7 +111,7 @@ impl<'a> Emitter<'a> {
|
||||
if lty != "ptr" {
|
||||
continue;
|
||||
}
|
||||
// FROZEN ABI (embedding boundary) — see design/contracts/embedding-abi.md.
|
||||
// FROZEN ABI (embedding boundary) — see design/contracts/0003-embedding-abi.md.
|
||||
let off = 8 + (j as i64) * 8;
|
||||
let addr_id = local;
|
||||
local += 1;
|
||||
|
||||
@@ -174,7 +174,7 @@ pub enum Target {
|
||||
/// published via `@__ail_tls_ctx` around the unchanged internal
|
||||
/// call. The ctx-threaded C signature is the M2 shape; the
|
||||
/// value/record layout is frozen as of M3
|
||||
/// (design/contracts/frozen-value-layout.md).
|
||||
/// (design/contracts/0006-frozen-value-layout.md).
|
||||
StaticLib,
|
||||
}
|
||||
|
||||
@@ -687,7 +687,7 @@ fn llvm_scalar(t: &Type) -> &'static str {
|
||||
// M3: any other Type::Con reaching an (export) signature is,
|
||||
// by the Task-2 export gate, a single-ctor scalar record;
|
||||
// it crosses the C ABI as a bare `ptr`
|
||||
// (design/contracts/frozen-value-layout.md — payload pointer).
|
||||
// (design/contracts/0006-frozen-value-layout.md — payload pointer).
|
||||
Type::Con { .. } => "ptr",
|
||||
_ => "i64",
|
||||
}
|
||||
@@ -2813,7 +2813,7 @@ impl<'a> Emitter<'a> {
|
||||
/// allocates an `Ordering` ctor per call via the
|
||||
/// `compare__Int` → `match` path, which surfaces as a
|
||||
/// `bench_closure_chain` regression. See
|
||||
/// `design/contracts/prelude-classes.md` for the full surface
|
||||
/// `design/contracts/0017-prelude-classes.md` for the full surface
|
||||
/// and the Int-only asymmetry rationale.
|
||||
fn try_emit_primitive_instance_body(
|
||||
&mut self,
|
||||
|
||||
@@ -105,7 +105,7 @@ impl<'a> Emitter<'a> {
|
||||
compiled.push((v, vty));
|
||||
}
|
||||
// FROZEN ABI (embedding boundary) —
|
||||
// design/contracts/frozen-value-layout.md. size = 8 + n*8, tag@0, fields@8+i*8.
|
||||
// design/contracts/0006-frozen-value-layout.md. size = 8 + n*8, tag@0, fields@8+i*8.
|
||||
let size_bytes = 8 + (compiled.len() * 8) as i64;
|
||||
let p = self.fresh_ssa();
|
||||
// pick allocator based on escape analysis. `alloca`
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! FROZEN ABI byte-pin — see design/contracts/frozen-value-layout.md.
|
||||
//! FROZEN ABI byte-pin — see design/contracts/0006-frozen-value-layout.md.
|
||||
//! Pins the heap (@ailang_rc_alloc) box layout a boundary-crossing
|
||||
//! single-ctor scalar record takes: size = 8 + n*8, tag i64 @ offset
|
||||
//! 0, fields i64-strided from offset 8. Goes RED if codegen moves an
|
||||
|
||||
@@ -45,7 +45,7 @@ fn staticlib_emits_forwarder_no_main() {
|
||||
|
||||
/// M3: a single-ctor scalar record param/ret crosses the embedding
|
||||
/// C boundary as a bare `ptr` (the frozen payload pointer —
|
||||
/// design/contracts/frozen-value-layout.md). The M2 forwarder body
|
||||
/// design/contracts/0006-frozen-value-layout.md). The M2 forwarder body
|
||||
/// is byte-unchanged: leading `ptr %ctx`, TLS save/store/restore, the
|
||||
/// internal `@ail_<module>_<fn>` call carrying no ctx arg and no
|
||||
/// aggregate calling convention (no `sret`/`byval` — a record is a
|
||||
|
||||
@@ -86,7 +86,7 @@ externally-callable entrypoint (Embedding ABI M1). The symbol is
|
||||
author-chosen and decoupled from the internal `ail_<module>_<def>`
|
||||
mangling. M1 requires the fn's parameter and return types to be
|
||||
scalar (`Int`/`Float`) and its effect set to be empty — see
|
||||
design/contracts/embedding-abi.md.
|
||||
design/contracts/0003-embedding-abi.md.
|
||||
|
||||
`suppress` clauses silence advisory diagnostics for this def. The
|
||||
`code` MUST be one of the registered codes (today: `over-strict-mode`).
|
||||
@@ -324,7 +324,7 @@ Notes:
|
||||
both enforced at typecheck (`recur-arity-mismatch`,
|
||||
`recur-not-in-tail-position`). `loop`/`recur` make no termination
|
||||
claim: a `loop` with no non-`recur` exit runs forever. See
|
||||
`docs/specs/2026-05-17-loop-recur.md`.
|
||||
`docs/specs/0034-loop-recur.md`.
|
||||
|
||||
## Patterns
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
//! AST nodes for the AILang language.
|
||||
//!
|
||||
//! **The canonical schema lives in `design/contracts/data-model.md`**;
|
||||
//! **The canonical schema lives in `design/contracts/0002-data-model.md`**;
|
||||
//! this module is the Rust-side projection of it. When the two drift,
|
||||
//! `crates/ailang-core/tests/design_schema_drift.rs` fires.
|
||||
//!
|
||||
@@ -208,7 +208,7 @@ pub struct FnDef {
|
||||
/// embedding boundary. Codegen's `Target::StaticLib` mode
|
||||
/// additionally emits an externally-visible C entrypoint
|
||||
/// `@<sym>` (signature frozen as of M3 —
|
||||
/// design/contracts/embedding-abi.md) forwarding
|
||||
/// design/contracts/0003-embedding-abi.md) forwarding
|
||||
/// to `@ail_<module>_<fn>`. The symbol is author-chosen and
|
||||
/// decoupled from the `ail_<module>_<def>` mangling so the
|
||||
/// M3-frozen ABI survives module/fn refactors.
|
||||
@@ -256,7 +256,7 @@ pub struct Suppress {
|
||||
}
|
||||
|
||||
/// A typeclass declaration (narrative in
|
||||
/// `design/contracts/typeclasses.md`).
|
||||
/// `design/contracts/0013-typeclasses.md`).
|
||||
///
|
||||
/// Single-parameter, multi-method, optional-default, optional-superclass
|
||||
/// typeclass. The `param` is a single string — multi-param classes are
|
||||
@@ -326,7 +326,7 @@ pub struct ClassMethod {
|
||||
}
|
||||
|
||||
/// An instance declaration (narrative in
|
||||
/// `design/contracts/typeclasses.md`).
|
||||
/// `design/contracts/0013-typeclasses.md`).
|
||||
///
|
||||
/// `class` is the name of the class being instantiated. `type_` is the
|
||||
/// concrete type expression the class is applied to — never the class
|
||||
@@ -360,7 +360,7 @@ pub struct InstanceMethod {
|
||||
}
|
||||
|
||||
/// A class constraint on a polymorphic function (narrative in
|
||||
/// `design/contracts/typeclasses.md`). `(class, type)` pair where
|
||||
/// `design/contracts/0013-typeclasses.md`). `(class, type)` pair where
|
||||
/// `class` is a class name and `type` is a `Type` expression —
|
||||
/// typically a single `Type::Var` (e.g. `(Eq, a)` for
|
||||
/// `Eq a => ...`). Concrete-type constraints are legal schema-wise
|
||||
@@ -412,7 +412,7 @@ pub enum Term {
|
||||
/// `args` are evaluated left-to-right.
|
||||
///
|
||||
/// `tail` marks this call as occurring in tail position (see
|
||||
/// `design/contracts/tail-calls.md`). When set, codegen lowers
|
||||
/// `design/contracts/0012-tail-calls.md`). When set, codegen lowers
|
||||
/// the call as `musttail call`. The flag defaults to `false` and
|
||||
/// is omitted during canonical-JSON serialisation when unset, so
|
||||
/// pre-tail-flag fixtures keep bit-identical hashes.
|
||||
@@ -548,7 +548,7 @@ pub enum Term {
|
||||
/// loop-carried SSA / phi form is produced by `clang -O2`
|
||||
/// mem2reg (NOT hand-emitted phi). No totality claim — an
|
||||
/// infinite loop is legal. See
|
||||
/// `docs/specs/2026-05-17-loop-recur.md`.
|
||||
/// `docs/specs/0034-loop-recur.md`.
|
||||
Loop {
|
||||
binders: Vec<LoopBinder>,
|
||||
body: Box<Term>,
|
||||
@@ -707,7 +707,7 @@ pub enum Type {
|
||||
/// every entry is `Implicit`; `ret_mode` is omitted when it is
|
||||
/// `Implicit`, so pre-mode-annotation fixtures hash
|
||||
/// bit-identically. Full contract in
|
||||
/// `design/contracts/memory-model.md`.
|
||||
/// `design/contracts/0008-memory-model.md`.
|
||||
Fn {
|
||||
params: Vec<Type>,
|
||||
#[serde(default, skip_serializing_if = "all_implicit")]
|
||||
@@ -780,7 +780,7 @@ impl Type {
|
||||
}
|
||||
|
||||
/// Per-parameter / return mode marker on a [`Type::Fn`]. Full
|
||||
/// contract lives in `design/contracts/memory-model.md`.
|
||||
/// contract lives in `design/contracts/0008-memory-model.md`.
|
||||
///
|
||||
/// `Implicit` is the legacy state for fn-types that were constructed
|
||||
/// before the borrow/own surface annotations existed. Semantically,
|
||||
|
||||
@@ -1090,12 +1090,12 @@ fn is_flat(p: &Pattern) -> bool {
|
||||
/// Builds the equality-test AST node for a literal pattern. Lowers
|
||||
/// `(pat-lit <lit>)` to `(if (eq sv <lit>) <body> <fall_k>)`. The
|
||||
/// emitted `eq` resolves via prelude.Eq's class-method dispatch
|
||||
/// (per design/contracts/method-dispatch.md) — Int/Bool/Str/Unit
|
||||
/// (per design/contracts/0016-method-dispatch.md) — Int/Bool/Str/Unit
|
||||
/// patterns all route through the corresponding primitive instance.
|
||||
///
|
||||
/// Float-Literal patterns are hard-rejected at typecheck
|
||||
/// (`CheckError::FloatPatternNotAllowed`, per
|
||||
/// design/contracts/float-semantics.md) before this fn is reached,
|
||||
/// design/contracts/0005-float-semantics.md) before this fn is reached,
|
||||
/// so the `eq`-dispatch never encounters Float at runtime; the
|
||||
/// Float arm of the match below remains for AST-level totality.
|
||||
///
|
||||
|
||||
@@ -108,7 +108,7 @@ pub type Result<T> = std::result::Result<T, Error>;
|
||||
pub const SCHEMA: &str = "ailang/v0";
|
||||
|
||||
/// Complete LLM-targeted specification of Form-A — the canonical
|
||||
/// authoring surface (see `design/contracts/authoring-surface.md`).
|
||||
/// authoring surface (see `design/contracts/0001-authoring-surface.md`).
|
||||
/// Embedded verbatim into any prompt that asks an LLM to produce or
|
||||
/// edit AILang code; in particular, `ail merge-prose` includes it in
|
||||
/// the round-trip prompt template. The string is the raw bytes of
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
//! dangles, a contract loses its ratifying test, docs/DESIGN.md is
|
||||
//! resurrected, or a design/ body cross-link fails to resolve into
|
||||
//! the durable tier (clause-5; spec
|
||||
//! docs/specs/2026-05-19-design-ledger-formal-links.md). Spec for
|
||||
//! the split: docs/specs/2026-05-19-design-md-rolesplit.md.
|
||||
//! docs/specs/0046-design-ledger-formal-links.md). Spec for
|
||||
//! the split: docs/specs/0045-design-md-rolesplit.md.
|
||||
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
//! Drift detection between ast.rs and `design/contracts/data-model.md`.
|
||||
//! Drift detection between ast.rs and `design/contracts/0002-data-model.md`.
|
||||
//!
|
||||
//! `design/contracts/data-model.md` is the canonical schema
|
||||
//! `design/contracts/0002-data-model.md` is the canonical schema
|
||||
//! source-of-truth. Every AST enum (`Term`, `Pattern`, `Type`, `Def`,
|
||||
//! `Literal`, `ParamMode`) must have a JSON-schema anchor (e.g.
|
||||
//! `"t": "lit"`, `"k": "fn"`) present in that document. These tests
|
||||
@@ -9,7 +9,7 @@
|
||||
//! The exhaustive `match` per enum is the load-bearing mechanism: adding a
|
||||
//! new variant without a matching arm fails compilation before the test runs.
|
||||
//! Once the variant is matched, the test asserts the anchor is present in
|
||||
//! `design/contracts/data-model.md`. The whole file is the data-model
|
||||
//! `design/contracts/0002-data-model.md`. The whole file is the data-model
|
||||
//! contract, so the file boundary now bounds the section — the former
|
||||
//! `## Data model` … `## Pipeline` slicer is no longer needed (the split
|
||||
//! gave the section its own file).
|
||||
@@ -19,7 +19,7 @@ use ailang_core::ast::{
|
||||
InstanceMethod, Literal, Pattern, ParamMode, Suppress, Term, Type, TypeDef,
|
||||
};
|
||||
|
||||
const DATA_MODEL: &str = include_str!("../../../design/contracts/data-model.md");
|
||||
const DATA_MODEL: &str = include_str!("../../../design/contracts/0002-data-model.md");
|
||||
|
||||
/// Scoped substring match: returns `true` iff `anchor` appears inside a
|
||||
/// fenced code block (``` or ~~~) of `md`. Info-string `jsonc` / `json` /
|
||||
@@ -47,7 +47,7 @@ fn anchor_in_jsonc_block(md: &str, anchor: &str) -> bool {
|
||||
}
|
||||
|
||||
/// Every `Term` variant must have its JSON-schema anchor present in
|
||||
/// design/contracts/data-model.md. An LLM author cannot produce a term variant
|
||||
/// design/contracts/0002-data-model.md. An LLM author cannot produce a term variant
|
||||
/// whose `"t"` tag is absent from the canonical schema document.
|
||||
#[test]
|
||||
fn design_md_anchors_every_term_variant() {
|
||||
@@ -182,8 +182,8 @@ fn design_md_anchors_every_term_variant() {
|
||||
};
|
||||
assert!(
|
||||
anchor_in_jsonc_block(DATA_MODEL, anchor),
|
||||
"design/contracts/data-model.md is missing anchor `{anchor}` for a Term variant — \
|
||||
add it to design/contracts/data-model.md"
|
||||
"design/contracts/0002-data-model.md is missing anchor `{anchor}` for a Term variant — \
|
||||
add it to design/contracts/0002-data-model.md"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -194,14 +194,14 @@ fn design_md_anchors_every_term_variant() {
|
||||
for anchor in [r#""param-types""#, r#""ret-type""#] {
|
||||
assert!(
|
||||
anchor_in_jsonc_block(DATA_MODEL, anchor),
|
||||
"design/contracts/data-model.md is missing Term::Lam kebab-key anchor `{anchor}` — \
|
||||
"design/contracts/0002-data-model.md is missing Term::Lam kebab-key anchor `{anchor}` — \
|
||||
check the `lam` fenced JSON block"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Every `Pattern` variant must have its JSON-schema anchor present in
|
||||
/// design/contracts/data-model.md. Missing anchors mean an LLM cannot produce
|
||||
/// design/contracts/0002-data-model.md. Missing anchors mean an LLM cannot produce
|
||||
/// the corresponding pattern form.
|
||||
#[test]
|
||||
fn design_md_anchors_every_pattern_variant() {
|
||||
@@ -224,13 +224,13 @@ fn design_md_anchors_every_pattern_variant() {
|
||||
};
|
||||
assert!(
|
||||
anchor_in_jsonc_block(DATA_MODEL, anchor),
|
||||
"design/contracts/data-model.md is missing anchor `{anchor}` for a Pattern variant"
|
||||
"design/contracts/0002-data-model.md is missing anchor `{anchor}` for a Pattern variant"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Every `Type` variant must have its JSON-schema anchor present in
|
||||
/// design/contracts/data-model.md. The `"k"` discriminator is load-bearing for
|
||||
/// design/contracts/0002-data-model.md. The `"k"` discriminator is load-bearing for
|
||||
/// the codegen and typechecker; an LLM must know all four forms.
|
||||
#[test]
|
||||
fn design_md_anchors_every_type_variant() {
|
||||
@@ -257,13 +257,13 @@ fn design_md_anchors_every_type_variant() {
|
||||
};
|
||||
assert!(
|
||||
anchor_in_jsonc_block(DATA_MODEL, anchor),
|
||||
"design/contracts/data-model.md is missing anchor `{anchor}` for a Type variant"
|
||||
"design/contracts/0002-data-model.md is missing anchor `{anchor}` for a Type variant"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Every `Literal` variant must have its JSON-schema anchor present in
|
||||
/// design/contracts/data-model.md. The `"kind"` discriminator identifies the
|
||||
/// design/contracts/0002-data-model.md. The `"kind"` discriminator identifies the
|
||||
/// literal type; an LLM cannot produce a literal it hasn't seen in the
|
||||
/// schema.
|
||||
#[test]
|
||||
@@ -286,13 +286,13 @@ fn design_md_anchors_every_literal_variant() {
|
||||
};
|
||||
assert!(
|
||||
anchor_in_jsonc_block(DATA_MODEL, anchor),
|
||||
"design/contracts/data-model.md is missing anchor `{anchor}` for a Literal variant"
|
||||
"design/contracts/0002-data-model.md is missing anchor `{anchor}` for a Literal variant"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Every `Def` kind must have its JSON-schema anchor present in
|
||||
/// design/contracts/data-model.md. All five kinds (`fn`, `const`, `type`,
|
||||
/// design/contracts/0002-data-model.md. All five kinds (`fn`, `const`, `type`,
|
||||
/// `class`, `instance`) must be documented so an LLM can write
|
||||
/// any kind of top-level definition.
|
||||
#[test]
|
||||
@@ -358,13 +358,13 @@ fn design_md_anchors_every_def_kind() {
|
||||
};
|
||||
assert!(
|
||||
anchor_in_jsonc_block(DATA_MODEL, anchor),
|
||||
"design/contracts/data-model.md is missing anchor `{anchor}` for a Def kind"
|
||||
"design/contracts/0002-data-model.md is missing anchor `{anchor}` for a Def kind"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Every `ParamMode` variant must have its serialized string form present
|
||||
/// in design/contracts/data-model.md. The mode annotations are load-bearing for
|
||||
/// in design/contracts/0002-data-model.md. The mode annotations are load-bearing for
|
||||
/// ownership checking; an LLM author must know all three forms.
|
||||
#[test]
|
||||
fn design_md_anchors_every_parammode_variant() {
|
||||
@@ -382,7 +382,7 @@ fn design_md_anchors_every_parammode_variant() {
|
||||
};
|
||||
assert!(
|
||||
anchor_in_jsonc_block(DATA_MODEL, anchor),
|
||||
"design/contracts/data-model.md is missing anchor `{anchor}` for a ParamMode variant"
|
||||
"design/contracts/0002-data-model.md is missing anchor `{anchor}` for a ParamMode variant"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -502,7 +502,7 @@ fn lam_serialises_with_kebab_keys() {
|
||||
);
|
||||
}
|
||||
|
||||
/// Nested struct key anchors must be present in design/contracts/data-model.md.
|
||||
/// Nested struct key anchors must be present in design/contracts/0002-data-model.md.
|
||||
/// These keys appear inside `Suppress`, `ClassMethod`, `InstanceMethod`,
|
||||
/// and `Type::Forall` — they are not discriminators but they ARE part
|
||||
/// of the canonical JSON schema and must remain documented.
|
||||
@@ -534,7 +534,7 @@ fn design_md_anchors_nested_struct_keys() {
|
||||
for anchor in anchors {
|
||||
assert!(
|
||||
anchor_in_jsonc_block(DATA_MODEL, anchor),
|
||||
"design/contracts/data-model.md is missing nested-struct-key anchor `{anchor}`"
|
||||
"design/contracts/0002-data-model.md is missing nested-struct-key anchor `{anchor}`"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,25 +28,25 @@ fn norm(s: &str) -> String {
|
||||
/// The `design/` prose set the absent-pins span after the role-split
|
||||
/// (these pins formerly scanned the single canonical design doc): the
|
||||
/// RC + bump memory-model narrative
|
||||
/// (`models/rc-uniqueness.md`), float semantics
|
||||
/// (`contracts/float-semantics.md`), the typeclass contract incl.
|
||||
/// (`models/0004-rc-uniqueness.md`), float semantics
|
||||
/// (`contracts/0005-float-semantics.md`), the typeclass contract incl.
|
||||
/// prelude classes & "does NOT commit to"
|
||||
/// (`contracts/typeclasses.md`), the effects model
|
||||
/// (`models/effects.md`), and the authoring-surface notation model
|
||||
/// (`models/authoring-surface.md`). A swept Wunschdenken /
|
||||
/// (`contracts/0013-typeclasses.md`), the effects model
|
||||
/// (`models/0002-effects.md`), and the authoring-surface notation model
|
||||
/// (`models/0001-authoring-surface.md`). A swept Wunschdenken /
|
||||
/// doc-archaeology string must be absent from **all** of them, so the
|
||||
/// reads are joined and a single absence assertion covers the set.
|
||||
fn design_corpus() -> String {
|
||||
[
|
||||
read("design/models/rc-uniqueness.md"),
|
||||
read("design/contracts/float-semantics.md"),
|
||||
read("design/contracts/typeclasses.md"),
|
||||
read("design/contracts/method-dispatch.md"),
|
||||
read("design/contracts/prelude-classes.md"),
|
||||
read("design/contracts/language-constraints.md"),
|
||||
read("design/models/effects.md"),
|
||||
read("design/models/authoring-surface.md"),
|
||||
read("design/models/prose-projection.md"),
|
||||
read("design/models/0004-rc-uniqueness.md"),
|
||||
read("design/contracts/0005-float-semantics.md"),
|
||||
read("design/contracts/0013-typeclasses.md"),
|
||||
read("design/contracts/0016-method-dispatch.md"),
|
||||
read("design/contracts/0017-prelude-classes.md"),
|
||||
read("design/contracts/0015-language-constraints.md"),
|
||||
read("design/models/0002-effects.md"),
|
||||
read("design/models/0001-authoring-surface.md"),
|
||||
read("design/models/0006-prose-projection.md"),
|
||||
]
|
||||
.join("\n")
|
||||
}
|
||||
@@ -103,13 +103,13 @@ fn design_md_present_tense_anchors_present() {
|
||||
// ever lived in the (now-removed) decision-records annex are
|
||||
// dropped — anything the design/ ledger truly needs lives in
|
||||
// design/contracts/ or design/models/.
|
||||
let honesty = norm(&read("design/contracts/honesty-rule.md"));
|
||||
let scope = norm(&read("design/contracts/scope-boundaries.md"));
|
||||
let memory = norm(&read("design/contracts/memory-model.md"));
|
||||
let str_abi = norm(&read("design/contracts/str-abi.md"));
|
||||
let prelude_classes = norm(&read("design/contracts/prelude-classes.md"));
|
||||
let honesty = norm(&read("design/contracts/0007-honesty-rule.md"));
|
||||
let scope = norm(&read("design/contracts/0010-scope-boundaries.md"));
|
||||
let memory = norm(&read("design/contracts/0008-memory-model.md"));
|
||||
let str_abi = norm(&read("design/contracts/0011-str-abi.md"));
|
||||
let prelude_classes = norm(&read("design/contracts/0017-prelude-classes.md"));
|
||||
|
||||
// the discriminator meta-subsection -> contracts/honesty-rule.md
|
||||
// the discriminator meta-subsection -> contracts/0007-honesty-rule.md
|
||||
assert!(honesty.contains("the honesty rule it holds itself to"),
|
||||
"honesty-rule.md must carry the discriminator meta-subsection heading");
|
||||
assert!(honesty.contains("whether the document asserts something exists, works, or changed that does not"),
|
||||
@@ -144,7 +144,7 @@ fn form_a_scalar_param_carveout_present_and_old_rule_absent() {
|
||||
// (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("design/contracts/embedding-abi.md"));
|
||||
let d = norm(&read("design/contracts/0003-embedding-abi.md"));
|
||||
|
||||
// --- ABSENT: the four old unconditional phrasings must be gone ---
|
||||
assert!(!f.contains("Mode annotations on every `(fn ...)` parameter."),
|
||||
@@ -166,7 +166,7 @@ fn form_a_scalar_param_carveout_present_and_old_rule_absent() {
|
||||
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/contracts/embedding-abi.md mirror ---
|
||||
// --- PRESENT: the design/contracts/0003-embedding-abi.md mirror ---
|
||||
assert!(d.contains("Export parameters are written **bare**: a scalar type carries no `own`/`borrow` mode"),
|
||||
"design/contracts/embedding-abi.md must mirror the canonical bare-scalar export-param rule");
|
||||
"design/contracts/0003-embedding-abi.md must mirror the canonical bare-scalar export-param rule");
|
||||
}
|
||||
|
||||
@@ -25,23 +25,23 @@ fn norm(s: &str) -> String {
|
||||
|
||||
#[test]
|
||||
fn design_md_effect_prose_is_true() {
|
||||
// Effect prose -> design/models/effects.md; the
|
||||
// Effect prose -> design/models/0002-effects.md; the
|
||||
// "the built-in IO and Diverge ops" absent-pin's home
|
||||
// `## What is not (yet) supported` -> design/contracts/scope-boundaries.md.
|
||||
let d = norm(&[read("design/models/effects.md"),
|
||||
read("design/contracts/scope-boundaries.md")].join("\n"));
|
||||
// `## What is not (yet) supported` -> design/contracts/0010-scope-boundaries.md.
|
||||
let d = norm(&[read("design/models/0002-effects.md"),
|
||||
read("design/contracts/0010-scope-boundaries.md")].join("\n"));
|
||||
// fiction absent
|
||||
assert!(!d.contains("row-polymorphic"),
|
||||
"design/: the effect row is NOT row-polymorphic (no EffectRow / row var exists)");
|
||||
assert!(!d.contains("`IO` and `Diverge` (for infinite"),
|
||||
"design/: `Diverge` is not a wired-up MVP effect");
|
||||
assert!(!d.contains("the built-in IO and Diverge ops"),
|
||||
"design/contracts/scope-boundaries.md: there is no built-in Diverge op");
|
||||
"design/contracts/0010-scope-boundaries.md: there is no built-in Diverge op");
|
||||
// corrected anchors present
|
||||
assert!(d.contains("flat, unordered, closed set of effect names"),
|
||||
"design/models/effects.md Decision 3 must describe the real (flat set-equality) effect model");
|
||||
"design/models/0002-effects.md Decision 3 must describe the real (flat set-equality) effect model");
|
||||
assert!(d.contains("`Diverge` (for non-termination) is reserved"),
|
||||
"design/models/effects.md Decision 3 must state Diverge is reserved/unimplemented");
|
||||
"design/models/0002-effects.md Decision 3 must state Diverge is reserved/unimplemented");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
//! effects, `clone`, `reuse-as`, doc strings, type annotations on
|
||||
//! signatures and lambdas, the `tail` flag) stays visible.
|
||||
//!
|
||||
//! See `design/contracts/authoring-surface.md` for the prose-surface
|
||||
//! See `design/contracts/0001-authoring-surface.md` for the prose-surface
|
||||
//! invariants this crate must respect.
|
||||
//!
|
||||
//! # Public API
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
//! [`ailang_core::ast::Module`] values. The JSON-AST in `ailang-core`
|
||||
//! remains the canonical, hashable, content-addressed source of truth;
|
||||
//! form (A) is the AI-authoring projection optimised for token-efficient
|
||||
//! production by LLMs (see `design/contracts/authoring-surface.md`).
|
||||
//! production by LLMs (see `design/contracts/0001-authoring-surface.md`).
|
||||
//!
|
||||
//! ## Modules
|
||||
//!
|
||||
|
||||
@@ -76,7 +76,7 @@
|
||||
//! ```
|
||||
//!
|
||||
//! Notes on the form (the binding constraints are in
|
||||
//! design/contracts/authoring-surface.md):
|
||||
//! design/contracts/0001-authoring-surface.md):
|
||||
//!
|
||||
//! - The `lam` form carries `param-types`, a `ret` type, and an
|
||||
//! optional `effects` clause. The AST stores them per-lambda and
|
||||
|
||||
@@ -125,7 +125,7 @@ fn write_type_def(out: &mut String, td: &TypeDef, level: usize) {
|
||||
}
|
||||
// `(drop-iterative)` annotation. Printed last (after
|
||||
// every ctor) so the canonical form lands consistent with the
|
||||
// design/contracts/data-model.md example. Omitted when `drop_iterative == false`.
|
||||
// design/contracts/0002-data-model.md example. Omitted when `drop_iterative == false`.
|
||||
if td.drop_iterative {
|
||||
out.push('\n');
|
||||
indent(out, level + 1);
|
||||
|
||||
@@ -23,7 +23,7 @@ fn prelude_ail_json_does_not_exist_on_disk() {
|
||||
re-introduced this file, you must also: (a) remove this pin, \
|
||||
(b) restore the §C4 (b) entry in carve_out_inventory.rs, \
|
||||
(c) revert the §C4 (b) status marker in \
|
||||
docs/specs/2026-05-13-form-a-default-authoring.md, and \
|
||||
docs/specs/0025-form-a-default-authoring.md, and \
|
||||
(d) record the rationale in the commit body."
|
||||
);
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
//!
|
||||
//! 1. `parse_is_deterministic_over_every_ail_fixture` — pins
|
||||
//! **parse-determinism** (property 1 of the post-form-a Roundtrip
|
||||
//! Invariant, design/contracts/roundtrip-invariant.md). For every
|
||||
//! Invariant, design/contracts/0009-roundtrip-invariant.md). For every
|
||||
//! well-formed `.ail` text `t`, `parse(t)` produces the same
|
||||
//! canonical bytes every invocation. The parser is a pure
|
||||
//! function of input — no randomness, no time dependence, no
|
||||
@@ -69,7 +69,7 @@ fn list_ail_fixtures() -> Vec<PathBuf> {
|
||||
}
|
||||
|
||||
/// Pins **idempotency-under-print** (property 2 of the post-form-a
|
||||
/// Roundtrip Invariant, design/contracts/roundtrip-invariant.md): for every
|
||||
/// Roundtrip Invariant, design/contracts/0009-roundtrip-invariant.md): for every
|
||||
/// well-formed `.ail` text `t`, the composition `parse → print → parse`
|
||||
/// is idempotent on the AST.
|
||||
///
|
||||
|
||||
Reference in New Issue
Block a user