Commit Graph

15 Commits

Author SHA1 Message Date
Brummel 4fc65ccb99 iter schema-camelcase-fix.1 (DONE 4/4): paramTypes/retType → param-types/ret-type — closes #30
`Term::Lam`'s two camelCase JSON tags become kebab-case, matching
the convention every other compound-key tag in the AST schema
already follows. After this iter the canonical JSON has zero
camelCase outliers.

## Schema swap (atomic, Task 2)

- `crates/ailang-core/src/ast.rs:492,494` — two
  `#[serde(rename)]` strings: `"paramTypes" → "param-types"`,
  `"retType" → "ret-type"`. Rust field names unchanged.
- `crates/ailang-core/src/workspace.rs:1925-1926` — in-source
  JSON literal in the `ct1_validator_walks_lam_embedded_types`
  test.
- `design/contracts/data-model.md:142-147` — fenced JSON block
  in the canonical data-model contract. Honesty-Rule touch-point:
  the contract document must describe the actual present-state
  schema.
- `examples/test_loop_binder_captured_by_lambda.ail.json` and
  `experiments/2026-05-12-cross-model-authoring/master/examples/fn_with_lambda.ail.json`
  — the two `.ail.json` fixtures whose canonical-JSON embeds
  the renamed tags.

The five files moved together within one task because `Term::Lam`
has no `#[serde(default)]` on `param_tys` / `ret_ty` — a
renamed-away key is a hard deserialise error. Splitting the swap
would have left the workspace untestable mid-step.

## RED → GREEN pin (Tasks 1, 2)

`crates/ailang-core/tests/design_schema_drift.rs` gains
`lam_serialises_with_kebab_keys`: pins that `serde_json::to_value(&Term::Lam{...})`
emits `"param-types"` / `"ret-type"` AND does not emit the old
camelCase keys. Companion: the existing
`design_md_anchors_every_term_variant` test now also asserts the
kebab anchors appear inside `data-model.md`'s `lam` fenced block.
Both confirmed RED on entry, GREEN after Task 2.

## Rustdoc honesty pass (Task 3)

