iter docs-honesty-lint.1: canonical docs present-tense-honest + wrap-robust pin + Sweep 5

Strips Wunschdenken (forward intent stated as fact) and non-citation
post-mortem / doc-archaeology from docs/DESIGN.md (14 corrections incl.
1 Step-14 procedural-catch-all site the spec's illustrative regex would
have missed) + docs/PROSE_ROUNDTRIP.md; genuine forward intent (LLM
tool-use / MCP / LSP) relocated to roadmap P3. Codifies the
tense+modality discriminator as a present-tense DESIGN.md meta-
subsection. Guards two ways: wrap-robust enumerated absent/present pin
(crates/ailang-core/tests/docs_honesty_pin.rs, norm() whitespace-
collapse helper — structurally discharges the recurring grep/contains
line-wrap failure family) + wrap-robust advisory Sweep 5 in
architect_sweeps.sh (contiguous-fragment regex) with full sweep-count
lockstep + new ailang-architect.md "DESIGN.md honesty drift" bullet.
KEEPs preserved (Diverge-reserved, regions-rejected, self-labelled
tiebreaker). No language/checker/codegen/runtime change — sole crates/
change is the additive pin; ail check/run/build byte-unchanged.
Workspace 609/0 (delta +4 pin), sentinel trio green, build clean.
This commit is contained in:
2026-05-18 12:30:03 +02:00
parent de66eb7ab2
commit 7580d434f0
8 changed files with 330 additions and 62 deletions
+53 -48
View File
@@ -53,6 +53,28 @@ evolving in lockstep with the language:
- **Tests**: unit tests per crate plus E2E in `crates/ail/tests/e2e.rs`. Every
new compiler path needs a test, otherwise the feature does not count as done.
### What this document is — and the honesty rule it holds itself to
DESIGN.md describes what AILang **is now**: schema, semantics,
invariants, runtime contracts, and the rationale for each. It is
present-tense by construction. Two things never belong here:
- **Forward intent** ("planned / will back / on the path to /
if-when X arrives") — that lives in `docs/roadmap.md`.
- **History** ("an earlier draft said / previously / what changed
in <date> / retired in iter Y") — that lives in `docs/journals/`.
The single legitimate exception is a present-tense reserved or
deliberately-excluded claim that is explicitly and correctly
labelled (e.g. "`Diverge` is a reserved effect name with no op and
no codegen"; "No `deriving` — instances are written by hand";
"Regions were considered and rejected; see X"). The discriminator
is not whether a sentence mentions past or future, but whether the
document asserts something exists, works, or changed that does
not. Design rationale — *why* the language excludes a thing — is
in scope and present-tense; the *narrative of how the document
reached that wording* is not.
When the language grows, these components grow with it. New tools that
strengthen the LLM tooling (e.g. `ail diff`, IR snapshot diffs, new agents)
explicitly belong in the ecosystem inventory of this section and are added
@@ -481,8 +503,8 @@ on the shelf for a future iter only if both fail.
to every existing subcommand; the parser is a producer, not a
gatekeeper.
**Both extensions accepted.** Since iter ext-cli.1 (2026-05-12),
every path-taking CLI subcommand accepts either `.ail` (Form A)
**Both extensions accepted.** Every path-taking CLI subcommand
accepts either `.ail` (Form A)
or `.ail.json` (Form B) as input. For `.ail` paths the subcommand
parses through `ailang_surface::parse` in-line and then proceeds
with the same loaded `Module` value that `.ail.json` would have
@@ -500,9 +522,8 @@ on the shelf for a future iter only if both fail.
- It does not change which form is canonical: the JSON-AST remains
the hashable, content-addressed representation, and all hashing,
content-addressing, and cross-module references flow through it
unchanged. What did change in 2026-05-13 (this milestone) is
which form is authored: authors now write Form A; the JSON-AST
is materialised in-process by callers that need it. The two
unchanged. The authored form is Form A; the JSON-AST is
materialised in-process by callers that need it. The two
forms are byte-isomorphic by the round-trip invariant. Form (A)
is one projection; the JSON-AST stays canonical.
- It does not foreclose visual or graphical front-ends. The crate
@@ -647,12 +668,11 @@ foreign LLM enough to produce valid output. The current prompt revises this:
test, before its assertions even run. Hand-written content,
mechanical drift detection.
The discussion of richer integration paths (LLM tool-use, MCP
server, LSP) was deferred — all three layer additively on the
static-prompt path ships, which remains the
lowest-common-denominator fallback that always works.
The cycle's lowest-common-denominator path is the static prompt
(`ail merge-prose | client | ail parse | ail check`), which works
with any client.
### Empirical addendum (2026-05-12)
### Empirical addendum — cross-model authoring measurement
Cross-model measurement against two foreign LLMs via IONOS,
temperature=0, top_p=1, max-turns=5. Two blind cohorts on the same
@@ -1315,10 +1335,8 @@ binders is what lets the lint correctly identify `head_or_zero`
as over-strict (could be `borrow`) while staying silent on
`sum_list` where `t: List` *is* moved out.
Severity: `Warning`. The first ever Warning-level diagnostic;
previously all diagnostics were `Error`. CLI exit semantics
adjusted: `ail check`, `ail build`, `ail emit-ir` exit 1 only on
at least one Error. Warnings print but do not abort.
Severity: `Warning`. `ail check`, `ail build`, `ail emit-ir` exit 1
only on at least one `Error`; warnings print but do not abort.
**The suppression: `mode-strict-because`.** Authors
who want to keep an over-strict annotation deliberately (e.g.
@@ -1501,18 +1519,14 @@ acyclicity invariant.
### What this Decision deliberately does not do
- **Does not infer everything.** An earlier draft of this
Decision said "uniqueness is fully inferred" — that was the
mainstream RC position. A follow-up discussion
replaced it: AILang demands annotations *because* the LLM
author can produce them effortlessly. The compiler does
inference *plus* verification of contracts.
- **Does not infer everything.** AILang demands annotations
*because* the LLM author can produce them effortlessly. The
compiler does inference *plus* verification of contracts.
- **Does not require existing fixtures to migrate immediately.**
`(con T)` is treated as `(own T)`. Existing JSON hashes
stay bit-identical until the fixture is intentionally updated.
- **Does not commit to atomic refcounts.** AILang is currently
single-threaded. If/when concurrency arrives, atomic-vs-non-
atomic will be a separate decision per allocation kind.
- **Does not commit to atomic refcounts.** AILang is
single-threaded; refcounts are non-atomic.
- **Does not introduce regions.** Regions were considered and
rejected; see "Why not other memory models" above.
@@ -1834,9 +1848,7 @@ Superclass chains are linear (single-superclass relation per class)
and auto-expansion closes transitively across the chain.
**No `deriving`.** AILang does not auto-derive instances. `instance Eq
MyType` must be written by hand. Auto-derivation may land in a future
iteration if the Feature-acceptance criterion holds for it; that
discussion is out of scope here.
MyType` must be written by hand.
### Diagnostic categories
@@ -2022,10 +2034,8 @@ a => a -> () !IO` shipped in iter 24.3 with body
`\x -> let s = show x in do io/print_str s` (explicit let-binder for
heap-Str RC discipline per eob.1 Str carve-out). The let-binder is
structurally pinned by `crates/ail/tests/print_mono_body_shape.rs`.
Routing through `print` replaces the ad-hoc per-type print
effect-ops; the `io/print_int`, `io/print_bool`, `io/print_float`
primitives were retired 2026-05-14 in iter rpe.1, leaving
`io/print_str` as the only surviving direct-output effect-op.
Routing through `print` is the path for non-`Str` primitives;
`io/print_str` is the only built-in direct-output effect-op.
### Heap-Str primitives
@@ -2042,8 +2052,8 @@ and backed by a `runtime/str.c` C helper.
of an `Int`. Backs `Show Int` in the prelude.
- `bool_to_str : (borrow Bool) -> Str` (iter 24.1) — `"true"`/`"false"`.
Backs `Show Bool` in the prelude.
- `float_to_str : (borrow Float) -> Str` (iter 24.1, type-installed;
codegen ships in a future iter per roadmap). Will back `Show Float`.
- `float_to_str : (borrow Float) -> Str` (iter 24.1) —
type-installed; codegen is reserved and not yet shipped.
- `str_clone : (borrow Str) -> Str` (iter 24.1) — allocates a fresh
heap-Str copy of the input's bytes. Backs `Show Str` in the prelude.
- `str_concat : (borrow Str, borrow Str) -> Str` (iter str-concat,
@@ -2060,9 +2070,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 (§"Polymorphic print"), which feeds
the heap-Str result of `show x` into `io/print_str`. The per-type
effect-ops `io/print_int`, `io/print_bool`, `io/print_float` were
retired in iter rpe.1 (2026-05-14).
the heap-Str result of `show x` into `io/print_str`.
`==`, `<`, `<=`, `>`, `>=` REMAIN primitive operators (unchanged
from the original draft). Class methods are accessed by name (`eq x y`,
@@ -2081,11 +2089,10 @@ would invoke literal-defaulting which axis-7 already excluded.
22. Class-routing operators is a future-iteration option, not a
Decision-11 commitment.
- **Form-B (prose) projection of class/instance.** The prose
renderer for the new schema nodes is one-way (no parser by
design) and was deferred from milestone 22 entirely; the
placeholder render in `crates/ailang-prose/src/lib.rs` is
informational only. A future iter ships the full prose
projection if/when prose-side authoring needs surface.
renderer for the class/instance schema nodes is one-way (no
parser by design); the render in
`crates/ailang-prose/src/lib.rs` is a placeholder and
informational only.
- **Specific monomorphised-symbol naming format.** Milestone 22
uses `<method>__<type-surface-name>` for primitive type targets
(e.g. `show__Int`) and `<method>__<8-hex-prefix>` for compound
@@ -2130,12 +2137,9 @@ Concretely:
4. **Carve-out anchor.** Seven `.ail.json`-only fixtures
(subject-matter rejection tests) survive in the corpus by
structural necessity. They participate in their own dedicated
rejection-shape tests, not in the round-trip gate. (The
prelude's compile-time-embed carve-out, originally listed
here as the eighth fixture, was retired 2026-05-14 by
milestone prelude-decouple; the prelude is now embedded as
`examples/prelude.ail` in `ailang-surface` and parsed at
compile time via `ailang_surface::parse_prelude`.)
rejection-shape tests, not in the round-trip gate. (The prelude is
embedded as `examples/prelude.ail` in `ailang-surface` and parsed
at compile time via `ailang_surface::parse_prelude`.)
Hashing is the consequence the language depends on: BLAKE3 of the
canonical bytes is well-defined for any `.ail` source via parse-
@@ -2516,8 +2520,9 @@ 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 and in-place
rewrites respectively. Boehm-on-`--alloc=gc` is on the path to
retirement; see `docs/roadmap.md` for the active queue.
rewrites respectively. `--alloc=gc` selects the transitional Boehm
backend; `--alloc=rc` is the canonical backend (Decision 10) and the
CLI default.
The **desugar** pass (`ailang-core::desugar::desugar_module`) runs
before typecheck and codegen in every entry point of `ailang-check`