Three pure-prose edits keep production rustdoc consistent with
the new schema vocabulary: `crates/ailang-core/src/ast.rs:8` (the
module-level rename enumeration), `crates/ailang-surface/src/parse.rs:81`
(prose mention of `lam`'s carry), `crates/ailang-check/src/lib.rs:1707`
(InstanceMethod routing helper rustdoc). No compile or test
consequence — Honesty-Rule maintenance.

## Plan-pseudo-vs-reality finding: under-audited hash blast radius

The brainstorm spec audited `crates/ailang-core/tests/hash_pin.rs`
exhaustively (5 pinned modules, zero `(lam ...)` occurrences,
"no hash refresh required"). It missed
`crates/ailang-surface/tests/prelude_module_hash_pin.rs` — a
separate hash-pin file in a different crate that pins `prelude.ail`,
which contains 11 `(lam ...)` forms. The prelude module hash
drifted (`6d0577ff0d4e50ac` → `562a03fc57e7e017`); the implementer
refreshed it with a Honesty-Rule provenance comment matching the
precedent set in commit `26fb345`. Form-A is unchanged — only the
canonical-JSON byte stream differs, which is exactly what the
rename targets. The spec-side learning is: schema-rename
brainstorms must walk *every* test crate for hash-pin files, not
just the home crate of the AST.

## Why this shape, and not the alternatives

- *`params-ty` / `ret-ty` (Rust-field-name vibe)* — rejected. The
  AST JSON schema follows its own kebab/single-word convention,
  not Rust field names. The other multi-word tag (`reuse-as`) is
  kebab; abbreviating `params-ty` mixes singular+plural and has
  no precedent.
- *Inline typed params as a sub-tag* (e.g. `params: [{name, type}, ...]`)
  — rejected. Stronger semantic locality, but it changes schema
  topology rather than tag spelling. Out of scope for a camelCase
  correction; would have re-pinned far more than this milestone.
- *Bundle with #27 (arith-rename)* — rejected. #27 carries four
  open brainstorm questions (mod vs rem, neg vs sub 0, Num class,
  deprecation window) and is structurally a separate milestone.
  Each gets one re-pin wave with its own rationale; no churn
  saving from bundling.

## Honest call on the feature-acceptance gate

Clause 1 (LLM author naturally uses the new form) is the weakest
of the three. Direct empirical evidence is absent — the 2026-05-21
naming-A/B run measured a different axis. The argument is
indirect: a future LLM author generalises from the rest of the
schema ("compound keys are kebab"), and the two camelCase
outliers are precisely the sites where that generalisation
diverged from reality. Clause 2 (redundancy reduction) and
Clause 3 (no semantic surface touched) are direct.

## Forbidden touches verified untouched

`docs/plans/*.md` historic plans (which embed old tag names in
example JSON), `experiments/2026-05-12-cross-model-authoring/master/spec.md`,
`experiments/.../rendered/*.md`, and `experiments/.../runs/**` —
all left as frozen historical artefacts per Honesty-Rule
analogue (describe state at time of writing, not present state).
Verified via `git diff --name-only HEAD` (Task 4 Step 4).

## Verification

- `cargo test --workspace`: all test groups OK, 0 failed,
  2 ignored (pre-existing).
- `cargo test -p ailang-surface --test round_trip`: GREEN
  (Form-A unchanged invariant).
- New schema-shape pin and the extended data-model anchor walk:
  GREEN after Task 2.
- Negative grep across `crates/`, `examples/`, `design/`,
  `runtime/`: the only remaining `paramTypes` / `retType`
  occurrences in checked-in code are the load-bearing
  negative-assertion strings inside the new pin (intentional —
  they are what makes the pin RED-able on regression).
- Stats: `bench/orchestrator-stats/2026-05-21-iter-schema-camelcase-fix.json`
  — 4/4 tasks DONE, no re-loops, no review-loops.

closes #30
2026-05-21 12:57:06 +02:00
Brummel 26fb3459d8 GREEN: io/print_str byte-faithful via @fputs(@stdout) — closes #29
The runtime print path now writes exactly the bytes of its
argument with no implicit trailing newline. `io/print_str` is
byte-faithful; authors who want a newline emit `(do io/print_str
"\n")` themselves.

## Codegen

`crates/ailang-codegen/src/lib.rs`:
- Module preamble: `@puts(ptr)` → `@fputs(ptr, ptr)` plus
  `@stdout = external global ptr` (libc's `FILE *stdout`).
- Effect-op lowering for `io/print_str`: emit
  `getelementptr +8` then `load ptr, ptr @stdout` then
  `call/tail call i32 @fputs(ptr bytes, ptr fp)`. Identical
  bytes-pointer GEP, distinct sink.
- The pinned IR-shape test renames from
  `print_str_calls_puts_with_bytes_pointer` to
  `print_str_calls_fputs_with_bytes_pointer_and_stdout` and now
  asserts: bytes-GEP present, stdout-load present, both module-
  preamble declarations present, and no `@puts(` call anywhere
  in the emitted IR.

## Why this shape, and not the alternatives

- *Rename `io/print_str` to `io/println_str` (issue #29 option 2)*
  — kept the auto-newline, just relabelled it. AILang's design
  bias is explicit-over-implicit (CLAUDE.md: implicit conversions
  cut). Auto-newline is a hidden runtime augmentation; the rename
  would have preserved it. Rejected.
- *Append `\n` inside the polymorphic `print` (Show-mediated)
  function in `examples/prelude.ail`* — would have been a one-line
  fix. Rejected: `print` is the Show-mediated formatter, not a
  newline emitter; baking a newline into it would have re-imposed
  the same implicit-augmentation problem one layer up, breaking
  callers that legitimately want pure-bytes output.

## Fixture / test sweep

30 `.ail` fixtures whose owning tests asserted line-separated
stdout now emit explicit `(do io/print_str "\n")` after each
print. Tests that asserted on multi-line stdout (`show_print_e2e`,
`floats_e2e`, `str_concat_e2e`, `eq_ord_e2e`, several `print_*`
smoke tests) had their fixtures sweetened the same way; assertions
themselves remain the canonical observable output. Fixtures that
never relied on the newline (no test ever read the absence of one)
were left untouched.

## Migrated metadata

- `crates/ailang-core/tests/hash_pin.rs`: the `ordering_match::main`
  canonical hash is refreshed (`b65a7f834703ffb4` →
  `8ed47b4062ce00f5`). The comment now names both successive
  corpus migrations honestly: the per-type-print-retirement (which
  moved `(do io/print_int x)` to `(app print x)`) AND this
  fputs swap (which wrapped that with `(seq ... (do io/print_str
  "\n"))`).
- `design/contracts/str-abi.md`: the consumer-ABI table now lists
  `@fputs` as the print sink. A prose paragraph documents the
  byte-faithful semantics and references this issue.
- `examples/ordering_match.prose.txt`: regenerated from the
  updated `ordering_match.ail`.
- `crates/ail/tests/snapshots/{hello,sum,max3,list,ws_main}.ll`:
  IR snapshots regenerated via `UPDATE_SNAPSHOTS=1`.
- Stale `@puts` comments in `runtime/str.c`,
  `crates/ail/tests/{e2e,show_print_e2e,print_no_leak_pin}.rs`
  replaced with `@fputs`.

## Verification

- `cargo test -p ail --test print_str_no_auto_newline_e2e` — both
  RED tests from commit c8ecfa3 now pass.
- `cargo test --workspace` — 90 test groups GREEN, 0 failures.
- `cargo build --workspace` — GREEN.
- No new clippy lints (24 warnings pre-existing in
  `crates/ailang-core/src/lib.rs:129`).
- Stats: `bench/orchestrator-stats/2026-05-21-iter-bugfix-print-
  str-fputs.json` — 1/1 tasks, 0 re-loops, 0 review loops.

## Empirical evidence cited

Caught in the 2026-05-21 Qwen3-Coder naming-A/B run
(`experiments/2026-05-21-naming-ab/runs/r1/`): every cohort wrote
`(do io/print_str "...\n")` with explicit `\n` and got doubled
newlines, failing the `t3_main_prints` stdout match across all
three cohorts. The empirical LLM-natural form already assumes the
new (post-this-commit) semantics — confirming the
feature-acceptance test in CLAUDE.md.

closes #29
2026-05-21 12:22:45 +02:00
Brummel 4d45bc6c56 audit + close: operator-routing-eq-ord — drift_found → post-audit tidy applied inline; CLEAN
Single-iter milestone closing Gitea #1. Architect surfaced 4
honesty-rule drift items (2 high, 2 medium) — all post-Task-13
documentation drift, no codegen behaviour shift. Bench all three
scripts exit 0. Inline tidy applied under the CLAUDE.md trivial-
mechanical carve-out (4 documentation/comment edits on architect-
pre-named sites; no code surface change). No separate tidy iter.

Architect: drift_found → CLEAN-after-tidy.

The architect walked design/INDEX.md, the contracts touched by
the milestone (float-semantics, prelude-classes, str-abi,
scope-boundaries, authoring-surface, typeclasses, method-dispatch),
`git log 86dd7d7..HEAD --format=full` for the three milestone
commits (spec a68d7b6, plan 400ad7c, iter 5170b6a), and the
diff. The 4 drift items + per-item resolution:

  - [high] `design/contracts/prelude-classes.md` — Task-13
    direct-icmp intercept for `lt__Int`/`le__Int`/`gt__Int`/
    `ge__Int`/`ne__Int` was invisible. The Ord-class free-helper
    paragraph described them as routing via `compare` + `match`
    over Ordering, with no statement that at Int they short-
    circuit to direct `icmp`. The IR shape is actively pinned by
    `ord_int_intercept_ir_pin.rs` and load-bearing for
    `bench_closure_chain` perf. Tidy: new paragraph added naming
    the Int-direct family explicitly, the bench_closure_chain
    rationale, and the deliberate Int-only asymmetry (Bool/Str
    still go through compare→match because no current fixture
    exercises Ord-helpers there at hot-loop frequency).

  - [high] `crates/ailang-codegen/src/lib.rs:2777-2789` —
    rustdoc on `try_emit_primitive_instance_body` was three
    iterations stale: claimed "currently inhabited arms: `eq__Str`
    and `compare__Int/Bool/Str`" (= 4 arms, was correct as of
    iter 23.3), and claimed `eq__Int`/`eq__Bool` "ride the natural
    `lower_eq` dispatch via their lambda body `(== x y)`". Both
    claims are factually false post-milestone (the arm set is now
    14: 4 Eq + 3 compare + 5 lt/le/gt/ge/ne + 6 float_*; lower_eq
    is deleted; `==` is no longer a surface name). Tidy: rustdoc
    rewritten to enumerate the actual 14 arms, explain the
    alwaysinline-pairing, name the Int-direct optimization
    asymmetry, and cross-reference prelude-classes.md.

  - [medium] `crates/ail/tests/prelude_eq_alwaysinline_ir_pin.rs:19-21`
    — comment asserted the test could not use `eq_demo.ail`
    because "`eq_demo.ail` calls `(app == …)` directly which today
    uses the `lower_eq` direct-emit short-circuit and never
    references the prelude eq__Int symbol". Both factual claims
    became false the moment Task 4's fixture migration landed
    (eq_demo.ail now calls `(app eq …)`) and Task 7 deleted
    lower_eq. The test functions correctly but the rationale-
    comment lied about why the fixture choice mattered. Tidy:
    comment rewritten to describe the actual test path —
    `eq_ord_user_adt.ail` invokes `(app eq …)` on IntBox whose
    user-instance body internally calls `(app eq ai bi)` on Int,
    monomorphising `prelude.eq__Int` into the emitted IR where
    the alwaysinline attribute on the `define` line is the
    assertion target.

  - [medium] `design/contracts/typeclasses.md` — implicit-prelude-
    import was load-bearing at codegen for the new bare-prelude-
    fn surface (`(app float_eq …)` etc. without explicit
    `prelude.float_eq` qualifier) but no contract named it. The
    codegen extension (`lower_app::resolve_callee`,
    `is_static_callee`, `resolve_top_level_fn` each gained a
    parallel prelude-fallback during iter Task 2/3) was a
    silent dependency on a workspace invariant that lived only
    in typechecker-side comments. Tidy: invariant (4) added as a
    sibling to the existing three invariants on cross-module
    typeclass references, naming the source-level codegen
    fallback as the symmetric partner to invariant (2)'s post-
    mono fallback; the summary paragraph extended from "three
    invariants are lockstep partners" to four, with
    `float_compare_smoke_e2e.rs` named as the invariant-(4)
    ratifier.

The four fixes are pure-docs/comment work (no `.rs` code path
changed, no IR shape changed); the workspace passing-test count
is unchanged (640 still GREEN, 0 FAILED). No code regression
risk; therefore inline-apply rather than dispatching a separate
planner+implement cycle was the right cost-benefit call.

Bench: 0/0/0.

  - bench/check.py exit 0 — 36 metrics, 0 regressed (full
    bench_closure_chain healed by Task 13's direct-icmp
    intercept arms — bump_s -6.05% rc_s -2.67% bump_rss_kb
    -0.77% rc_rss_kb +0.46%, all four previously-regressed
    metrics back inside tolerance).
  - bench/compile_check.py exit 0 — 24 metrics, 0 regressed.
    Largest delta build_O0_ms.bench_list_sum_explicit +11.04%
    (within 20% tolerance — operator-routing adds two extra
    intercept-arm matches on the compile hot-path, marginal).
  - bench/cross_lang.py exit 0 — 25 metrics, 0 regressed.

No baseline updates needed; no `--update-baseline` invoked. The
intercept-arm extension in Task 13 explicitly aimed at restoring
the pre-milestone baseline; bench data ratifies that the aim was
met.

Pipeline this milestone followed:

  brainstorm (one-question-at-a-time Q&A, three design forks
              resolved: operator-names die from surface; Float
              keeps named fns; codegen via always-call +
              alwaysinline pre-emptive mitigation; grounding-
              check PASS over 11 load-bearing assumptions, after
              one BLOCK → spec defect about desugar.rs:2414
              mischaracterisation fixed)
  -> planner (recon DONE_WITH_CONCERNS, 4 substantive spec gaps
              absorbed inline: 60 fixtures not ≈10, 8 in-source
              AST-literal sites not 3, 5 contract files not 3,
              alwaysinline mechanism new to crate; plus 2 self-
              review items absorbed inline: north-star RED-first
              via Unit-eq line; lockstep edits at codegen/lib.rs:2529)
  -> implement (DONE 12/12 from orchestrator; Boss reclassified
                as PARTIAL via acceptance-#8-fail after independent
                bench re-run; iter scope extended to Task 13 via
                SendMessage to same orchestrator-context; final
                DONE 13/13)
  -> audit (drift_found, 4 items inline-tidy applied: prelude-
            classes.md paragraph + codegen rustdoc refresh + test
            comment rewrite + typeclasses.md invariant-(4)
            addition; bench 0/0/0; CLEAN-after-tidy)

Closing Gitea #1. The `closes #1` trailer lived on the iter
commit `5170b6a` and fired on push.

Spec: docs/specs/2026-05-20-operator-routing-eq-ord.md
Plan: docs/plans/operator-routing-eq-ord.1.md
2026-05-21 01:26:13 +02:00
Brummel 5170b6abd1 iter operator-routing-eq-ord.1 (DONE 13/13): drop comparator builtins, route through Eq/Ord
Closes Gitea #1. Realises the "P2 follow-up" called out in
examples/prelude.ail:9 — removes the surface comparator names
`==` / `!=` / `<` / `<=` / `>` / `>=` from the language entirely,
routes the LLM-author's `(app eq …)` / `(app compare …)` /
`(app ne|lt|le|gt|ge …)` through prelude.Eq / prelude.Ord class-
dispatch, ships six named Float-comparison fns
(`float_eq`/`float_ne`/`float_lt`/`float_le`/`float_gt`/`float_ge`)
so Float keeps comparability without an Eq/Ord instance, and emits
primitive instance bodies with `alwaysinline` so the -O0 IR shape
stays at one instruction per comparison.

Plan-task journal (13 tasks, single atomic iter per Approach A):

  Task 1 — Bootstrap: 4 new fixtures (eq_user_adt_smoke.ail,
    eq_float_must_fail.ail, float_compare_smoke.ail,
    operator_unbound_check.ail) + 5 new E2E/pin tests as RED
    starting state. All 5 confirmed RED at start: north-star
    fixed `NoInstance Eq Unit` (preserved by Unit-eq opening line
    intentionally added in plan-self-review); float_compare unknown
    `float_eq`; operator-name typechecked (== still polymorphic);
    must-fail diagnostic lacked `float_eq`; alwaysinline absent
    from IR.

  Task 2 — Codegen alwaysinline + intercept arms: introduced
    `intercept_emit_wants_alwaysinline` allowlist + appended
    ` alwaysinline` between `)` and `{` of the `define` line in
    `emit_fn`; added 9 new intercept arms to
    `try_emit_primitive_instance_body` — `eq__Int`/`eq__Bool`/
    `eq__Unit` + the six `float_*` arms.

  Task 3 — Prelude reshape: `instance Eq Unit` added; Eq Int/Bool
    bodies become placeholder-`false` (intercept overrides); six
    `float_*` free fns added; line-9 P2-follow-up comment removed.
    Lockstep hash re-pins: prelude_module_hash_pin (3abe0d3fa3c11c99
    → new), mono_hash_stability six body-hash literals
    (eq__Int/Bool/Str + compare__Int/Bool/Str all shifted because
    placeholder body changes the canonical hash). IR-snapshot
    regen (hello/list/max3/sum/ws_main) rolled forward into this
    task at orchestrator's pragmatic call — prelude shift forces
    the snapshots immediately.

  Task 4 — Fixture migration: 58 .ail fixtures rewritten
    (`(app == …)` → `(app eq …)`, etc.; Float-typed operand sites
    to `(app float_eq …)` / `(app float_lt …)`). eq_ord_user_adt.ail:21
    inner `==` → `eq` migration with lockstep eq_ord_e2e.rs:134
    body-hash re-pin (3c4cf040cb4e8bb2 → new). Two prose snapshots
    accepted; deps test + 5 hash_pin literals updated as cascading
    consequences.

  Task 5 — Test-scaffold migration: all 8 in-source
    `#[cfg(test)] mod tests` AST-literal sites threaded
    (desugar.rs:2414, check/lib.rs:5656/6092/6220, codegen/lib.rs
    sites). 7 obsolete in-source tests deleted (5 eq_typechecks +
    2 lower_eq ADT/Fn rejection — coverage moves to E2E
    eq_user_adt_smoke + eq_float_must_fail). 2 additional letrec
    tests deleted (single-module check env can't resolve eq/ge
    without prelude auto-import; covered by workspace E2E).

  Task 6 — Lit-pattern desugar: build_eq emits
    `Term::Var { name: "eq" }` instead of `"=="` at desugar.rs:1109;
    doc-comment rewritten to describe class-dispatch.

  Task 7 — Dead-machinery deletion sweep (compile-gated): typchecker
    builtins (install + list comparator entries deleted); codegen
    builtin_binop_typed (10 comparator arms deleted from synth.rs,
    table reduced to arithmetic core); lower_eq fn entirely
    deleted; `==` short-circuit in lower_app deleted;
    `is_static_callee` `==` clause deleted;
    `is_arithmetic_or_comparison_op` renamed to `is_arithmetic_op`
    + caller-update at codegen/lib.rs:2152 + :2529. Dead
    `poly_a_a_to_bool` helpers also removed. Workspace build green
    after compile gate — every caller of the deleted surface was
    migrated by Tasks 4-6.

  Task 8 — Float-aware NoInstance diagnostic: check/lib.rs:856-880
    addendum extended to name `float_eq` / `float_lt` as the
    explicit alternative for Eq/Ord at Float;
    eq_float_noinstance.rs:32-44 assertion extended.

  Task 9 — IR snapshot regen: subsumed by Task 3 (prelude shift
    forced immediate snapshot regen; deferring to Task 9 would
    have left the workspace red between tasks).

  Task 10 — Prose-projection cleanup: 6 comparator arms deleted
    from binop_info; 3 in-source mod-tests updated/deleted
    (comparator-infix rendering would be dishonest now that the
    operators are no longer language identifiers).

  Task 11 — Contract updates: 5 design files rewritten to the
    class-dispatch present —
      float-semantics.md (arithmetic guarantees retained on
        +/-/*/; comparison guarantees transferred from ==/!=/<
        to float_eq/float_ne/float_lt/etc.);
      prelude-classes.md (Eq Unit added to instance list;
        new paragraph on six float_* fns; Float-no-Eq/Ord clause
        gains `→ use float_eq` cross-reference);
      str-abi.md (clause "REMAIN primitive operators" rewritten
        to describe class-method dispatch);
      scope-boundaries.md (multiple operator-name and
        Pattern::Lit-desugar clauses rewritten);
      authoring-surface.md (==, <= dropped from operator-example
        list).

  Task 12 — Initial acceptance gate: workspace 638/0 GREEN;
    bench/compile_check + cross_lang 0 regressed; bench/check.py
    flagged 4 regressions on bench_closure_chain (+29% bump_s,
    +47% rc_s, +29% bump_rss_kb, +48% rc_rss_kb). Orchestrator
    initially classified as DONE-with-concerns; Boss reclassified
    as PARTIAL-via-acceptance-#8-fail after independent re-run,
    extended iter scope to Task 13.

  Task 13 — Direct icmp intercept arms (Boss-extension):
    try_emit_primitive_instance_body gains direct-icmp arms for
    lt__Int / le__Int / gt__Int / ge__Int / ne__Int, bypassing
    the compare__Int → Ordering → match indirection that allocated
    one Ordering ctor per call in tight loops. Each new arm
    inherits `alwaysinline` via the existing allowlist (extended
    accordingly). New IR pin test `ord_int_intercept_ir_pin.rs`
    + smoke fixture `ord_int_intercept_smoke.ail` ratify the
    optimization — opt -O2 -S confirms zero `call
    @ail_prelude_lt__Int` in optimized IR; the icmp folds directly
    at every use site. Bool variants (lt__Bool/etc.) deliberately
    NOT added — no bench/example calls Ord at Bool; spec-extension
    permits skipping if unreachable at bench level. Family can be
    extended symmetrically when first Bool-ordered perf workload
    appears.

Bench-gate post-Task-13: bench_closure_chain bump_s -6.05%,
rc_s -2.67%, bump_rss_kb -0.77%, rc_rss_kb +0.46% — all four
previously-regressed metrics back inside tolerance. Full bench
corpus: 36 metrics, 0 regressed, 0 improved beyond tolerance,
36 stable. bench/check.py exit 0 ✓; bench/compile_check.py exit
0 ✓; bench/cross_lang.py exit 0 ✓.

Workspace: 640 passed, 0 failed across all binaries (delta vs.
milestone start: +5 new E2E/pin tests added in Task 1 + 1 IR pin
added in Task 13 − 9 in-source mod tests deleted as obsolete net
≈ −3). The eq_user_adt_smoke fixture's Unit-opening line was
the deliberate RED-first device added in planner self-review so
the north-star wouldn't accidentally GREEN at start (user-ADT-Eq
on Point with hand-written instance was already operable today;
the milestone's actual delivery is operator-name death + Eq Unit
+ Float-named-fns + cleanup of the two-pathy primitive
comparator machinery).

Net delta: 96 files changed, 1760 insertions, 1101 deletions;
12 net-new files (6 new fixtures, 5 new E2E/pin tests, 1 stats
file). main passes-test-count: 640 (was 633 pre-iter, accounting
for the deletions).

Spec-vs-acceptance addendum: spec §Testing strategy anticipated
the bench-gate-regression case with two recovery paths
(`alwaysinline` investigation or "spec needs revisiting toward α").
Task 13 is the third path — direct intercept arms for `lt`/etc.
that bypass the compare→match path entirely. The spec's contingency
clause was thus generous enough to absorb Task 13 without spec
revision, but a future iter that hits a class-method primitive
where the body shape introduces a similar codegen cost (Ordering
allocation, RC tax, deferred-init) should expect a parallel
extension. The pattern is "primitive instance whose canonical body
indirects through other class methods that allocate" → add a
direct-emit intercept arm + alwaysinline + IR-shape pin.

Concerns absorbed:
  - Codegen lower_app / resolve_top_level_fn gained a
    prelude-fallback lookup so bare monomorphic prelude fns
    (`float_eq` etc.) resolve from non-prelude modules without
    explicit `prelude.float_eq` qualifier. Mirrors the
    typechecker's implicit prelude import — not in plan but
    necessary infra for the named-fn surface to be callable.
  - Recon's "≈10 fixtures" estimate was 6× too low — 58 actual.
    Mechanical migration; no design impact.
  - Recon caught 4 in-source AST-literal sites the spec missed
    (lib.rs:5656 `>=` site + three codegen/lib.rs sites);
    Task 5 covered all.
  - Recon caught 2 contract files outside the spec's update set
    that directly contradicted the milestone (str-abi.md +
    scope-boundaries.md "REMAIN primitive operators" /
    "Pattern::Lit desugar to ==" clauses); Task 11 covered all 5.

Stats file:
`bench/orchestrator-stats/2026-05-21-iter-operator-routing-eq-ord.1.json`.

closes #1
2026-05-21 01:16:21 +02:00
Brummel 14a91f0ae5 iter boehm-retirement.1 (DONE 10/10): retire the transitional Boehm GC backend
Closes Gitea #4. Removes the Boehm-Demers-Weiser conservative GC
backend wholesale across six layers in one atomic iteration. After
this iter, `AllocStrategy` has two variants (`Rc`, `Bump`),
`--alloc=gc` is rejected at CLI parse with `unknown --alloc value`,
the libgc link arm is gone, and the design ledger describes RC
(canonical) + bump (raw-alloc bench-floor) as the only allocators.

Layer-by-layer summary:

  CLI surface — `crates/ail/src/main.rs`:
    `parse_alloc_strategy` arm `"gc" => Ok(AllocStrategy::Gc)`
    removed; error wording updated to `(expected `rc` or `bump`)`;
    clap-derive `value_parser = ["gc","bump","rc"]` allowlist on
    BOTH `Build` and `Run` subcommands DROPPED so that
    `parse_alloc_strategy` remains the sole gatekeeper for the
    unknown-value diagnostic (otherwise clap shadows the runtime
    diagnostic with `invalid value 'gc' for '--alloc'`, which would
    miss the milestone-pin's stderr substring check). The
    `default_value = "rc"` stays.

  Codegen — `crates/ailang-codegen/src/lib.rs`:
    `AllocStrategy::Gc` variant + `Default` derive removed (no
    caller of `AllocStrategy::default()` existed in the workspace,
    so the trait derivation was dead). `fn_name` (spec called it
    `runtime_alloc_fn` loosely; actual identifier is `fn_name`)
    drops the `Gc => "GC_malloc"` arm. `lower_workspace` and
    `lower_workspace_staticlib` defaults flip from `Gc` to `Rc`.
    In-source negative-complement codegen test (mod tests, lib.rs:3571ff)
    retargets from `AllocStrategy::Gc` to `AllocStrategy::Bump`
    (bump also doesn't emit per-type drop fns; the test's semantic
    "no drop fns under non-RC" is preserved).

  Link branch — `crates/ail/src/main.rs:2389ff`:
    The `match strategy { AllocStrategy::Gc => { ... cmd.arg("-lgc"); ... } }`
    arm and its libgc-link block are entirely gone. The surviving
    match exhausts on `Bump` and `Rc` (Rust's exhaustiveness check
    confirms; no `error[E0004]`). Staticlib-guard diagnostic
    rewritten to drop the "shared Boehm collector" phrasing while
    preserving the prefix `staticlib (swarm) artefact is RC-only`
    verbatim (the surviving `staticlib_bump_is_rejected` test
    depends on that substring).

  Test suite — 3 pure-differential e2e tests deleted
    (`gc_handles_recursive_list_construction`,
    `alloc_rc_produces_same_stdout_as_gc`,
    `alloc_rc_matches_gc_on_std_list_demo`); 9 RC-feature tests
    stripped of their `stdout_gc` build call and differential
    `assert_eq!(stdout_gc, stdout_rc, ...)` (absolute
    `assert_eq!(stdout_rc.trim(), "<n>")` pin retained as
    correctness oracle); `staticlib_gc_is_rejected` deleted; new
    milestone-pin `crates/ail/tests/boehm_retirement_pin.rs`
    asserts `ail build --alloc=gc` exits ≠ 0 with stderr containing
    `unknown --alloc value` and `\`gc\``; `examples/gc_stress.ail`
    fixture deleted (no remaining references).

    Implementer expansion (not in plan): `iter17a_local_box_alloca`
    (in `e2e.rs`) carried an IR-shape assertion against
    `@GC_malloc`-absence as the witness for non-escaping
    allocation. After the Task-2 codegen default flip, the witness
    shifts to `@ailang_rc_alloc`-absence in escape-targeted
    positions; assertion + doc-comment updated. Property
    protected ("no heap allocation in non-escaping contexts") is
    unchanged; only the named allocator shifts.

  Bench harness — `bench/run.sh` 9→6 column compaction
    (workload + bump(s) + rc(s) + rc/bump + bump RSS + rc RSS);
    gc-arm `bench_latency_implicit_gc` build call + harness
    invocation dropped from latency block; header comment reframed
    from "GC-overhead bench harness" to "RC-overhead bench
    harness"; "Decision 10's Boehm-retirement target (1.3x)"
    rewording to "RC-overhead-vs-bump bench-health regression gate".

    `bench/check.py:62` header-sentinel changes from
    `"gc(s)" in line` to `"bump(s)" in line`; column-count check
    at `:72` flips from `!= 9` to `!= 6`; per-workload field set
    drops `gc_s`/`gc_over_bump`/`gc_rss_kb`; `ARM_LABEL_TO_KEY`
    drops the `"implicit @ gc": "implicit_at_gc"` entry.
    `bench/baseline.json` regenerated via `--update-baseline`.

    Implementer note (planner-defect): `write_new_baseline`
    iterated over the *existing* baseline's metric list when
    emitting the regenerated file, so even after parser-level
    `gc_*` removal, the fallback emitted them back into the JSON.
    Scrubbed post-update; the cleaner fix (have
    `write_new_baseline` emit only keys present in
    `parsed_throughput[workload]`) is a follow-up if the script
    becomes load-bearing for further allocator changes.

  Design ledger — `design/models/rc-uniqueness.md` excises the
    `## Dual allocator — RC canonical, Boehm parity oracle`
    section and the `Boehm-Demers-Weiser conservative GC` choice
    block + rationale + trade-offs; the per-fn-alloca section
    generalises Boehm-specific language to allocator-agnostic;
    the memory-model section's `## Choice.` paragraph reframes the
    1.3× target from "Boehm-retirement gate" to "bench-health
    regression gate".

    `design/models/pipeline.md` drops the `--alloc=gc → links libgc`
    arm of the pipeline diagram and replaces it with
    `--alloc=bump → links bump-floor`; the accompanying prose
    rewrites accordingly.

    `design/contracts/scope-boundaries.md` rewrites the
    "Memory management via Boehm conservative GC" bullet to
    describe RC + per-fn-arena present-tense; the dead reference
    to `examples/gc_stress.ail.json` (file never existed; the
    fixture only ever had a `.ail` form, deleted by this iter) is
    dropped along with the `examples/std_list_stress.ail.json`
    reference whose purpose was Boehm-only soak testing.
    `:67`'s `@printf` / `@GC_malloc` parenthetical updated.

    `design/contracts/memory-model.md:232` drops the
    "leaks like the pre-Boehm era" phrase; the RC inc/dec
    instrumentation is wired up, so the "until then" conditional
    that referenced pre-Boehm is closed.

    `design/contracts/embedding-abi.md:42-44` rewrites the
    staticlib-guard prose to drop the `--alloc=gc` clause (gc is
    now a CLI-parser-level unknown-value, not a staticlib-guard
    rejection) and reframe the swarm-safety justification around
    `--alloc=bump` (leak-only bench instrument) rather than the
    historical Boehm collector.

  Honesty pin — `crates/ailang-core/tests/docs_honesty_pin.rs`
    inverts the polarity: the present-tense Boehm-anchor assertion
    on `pipeline.md` (`:116-117`) is deleted, and four
    absence-pins are added to `design_md_has_no_wunschdenken`
    against the Boehm-zombie strings `transitional Boehm`,
    `parity oracle`, `GC_malloc`, `libgc`. The
    `design_corpus()` already includes `rc-uniqueness.md` so no
    path-list change was needed for the new pins to scan.

    `crates/ailang-core/tests/design_index_pin.rs:166` drops the
    `"pre-Boehm"` token from the protected-exception comment list
    (the phrase no longer appears in `memory-model.md` after this
    iter, so the exception is dead).

  Runtime docs — `runtime/bump.c`, `runtime/rc.c`, `runtime/str.c`
    header comments scrubbed of Boehm/`GC_malloc`/`libgc`
    references. `bump.c`'s function signature description still
    documents `void *bump_malloc(size_t)` as the bench-floor
    allocator interface, but no longer cross-references libgc.

  Example fixtures — `examples/bench_latency_implicit.ail`,
    `bench_latency_explicit.ail`, `escape_local_demo.ail`,
    `reuse_as_demo.ail`, `rc_pin_recurse_implicit.ail` doc-comment
    headers scrubbed of `--alloc=gc` / Boehm references. The
    `.ail` surface (AST) is untouched in every case; round-trip
    invariant holds (`cargo test -p ailang-surface --test round_trip`
    green).

  Skill / agent prompts — `skills/audit/agents/ailang-bencher.md`
    rewritten to use an RC-vs-bump worked example pattern for the
    hypothesis-driven bench tutorial, replacing the recurring
    "RC vs Boehm under heap pressure" example.
    `skills/implement/agents/ailang-implementer.md` Decision-10 /
    Boehm references replaced with present-tense RC-commitment
    framing.

  IR snapshots — the 5 checked-in snapshots
    (`crates/ail/tests/snapshots/{hello,list,max3,sum,ws_main}.ll`)
    regenerated via `UPDATE_SNAPSHOTS=1 cargo test -p ail --test
    ir_snapshot`. Each previously contained
    `declare ptr @GC_malloc(i64)` and (for `list.ll`) a `call ptr
    @GC_malloc(...)` invocation; post-flip the snapshots contain
    `declare ptr @ailang_rc_alloc(i64)` plus the rc inc/dec runtime
    declarations.

Spec-vs-acceptance addendum (caught at orchestrator end-report,
absorbed here rather than in a follow-up spec edit): spec §6
acceptance criteria said "Boehm-grep returns matches ONLY in
docs_honesty_pin.rs". The plan itself prescribed historical Boehm
references in 3 additional files: (a) the new milestone-pin
`boehm_retirement_pin.rs` (must literally invoke `--alloc=gc` to
assert its rejection), (b) `embed_staticlib_alloc_guard.rs` file
doc-comment historical note ("`--alloc=gc` no longer exists as a
CLI value"), (c) `embedding-abi.md:44-45` contract historical
clause ("see the Boehm-retirement iter"). All three are
prescribed; the spec's grep wording was too narrow. The four
absence-pins in `docs_honesty_pin.rs` catch the actual zombies
(Boehm-narrative re-emerging in the design ledger), which is the
substantive intent the spec was aiming at — the four extra
documented-by-design exceptions are the cost of having an
explicit milestone-pin and contract-level historical anchors.

Net delta:
  - 32 files modified, 2 new (boehm_retirement_pin.rs + stats),
    1 deleted (gc_stress.ail);
  - workspace tests: every binary `0 failed`. Pass-count delta:
    -3 net (4 e2e tests deleted, 1 new milestone-pin test added);
  - boehm-grep state: hits only in the four by-design exceptions
    documented above;
  - `bench/check.py` exit 0 against regenerated baseline;
  - CLI must-fail fixture: `ail build --alloc=gc examples/hello.ail`
    exits non-zero with stderr containing `unknown --alloc value`
    and `\`gc\``;
  - design ledger present-tense honest (Boehm-narrative gone from
    `rc-uniqueness.md` + `pipeline.md`; the few historical
    references in `embedding-abi.md` / `boehm_retirement_pin.rs` /
    `embed_staticlib_alloc_guard.rs` are explicit milestone-pins
    or contract anchors, not silent ledger residue).

Bench measurement variance noted: closure-chain and hof-pipeline
are ±1-5% jittery between runs; one regeneration flagged 2
metrics as `regressed` before a second run returned 0. The
captured baseline is within self-comparison range. Existing
per-metric tolerances absorb the jitter.

Stats file:
`bench/orchestrator-stats/2026-05-20-iter-boehm-retirement.1.json`.

closes #4
2026-05-20 20:51:53 +02:00
Brummel 7a42989b34 iter nullary-app.1 (DONE 5/5): accept (app f) as canonical zero-arg call
Resolves Gitea #12 — the design fork from the 2026-05-20 /boss
session, surfaced independently by two prior fieldtests
(mut-local F3 + loop-recur `run_forever`).

Mechanics, layer by layer:

  Parser — `crates/ailang-surface/src/parse.rs:1322-1331` drops
    the 4-line `expected at least one argument` guard in
    `parse_app_body`. Grammar comments at file top change `term+`
    to `term*` on both `app-term` and `tail-app-term`; doc comment
    on `parse_app_body` changes `1+ args` to `0+ args`. Inline
    comment cites #12 and notes which layers below already accept
    empty args.

  Serde — `crates/ailang-core/src/ast.rs:419-425` annotates
    `Term::App.args` with `#[serde(default)]`, mirroring
    `Term::Ctor.args`'s *actual* attrs verbatim (no
    `skip_serializing_if`). Write behaviour unchanged — canonical
    JSON still emits `"args":[]` for nullary calls; only read
    behaviour gains tolerance for the `args` key being absent.
    Verified hash-impact-free at plan time:
    `grep -rn '"args":\[\]' examples/*.ail.json` returns zero
    matches, so no existing fixture's canonical bytes shift.

  Doc-honesty — `design/contracts/data-model.md`:
    (a) the `app` jsonc shape gains an explicit "args may be
        empty / read-tolerant" note;
    (b) the existing `ctor` comment claiming "args omitted when
        empty" was factually false (Ctor.args has no
        `skip_serializing_if`; a serde probe at plan time
        confirmed writes always emit `"args":[]`). Rewritten to
        describe the actual write/read asymmetry, with a
        cross-reference to the new `app` note.

  E2E pin — `crates/ail/tests/nullary_app_e2e.rs` +
    `examples/nullary_app_smoke.ail`. Defines `greet : fn()
    -> Unit !IO` and calls it as `(app greet)`; asserts stdout
    `"hello\n"`. RED→GREEN pin for the parser change AND the
    milestone-protecting E2E for nullary call surface going
    forward. Fixture literal is `"hello"` (no `\n`) because
    `io/print_str` lowers via `puts` which appends a newline —
    the plan body had a `"hello\n"` literal which would have
    yielded `"hello\n\n"`; the implementer caught and aligned to
    the canonical pattern in `examples/hello.ail` while writing
    the fixture, fix scoped to that single file.

Layers below parser untouched: typechecker's arity check at
`crates/ailang-check/src/lib.rs:3300` is `args.len() !=
params.len()` which is `0 != 0 → false` for nullary; codegen's
`args.iter().zip(sig.params.iter())` at
`crates/ailang-codegen/src/lib.rs:2410` is an empty loop;
LLVM `call @ail_<mod>_<name>()` with empty arglist is valid;
surface printer's arg-emit loop at
`crates/ailang-surface/src/print.rs:430-434` writes nothing for
empty args, producing exactly `(app f)`.

Verification: full workspace `cargo test --workspace --quiet`
green (0 failed across all crates); drift pins
`design_index_pin 5/5` + `design_schema_drift 8/8`; round-trip
`2/2`; new `nullary_app_e2e 1/1`. Stats file:
`bench/orchestrator-stats/2026-05-20-iter-nullary-app.1.json`.

closes #12
2026-05-20 18:57:14 +02:00
Brummel 93887aa03b workflow: replace docs/roadmap.md with Gitea issue backlog
The forward queue moves out of the in-tree markdown file and into
Gitea issues at http://192.168.178.103:3000/Brummel/AILang/issues.
Labels: kind:{milestone,feature,todo,idea} + prio:{p1,p2,p3}
+ state:in-progress. Big chunks live as Gitea milestones
(containers) with full prose in the description; smaller items are
standalone issues. Browse-and-filter scales constant against
growing item count; the previous markdown file was 1059 lines, of
which ~850 were already-closed-entry verlauf (the same failure
class the JOURNAL cut removed).

Sync-drift Code<>Tracker mitigation: Soft-convention `closes #N`
/ `refs #N` in commit bodies — Gitea auto-closes the issue on
push. Captured in user-level CLAUDE.md (~/.claude/CLAUDE.md, not
in this commit) as the durable rule; no hook enforcement.

In-repo changes:

- docs/roadmap.md deleted.
- CLAUDE.md (project): Code-layout drops roadmap; /boss gating
  retargeted; Roles section rewritten with a new "Gitea issues"
  bullet (URL + tea-CLI snippet) and the closes-#N trailer note.
- skills/boss/SKILL.md: 10 sites retargeted, plus Step 1 now
  prescribes `tea issues ls --labels prio:p1` as the queue read.
- skills/brainstorm/SKILL.md: Step 7.5 no-override BLOCK now
  files a Gitea issue via `tea issues create` instead of
  appending a roadmap entry; spec deletion stays.
- skills/audit/SKILL.md + ailang-architect.md: deferral
  requirement and debt-heuristic retargeted; forward-intent
  belongs in the Gitea backlog.
- skills/fieldtest/SKILL.md, skills/docwriter/SKILL.md +
  ailang-docwriter.md: roadmap → backlog.
- design/contracts/honesty-rule.md: forward intent lives in
  the Gitea backlog (pinned phrases unchanged).
- design/INDEX.md: Docs bullet drops roadmap, adds the backlog
  URL.
- crates/ailang-core/tests/docs_honesty_pin.rs: two assert
  messages retargeted (assertion bodies unchanged).
- bench/architect_sweeps.sh: Sweep-4 TABU extended with
  `docs/roadmap\.md` so the deleted path cannot quietly regrow
  as a cross-reference in design/contracts/.

Verification:

- cargo build --workspace clean.
- cargo test --workspace: 647 passed, 0 failed, 2 ignored.
- bench/architect_sweeps.sh exit 0 (all five sweeps clean, incl.
  new TABU).
- grep over the live tree (excluding docs/specs/, docs/plans/)
  shows zero residual docs/roadmap.md refs.

Not touched: ~55 historical files under docs/specs/ and
docs/plans/ that mention docs/roadmap.md. Snapshot-character,
analogous to the JOURNAL-cut precedent — historical specs are
not mass-edited just because a live file was retired; their
mentions were correct at write time.
2026-05-20 14:48:27 +02:00
Brummel 54f0ced148 workflow: delete docs/journals/ and docs/journal-archive.md
Strict application of the "Future-Use, not Verlauf" criterion to the
two remaining journal artefacts:

- `docs/journals/` (110 files): the per-iter and audit journals from
  2026-05-11 onward. Pure history; no live reader after the previous
  sweep. Entire directory removed.
- `docs/journals/2026-05-19-design-decision-records.md`: the one file
  that had live readers (`docs_honesty_pin.rs:108`, `parse.rs:80`,
  `duplicate_ctor_pin.rs:8`, three roadmap.md mentions) and was framed
  as a "relitigation guard". On re-examination its three asserted
  pinned phrases ("Regions were considered and rejected", the
  "demands annotations *because*" rationale, the prose-render
  placeholder statement) are rationale-prose, not load-bearing
  invariants — the test pinned itself, not a system property. Any
  Decision that still holds lives in the code, `design/contracts/`,
  or `design/models/`. Removed with the rest.
- `docs/journal-archive.md` (pre-2026-05-11 history): content-frozen
  long-tail history with no live reader. Removed; if anyone ever
  needs the pre-cutoff rationale they can `git log --before=2026-05-11
  --grep=<keyword>`.

Live-file consequences:
- `docs_honesty_pin.rs` `design_md_present_tense_anchors_present`:
  the three `records.*` assertions and the `read("docs/journals/…")`
  are dropped; the contract/model anchor pins remain.
- `duplicate_ctor_pin.rs`: the "why-two-overlays rationale lives in
  docs/journals/…" comment is replaced with the rationale inline.
- `parse.rs`: the "form-refinement rationale lives in docs/journals/…"
  comment is dropped (the rule body already states the refinement).
- `roadmap.md`: the decision-records mention in the DESIGN.md →
  design/ entry's body is dropped; the journal-archive.md `context:`
  pointers across ~12 closed entries are either rephrased to point
  at the relevant iter/audit (no path), or simplified to a one-line
  comment when the iter name alone carries the story.
- `CLAUDE.md` Roles section: the `docs/journal-archive.md` slot is
  removed; vocabulary note rephrased.
- `design/INDEX.md`: the Docs bullet drops `journal-archive.md`.
- `skills/README.md` bootstrap-rationale pointer rephrased.

`docs/specs/` and `docs/plans/` (per-milestone specs and per-iteration
plans) are unaffected — they remain the structured-design artefacts
they were before this sweep.

Workspace builds, full test suite green.
2026-05-20 11:25:15 +02:00
Brummel 8e586f493f workflow: replace per-iter journal system with git log + BLOCKED.md
The per-iter journal under docs/journals/ duplicated the iter commit
body's substance and accumulated as Verlauf-Doku with no Future-Use.
Sweep across all live control documents: CLAUDE.md, the 7 SKILL.md
files, the 11 agent files, design/INDEX.md and the contracts/models
that referenced journals, docs/roadmap.md, and the handful of source
comments + tests that pointed at journal files for rationale.

Mechanism changes:
- Standing-reading-lists in every agent now read `git log -N --format=full`
  for recent project state, never per-iter journal files. The architect
  reads `git log <prev-milestone-close>..HEAD --format=full` for audit
  scope.
- implement-orchestrator no longer writes a journal file. DONE outcomes
  emit just code + stats; the end-report is the per-task summary the
  Boss uses to write the commit body. PARTIAL/BLOCKED outcomes emit
  BLOCKED.md at the repo root — uncommitted by convention, Boss removes
  on repair or discard. New iron-law line + four-rationalisation row
  + red-flag bullet codify it.
- audit ratify mechanic: --update-baseline is now paired with an explicit
  ratify paragraph in the audit-close commit body, not a separate
  JOURNAL ratify entry.
- design/contracts/honesty-rule.md: "history and rationale lives in
  docs/journals/" → "lives in git log (iter and audit commit bodies)".
  Pinned phrase preserved verbatim.
- CLAUDE.md "Roles of …" section reframed: design/, git log,
  journal-archive.md (content-frozen), roadmap.md, specs/, plans/.
  No docs/journals/ slot anymore.
- roadmap.md context-lines that pointed at per-iter journals are
  dropped where the spec/commit already carries the rationale, or
  rephrased to "shipped in the <iter> iter commit" / "docs/journal-
  archive.md (<date> entry)" for pre-2026-05-11 references.

What stays (this commit):
- docs/journals/ directory and contents are NOT touched. Removing the
  contents is a separate follow-up.
- docs/journals/2026-05-19-design-decision-records.md still has live
  readers (docs_honesty_pin.rs Z 108 + parse.rs + duplicate_ctor_pin.rs
  + 3 roadmap mentions) — also follow-up.
- docs/journal-archive.md still exists; its self-pointer header has
  been updated to drop the "see docs/journals/INDEX.md" mention.

Workspace builds, full test suite green.
2026-05-20 11:21:37 +02:00
Brummel bcd41810f4 design/ + source rustdoc: replace opaque shorthand with content phrases + links
Reader-facing prose and rustdoc carried opaque shorthand like
"Decision 10", "clause-5", "mq.1", "ct.1", "eob.1", "rpe.1",
"post-mq.3", and "Iter 22b.1:" with no in-repo definition the reader
could follow. This commit replaces every such occurrence in the
durable tier the reader is most likely to land on (design/ ledger +
source //! module headers + the central /// public-item rustdoc) with
an inline content phrase plus, where applicable, a Markdown link to
the file that defines the referenced concept.

design/ ledger — 16 files:
  Definition-site headings demoted from "Decision N: <title>" to
  "<title>": authoring-surface, tail-calls, memory-model section in
  rc-uniqueness.md, dual-allocator section, typeclass design,
  effects "pure core + algebraic effects".
  Cross-reference sites: "Decision 1" -> canonical-schema principle
  (data-model); "Decision 3/4" -> effects + scope-boundaries; "Decision
  6" -> authoring-surface; "Decision 8" -> tail-calls; "Decision 9" ->
  rc-uniqueness (dual-allocator); "Decision 10" -> memory-model;
  "Decision 11" -> typeclasses (model). "clause-5" -> body-link
  durability gate. "clause-3" (in language-constraints) ->
  bug-class-reintroduction discriminator. "mq.1/2/3", "ct.1/4",
  "eob.1", "rpe.1" -> the canonical-form rule / the type-driven
  dispatch / the Str carve-out / etc. "post-mq.3" -> "type-driven".

design/contracts/feature-acceptance.md: file-local "clauses 1/2/3"
-> "criteria 1/2/3" (sprachliche Kohärenz mit der File-Überschrift
"Feature-acceptance criterion"); "the clause-3 mechanism" -> "the
bug-class-reintroduction discriminator".

Source //! module headers — 24 files:
  Stripped "Iter X.Y:" prefixes and "(Decision N)" / "(mq.X)" tags
  from spec_drift, uniqueness, reuse_shape, migrate_canonical_types,
  typeclass_22b{2,3,c}, suppress_filter, lift, mono, linearity,
  diagnostic, method_dispatch_pin, method_collision_pin,
  no_per_type_print_ops, mq3_multi_class_e2e, print_mono_body_shape,
  print_no_leak_pin, cli_diag_human_workspace_load_error,
  ct1_check_cli, prose snapshot, unbound_in_instance_method_pin,
  mono_xmod_ctor_pattern, desugar.

Central /// public-item rustdoc:
  ast.rs (full sweep — every "Iter X" + "Decision N" prefix
  reformulated; mode/Type::Fn rustdoc now points at memory-model.md;
  Constraint / SuperclassRef / InstanceDef / ClassDef rustdoc points
  at typeclasses contract).
  diagnostic.rs (all "(Iter X)" / "(mq.X)" tags on diagnostic codes
  removed).
  lib.rs (FORM_A_SPEC rustdoc points at authoring-surface.md
  instead of "Decision 6").
  canonical.rs (type_hash + Float-literal rustdoc).

Still outstanding (for a follow-up commit): ~500 inline `//`
code-body comments with `Iter X.Y` markers across the workspace, and
a handful of `///` rustdoc items in hash_pin / workspace_pin / lift /
mono / suppress_filter test-pin and internal-function bodies. Code
identifiers (test filenames like `mq3_multi_class_e2e.rs`, function
names like `iter18e_drop_iterative_default_preserves_hashes`) stay
verbatim per the user's "code identifiers stay verbatim" rule.

Tests: design_index_pin 5/5 + docs_honesty_pin 5/5; workspace builds
clean; full `cargo test --workspace` previously green (every
`test result: ok` line, no FAILED line).
2026-05-20 09:47:33 +02:00
Brummel 3e087d759a design/ ledger: prose-bridges for in-fence §-refs in data-model.md
Five §"..." cross-refs survived the main sweep because they live
inside jsonc Schema-comments — clause-5 fence-skip would treat any
[label](path) there as literal text, not as a navigable link.

Instead of touching the schema-comment style (it is self-documenting
where it sits, against the field it qualifies), this commit adds
prose paragraphs just outside the fence that turn the same
cross-references into real links:

  - Before the Def jsonc block: pointer to memory-model.md
    (canonical-form rule for cross-module class/type names) and
    embedding-abi.md (exported fn surface).
  - Before the Type jsonc block: pointer to memory-model.md for
    Type::Con.name scoping and Type::Fn parameter-mode metadata.

Also re-phrases an intra-file §"Str ABI" self-reference in
str-abi.md to "the Str ABI table below" — file-only granularity
makes a self-link sense-less; the reader wants intra-file
direction, which prose carries fine without a link.

Tests: design_index_pin (5/5) + docs_honesty_pin (5/5) stay green.
2026-05-20 00:46:37 +02:00
Brummel 19dc42f5ca design/ ledger: dense cross-linking via three topical splits + 88-link sweep
The first formal-links milestone shipped clause-5 + 8 links across the
existing file layout. Browsing surfaced that file-only granularity is
only as precise as the file boundaries — three files mixed two or
three navigation targets under one address, so the 8 links could not
multiply without ambiguity. This commit fixes the substrate, then
applies the sweep the original milestone deferred.

Splits (each extracts an already-self-contained section into its own
file so links land on the topic, not the parent doc's TOC):

  contracts/typeclasses.md
    → +contracts/prelude-classes.md  (Eq/Ord/Show ships, polymorphic `print`)
    → +contracts/method-dispatch.md  (5-step dispatch rule, candidate index)

  contracts/memory-model.md
    → +contracts/language-constraints.md  (the 4 binding constraints
                                           making RC sound without a
                                           cycle collector)

  models/authoring-surface.md
    → +models/prose-projection.md  (Form-B / `ail prose` / merge-prose)

Each new file enters design/INDEX.md as its own row (three contracts
share show_no_instance_e2e.rs / uniqueness.rs as ratifying tests;
prose-projection is a model). Two pre-existing links rebind to the
new topic-files (memory-model.md → method-dispatch.md;
float-semantics.md → prelude-classes.md).

Link sweep: 8 → 88 formal Markdown links over 23 files. Every file
in design/contracts/ + design/models/ now has at least one outgoing
link; the tree is fully connected. Links are file-relative
`[label](path)` per the established convention, fenced code blocks
are skipped (a `](` inside ```jsonc``` is literal text), the durable
tier (design/ + crates/ + runtime/) is enforced by clause-5.

Tests:
  - design_index_pin.rs (5/5 clauses): clean-cut, INDEX resolution,
    ratifying-test resolution, no decision-record prose in contracts/,
    body links durable + resolving.
  - docs_honesty_pin.rs (5/5): one assertion rebinds from typeclasses.md
    to prelude-classes.md (where the gated sentence now lives);
    design_corpus widens to include the 4 new files so the Wunschdenken
    / doc-archaeology sweeps continue to cover everything that used to
    live in the parents.

No spec/plan/journal for this batch — interactive collaboration after
the milestone closed; the user gated the splits explicitly before the
sweep.
2026-05-20 00:24:08 +02:00
Brummel 8ad91e7f24 iter design-ledger-formal-links.1 (DONE 5/5): clause-5 hard gate + 7 prose-ref conversions + 2 disposition-(b) homeless removals + honesty-rule positive-half (whole milestone in one iter)
Positive-half completion of the DESIGN.md -> design/ split: design/
body cross-references are now formal, file-relative Markdown links
into the durable tier (design/ or source), and a new in-tree hard
gate (design_index_pin.rs clause-5,
design_body_links_are_durable_and_resolve) walks every
design/contracts/*.md + design/models/*.md, strips fenced code
(strip_fences toggles on ```/~~~ lines so a ](  inside a fence is not
treated as a link), extracts every ](path), and asserts the target
resolves file-relative to a real file under design/-or-crates/-or-
runtime/; never docs/, never an in-file #anchor.

RED-first via identity-stubbed strip_fences (four embedded synthetic
vectors -- first one FAILS); replacing the stub with the real
toggle-on-fence impl turns the test GREEN. clause-5 composes with
clause-3 into the complete invariant the milestone establishes:
every contract cross-reference is EITHER a resolving durable
file-link OR clause-3-forbidden decision-record prose.

Conversions (recon-and-corpus-verified closed set):
  Task 2 (7 prose refs, 8 link tokens):
    float-semantics.md:69    Prelude classes -> [..](typeclasses.md)
    float-semantics.md:100   bare-path -> [Str ABI](str-abi.md)
    embedding-abi.md:45      "Frozen value layout" -> [..](frozen-value-layout.md)  (drop stale "below")
    memory-model.md:44       Data model -> [..](data-model.md)
    memory-model.md:105-106  Method dispatch -> [..](typeclasses.md)  (drop stale "below"; the target heading lives in typeclasses.md:227, not in this file)
    scope-boundaries.md:48   Str ABI -> [..](str-abi.md)
    scope-boundaries.md:88   mixed split: ailang-core::desugar -> source link + Pipeline -> ../models/pipeline.md (drop stale "above")
  Task 3 (2 disposition-(b) homeless removals):
    pipeline.md:60-61            (see docs/PROSE_ROUNDTRIP.md) pointer removed, CLI prose preserved
    authoring-surface.md:178-181 cross-tier pointer clause removed, ail merge-prose sentence preserved
  Task 4: honesty-rule.md positive-half paragraph inserted between L14 and the existing L15-blank-L16; both docs_honesty_pin.rs-pinned phrases byte-identical at L14/L19 (now shifted to L19 -> L25 by the +6 lines).

Out of scope, preserved (asserted independently): every intra-file
"above/below"; embedding-abi.md:51 "frozen value layout below
specifies" (no quoted title, no (see) form); data-model.md
38/66/79/206/226 (in-fence ```jsonc schema annotations -- the inline
analog of the nominal-mention carve-out). INDEX.md and the
decision-records journal byte-unchanged; clauses 1-4 of
design_index_pin.rs source byte-unchanged (the only `-` lines in
the diff are the two-line //! header rewrite Task 1 Step 5 itself
delivers).

Boss-verified independently (not on agent report alone):
  cargo test --workspace               647 passed / 0 failed
                                       (+1 vs pre-milestone 646:
                                        the new clause-5)
  cargo test --test design_index_pin   5 / 5 passed
  cargo test --test docs_honesty_pin   5 / 5 passed (additive
                                       paragraph is pin-safe)
  grep ](.../docs/.../) under design/  zero
  grep ](#)        under design/  zero
  ](-link count under design/          8 (closed convert-set)
  git diff --quiet design/INDEX.md     ok
  git diff --quiet decision-records    ok
  embedding-abi.md:48 pinned phrase    byte-identical

One Concerns item: Task-5 Step-7's plan-predicted "`-` line count = 1"
was actually 2 because Task 1 Step 5 rewrote the //! header 5 -> 8
lines (removing the original L4 + L5, not just L5). Planner self-
review-item-8 miss on my part -- a verification-arithmetic error in
the plan, NOT an implementation defect. The substantive assertion
(clauses 1-4 source byte-unchanged) is fully satisfied; the
implementer correctly flagged it and proceeded. The plan stands as
written; the assertion's `1` should have been `2`. Lesson noted for
future header-rewrite tasks.

Spec: docs/specs/2026-05-19-design-ledger-formal-links.md
(grounding-check PASS x3 across two corpus-grounded amendments --
clause-6 + cross-ref definition; clause-5 fence-skip + closed
convert-set enumeration).

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

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

Boss-verified independently: cargo test --workspace 646/0,
design_index_pin 4/4 (clause-3 RED->GREEN), architect_sweeps.sh exit
0 'All five sweeps clean' (acceptance criterion 9 met), acceptance
grep CLEAN, 3 docs_honesty_pin pinned runs each exactly 1 contiguous
match. Zero spec/quality re-loops. FINAL design-md-rolesplit
iteration — milestone functionally complete, audited, drift-resolved,
hard gate enforces the honesty spirit.
2026-05-19 13:44:37 +02:00
Brummel 176821c2e7 iter design-md-rolesplit.1 (DONE 9/9): DESIGN.md -> design/ ledger role-split
The 3020-line docs/DESIGN.md is replaced by the design/ ledger:
design/INDEX.md (sole addressable spine, typed Contracts+Models tables,
polymorphic links — prose file OR authoritative source //!), 14
design/contracts/*.md test-linked invariants + 3 source-link-only
contracts (mangling/env-construction/qualified-xref, no prose file —
code is SoT), 5 design/models/*.md whitepapers, and
docs/journals/2026-05-19-design-decision-records.md (the
relitigation-guard archive — every why/rejected/does-not-do/rollback/
empirical ### moved out at ###-granularity). Clean cut: git rm
docs/DESIGN.md, no stub.

RED-first crates/ailang-core/tests/design_index_pin.rs — the 4-clause
anti-regrowth spine (DESIGN.md-gone / every-INDEX-link-resolves /
every-contract-names-a-resolvable-ratifier /
contracts-carry-no-decision-record-prose) — demonstrably RED before,
GREEN after. Build-atomic by task ordering: design_schema_drift.rs's
include_str! (the only compile-time consumer) retargeted to
design/contracts/data-model.md BEFORE the deletion; its
## Data model/## Pipeline slicer dropped (a simplification the split
enables). 2 NoInstance diagnostics + 2 lockstep E2Es retargeted to
design/contracts/{float-semantics,typeclasses}.md. ~12 agent reading
lists + 5 SKILL bodies + CLAUDE.md + skills/README.md + ~25
code/C/.ail/spec comment xrefs retargeted; OQ7 dangling 'Iter 13b'
cite deleted (no forward target — a pointer would be fiction).
honesty-rule.md rewritten so the rule names the new home
(rationale->journals), resolving the recon-found internal
contradiction; the two docs_honesty_pin.rs:70,72 pinned phrases kept
verbatim+contiguous.

Boss-verified independently: cargo test --workspace 646 passed /
0 failed; design_index_pin 4/4; acceptance grep CLEAN of live
DESIGN.md refs (residuals = only the spec-mandated clause-4
deletion-enforcer). 2 DONE_WITH_CONCERNS routed to the mandatory
milestone-close audit: (a) str-abi.md:23 '(iter str-concat,
2026-05-13)' provenance stamp trips advisory architect_sweeps Sweep-1
— Boss-confirmed byte-identical to DESIGN.md@deeffb1:2062-2065, a
faithfully-migrated PRE-EXISTING anchor (regexes verbatim, only path
retargeted), NOT split-introduced — RATIFY-or-tidy at audit; (b) a
now stale-direction intra-prose 'see Str ABI below' cross-ref in
float-semantics.md — audit-adjudication candidate. Plan defect noted:
Task 9 Step 4's verbatim acceptance grep used a ^./ anchor not
matching the system's grep -rIn output; substance re-verified CLEAN.

Spec grounding-check PASS x2. Journals INDEX + decision-records
pointer appended (Boss-only).
2026-05-19 13:04:22 +02:00