Commit Graph

573 Commits

Author SHA1 Message Date
Brummel 3ec406e687 spec: raw-buf — re-carve .2/.3/.4 post intrinsic-bodies (refs #7)
raw-buf.1 (intercept registry) shipped (140a0c0). The intrinsic-bodies
milestone (#9) then landed and pulled the foundation out from under the
original raw-buf.2/.3 split, so this revises the spec.

Two foundation changes from intrinsic-bodies:

1. The placeholder-body convention the original spec leaned on
   ((body x) round-trip stubs) is gone. prelude.ail now uses
   (intrinsic) markers, and check_fn (lib.rs:2173) typechecks every
   non-intrinsic body including kernel-tier ones. RawBuf.get with a
   placeholder body (declared (ret a), body constructs a RawBuf) is now
   a hard type error — get/new/set/size MUST be (intrinsic).

2. An (intrinsic) marker now requires a matching INTERCEPTS entry as a
   lockstep pair (intercepts_bijection_with_intrinsic_markers). The
   "manifest visible / codegen deferred" intermediate state the original
   raw-buf.2 was designed to ship is forbidden by that invariant — the
   marker and its codegen entry must land in the same iteration.

Re-carve (3 remaining iterations, retirement isolated):
  raw-buf.2 — kernel family-crate rename ailang-kernel-stub →
              ailang-kernel. Pure refactor, zero behavioural change.
  raw-buf.3 — RawBuf end-to-end: raw_buf module ((intrinsic) markers)
              + 12 INTERCEPTS entries + Term::New desugar + drop +
              ailang-surface wiring + E2E. Manifest+codegen land
              together. Workspace count 4 → 5.
  raw-buf.4 — kernel_stub retirement: RawBuf subsumes every
              ratification role (Term::New, param-in, kernel-tier
              auto-import, intrinsic); answer entry + stub removed,
              count 5 → 4.

Preserved unchanged: Goal, slab layout, feature-acceptance argument.

The raw_buf module Form-A source in § Concrete code shapes is
gate-verified: ail check → ok (35 symbols across 3 modules); the four
(intrinsic) markers are legal because the module is (kernel).

Grounding-check: 8/9 load-bearing current-behaviour assumptions
ratified by named green tests. The single override is the Term::New
codegen-deferral arm (lib.rs:2094 + sibling at lib.rs:3296, the latter
surfaced by the grounding agent) — a transient prep.2 marker that
raw-buf.3 deletes, not a relied-upon invariant; forward-ratified by
raw-buf.3's build-and-run E2E. Override logged, not a discard.
2026-05-29 18:26:59 +02:00
Brummel 298fc2d36f plan: intrinsic-bodies.2-migration-lock — 4-task prelude migration + bijection pin (refs #9)
Decomposes intrinsic-bodies.2 (parent spec § Architecture points 6-8)
into 4 tasks + a final gate:

  Task 1 — migrate the 13 authored prelude dummy bodies to (intrinsic):
    7 Eq/Ord instance methods (inner (body false)/(body true)/
    (body (term-ctor Ordering EQ)) → (intrinsic), Design X lambda-body
    placement) + 6 float_* fns ((body false) → (intrinsic)). Gated by
    round-trip + the 4 eq/compare/float E2E ratifiers staying green
    (behaviour-preserving: the intercepts emit identical IR).
  Task 2 — rebaseline the two moving hash pins: prelude_module_hash_pin
    (prelude module hash) and mono_hash_stability's 6 eq/compare
    def-hashes (capture-from-failure, replace the constant). The 4
    show__* pins do NOT move.
  Task 3 — replace the one-directional registry_contains_all_legacy_arms
    pin with intercepts_bijection_with_intrinsic_markers: an in-source
    test that walks prelude + kernel_stub pre-mono for Term::Intrinsic
    markers, recovers mangled names (mono_symbol for instance methods,
    fn name for top-level), and asserts the bijection over the
    intrinsic-backed class (14) with the 5-name *__Int optimisation-only
    allowlist. Both directions + a stale-allowlist guard.
  Task 4 — confirm no codegen path lowers an intercepted body (already
    true post-.1: intercept-by-name precedes lower_term), delete the
    stale dummy-body comment at lib.rs:1310-1319, and conditionally edit
    0007-honesty-rule.md ONLY if it names the dummies (recon: it does
    not — a general rule; no forced edit).

Recon-driven plan decisions:

1. The bijection pin lives in intercepts.rs's in-source #[cfg(test)]
   mod tests, NOT a crates/ail/tests integration test. Visibility
   forces it: the pin needs INTERCEPTS (pub(crate) in codegen, in-source
   only) AND the parse hops (ailang-surface dev-dep). Verified
   ailang-surface does not dep ailang-codegen, so there is no
   dev-dep cycle blocking the in-source test from calling parse_prelude
   (the dev-dep-cycle hazard that bit pd.2.4/pd.3.1 does not apply
   here — that was the ailang-core <-> ailang-surface edge).
2. The pin walks PRE-mono (parse_prelude/parse_kernel_stub) and
   reconstructs mangled names via mono_symbol, rather than walking
   post-mono. Post-mono only synthesises USED mono symbols, so a
   post-mono walk would miss any instance method the pin's entry
   fixture does not exercise (e.g. eq__Unit). Pre-mono + mono_symbol
   sees all 14 markers unconditionally.
3. Task 2's hashes are capture-from-failure, not pre-computed — the new
   prelude bytes determine them. The old->new values get recorded in
   the iter commit body by the Boss.

All verification filter strings checked against the tree: the 4 E2E
ratifier fn names, the mono pin name, and the prelude pin target all
resolve to ≥1 real test. No ail/ail-json/ll fenced block in the plan
(the migrated-form snippets are scheme fragments — the (intrinsic) form
is already ratified by .1's kernel_intrinsic_smoke.ail), so the
parse-bytes gate is a documented no-op.

Handoff target: skills/implement on docs/plans/0107-intrinsic-bodies.2-migration-lock.md
2026-05-29 17:37:45 +02:00
Brummel 8301ca3ee8 spec: intrinsic-bodies — correct .2 count + bijection split (refs #9)
Forward-fix on 5b66de7, prompted by plan-recon for intrinsic-bodies.2.
Two spec-vs-reality gaps in the .2 (migration + lock) sections, both
caught before the .2 plan was written:

1. Count. The spec said "18 dummy bodies" migrate to (intrinsic). That
   conflated the INTERCEPTS entry count with authored prelude bodies.
   Reality: examples/prelude.ail carries 13 authored dummy bodies — the
   7 Eq/Ord instance methods (eq Int/Bool/Str/Unit, compare
   Int/Bool/Str) + the 6 float_* free fns. The registry has 19 entries
   (18 legacy + the .1 `answer`); the other 6 are not prelude dummies.

2. The strict bijection cannot hold. lt__Int/le__Int/gt__Int/ge__Int/
   ne__Int (5 INTERCEPTS entries) intercept the monomorphised __Int
   specialisations of the polymorphic free fns lt/le/gt/ge/ne, which
   carry REAL bodies in the prelude (ne = (app not (app eq x y)); the
   four ordering helpers are (match (app compare x y) ...)). Those
   bodies are honest and live — lowered for every non-Int instantiation;
   only the Int specialisation is intercepted for a faster direct icmp.
   They are an optimisation class, not a compiler-supplied-body class:
   no lie, no source body to replace, no (intrinsic) marker. A strict
   bijection over all INTERCEPTS entries would be red for these 5.

Corrections:
- § Architecture point 6: 13 authored prelude sites (named), with the
  5 icmp-family + `answer` explicitly listed as non-prelude / not
  migrated and why.
- § Architecture point 7: the pin splits the registry into
  intrinsic-backed (13 prelude markers + answer = 14) and
  optimisation-only (the 5 *__Int, an explicit documented allowlist).
  The bijection holds over the intrinsic-backed class only; the pin
  loads the workspace + monomorphises to recover mangled names for the
  marker direction.
- § Architecture point 8: reframed from "dead-path removal" to
  "dead-path confirmation" — .1's intercept-by-name already bypasses
  the dummy body before lower_term sees it (committed reality at
  52ff873), so .2 confirms no path lowers an intercepted body and
  deletes stale comments; the .1 Term::Intrinsic escape-guards stay.
- § Components + § Testing: the two moving hash pins named
  (prelude_module_hash_pin.rs; mono_hash_stability.rs's 6 eq/compare
  pins move, its 4 show pins do not); hash_pin.rs carries no
  prelude-derived hash. IR snapshots do not change.

Grounding-check PASS on all five corrected .2 claims against the
shipped .1 baseline (count, the 5 real-bodied helpers, the three hash
pins' behaviour, the already-dead path, the bijection-pin pipeline
reachability). No ail/ail-json/ll fenced block changed — parse-gate a
documented no-op for this revision.

The deeper observation this surfaced — that INTERCEPTS conflates two
concepts (compiler-supplied bodies vs. optimisation of a real body) —
is noted but NOT resolved here; splitting the registry is out of scope
for intrinsic-bodies and would be its own milestone.
2026-05-29 17:31:25 +02:00
Brummel ce0374ac0c plan: intrinsic-bodies.1-mechanism — 8-task Term::Intrinsic cross-crate landing (refs #9)
Decomposes intrinsic-bodies.1 (parent spec docs/specs/0055-intrinsic-bodies.md
§ Architecture points 1-5) into 8 tasks plus a final gate:

  Task 1 — Term::Intrinsic unit variant in ast.rs + the in-core
    exhaustive-match arms (canonical/hash/visit/pretty), enumerated by
    cargo build -p ailang-core (no-wildcard matches break until armed).
  Task 2 — surface parse + print: (intrinsic) as a fn body-slot clause
    and a lambda positional body, mapped to/from Term::Intrinsic;
    round-trip rides the examples/ corpus gate.
  Task 3 — cross-crate walker sweep (check + codegen + prose): leaf
    arms at the sites that match Term::New today, enumerated by
    cargo build --workspace. The two MEANINGFUL arms (codegen lower_term,
    checker body-check) are bridged with a temp unreachable! so the gate
    is green, then replaced by Tasks 4-5 — no deferred-caller across a
    0-error gate.
  Task 4 — checker: env.current_module_kernel_tier flag, signature-only
    body check for an intrinsic body, intrinsic-outside-kernel-tier
    reject (CheckError variant + code() arm). Reject test is subprocess
    `ail check --json` on a temp file, modelled exactly on the verified
    check_json_unbound_var pattern.
  Task 5 — codegen: a Term::Intrinsic body routes through
    intercepts::lookup; never reaches lower_term; lower_term's
    Term::Intrinsic arm is a codegen-internal error.
  Task 6 — `answer` intercept (ret i64 42) + the answer intrinsic in
    STUB_AIL + examples/kernel_intrinsic_smoke.ail so the
    schema_coverage corpus observes Term::Intrinsic (avoids the
    Term::New-in-match-but-not-in-corpus gap).
  Task 7 — E2E ratifier examples/kernel_answer.ail (calls
    kernel_stub.answer, prints 42); build_and_run("kernel_answer.ail")
    asserts stdout 42.
  Task 8 — design/contracts/0002-data-model.md gains the
    { "t": "intrinsic" } Term entry; design_schema_drift mirror stays
    green.

Three plan-time corrections after plan-recon + harness verification:

1. The reject test was first drafted against an invented ailang_check
   API (Workspace::single_with_prelude / check_workspace). Verified
   against e2e.rs:1236 that the established reject pattern is
   subprocess `ail check --json` + exit-1 + JSON code assertion;
   rewrote on a temp file.
2. build_and_run takes the fixture filename WITH .ail extension
   (verified e2e.rs:13-38, existing calls build_and_run("sum.ail")) —
   the ratifier call is build_and_run("kernel_answer.ail").
3. kernel_intrinsic_smoke.ail carries an intrinsic with no registered
   intercept; confirmed no gate builds it (compile_check.py uses a
   curated list, no test builds all examples/*.ail) — it rides only
   the parse-level round-trip + schema-coverage gates, never a build.

Scope guard: .1 does NOT migrate prelude dummies, does NOT upgrade the
registry pin to a bijection, does NOT remove the dead body-lowering
path — all .2. Hashes stay stable in .1 (Term::Intrinsic is additive;
no existing fixture carries it).

Test trajectory: 667 (post-raw-buf.1) → ~669 (+intrinsic_in_user_module_is_rejected,
+answer_intrinsic_builds_and_runs_printing_42; corpus/round-trip
fixtures ride existing dynamic gates).

Handoff target: skills/implement on docs/plans/0106-intrinsic-bodies.1-mechanism.md
2026-05-29 16:55:01 +02:00
Brummel 5b66de77ac spec: intrinsic-bodies — revise AST repr to Term::Intrinsic leaf (refs #9)
Forward-fix on c42034b. plan-recon for intrinsic-bodies.1 surfaced
that the original AST representation — FnDef.body / Term::Lam.body
made Option<Term> plus an `intrinsic: bool` flag — has a ~150-site
blast radius across six crates: every body read/construct site breaks
when a mandatory public field goes optional. That blast radius is the
signal (CLAUDE.md design-rationale rule) that the representation was
wrong, not merely expensive.

The Form-A surface (user's chosen Approach 1) and the Local-Reasoning
semantics (Design X marker placement) are UNCHANGED. Only the internal
AST representation changes, which is orchestrator authority over AST
design.

New representation: a single new leaf Term variant, Term::Intrinsic
({ "t": "intrinsic" }), is the body of a compiler-supplied definition.
FnDef.body (Term) and Term::Lam.body (Box<Term>) keep their existing
types. A def is intrinsic iff matches!(body, Term::Intrinsic).

Three structural reasons (not effort):

1. Established pattern. The project adds new constructs as additive
   Term variants — Term::New, Term::Loop, Term::Recur, Term::Clone,
   Term::ReuseAs all landed this way, documented "strictly additive,
   pre-existing fixtures hash bit-identically" in
   design/contracts/0002-data-model.md. Term::Intrinsic follows it.
   Option A introduced a brand-new pattern (mandatory field → optional)
   absent from the schema.

2. Meaning at the right locus. "This body is compiler-supplied" is a
   property of the body, not the container. Term::Intrinsic sits at the
   body position — fn body, or instance-method lambda body (Design X
   local-signature placement preserved exactly).

3. Illegal state unrepresentable. Option A admitted intrinsic:true with
   body:Some(...), forcing an intrinsic-with-body reject. Under the
   variant a body is either Term::Intrinsic or a real term, never both
   — the reject is deleted, the state cannot occur. This is the same
   make-illegal-states-unrepresentable discipline as the honesty theme
   the milestone exists to serve.

Blast radius collapses from ~150 body-read/construct sites to the
exhaustive match-on-Term arms (canonical/hash/visit + schema_coverage),
which the no-wildcard Term match turns into compile errors until each
gains a Term::Intrinsic case — the project's normal new-variant
discipline.

Sections revised: § Architecture points 1/3/4, § Concrete code shapes
(Implementation shape now shows the leaf variant, not the
Option+flag), § Components, § Data flow, § Error handling (the
intrinsic-with-body row removed), § Testing strategy (the
both-body-and-intrinsic reject test removed; schema_coverage Term::Intrinsic
observation added). The scheme/ail surface examples are byte-unchanged.

Re-ran the brainstorm gates on the revision: Step-7 parse gate green
(both ail blocks exit 0, unchanged); Step-7.5 grounding-check PASS on
the four new load-bearing claims (additive-variant precedent +
contract wording, exhaustive-Term-match mechanism, mono.rs
synthesise_mono_fn destructure unchanged under preserved body type,
Term::Recur as non-reducing-leaf precedent).
2026-05-29 16:45:47 +02:00
Brummel c42034b38d spec: intrinsic-bodies — (intrinsic) Form-A body marker (refs #9)
New milestone, triggered by the raw-buf.2 BLOCKED chain (the .2 work
was discarded; spec 4ad003d and plan 647121c stay on main per the
forward-only rule). The Form-A surface currently forces every fn /
instance-method to carry a (body ...) clause, which produces three
problems the marker resolves:

1. Prelude dummy-body lies. The eq/compare/ne/lt/le/gt/ge instance
   methods ship placeholder bodies — (body false), (body (term-ctor
   Ordering EQ)) — that parse and type-check but never run; codegen
   discards them and emits the intercept (registry shipped in
   raw-buf.1, intercepts.rs). A standing honesty-rule infraction
   (design/contracts/0007-honesty-rule.md): a reader who trusts the
   source is wrong about what runs.

2. Polymorphic kernel-tier fns are structurally impossible. RawBuf's
   get : RawBuf a -> Int -> a needs a placeholder body producing a
   value of type a, and AILang has no value of polymorphic type. The
   dummy-body requirement made the fn unauthorable — what BLOCKED
   raw-buf.2.

3. No surface affordance for "compiler supplies this body". Every
   systems language has one (LLVM declare, Rust extern
   "rust-intrinsic", Haskell foreign import prim, C builtins). The
   intercept registry IS AILang's compiler-supplied-body table;
   (intrinsic) is the surface declaration of membership.

Decomposition (2 iterations, full cut):

  intrinsic-bodies.1 — the mechanism. FnDef.body / Term::Lam.body
  become optional; an additive intrinsic: bool rides each
  (skip_serializing_if, hash-stable when omitted). Form-A parses +
  prints (intrinsic); round-trip gated. Checker checks signature-only,
  rejects body+intrinsic, rejects intrinsic outside kernel-tier /
  prelude (intrinsic-outside-kernel-tier). Codegen routes intrinsic
  defs through intercepts::lookup. Ratified by a throwaway `answer`
  smoke intrinsic in the kernel_stub fixture, end-to-end to native.

  intrinsic-bodies.2 — migration + lock. The dummy bodies swap for
  (intrinsic); a hard-lockstep pin asserts a bijection between
  INTERCEPTS entries and intrinsic markers reachable in the loaded
  workspace (extends raw-buf.1's registry_contains_all_legacy_arms
  from a one-way name check to a two-way source<->registry bijection).
  Dead body-lowering path for intercepted defs removed.

Design decisions locked during brainstorm:

- Marker placement (Design X). For a top-level fn, (intrinsic)
  replaces the (body ...) clause directly — the (type ...) signature
  stays beside it. For an instance method, the marker sits on the
  lambda BODY, not the method: the lambda's typed shell (params / ret)
  IS the method's local signature, and intrinsic drops the body, not
  the signature — exactly as LLVM declare / Rust extern-intrinsic keep
  the full signature. Hoisting the marker to (method eq (intrinsic))
  would erase the local signature (a reader would have to climb to the
  Eq class decl and substitute a := Int), violating local reasoning
  (design/INDEX.md § Goal). The mono pass
  (mono.rs::synthesise_mono_fn) reads params + inner body out of this
  lambda today, so the placement keeps that path unchanged. The Design
  Y alternative was considered and rejected on this signature-locality
  ground.

- Scope guard. (intrinsic) is legal only in (kernel)-tier modules and
  the prelude; user modules are rejected. This is the honesty-rule
  guard at the workspace boundary — user code cannot mark a body as
  compiler-supplied, so the lie cannot re-enter through user modules.

Process note: first spec under the hardened brainstorm pipeline
(Skills issue #1 fixes + the spec_validation parse gates retrofitted
in a2698a8). The Step-7 parse-every-block gate caught a real defect in
the spec's own ail examples on first run (an invalid module-level
(doc ...) head) — the defense line that was absent on raw-buf.2 and
let its unparseable spec bytes through to implement. Grounding-check
PASS on 12 load-bearing assumptions, each ratified by a named green
test.

Out of scope: a user-facing plugin API for custom intercepts (the
scope guard forbids user-module intrinsics); any change to the
raw-buf.1 dispatch mechanism; the raw-buf.2 redo itself (milestone #7,
parked behind this one).
2026-05-29 16:32:36 +02:00
Brummel 647121cb8f plan: raw-buf.2-kernel-manifest — 7-task crate-rename + RawBuf submodule + checker-side surface (refs #7)
Decomposes raw-buf.2 (parent spec docs/specs/0054-raw-buf.md
§ Architecture point 2, § Components row 2, § Testing strategy
"raw-buf.2") into 7 atomic tasks:

  Task 1 — crate rename + reshape: crates/ailang-kernel-stub/ →
    crates/ailang-kernel/, restructured as a family-crate with
    src/kernel_stub/{mod.rs,source.ail}; 13 steps covering 8
    compile-checked rename sites plus design/INDEX.md + CLAUDE.md
    path updates.
  Task 2 — add raw_buf submodule: src/raw_buf/{mod.rs,source.ail}
    with the spec's Form-A source (4 fns + 1 TypeDef with
    param-in (a Int Float Bool)); RAW_BUF_AIL re-export.
  Task 3 — wire raw_buf into ailang-surface: parse_raw_buf fn,
    re-export at lib.rs:39, workspace injection alongside the
    existing kernel_stub block, workspace_pin count bump 4→5.
  Task 4 — round-trip test raw_buf_module_round_trips, verbatim
    structural clone of kernel_stub_module_round_trips.
  Task 5 — E2E raw_buf_check_e2e: (con RawBuf (con Int)) consumer
    checks clean.
  Task 6 — E2E raw_buf_param_in_reject_e2e: (con RawBuf (con Str))
    consumer rejected at check with param-not-in-restricted-set.
  Task 7 — E2E raw_buf_build_intercept_missing_e2e: (new RawBuf …)
    consumer accepted at check, rejected at build with the
    existing Term::New deferral message.

Three substantive plan-time decisions resolving plan-recon's
open questions:

1. The intercept-not-registered diagnostic is NOT introduced.
   The spec hedged ("intercept-not-registered: new__RawBuf__Int
   (or the existing Term::New-deferral diagnostic)") between
   shipping a fresh diagnostic and reusing the existing deferral
   at crates/ailang-codegen/src/lib.rs:2075-2078. Plan picks
   reuse: the deferral already names "milestone raw-buf" and is
   self-describing; a new diagnostic would exist for ~1 iter
   (raw-buf.2 → raw-buf.3) before going away — disposable infra.
   Task 7's test asserts on the substring "Term::New requires
   the type's" which is stable in the existing message.

2. In-tree references to crates/ailang-kernel-stub/ AFTER the
   rename — split-scope between raw-buf.2 and raw-buf.3.
   The path changes in raw-buf.2 (the crate moves now), so
   path-references in design/INDEX.md:112 and the CLAUDE.md
   Code-layout table get updated in Task 1 Steps 10-11. The
   retire-language (stub as "ratifying fixture for kernel-
   extension-mechanics" → "retired by raw-buf") stays for
   raw-buf.3 close, per spec § Components row 3.

3. parse_raw_buf gets a mirror re-export at ailang-surface/
   src/lib.rs:39 alongside parse_kernel_stub. Recon's natural
   reading; load-bearing because the new round-trip test calls
   ailang_surface::parse_raw_buf() directly.

Plan honours the project's compile-gate discipline. Task 1's
13 steps thread all 8 rename sites inside the same task —
no deferred caller across the build gate. Task 3 bundles the
workspace_pin count bump with the loader injection that drives
it; the assertion goes 4→5 in the same task that makes it true.

Test-count trajectory: 667 (post-raw-buf.1 baseline) → 667
(Tasks 1, 2, 3 — refactor + wiring, no new test) → 668 (Task 4
round-trip) → 669 (Task 5 check E2E) → 670 (Task 6 reject E2E)
→ 671 (Task 7 deferral E2E).

Handoff target: skills/implement on docs/plans/0105-raw-buf.2-kernel-manifest.md
2026-05-29 11:17:30 +02:00
Brummel d1612d5463 plan: raw-buf.1-intercept-registry — 5-task atomic codegen-registry refactor (refs #7)
Decomposes raw-buf.1 (parent spec docs/specs/0054-raw-buf.md
§ Architecture point 1, § Components row 1) into 5 tasks:

  Task 1 — intercepts module skeleton (3 steps)
  Task 2 — populate the table: 18 emit fns + visibility bumps (9 steps)
  Task 3 — rewire the dispatch site to a thin shim (3 steps)
  Task 4 — fold wants_alwaysinline into the registry (4 steps)
  Task 5 — registry_contains_all_legacy_arms pin test (3 steps)

Three substantive decisions made at plan time beyond what the spec
dictates, surfaced by the plan-recon report:

1. The match arm count is 18, not the ~8 the spec sentence hand-listed
   (eq__Str, compare__Int|Bool|Str, float_*). Five additional arms
   (eq__Int, eq__Bool, eq__Unit, plus the lt|le|gt|ge|ne__Int
   direct-icmp family) shipped after the spec sentence was written.
   All 18 are absorbed by the registry; the pin test enumerates all 18.

2. The `intercept_emit_wants_alwaysinline` predicate at lib.rs:1172 is
   a separate hardcoded name-list that mirrors the dispatch arms.
   This is a silent-drift class (regression seen earlier in the cycle
   on compare__Int perf at +29-47% when the lists diverged). The plan
   folds wants_alwaysinline into the Intercept entry as a per-row bool
   and rewrites the predicate to consult the registry — one source of
   truth instead of two.

3. The wrapper fn `try_emit_primitive_instance_body` survives as a
   thin shim (`match intercepts::lookup(name) { Some(i) => ..., None
   => Ok(false) }`) rather than being deleted. Trade-off: a 6-line
   wrapper vs. a 14-file comment-ref churn. The wrapper wins —
   landmark name preserved, all in-source doc comments stay valid.

Plan honours the project's compile-gate discipline: fn signatures
stay stable throughout (only bodies change), no caller-threading is
deferred across a build gate. Visibility bumps in Task 2
(pub(crate) on three Emitter fields and four helper methods) are
additive and do not change any caller's signature.

Verification commands in each Run step name actual test fns the
plan-recon confirmed exist at specific lines; no filter-zero-match
risk. Pre-flight workspace baseline: 668 tests; post-iter:
669 (the new pin).

Handoff target: skills/implement on docs/plans/0104-raw-buf.1-intercept-registry.md
2026-05-29 10:53:08 +02:00
Brummel 4ad003d21f spec: raw-buf — 3-iter base-extension milestone (refs #7)
First new milestone after kernel-extension-mechanics close.
RawBuf is the canonical kernel-tier *base* extension: mutable,
indexed, bounded-size flat buffer of primitive elements
({Int, Float, Bool}, restricted via prep.3's param-in). It
unblocks two downstream needs already in the backlog:

- series milestone #8 — library-tier ring buffer wrapping RawBuf.
- Embedding-ABI batch-FFI (subsumes closed #2) — the M5
  friction-harvest measured per-tick FFI at ~206 ns/tick on
  real EURUSD volume; RawBuf is the contiguous-slice primitive
  that amortises that per-tick cost.

The milestone also fulfils the whitepaper § "Plugin contract"
commitment: the migration of the hardcoded
try_emit_primitive_instance_body into a registry is triggered
by the first real base extension shipping.

Decomposition (R — registry-first refactor, 3 iters):

  raw-buf.1 — Intercept registry refactor. Lift the existing
  hard-coded match (eq__Str, compare__Int|Bool|Str, float_*) into
  a registry table. Zero behavioural change; ratified by the
  existing E2E suite (eq_primitives_smoke / compare_primitives_smoke
  in e2e.rs, float_compare_smoke, eq_ord_polymorphic). Pure
  refactor — the cleanest possible bisection target.

  raw-buf.2 — RawBuf kernel-tier manifest + checker side. Rename
  crates/ailang-kernel-stub/ → crates/ailang-kernel/ and reshape
  as a family-crate (src/{kernel_stub,raw_buf}/{mod.rs,source.ail});
  public surface preserved via re-exports. Consumer code with
  (con RawBuf (con Int)) passes ail check; ail build fails with
  intercept-not-registered — that diagnostic IS the ratification.

  raw-buf.3 — RawBuf codegen intercepts + Term::New desugar +
  stub retirement. Register 12 element-type-specialised entries
  (4 ops × {Int,Float,Bool}); lower via @ailang_rc_alloc +
  getelementptr + load/store. Desugar (new T args) →
  (app T.new args) so Term::New is eliminated before codegen
  (completes the prep.2 deferral). Retire the kernel_stub
  submodule + the round-trip test at design_schema_drift.rs:743;
  ailang-kernel crate stays as the family-crate for future
  series/matrix/… modules.

Ordering rationale: raw-buf.1 ships zero behavioural change so
its failure mode is "existing tests break" — cleanest bisection.
raw-buf.2 ships the checker-visible surface on an unchanged
codegen substrate, so its failure mode is checker-isolated.
raw-buf.3 lands codegen on a registry that has already absorbed
every legacy intercept, so the RawBuf entries do not co-mingle
with a registry move.

Kernel-tier crate organisation: rejected pro-modul-crate
(crates/ailang-series/, crates/ailang-matrix/, ...) for sublinear
scaling and onboarding locality; rejected sub-Cargo-crates under
crates/ailang-kernel/ (C1) for Cargo-boilerplate overhead with no
real consumer benefit at AILang's scale. C2 (one family-crate,
sub-folders per module, lib.rs re-exports) keeps Cargo dep
surface flat (ailang-surface needs one dep, not N) and stub
retirement is a submodule delete + re-export drop.

Out of scope: bounds checks (caller checks via RawBuf.size per
whitepaper — UB-on-overflow is the contract), RawBuf.fill /
.copy / .iter (deferred until series or Embedding-ABI concretely
asks), record element types (SoA — Forward Axis).

Grounding-check PASS on 10 load-bearing assumptions about
current code state (intercept dispatch site, existing E2E
ratifiers, ailang-kernel-stub crate layout, parse_kernel_stub
location).

Brainstorm → planner handoff: first iteration scope is raw-buf.1
(§ Architecture point 1, § Components row 1).
2026-05-29 10:44:28 +02:00
Brummel aa49a56d5a fieldtest: kernel-extension-mechanics — 6 examples, 9 findings (3 bugs, 1 friction, 1 spec_gap, 4 working)
Post-audit fieldtest for the kernel-extension-mechanics milestone.
Fieldtester wrote 6 .ail consumer programs against the public
interface only (design ledger + spec + ail CLI; no source crate
reads), one per axis with two for the param-in pair (accept +
reject). All artefacts in examples/fieldtest/ + the spec.

**Working (4):** F5 NewTypeNotConstructible diagnostic is precise
(names the home module + missing def in one sentence). F6 Term::New
codegen-deferral diagnostic explicitly names the raw-buf milestone
where support lands. F7 param-in accept-path is end-to-end (check +
build + run prints 17). F8 ParamNotInRestrictedSet names type-arg
+ var + TypeDef.

**Bugs (3, action: debug):**

- F1 (axis 1) — Same-module type-scoped call `(app Type.member …)`
  rejected with phantom-qualified `expected <this>.T, got T`.
  Bare `(app member …)` from inside Type's home module works.
  Spec's "Canonical form decision" promises type-scoped works
  uniformly; the resolver appears to disagree with the workspace
  pre-pass on whether `<this-module>.T` equals bare `T`. Repro:
  examples/fieldtest/kem_2b_min_repro.ail.

- F3 (axis 3) — Kernel-tier auto-import scopes the name but does
  not register the module as a resolvable qualifier prefix. Per
  the pre-pass, a bare `StubT` in a type slot is qualified to
  `kernel_stub.StubT`; the resolver then rejects `kernel_stub`
  as unknown. Asymmetric: prelude's free fns work (per
  prelude_free_fns.rs), but the stub's TypeDef is effectively
  unreachable from any consumer. Repro:
  examples/fieldtest/kem_3_stub_consumer.ail.

- F4 (axis 3, spec/ship coherence) — The spec's prep.3 worked-
  consumer relies on `(new StubT 42)`, but the shipped STUB_AIL
  has only a TypeDef + ctor — the `(fn new ...)` the spec
  designed is missing from the implementation. The diagnostic
  the resolver does emit on the consumer (F5) is precise; the
  bug is the absent def itself. Resolution: re-add the `new` to
  STUB_AIL (the spec's design), refresh the drift pin.

**Friction (1, action: plan-or-defer):**

- F2 — Loader's sibling-only module resolution forces symlinks
  for any cross-module fixture sitting outside the canonical
  `examples/` directory. The kem_1 example required local
  symlinks for std_list.ail + std_maybe.ail. Secondary: the
  diagnostic message names only the .ail.json path even though
  .ail also resolves — actively misleading. Recommended: small
  tidy iter for loader workspace-search-path + diagnostic clarity.

**Spec-gap (1, action: ratify):**

- F9 — `design/models/0007-kernel-extensions.md:80` uses `unit`
  as a value literal of type Unit; the checker treats it as a
  Term::Var lookup and emits [unbound-var]. Either ratify `unit`
  as the canonical Unit-value literal in the language, or tighten
  the design model to use the existing idiom. The current model/
  surface disagreement is a small but real LLM-author trap.

**Symlinks committed** as evidence of the F2 workaround:
examples/fieldtest/std_list.ail and std_maybe.ail are symlinks
into ../. They are the fingerprint of the friction — removing
them silently would erase the evidence.

Per Iron Law, fieldtester did not work around bugs — they were
all surfaced and recorded. The kem_2b_min_repro.ail fixture
exists specifically because F1 surfaced mid-drafting and got
minimised; kem_3_stub_consumer.ail also stays as the F3 RED-side
fixture.

Triage (next-step routing for /boss):

  F4 → small inline fix (add (fn new ...) to STUB_AIL + drift refresh)
  F9 → small inline ratify (whitepaper edit)
  F2 → backlog issue, deferred (single tidy iter someday)
  F1, F3 → debug skill dispatches, then implement mini-mode
  F5-F8 → carry-on, recorded as wins
2026-05-28 19:19:00 +02:00
Brummel 3b4fb42771 plan: prep.3-kernel-tier-modules — 9-task schema + surface + workspace + diagnostic + stub crate (refs #33)
Terminal iteration of the kernel-extension-mechanics milestone.
Carves the spec's § Iteration prep.3 into nine bite-sized tasks:

  1. Module.kernel field + ~130-site struct-literal sweep + drift
     round-trip + data-model anchor.
  2. TypeDef.param_in field + ~30-site sweep + drift round-trip +
     anchor (BTreeMap<String, BTreeSet<String>>, kebab-cased
     `param-in`, skip-if-empty for hash stability).
  3. Form-A (kernel) module-header attribute — parse + print +
     round-trip.
  4. Form-A (param-in (a Int Float) (b Str)) TypeDef attribute —
     one outer clause, multiple inner var-lists (OQ1 decision).
  5. Prelude becomes kernel-tier + loader generalisation: the
     hardcoded `&["prelude"]` literal at loader.rs:108 migrates to
     a `modules.values().filter(|m| m.kernel)` derivation;
     ReservedModuleName diagnostic repurposed; CLI mapping +
     hash-pin refresh in lockstep.
  6. New crate `crates/ailang-kernel-stub/` — programmatic stub
     module via parse_kernel_stub() mirroring parse_prelude
     (OQ2 decision); wired into the loader; basic stub round-trip
     drift test ratifies the end-to-end mechanism.
  7. CheckError::ParamNotInRestrictedSet variant + code() + ctx() +
     enforcement in check_type_well_formed's Type::Con arm +
     in-source RED/GREEN tests.
  8. New workspace_kernel.rs integration tests — auto-import without
     explicit (import …), two kernel-tier modules co-load,
     explicit-import-overrides-auto-import precedence.
  9. design/INDEX.md + design/models/0007-kernel-extensions.md
     STATUS + forward→present transitions for the now-shipped
     mechanisms.

The four open questions raised by plan-recon are resolved up front
in the "Open-question decisions" block so no task carries a TBD.
ReservedModuleName variant shape stays `{ name: String }` (OQ3);
workspace.rs:2655 stays a test-site literal (OQ4).

The Module struct-literal sweep is the largest single mechanical
edit (~130 sites; the additive #[serde(default)] covers JSON
deserialise paths, only Rust struct literals break) — handled as
a compiler-driven sweep inside Task 1, with the workspace-build
gate validating no caller is deferred. TypeDef has ~30 sites.

Compile-gate-vs-deferred-caller and pin/replacement-substring
contiguity rules from planner self-review Step 5 are scrubbed:
no signature change defers a caller past its own compile gate,
no verbatim text edit pairs with a soft-wrappable presence pin.

Recon report received DONE_WITH_CONCERNS — concerns were the
four OQs, all decided in this plan.
2026-05-28 17:04:41 +02:00
Brummel 70e6fcd5c0 plan: prep.2 Term::New construct — 4-task atomic AST + surface + checker + drift (refs #32)
Second iteration of the kernel-extension-mechanics milestone. The
plan ships the `new` Form-A keyword + Term::New AST variant + NewArg
enum + checker elaboration + two diagnostics, plus the schema-drift
pin and the data-model.md / form_a.md anchor additions.

Decomposition:

- Task 1 (large mechanical): AST variant declaration + workspace-wide
  compile-forced arms across 24 files (44+ exhaustive Term-match
  sites). Six arm patterns inlined; per-site table assigns one
  pattern per site. Includes the prep.1 pre-pass partner — the
  `qualify_workspace_term` arm that rewrites bare Term::New.type_name
  to qualified form, mirroring the existing Term::Ctor arm. Synth
  gets a Pattern-E stub here; Task 3 replaces with real logic.
- Task 2: Form-A lex/parse/print + round-trip fixture. New
  `parse_new` method with Type-vs-Term arg disambiguation by
  syntactic form (head keyword = Type-production heads
  `con`/`fn-type`/`borrow`/`own` → NewArg::Type; else NewArg::Value).
- Task 3: Checker synth real-logic + 2 new CheckError variants
  (NewTypeNotConstructible, NewArgKindMismatch) + 3 RED-first
  in-source tests covering the spec's worked example, the missing-
  `new`-def case, and the kind-mismatch case.
- Task 4: Schema-drift pin (term_new_round_trips +
  term_new_type_arg_round_trips) + spec_drift exemplar + form_a.md
  keyword cheatsheet + design/contracts/0002-data-model.md JSON-tag
  block.

Spec is already correct on prep.2 scope; the recon surfaced one
design decision the plan resolves inline: NewArg uses
`#[serde(tag = "kind", content = "value", rename_all = "lowercase")]`
to land the spec's exact JSON byte shape. Codegen remains explicitly
out of scope per the milestone spec; Task 1's codegen sites get
Pattern-D error-arms ("Term::New requires desugar first; codegen
support lands in the raw-buf milestone").

Forward-reference: Task 1's Pattern-C arm in `qualify_workspace_term`
extends prep.1's workspace-wide normalisation pre-pass — Term::New
joins Term::Ctor and Type::Con as a type_name-bearing site that
gets bare→qualified normalised before the checker sees it.
2026-05-28 15:00:27 +02:00
Brummel b586999e81 iter prep.1-type-scoped-namespacing (DONE 5/5): TypeDef-first resolution + workspace pre-pass — closes #31
First iteration of the kernel-extension-mechanics milestone. Ships
the type-scoped `<TypeName>.<member>` resolution path as the
canonical form for type-associated operations, narrows the
`BareCrossModuleTypeRef` / `BadCrossModuleTypeRef` diagnostics from
"bare = strictly local" to "bare = in-scope by any path", migrates
12 std-library example fixtures, and introduces a workspace-wide
normalisation pre-pass `prepare_workspace_for_check` shared between
`check_workspace` and `monomorphise_workspace`.

Architectural discovery during implementation: the plan covered the
`Term::Var` dot-qualified resolver layer plus the workspace
validator's bare-name acceptance, but the migration of bare-form
fixtures exposed five sites where bare vs. qualified type-names
needed symmetric treatment — `Term::Ctor` resolution, `Type::Con`
well-formedness, mono's poly-free-fn name/constraint-count
enumeration, codegen's `lookup_ctor_by_type` bare-name path, and
the upstream desugar-then-qualify composition. Rather than
scattering TypeDef-first ladders across each site, the implementer
centralised the work into one pre-pass that walks every consumer
module's `Type::Con.name` and `Term::Ctor.type_name`, rewriting
bare cross-module references to their qualified `<home>.<Type>`
form. This is symmetric to the pre-existing `qualify_local_types`
(owner-side); the new pre-pass is the consumer-side mirror.
Downstream passes see qualified Types regardless of authoring form.
The TypeDef-first ladder still lives in `synth`'s `Term::Var` arm
because `<TypeName>.<member>` is term-position-only — `Maybe.from_maybe`
is a Var, not a Type expression, and the pre-pass does not rewrite
Var names.

Alternatives considered:

(a) Add TypeDef-first ladder at every resolution site separately
    (the plan's implicit assumption). Rejected: O(N) extension
    sites, each carrying the same workspace-walking logic; the
    pre-pass version is O(1) — one pass, every downstream consumer
    benefits.
(b) BLOCKED + spec re-brainstorm. Rejected: the architecture
    extension is consistent with prep.1's thesis (bare type-name
    resolves to the workspace-wide TypeDef) and forward-compatible
    with prep.2 (Term::New.type_name falls under the same rewrite)
    and prep.3 (kernel-tier TypeDefs enter the workspace map
    automatically). No design regression to bounce back over.

Spec updated to document the realisation mechanism honestly: the
"Realisation mechanism — workspace pre-pass" subsection clarifies
that the resolver-level semantics described in "Implementation
shape" are the user-facing contract, and the actual code path is
the pre-pass.

Verification:

- `cargo test --workspace`: ALL GREEN. 87 e2e + every crate's unit
  + integration tests pass with no regressions.
- Three NEW in-source tests pin Task 1's resolver paths:
  `type_scoped_member_resolves`, `type_scoped_member_not_found`,
  `type_scoped_receiver_not_a_type`.
- One NEW workspace test pins the narrowed validator:
  `ct1_validator_accepts_bare_with_explicit_import`.
- One renamed-and-flipped existing test:
  `ct1_validator_rejects_bare_xmod_with_import_candidate` →
  `ct1_validator_accepts_bare_xmod_with_import_candidate` (the
  bare-with-import path is now ACCEPTED).
- One NEW companion test for the workspace-wide ctor lookup:
  `ct2_term_ctor_bare_cross_module_via_workspace_resolves`.
- Two pre-existing tests' assertions updated for the new error
  wording: `ct1_check_cli::check_human_mode_emits_actionable_message_to_stderr`
  and `crates/ailang-check/tests/workspace.rs::unknown_module_prefix_is_reported`.
- 12 migrated `.ail` fixtures verified via the existing e2e
  suite (each fixture is the test runner's target for an existing
  `build_and_run` assertion).
- Negative fixture `ct_2_bare_cross_module.ail` semantically
  preserved: dropped its `(import std_maybe)` so bare `Maybe` is
  out-of-scope under the narrowed rule and still fires
  `BareCrossModuleTypeRef`.

Concerns:

- The pre-pass introduces a new architectural layer (consumer-side
  qualification) that the spec did not originally anticipate. Spec
  amendment in this commit documents the layer. Future iterations
  reference `prepare_workspace_for_check` as established
  infrastructure.
- `examples/test_ct1_bare_xmod_rejected.ail.json` switched its
  offending name from bare `Ordering` (which under the prep.1
  semantics may now resolve via implicit prelude) to a still-
  unresolvable `Mystery_Type`. The CLI test's intent (assert that
  a human-mode `ail check` exits non-zero on a still-RED case) is
  preserved.

Milestone status: kernel-extension-mechanics (Gitea #6) advances
1/3 iters. Next: prep.2 (`Term::New` construct) issue #32.
2026-05-28 14:43:03 +02:00
Brummel 46c9aabf00 plan: prep.1 type-scoped namespacing — 5-task atomic resolver + 12-fixture migration (refs #31)
The plan covers the first iteration of the kernel-extension-mechanics
milestone: type-scoped `<TypeName>.<member>` resolution in
`ailang-check`, narrowed `BareCrossModuleTypeRef` /
`BadCrossModuleTypeRef` diagnostics in `ailang-core::workspace`,
two new `CheckError` variants, CLI diagnostic-message rewording,
and atomic rewrite of 12 `.ail` example fixtures from `std_X.Y` to
the type-scoped form. Five tasks, each unit-of-review.

Includes a spec correction (same commit because plan recon
surfaced it): the prep.1 Blast Radius previously claimed
`hash_pin.rs` + `prelude_module_hash_pin.rs` refreshes and a
`design_schema_drift.rs` pin addition. Plan-recon walked every
test crate (per the schema-camelcase-fix hash-pin-blast-radius
lesson) and found:
  * neither hash-pin file pins any fixture in prep.1's migration
    set — refresh is empty;
  * type-scoped resolution is a checker-only change (no new JSON
    tags, no AST shape change) — the drift pin belongs to prep.2
    (`Term::New`) and prep.3 (`kernel: true` + `param-in`), not
    prep.1.

Spec section 'Blast radius' and the 'Iteration scope' summary now
reflect the recon-cleared reality.
2026-05-28 14:04:01 +02:00
Brummel 832375f2ac 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.
2026-05-28 13:31:31 +02:00
Brummel 7b8596cef0 spec: kernel-extension-mechanics — three-iter prep milestone
docs/specs/2026-05-28-kernel-extension-mechanics.md: per-milestone
implementation container for the four language-level mechanisms
described in design/models/kernel-extensions.md.

Three iterations, each independently shippable:

- prep.1 — Type-scoped namespacing. Resolver change so
  `<TypeName>.<member>` resolves to the type's home module.
  Migrates ~14 std-library example fixtures from `std_X.Y`-style
  cross-module references to type-scoped form. Hash pins
  refreshed in lockstep (ailang-core + ailang-surface — full
  blast-radius walk, per the hash-pin-audit lesson from
  schema-camelcase-fix).

- prep.2 — Term::New. New AST variant and Form-A keyword
  `(new T args...)`, calls the `new` def in T's home module.
  Uses prep.1's resolution.

- prep.3 — Kernel-tier modules + `param-in`. Schema flag
  `Module.kernel` + TypeDef `param-in` field. Workspace-load
  generalises the existing hardcoded prelude auto-injection
  (loader.rs:98-108 + workspace.rs:308-311, 467, 2655) into a
  flag-driven mechanism; prelude itself gains `kernel: true` as
  a code-path migration (consumer-observable behaviour
  unchanged — prelude_free_fns.rs regression test stays green).
  A new minimal ailang-kernel-stub crate ratifies the mechanism
  without domain content.

Five new diagnostics across the three iters:
TypeScopedMemberNotFound, TypeScopedReceiverNotAType,
NewTypeNotConstructible, NewArgKindMismatch,
ParamNotInRestrictedSet. BareCrossModuleTypeRef and
BadCrossModuleTypeRef diagnostics repositioned (still "type
name not resolvable" but with revised remediation pointers).

Each iter's spec section carries explicit `## Canonical form
decision`, `## Blast radius`, and `## Integration with existing
mechanisms` subsections — the migration-policy discipline made
visible per spec section.

Grounding-check (two dispatches): first BLOCK on prelude
auto-injection mis-framing (claimed it was a new mechanism;
in fact prelude is hardcoded-auto-injected today), corrected
inline; second PASS after the revised two-tier-architecture
framing also landed. All load-bearing assumptions ratified by
named tests in the working tree.

Out of scope, named explicitly: the raw-buf milestone (#7), the
series milestone (#8), record-element SoA support, LSP/MCP
integration for `ail describe`, higher-kinded `param-in`, and
primitive-instance intercept migration (deferred to #7 when
there is a real second consumer for the registry).

Refs Gitea milestone #6.
2026-05-28 13:14:35 +02:00
Brummel 90977663ff plan: schema-camelcase-fix — 4-task atomic schema-tag migration (refs #30)
Iteration plan derived from `docs/specs/2026-05-21-schema-camelcase-fix.md`
(commit 55ce6d0, amended). Single-iteration milestone, four tasks:

- **Task 1 (RED)**: add `lam_serialises_with_kebab_keys` pin to
  `design_schema_drift.rs` + extend `anchor_in_jsonc_block` walk
  to require the new tag spellings inside `data-model.md`'s `lam`
  fenced block. Verifies pin fires RED on current state.

- **Task 2 (GREEN atomic schema swap)**: 10-step coupled
  migration of `ast.rs:492,494` + `workspace.rs:1925-1926` +
  `examples/test_loop_binder_captured_by_lambda.ail.json` +
  `experiments/.../master/examples/fn_with_lambda.ail.json` +
  `design/contracts/data-model.md:142-147`. All five files MUST
  move together — changing only `ast.rs` makes the JSON literals
  undeserialisable (no `#[serde(default)]` on `param_tys`/`ret_ty`,
  so the renamed-away key is a hard error).

- **Task 3 (GREEN-2 rustdoc honesty)**: three pure-prose edits in
  `ast.rs:8`, `parse.rs:81`, `check/lib.rs:1707`. Decoupled from
  the schema swap — no compile or test consequence.

- **Task 4 (Verify)**: exhaustive negative grep, full workspace
  test suite, round-trip invariance, forbidden-files check.

Plan pre-recon by `ailang-plan-recon` (DONE_WITH_CONCERNS) caught
the data-model.md + rustdoc layer the brainstorm initially missed
— spec was amended in commit 55ce6d0 + re-grounding-check PASS
(9 claims ratified, 2 of them via exhaustive negative grep as
"no-test-pins-this" Boss-overrideable shape).

Self-review: 8/8 (spec coverage, no placeholders, name consistency,
step granularity 2-5 min, no commit steps, pin/replacement
contiguity, compile-gate threading complete within Task 2,
verification filters all resolve to named real tests).
2026-05-21 12:48:59 +02:00
Brummel 55ce6d0d70 spec: schema-camelcase-fix — expand scope to data-model.md + rustdoc layer (refs #30)
Spec amendment after plan-recon flagged four missed touch-points
in the brainstorm grounding-check loop:

1. `design/contracts/data-model.md:142-147` — fenced JSON-block in
   the canonical data-model contract. The data-model contract IS
   the canonical-schema doc (INDEX.md row, ratifying test
   `tests/design_schema_drift.rs`); leaving it on camelCase after
   `ast.rs` ships kebab is a direct Honesty-Rule violation.

2. Three rustdoc strings in production source that describe the
   present-state schema vocabulary: `ast.rs:8`, `parse.rs:81`,
   `check/lib.rs:1707`. Each enumerates the rename targets in
   prose; left unchanged they would describe a state that no
   longer exists.

3. Better home for the new schema-shape pin: `design_schema_drift.rs`
   (not `schema_coverage.rs`). That file already operates as the
   data-model-contract ratifying test, already builds Term::Lam
   exemplars at L121-129, and uses `anchor_in_jsonc_block` to walk
   data-model.md fenced blocks — the proposed extension slots
   directly into the existing pin family.

4. Experiment-tree files (`experiments/2026-05-12-.../master/spec.md`,
   `rendered/*.md`, `runs/**`) carry old tags. Per Honesty-Rule
   analogy with docs/plans/* — these are frozen historical
   artefacts of the 2026-05-12 cross-model-authoring experiment
   and are NOT migrated. The experiment's `master/examples/*.ail.json`
   fixture IS migrated (live JSON the workspace loader can
   deserialise); surrounding prose is not.

Plus an editorial fix: the fixture-occurrence count parenthetical
corrected from "3" to "2" — each fixture has one `paramTypes` +
one `retType`, one per line.

Spec re-dispatched through `ailang-grounding-check` (Step 7.5
re-PASS). All 9 load-bearing claims ratified, with 2 negative-grep
"no test pins this" ratifications openly flagged in the agent
report as a Boss-override-eligible shape. Acceptance criteria
renumbered to 7 (was 6).

Touch-point count now: 2 serde-renames + 1 workspace.rs literal +
2 .ail.json fixtures + 1 data-model.md fenced block + 3 rustdoc
strings = 9 files, all small edits. No hash-pin refresh required.
2026-05-21 12:45:51 +02:00
Brummel 7d086e69ce spec: schema-camelcase-fix — paramTypes/retType → param-types/ret-type (closes #30)
Brainstorm output. Single-iteration milestone: rename the two
camelCase JSON tags on `Term::Lam` — the only camelCase outliers
in the AST schema — to kebab-case, matching the convention every
other compound-key tag (`reuse-as`, etc.) already follows.

Scope is verified-tiny: ast.rs (2 serde-rename strings) +
workspace.rs in-source test JSON-literal + 2 `.ail.json` fixtures.
None of the five hash-pinned `.ail` modules contains a lambda,
so the milestone refreshes zero hash pins. Form-A is untouched —
the surface uses `(params (typed ...))` / `(ret ...)`, never the
camelCase tags.

Feature-acceptance gate: passes weakly-but-honestly. Clause 1 is
indirect (schema-internal consistency reduces the LLM author's
"compound-tag-is-kebab" generalisation failure rate); there is no
empirical preference measurement for this axis. Clause 2 holds
(one fewer special case to memorise). Clause 3 vacuous (no
semantic surface touched).

Grounding-check (Step 7.5) PASS: all four load-bearing claims
ratified by currently-green tests — round_trip.rs (Form-A
invariance), hash_pin.rs (no lambda in pinned modules),
workspace.rs in-source ct1_validator test (exhaustive call-site
list), design_schema_drift.rs (kebab convention pin).

Not bundled with #27 (arith-rename) per the spec preamble: #27
carries its own four open brainstorm questions and is a separate
milestone; each gets one re-pin wave with its own rationale, no
churn savings from bundling.
2026-05-21 12:37:45 +02:00
Brummel 8d61599b8d doc: fix bare (class Eq) → (class prelude.Eq) in operator-routing-eq-ord spec north-star
Fieldtest (505eb84) finding spec_gap → tighten-the-design-ledger:
the spec's §"Concrete code shapes" north-star example at line 113
wrote `(class Eq)` (bare). An LLM-author who copies the spec
verbatim hits `bare-cross-module-class-ref` — the language
requires `(class prelude.Eq)` (qualified) at instance-declaration
sites. The shipped fixture `examples/eq_user_adt_smoke.ail`
already uses the qualified form (line 5 — implementer caught the
drift during Task 1 fixture authoring), so the corpus was
consistent; only the spec text drifted. Fix: rewrite line 113
to match the qualified form the language and the existing
corpus require.

One-line doc fix; no code surface change. Spec stays as
`docs/specs/2026-05-20-operator-routing-eq-ord.md` (Status: Draft
unchanged — content correction, not a re-issue).
2026-05-21 01:34:54 +02:00
Brummel 505eb8484e fieldtest: operator-routing-eq-ord — 5 examples, 6 findings (4 working, 1 friction, 1 spec_gap)
Post-audit field test of the operator-routing-eq-ord milestone
(closed via 5170b6a + 4d45bc6). Five `.ail` Surface-form fixtures
under `examples/fieldtest/` written by an LLM-author working
strictly from the design/ ledger + public examples (no
`crates/` / `runtime/` / `bench/` reads — Iron Law honoured):

  eqord_1_fizzbuzz.ail        — FizzBuzz [1..15] via (app eq …)
                                 on Int+Str and (app gt …) on Int
  eqord_2_rational_eq.ail     — data Rational + user-instance
                                 (class prelude.Eq) by cross-mult;
                                 inner Int-eq nested in instance body
  eqord_3_newton_sqrt.ail     — Newton's method via float_lt
                                 (convergence + fabs) and float_eq
                                 (zero-guard)
  eqord_4_float_ord_must_fail.ail — (app lt 1.5 2.5) must reject
                                     at typecheck with float_lt hint
  eqord_5_float_eq_must_fail.ail  — (app eq 1.5 1.5) must reject
                                     at typecheck with float_eq hint

Findings:

  [working] x4 — primitive Eq/Ord at Int/Bool/Str/Unit;
    user-ADT Eq with nested primitive eq calls; Float named-fn
    surface (float_eq/float_lt/etc.); NoInstance Eq/Ord Float
    diagnostic with float_eq / float_lt addendum. The milestone's
    central thesis (class-dispatch is the only comparison
    surface; Float opts out via named fns) is LLM-natural —
    every (app eq …) / (app gt …) / (app float_lt …) call I
    wrote compiled on first try with no diagnostic friction.

  [spec_gap] x1 — `docs/specs/2026-05-20-operator-routing-eq-ord.md:113`
    north-star example writes bare `(class Eq)` where the
    language requires `(class prelude.Eq)`. An LLM-author who
    copies the spec verbatim hits `bare-cross-module-class-ref`.
    The milestone spec is the highest-information reference an
    LLM-author consults; the bare-vs-qualified drift mis-primes
    the pattern. Resolution: tighten-the-design-ledger — fix
    the spec example inline (separate follow-up commit). The
    existing `examples/eq_ord_user_adt.ail` already uses the
    qualified form, so the corpus is consistent; only the spec
    drifted.

  [friction] x1 — `(app compare 1.5 2.5)` at Float fires the
    same diagnostic as `(app lt …)` at Float, naming
    `float_eq / float_lt (and siblings)` as the alternative.
    But the alternatives don't return Ordering; an LLM-author
    who wanted three-way LT/EQ/GT can't satisfy that with
    float_lt + float_eq alone. The spec (lines 433-436)
    acknowledged this case in commentary — "no float_compare
    ships; build it from float_lt + float_eq if you need
    three-way" — but the diagnostic doesn't say so. Resolution:
    file as Gitea backlog issue for a follow-up tidy iteration
    (codegen-side: branch the Float-aware NoInstance addendum
    on the called method-name; the `compare` arm gets a
    one-sentence "no float_compare; build it from float_lt +
    float_eq if you need three-way" addendum).

Status: clean — no bugs, no blockers, the milestone surface is
solidly LLM-usable. Both non-working findings are tractable
forward-fixes that don't disturb the milestone's core
contracts.

Spec: docs/specs/2026-05-21-fieldtest-operator-routing-eq-ord.md
2026-05-21 01:34:19 +02:00
Brummel 400ad7c067 plan: operator-routing-eq-ord.1 — atomic single-iter execution layout
Decomposes the operator-routing-eq-ord spec (a68d7b6) into 12
bite-sized tasks. Task 1 bootstraps all 5 RED tests + 4 new
fixtures (TDD). Tasks 2-3 ship the load-bearing additions
(codegen alwaysinline mechanism + new intercept arms; prelude
reshape with Eq Unit + six float_* fns + body-hash re-pins).
Tasks 4-6 migrate everything that references the dying surface
(60 fixtures, 8 in-source test scaffolds, lit-pattern desugar).
Task 7 deletes the now-dead operator machinery (typchecker
builtin entries + codegen lower_eq + comparator arms + `==`-
short-circuits) in one cohesive sweep behind a compile-gate.
Task 8 extends the Float-aware NoInstance diagnostic. Tasks
9-11 are mechanical follow (IR snapshot regen, prose-projection
cleanup, 5-contract-file honesty-rule updates). Task 12 is the
acceptance gate.

Plan-recon (DONE_WITH_CONCERNS) surfaced 4 substantive spec
gaps; the plan absorbs all 4:

  - Fixture-migration scope is 60 files / 76 occurrences, not
    the spec's "~10". Task 4 is correspondingly large but the
    work is mechanical.
  - The spec named 3 in-source `#[cfg(test)] mod tests` AST-
    literal sites that need migration; recon enumerated 8
    (incl. `>=` at lib.rs:5656, three `==` sites in codegen/
    lib.rs at 3371/3419/4354, two `cmp("==", …)` Float-arith
    tests at codegen/lib.rs:3729-3734). Task 5 covers the
    full 8.
  - The spec named 3 contract files for honesty-rule update;
    recon flagged 2 additional load-bearing files where the
    dying surface is described present-tense — `str-abi.md:40-42`
    ("REMAIN primitive operators" — directly contradicts the
    milestone) and `scope-boundaries.md:42-96` (multiple
    operator + Pattern::Lit-desugar clauses) — plus a model
    file `authoring-surface.md:58` (operator-example list).
    Task 11 covers all 5.
  - `alwaysinline` codegen attribute machinery does not exist
    in the codebase today (no `attributes #N = { … }` group
    emission, no inline-attribute on any `define` line — recon
    verified). Task 2 introduces it as inline-attribute-on-
    define (LLVM-13+ permits attribute-name directly between
    `)` and `{`), keyed by a new `intercept_emit_wants_alwaysinline`
    allowlist that mirrors the intercept-arm symbol set in
    `try_emit_primitive_instance_body`.

Plan self-review caught two plan-grade items that recon did not
flag, both absorbed:

  - North-star test (`eq_user_adt_smoke_e2e`) would have been
    structurally GREEN at start-of-iter because today's
    `(app eq …)` on user-ADT-with-instance already works (the
    `eq_ord_user_adt.ail` fixture has shipped since 23.5). To
    hold the TDD-RED-first discipline, the north-star fixture
    adds an opening `(app print (app eq () ()))` line which
    fires `NoInstance Eq Unit` today; the new `instance Eq Unit`
    in Task 3 turns this from RED to GREEN. The expected
    stdout becomes `"true\ntrue\nfalse\n"` (Unit-eq, then
    Point-eq, then Point-neq).
  - `is_arithmetic_or_comparison_op` rename + the `name == "=="`
    clause deletion at codegen/lib.rs:2529 both touch the same
    line; the plan explicitly notes both edits land at this
    site in lockstep (Task 7 step 3 + step 5).

Per-task expected-state map names which RED-test flips GREEN at
which task; the workspace test-suite goes through known partial-
pass states between tasks but Task 12 is the only "all green"
gate. The four code-path-deletion tasks (Tasks 4-7) each end
with `cargo test --workspace --quiet` returning `0 failed`
because all callers of the about-to-delete surface are migrated
in earlier tasks — the compile-gate at Task 7 step 7 is
satisfiable by construction (recurring "compile-gate-vs-deferred-
caller" defect family, explicitly scrubbed in self-review #7).

All `cargo test --…<filter>` Run steps use filter substrings
verified to resolve to a real named test or test binary in the
current tree (recurring "verification-filter doesn't resolve"
defect family, scrubbed in self-review #8).

Net delta on landing: 9 new files (4 fixtures + 5 tests), ~30
.rs files modified across `ailang-check` / `ailang-codegen` /
`ailang-core` / `ailang-prose`, 60 .ail fixture migrations,
2 IR snapshots regenerated, 5 design contract / model files
updated, 8 hash-pin literals re-pinned across 3 pin-files.
Workspace pass-count delta: +5 (new tests) − 3-to-5 (test-
scaffold deletions in Tasks 5+10) ≈ +0 to +2 net.

refs #1
2026-05-21 00:05:15 +02:00
Brummel a68d7b6353 spec: operator-routing-eq-ord — drop comparator builtins, route through Eq/Ord
Resolves Gitea #1. Realises the "P2 follow-up" called out in
examples/prelude.ail line 9. Approach A (single-iter atomic
milestone): one cohesive cut across seven layers in one iter,
plus the regenerated prelude module hash pin.

Three design forks resolved with the user via brainstorm Q&A:

  (1) Operator-name surface: `==` `!=` `<` `<=` `>` `>=` die from
      the language. The LLM-author writes only the class-method
      names `eq` `ne` `lt` `le` `gt` `ge` `compare`. One mental
      model: AILang has class-dispatch, operators are not a
      separate concept. Option 2 (keep names as surface aliases)
      was rejected because it adds a second spelling for an
      identical operation — the redundancy the milestone is
      supposed to remove gets reintroduced syntactically. Option 3
      (two-track: primitive Built-in for Int/Bool/Str/Unit, class
      for user-types) was rejected because the two-pathy state is
      precisely what this milestone exists to dismantle.

  (2) Float comparison surface: Float keeps comparison capability
      via six named prelude fns (`float_eq` `float_ne` `float_lt`
      `float_le` `float_gt` `float_ge`), no `Eq Float` /
      `Ord Float` instance. Motivation: all three feature-acceptance
      clauses simultaneously satisfied — clause 1 (LLM-natural via
      Library-Convention-Pattern like Python's `math.isclose` or
      Rust's `approx`-crates), clause 2 (within the polymorphic
      surface there is one path; Float is honestly stamped
      "non-polymorphic"), clause 3 (the NaN-comparison anti-pattern
      stays visible in code as `float_eq` rather than hiding
      behind `eq`). Option A (Float loses all comparison) was
      rejected as overstretch — workarounds via `is_nan` +
      arithmetic are more bug-prone than a named fn that emits the
      right fcmp directly. Option C (Eq Float with IEEE semantics)
      was rejected as clause-3 violation — it would reintroduce
      the silent NaN-comparison bug class that the existing
      Float-no-Eq/Ord design exists to prevent.

  (3) Codegen mechanism for primitive Eq/Ord instances: option β
      (always-call through dispatch, with `alwaysinline` attribute
      on intercept-emitted bodies as pre-emptive `-O0` mitigation,
      and option α — call-site intercept — held as the bench-gate
      fallback if measurements show real regression). Motivation:
      semantic honesty (class methods ARE calls, optimiser folds
      primitives uniformly at Int and User-Point alike), parity
      with `Show` (which is also full-call today with no
      intercept), smaller IR-shape contract for the existing pins.
      Under `-O2` the inliner deterministically collapses 2-
      instruction bodies; bench corpus runs `-O2`. Under `-O0`
      `alwaysinline` overrides the no-inline default, giving the
      same per-call IR shape as today. α was the initial-framed
      Recommendation but was scrutinised in user pushback ("spricht
      irgendwas FÜR β?") and after substantive re-balancing β
      came out coherent.

Grounding-check (ailang-grounding-check) PASS on re-dispatch — 11
load-bearing assumptions ratified by:
`crates/ail/tests/e2e.rs::eq_demo` + `::lit_pat_demo`,
`crates/ail/tests/eq_ord_e2e.rs::eq_ord_polymorphic_runs_end_to_end`
+ `::eq_ord_user_adt_runs_end_to_end` + `::eq_ord_user_adt_eq_intbox_hash_stable`,
`crates/ail/tests/eq_float_noinstance.rs::eq_at_float_fires_float_aware_noinstance`,
`crates/ail/tests/prelude_free_fns.rs::ne_at_int_produces_mono_symbol` (+4 siblings),
`crates/ailang-surface/tests/prelude_module_hash_pin.rs::prelude_parse_yields_canonical_hash`,
plus in-source `#[cfg(test)] mod tests` ratifiers in
`crates/ailang-check/src/lib.rs` (`eq_typechecks_at_int/bool/str/unit`,
`mq2_env_method_to_candidate_classes_built`) and
`crates/ailang-codegen/src/lib.rs` (`lower_eq_str_calls_strcmp_with_bytes_pointer`).
The `alwaysinline` LLVM attribute is correctly classified as new
feature-work commitment (no live occurrences today), not as a
load-bearing assumption about present state.

First grounding-check pass BLOCKED on one spec defect — the spec
mischaracterised `crates/ailang-core/src/desugar.rs:2414` as "a
separate desugar pass" when it is in fact a `#[cfg(test)] mod tests`
AST-literal scaffold. Fixed inline: §Architecture and
§Components/4 now enumerate only `build_eq` (desugar.rs:1099) as
the sole production desugar site; `desugar.rs:2414`, `lib.rs:6092`,
`lib.rs:6220` are framed as test-scaffold migrations alongside the
production change, not as desugar-pass work. Re-dispatch PASS.

Out of scope (tracked separately):
  - Deriving for Eq/Ord — `typeclasses.md:177` "No deriving"
    stands; instance bodies remain hand-written.
  - Parameterised-ADT instances (`instance Eq (List a)` etc.) —
    requires constraint-propagation infrastructure, belongs to
    Gitea #2 ("22c typeclass corpus expansion").
  - Eq/Ord for the `Ordering` ADT itself — no use case; consumed
    via match, not compared.
  - `and` / `or` as Builtins — north-star fixture uses
    `(if … … false)` for short-circuit conjunction; separate
    concern.

refs #1
2026-05-20 23:43:13 +02:00
Brummel ad0a8d8786 plan: boehm-retirement.1 — atomic single-iter execution layout
Decomposes the Boehm-retirement spec (50dc478) into 10 bite-sized
tasks. Task 1 ships the RED milestone-pin first (TDD); Task 2 is
the compile-gated atomic core (drops `AllocStrategy::Gc` variant +
the `Default` derive + threads every caller in codegen + main.rs in
one cohesive task, so the workspace-build gate at the end is
satisfiable). Tasks 3-9 cover the scrub work that cascades off the
core (e2e suite, doc-comments, bench harness, design ledger,
honesty-pin inversion, example fixtures + agent prompts, IR
snapshots). Task 10 is the acceptance gate.

Plan-recon (DONE_WITH_CONCERNS) surfaced 9 spec gaps; the plan
absorbs all 9:
  - `runtime_alloc_fn` → actual identifier `fn_name` (no rename)
  - 5 checked-in IR snapshots (`hello.ll`, `list.ll`, `max3.ll`,
    `sum.ll`, `ws_main.ll`) regenerate via `UPDATE_SNAPSHOTS=1`
  - `design/contracts/scope-boundaries.md:67,114-127` Boehm block
    + `examples/gc_stress.ail.json` dead-reference
  - `design/contracts/memory-model.md:232` "pre-Boehm era" phrase
  - `bench/check.py:91,94` `implicit @ gc` arm label map
  - `crates/ailang-codegen/src/match_lower.rs:40,113`
    `@GC_malloc` doc-comments
  - `skills/implement/agents/ailang-implementer.md:95,185` Decision
    10/Boehm references
  - `AllocStrategy`'s `Default` derive (no callers — strip)
  - e2e.rs spec line numbers refer to gc-call lines, not test-fn
    header lines

Plan self-review caught a planner-grade defect that recon had
not flagged: `bench/check.py:62` hardcodes `"gc(s)" in line` as
the throughput-table header sentinel and `:72` hardcodes the
9-column count — both must shift in lockstep with the
`bench/run.sh` 9→6 column compaction; without the pair-edit,
`parse_throughput_table` silently returns an empty dict and the
"green" check pins nothing. Task 5 now carries Steps 4a-4c (header
+ count + field-set) as a coherent unit, plus the explicit pin in
the rationale.

Net delta on landing: 1 new test, ~30 modified files, 5
regenerated IR snapshots, 1 deleted fixture. Workspace passing-
test count shifts by `-3` (4 deletions − 1 new). The iter commit
will close Gitea #4 via the trailer.

refs #4
2026-05-20 20:24:22 +02:00
Brummel 50dc478ca5 spec: boehm-retirement — drop the transitional Boehm GC path
Resolves Gitea #4. Approach A (atomic single iteration). Six layers
in one cohesive cut: CLI `--alloc=gc` arm removed; codegen
`AllocStrategy::Gc` variant deleted with the default flipped to
`Rc`; libgc link branch removed; ~3 pure-differential e2e tests
plus the `gc_stress.ail` fixture deleted; ~9 RC-feature tests that
used GC-stdout as backstop lose only the differential assertion
(absolute fixed-stdout pin retained); M2 staticlib alloc-guard
drops its gc-arm (bump-arm preserved); design/models/rc-uniqueness
excises the Boehm-parity-oracle narrative; pipeline.md drops the
libgc pipeline-diagram arm; docs_honesty_pin flips from
Boehm-present-tense anchor to four absence-pins against
Boehm-zombie strings.

Three design forks were resolved with the user via brainstorm Q&A:

  (1) bump survives as bench-floor — `AllocStrategy::Bump`, the
      `--alloc=bump` CLI flag, and `runtime/bump.c` all stay; the
      enum keeps two variants (Rc, Bump); the codegen
      negative-complement test retargets from `AllocStrategy::Gc`
      to `AllocStrategy::Bump`. Bump's standing role is the
      raw-alloc bench-floor for RC-overhead measurement, not a
      production target.
  (2) the ~12 RC-vs-GC differential e2e tests are NOT all deleted
      wholesale — pure-differential ones (test name literally
      `*_matches_gc_*` or `*_same_stdout_as_gc`) are deleted; the
      RC-feature tests with GC as backstop keep their absolute
      stdout pin (`assert_eq!(stdout_rc.trim(), "<n>")`) and only
      lose the differential assertion. This nuance was added at
      spec time on top of the user's "delete the differential
      pattern" answer, because the differential was incidental to
      tests like `rc_box_drop` / `rc_list_drop_borrow` that pin
      drop-fn correctness under RC and would lose unrelated
      coverage if deleted entirely.
  (3) the 1.3× RC-over-bump number is retained in
      design/models/rc-uniqueness.md but reframed as a
      bench-health regression gate (not a Boehm-retirement gate);
      the closure-chain ±15% wider band is preserved analogously.

Grounding-check (ailang-grounding-check) PASS — 7 load-bearing
assumptions ratified, all spec-named paths and line numbers
verified (±2 lines), all proposed-for-removal strings present at
the spec-named locations. Assumption #7 (codegen default
currently Gc) is structurally self-evident: removing the variant
forces the default onto a surviving one by construction.

Out of scope, tracked separately:
  - Gitea #3 "Closure-pair slab / pool" — would tighten the
    closure-chain ±15% band; not blocked by retirement.
  - Any further `AllocStrategy::Bump` rework — still a
    single-variant bench instrument.

refs #4
2026-05-20 20:02:32 +02:00
Brummel 7d8b3a3c10 plan: nullary-app.1 — accept (app f) as canonical zero-arg call form
Resolves the design fork in Gitea #12 (the bounce-back from the
2026-05-20 /boss session). User accepted both half-decisions:

  (a) the surface form `(app f)` becomes the canonical nullary
      call shape, dropping the "expected at least one argument"
      parser guard in `parse_app_body`;
  (b) `Term::App.args` mirrors the *actual* serde attrs of
      `Term::Ctor.args` — `#[serde(default)]` for read-tolerance,
      no `skip_serializing_if`, so writes always emit
      `"args":[]`.

The plan covers five small tasks: a RED→GREEN E2E (nullary user fn
called as `(app greet)`), the four-line parser-guard removal, the
one-attribute serde edit, and a doc-honesty fix on
`design/contracts/data-model.md` — the current "args omitted when
empty" comment on `Term::Ctor` is factually false relative to the
code (verified by a serde probe at plan time) and gets rewritten to
describe actual write/read behaviour while we are in the same jsonc
block. `design/contracts/honesty-rule.md` enforcement folded into
the same iter because leaving a stale comment behind in the block
we're modifying would be the exact failure mode the rule names.

Hash-impact verified at plan time: `grep -rn '"args":\[\]'
examples/*.ail.json` returns zero matches, so the new
`#[serde(default)]` on `App.args` is read-only behaviour change —
write side is unchanged, no existing fixture hash mutates. The
plan documents this explicitly under Task 3.2.

Two prior fieldtest specs already supplied the LLM-natural shape
evidence — `docs/specs/2026-05-15-fieldtest-mut-local.md` F3
(mut-local) and `docs/specs/2026-05-18-fieldtest-loop-recur.md`
spec_gap (`run_forever`) — so the feature-acceptance gate in
`design/contracts/feature-acceptance.md` is already passed; no
brainstorm phase needed for this iter.

refs #12
2026-05-20 18:52:22 +02:00
Brummel ddb50c3cb3 plan: bench-harness-recalibration.1 — drop 6 latency entries + recapture
One terminal iteration covering the whole spec.  Four tasks, all on
`bench/baseline.json`:

- Task 1: pre-recapture JSON edit — drop `max_us` + `p99_9_us` × 3
  latency arms (6 entries) and rewrite the `note` field forward-
  looking.  The 6 entries must go before `--update-baseline` runs
  because `write_new_baseline` faithfully re-baselines anything
  left in the file (recon Open Q).  The note rewrite is folded in
  here because `--update-baseline` preserves the existing note as-
  is, so any change has to happen before recapture (or as a
  separate post-recapture edit; one-edit is cleaner).
- Task 2: `bench/check.py --update-baseline` regenerates every
  remaining `baseline` value from a fresh `bench/run.sh -n 5`,
  updating `captured` → 2026-05-20 and `captured_via`.
- Task 3: acceptance §1 — fresh-HEAD replay → exit 0, 0 regressed,
  57 metrics in summary (= pre-edit 63 minus the 6 drops).
- Task 4: acceptance §2 — synthetic injection (halve
  `bench_list_sum.bump_s.baseline` via `jq`) → exit 1 + REGRESSION
  row on that metric, then restore via `jq` and confirm a final
  exit-0 replay.

Recon adjudications:

- Replay-source (recon Open Q1): fresh `bench/run.sh -n 5` for
  replay, not the same output that produced the recapture.
  Realistic acceptance scenario; with 10% throughput tolerance vs.
  ~1-2% measured run-to-run variance on the affected metrics
  (run-1 / run-2 reproduction data in spec body), there is ample
  margin.
- Note rewrite (recon Open Q2): forward-looking — drop stale
  refs (JOURNAL workflow retired 8e586f4; the `*.max_us`
  tolerance convention is now moot since the metric is gone),
  replace with pointer to docs/specs/2026-05-20-bench-harness-
  recalibration.md + closed issues #15 / #16.  Spec lives where
  the rationale lives; the note carries the gate-policy
  one-liner only.

No Rust crate touched.  `bench/check.py` / `bench/run.sh` /
`bench/latency_harness.py` unmodified.

Plan self-review (all 8 checklist items): clean.  Step granularity
checked; Task 3 collapsed from two `bench/check.py` runs to one
(single 3-minute run captures both summary and exit code).

Ready for handoff to `skills/implement`.
2026-05-20 16:07:53 +02:00
Brummel a97aaebd45 spec: bench-harness-recalibration — degate max_us/p99_9, recapture baselines
One-iteration infra milestone closing Gitea #15 + #16. Two issues
framed distinct symptoms; reproduction on 2026-05-20 HEAD via two
back-to-back `bench/check.py -n 5` runs collapses them onto a
single picture:

- #15 ("*.bump_s stale") is wider than the issue framed.  Drift
  reproduces deterministically on `bench_list_sum.bump_s`
  (+13.91% / +15.56%) AND on `bench_hof_pipeline.gc_s` (+10.60% /
  +10.94%) — not just the bump_s family.  Drift correlates with
  memory pressure: `bench_tree_walk` and `bench_compute_collatz`
  are clean across both runs.

- #16 ("*.max_us structural false-positive") has matured.  The
  "vanishes on rerun" pattern the issue documented from
  2026-05-16 / 2026-05-18 audits is gone: today
  `implicit_at_rc.max_us` reproduces at +47.20% / +49.42%, and
  `implicit_at_rc.p99_9_us` at +27.79% / +41.92%.  Same arm
  only (`implicit @ rc`); the other two latency arms are clean.
  The metrics are now drift-elevated AND structurally jitter-
  prone — both reasons to retire them from the gate.

Decision: one cohesive change, all in `bench/baseline.json`.  Drop
the six unreliable entries (`max_us` + `p99_9_us` × 3 latency
arms), then `bench/check.py --update-baseline` from current HEAD
to absorb the environmental drift in one honest cut.  No code
change to `check.py` / `run.sh` / `latency_harness.py` — the
harness already iterates only entries present in `baseline.json`,
so the schema mechanics work as-is.

Alternatives considered and rejected:

- Adding a `gated: bool` field per metric to keep `max_us` /
  `p99_9_us` measured-but-not-gated.  Pure speculative
  infrastructure: today's pathology is drift, not jitter, and
  the diagnostic value of `max_us` in the `check.py` report is
  hypothetical (it's already in `run.sh` output upstream).
  User push-back ("was soll 1?") was correct.

- Tolerance widening on the drifted metrics.  Hides the drift
  under a wider band; a real +10% codegen regress would
  slip through.

- `-n` raise for tail metrics.  Doesn't address the structural
  problem (max-of-distribution is OS-jitter-dominated regardless
  of N); blows up bench wall time ~10x.

- cpuset / `isolcpus=` pinning.  Sysadmin-layer fix that
  doesn't survive CI move or a new dev machine.

- Histogram-based latency methodology rework (Gitea #19).  The
  proper long-term fix, but a separate brainstorm; this
  milestone is the stop-gap that buys back the noise floor in
  the meantime.

Grounding-check (Step 7.5): PASS, trivial-spec path — no Rust
compiler / checker / codegen / schema / runtime claims (all
load-bearing claims are about the out-of-tree Python harness, and
are covered by the spec's own replay-pass + synthetic-injection
acceptance checks).
2026-05-20 16:02:46 +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 55ad0fa37f workflow: drop docs/WhatsNew.md (Notify carries done-state on its own)
WhatsNew.md duplicated the Notify text into a file the only reader
(the user) does not consult. Removed the file and trimmed every
reference in the live control docs (CLAUDE.md, skills/boss,
skills/implement). The editorial rules (no internals,
telegram-pragmatic, factual) are preserved in skills/boss as
notify-text discipline. Historical specs/plans/journals are not
rewritten — they show the contemporaneous state.
2026-05-20 10:27:33 +02:00
Brummel cca6e3959d audit + close: design-ledger-formal-links CLEAN milestone close (no drift, bench 0/0/0, no tidy)
Cleanest close pattern in recent project history. Architect: zero
drift -- every commitment + invariant verified positively (clause-5
implements all 4 predicates incl. fence-skip, 8 link tokens match
the closed convert-set exactly, disposition-(b) applied to the 2
homeless pointers, honesty-rule positive-half pin-safe, commitments
4/7 byte-unchanged, composition invariant clause-3 ∧ clause-5 GREEN
simultaneously, out-of-scope set untouched incl. the 5 in-fence
data-model annotations, no commitment-2 violation in any reading
list, architect_sweeps clean). Bench: check.py exit 0 (63/0/0/63),
compile_check exit 0 (24/0/0/24), cross_lang exit 0 (25/0/0/25);
baseline pristine, no --update-baseline, no paired ratify.

The two spec amendments (clause-6 + cross-ref definition;
clause-5 fence-skip + closed convert-set enumeration) were the
discipline working: planner-recon and plan-corpus-fetch surfaced
gaps the brainstorm-sample missed; the spec was corpus-grounded
*before* any byte moved (grounding-check PASS x3); implementation
then ran clean 5/5 with one non-blocking concern (planner
self-review-item-8 arithmetic miss on the //! header line count --
substantive assertion unaffected; recorded as lesson).

No tidy iteration. No fieldtest -- zero authoring-surface change
(no .ail, no language construct, no compiler/checker/codegen path)
is the spec-stated reasoned exclusion, audit-confirmed.

This commit ratifies the milestone close:
- audit journal: docs/journals/2026-05-19-audit-design-ledger-formal-links.md
- journals/INDEX.md: audit pointer appended
- roadmap.md: [~] -> [x] CLOSED 2026-05-19 with full pipeline record
- WhatsNew.md: user-facing done-state entry (verbatim with notify)

Spec: docs/specs/2026-05-19-design-ledger-formal-links.md
Plan: docs/plans/design-ledger-formal-links.1.md
Iter journal: docs/journals/2026-05-19-iter-design-ledger-formal-links.1.md
Pipeline: brainstorm (PASS x3) -> planner (recon + plan +
self-review 8/8) -> implement (DONE 5/5, Boss-verified independently)
-> audit (CLEAN, no tidy).
2026-05-19 23:37:59 +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 36599bedd9 plan: design-ledger-formal-links.1 — clause-5 + 7 conversions + 2 disposition-(b) + honesty-rule positive-half
Single iteration covering the whole milestone (same shape as
rolesplit.1; spec is small, no build-atomicity issue — clause-5 is
isolated additive code, prose edits don't affect compilation).
Five tasks, each at task-level review granularity:

  Task 1: clause-5 added to design_index_pin.rs RED-first via
          identity-stubbed strip_fences (synthetic vector FAILS) →
          correct toggle-on-fence impl (GREEN); module //!-header
          extended to name clause-5. Test count 4 → 5.
  Task 2: 7 prose-ref conversions to file-relative Markdown links
          (float-semantics:69/100, embedding-abi:45, memory-model:44/
          105, scope-boundaries:48/88 with the source+Pipeline split).
          Exact verbatim before→after for every line. clause-5 stays
          GREEN by construction (every introduced link resolves
          durable).
  Task 3: 2 disposition-(b) homeless-ref removals (pipeline:60-61
          in-fence CLI block + authoring-surface:178-181 prose).
          Pointer dropped, behavioural prose preserved.
  Task 4: honesty-rule.md pin-safe positive-half paragraph between
          L14 and the existing L15-blank (the two docs_honesty_pin
          phrases at L14/L19 each stay verbatim on their physical
          line; the additive insertion splits neither). The new
          paragraph names design_index_pin.rs clause-5 by name
          (nominal mention, not a link — consistent with the
          existing 'Ratified by:' footer style and with §Scope).
  Task 5: Whole-suite gate (646 → 647) + 5 grep+diff verifications
          (no docs/ link, no #fragment, link count = 8, in-fence
          schema-annotations intact, INDEX.md + decision-records
          journal + clauses 1-4 byte-unchanged).

Self-review 8/8: spec coverage complete; zero placeholders; names
consistent; bite-sized; no commit steps; pin contiguity verified
(L14 + L19 stay verbatim on their physical lines, replacement body
shows L14 contiguous); no signature change ⇒ no compile-gate
ordering issue; every cargo-test filter substring is a real test
name + unfiltered runs have explicit count assertions.
2026-05-19 23:21:29 +02:00
Brummel 42ff44adf6 spec: design-ledger-formal-links — corpus-grounded amendment (clause-5 fence-skip + closed enumeration)
Plan-time corpus verification surfaced a third distinct gap the
brainstorm-sample missed: data-model.md:38/66/79/206/226 are 'see §"..."'
annotations inside fenced jsonc code blocks (verified: fences at
30-87 and 203-228; the 5 refs fall inside), where a Markdown link is
literal text — not a navigable link on any renderer.

Per the "two+ surfaced ⇒ ground the spec properly once, don't iter-patch"
discipline: amendment 2 is the definitive corpus-grounded pass, not a
fourth-patch returning later. Every convert-set ref's prose-vs-fence
status + exact bytes personally verified before amending.

- clause-5 (Concrete a): adds a strip_fences helper toggling on
  triple-backtick / tilde lines; the per-file scan runs
  targets(&strip_fences(&raw)); the RED-first synthetic vector gains
  two fence-skip assertions (identity-stub of strip_fences ⇒ first
  assert fails ⇒ genuine RED). Contract grows from 3 to 4 predicates
  (+ fenced-code-not-scanned).
- Scope section: third out-of-scope carve-out — a 'see §"..."' inside
  a fenced code block is schema-example documentation = the inline-
  annotation analog of the nominal-mention carve-out (a // comment in
  a code example is code documentation, not a 'go browse there'
  pointer). Clean extension of the converged navigational-vs-nominal
  principle.
- Acceptance 3: convert-set CLOSED and exhaustive — 8 prose refs
  (float-semantics:69/100, embedding-abi:45, memory-model:44/105,
  scope-boundaries:48/88), 2 disposition-(b) homeless removals
  (pipeline:61, authoring-surface:180), 6 stay-prose (data-model
  in-fence x5, embedding-abi:51). Iter-provenance suffixes stripped
  on conversion. scope-boundaries:88 splits into a source link plus a
  Pipeline cross-file link.

grounding-check third dispatch on the amended bytes: PASS, all 8
assumptions ratified (incl. corpus-state-verified A4-A7 per the
rolesplit precedent for text-enumeration claims).
2026-05-19 23:14:56 +02:00
Brummel b1a0364bf2 spec: design-ledger-formal-links — recon-driven amendment (clause-6 + cross-ref definition)
Planner Step-2 recon (ailang-plan-recon) surfaced two spec defects;
forward-fix amendment (63b669f stands, main forward-only):

- clause-6 homeless-ref remedy was an incomplete binary. Recon proved
  roundtrip-invariant.md does NOT carry the ail-merge-prose-cycle
  content that pipeline.md:61 / authoring-surface.md:180 cite — so the
  prior canonical retarget sample was wrong. Replaced with three
  priority-ordered dispositions; PROSE_ROUNDTRIP refs take (b) (drop
  the cross-tier pointer, preserve the present-tense behavioural prose).
- Added a precise 'what counts as a cross-reference' definition:
  navigational pointer = in scope; nominal source-symbol mention = out
  of scope (keeps the milestone 'formalise the cross-references', not
  'hyperlink the ledger'). Source link only for a genuine navigational
  (see crates/...). Folds OQ2 (strip iter-provenance suffixes on
  conversion) + OQ3 (no-title directional stays prose) as byte-policies.

grounding-check re-dispatched on the amended bytes: PASS, all
assumptions ratified incl. the amendment-introduced ones.
2026-05-19 23:03:03 +02:00
Brummel 63b669fa8f spec: design-ledger-formal-links — formal cross-links in the design/ ledger
Positive completion of the DESIGN.md -> design/ split: informal prose
cross-references become formal file-relative Markdown links, gated by a
new RED-first design_index_pin.rs clause-5 (every design/ body link
resolves into the durable tier or fails the build). INDEX spine stays
repo-root-relative (registry tier, clause-1 unchanged). No authoring
surface. Eight converged commitments + the user-decided INDEX sub-fork;
grounding-check PASS 7/7. roadmap [~] in-flight.
2026-05-19 22:51:21 +02:00
Brummel 2ee408770e roadmap: close DESIGN.md->design/ ([~]->[x]); WhatsNew: design-spec-restructure done-state 2026-05-19 13:46:14 +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 f2cdd67e69 plan+audit: design-md-rolesplit.tidy re-derived on planning-time evidence (mechanism + scope corrected)
The tidy's gate-first Task 1 RED-verification + Boss independent
verification surfaced two defects in the audit Resolution as first
written — caught exactly where the planner->Boss loop is designed to,
before any contract byte moved. Per the 'two+ defects in one
iteration => fix the upstream artifact, do not patch a third time'
discipline, the audit Resolution is corrected in lockstep with the
plan re-derivation:

- Mechanism (Resolution-4): the 'case-insensitive iter-code regex'
  clause-3 design is REJECTED as unworkable — a blanket
  iter/milestone detector conflates the memory-model rule-names
  'Iter A'/'Iter B' and ordinary words 'pre-existing'/'pre-set'/
  'pre-Boehm' with provenance, over-firing on legit present-tense
  contract prose (4 no-strip files). Replaced by a FAITHFUL Sweep-1
  superset: case-sensitive digit-anchored Sweep-1 line anchors
  (confirmed ZERO across all contracts) + Sweep-1's ^[^/]*
  path-excluded date (so docs/specs/2026-.. citations are not
  flagged — repairs the iso_date over-fire the implement
  orchestrator correctly BLOCKED on) + the audit-named
  decision-record phrases (case-insensitive). No regex dep. The
  load-bearing invariant (clause-3 GREEN => Sweep-1 clean) is
  preserved; it never required the blanket detector.
- Scope (Resolution-1): the architect's [medium] was a 3-file
  spot-check; the exhaustive scan found roundtrip-invariant.md:73
  ('at iter form-a.1') and data-model.md:149,161 ('loop-recur iter
  1:') also carry lowercase provenance. True strip set = 5 files;
  plan Task 5b adds the 2 (spirit-cleanup, not gate-blocking).

Plan self-review 8/8 re-run; clause-3 = hand-rolled faithful Sweep-1
superset, no regex dep, no blanket iter detector.
2026-05-19 13:36:14 +02:00
Brummel 4f61a7aa4f plan: design-md-rolesplit.tidy — audit drift fix (history->journal, sweep re-scope, clause-3 widen)
7 tasks executing the audit Resolution's 5 points: (1) gate-first
RED — widen design_index_pin.rs clause-3 to a hand-rolled PROVABLE
SUPERSET of architect_sweeps Sweep-1 over design/contracts/ (no
regex dep; subsumption proof inline), verify RED vs the un-stripped
tree; (2-4) sentence-level strip the faithfully-migrated
history/decision-record prose out of typeclasses.md / str-abi.md /
scope-boundaries.md into the decision-record journal, per recon's
per-sentence contract-residue map, each docs_honesty_pin pinned run
guarded contiguous; (5) fix float-semantics.md stale 'see Str ABI
below' -> str-abi.md; (6) re-scope architect_sweeps DESIGN_GLOB to
design/contracts only (models/ is the narrative tier) + lockstep
ailang-architect.md; (7) whole-tree GREEN gate (clause-3 RED->GREEN,
4/4, sweep exit 0, suite >=646/0, acceptance grep CLEAN).
Self-review 8/8.
2026-05-19 13:22:22 +02:00
Brummel 2ba5e16806 audit design-md-rolesplit (milestone close): DRIFT (one tidy iter) + bench causally-exonerated (baseline pristine)
Architect: relocation byte-faithful; one [medium] spirit-finding —
faithfully-migrated decision-record/history prose in
design/contracts/{typeclasses,str-abi,scope-boundaries}.md dodges the
6 literal clause-3 markers but is the relitigation content the
split's spirit sends to journals; [low] float-semantics.md
stale-direction 'see Str ABI below'. Both routed items: tidy not
ratify (the str-abi.md:23 advisory Sweep-1 exit-1 correctly
diagnoses real pre-existing history residue, byte-identical
DESIGN.md@deeffb1 — faithfully migrated, not split-introduced).

Bencher: causally-exonerated, DECISIVE on byte-evidence — emitted IR
AND final -O2 binaries byte-identical 176821c vs dd5b183 for all 6
check.py firings; all ~11 changed files audited
comment/docstring/diagnostic-string-only; tracked-P2 families;
compile_check/cross_lang pristine. NO-ratify, baseline pristine
(M2/M3/M5 disposition).

Resolution: one tidy iter (move history prose to the decision-record
journal; fix stale 'below'; re-scope architect_sweeps honesty sweeps
to design/contracts only — models/ is the narrative tier; widen
design_index_pin.rs clause-3 to subsume Sweep-1's history-anchor
regex over contracts/ so the hard gate enforces the spirit). No
fieldtest (zero authoring-surface change). Milestone closes after
the tidy lands Boss-verified.
2026-05-19 13:14:15 +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
Brummel deeffb1872 plan: design-md-rolesplit.1 — the whole milestone, build-atomic by task ordering
9 tasks: (1) RED-first design_index_pin.rs 4-clause guard; (2)
design/INDEX.md + 14 prose contracts; (3) 5 model whitepapers; (4)
decision-record journal + INDEX pointer; (5) retarget the 3
doc-reading tests GREEN vs design/; (6) 2 diagnostics + 2 E2Es
lockstep; (7) architect_sweeps.sh spine repoint; (8) ~12 agent lists
+ 5 SKILL bodies + CLAUDE.md + README + bucket-(e) comment xrefs +
OQ7 cite deletion; (9) clean cut (git rm DESIGN.md) + whole-tree gate
+ acceptance grep. Build-atomicity by construction: include_str!
retarget (Task 5) precedes DESIGN.md deletion (Task 9), data-model.md
exists from Task 2 — every intermediate cargo build is green, no
monster task. Self-review 8/8 (contiguity + compile-gate-ordering +
filter-resolution scrubbed).
2026-05-19 12:16:21 +02:00
Brummel 314e5e4920 spec: design-md-rolesplit — Boss-adjudicated relocation appendix (planner recon resolution)
plan-recon surfaced 7 open questions (genuine Boss design judgement,
not architectural forks). Adjudicated and folded into the spec as an
authoritative placeholder-free Appendix relocation map (every ##/###
of DESIGN.md -> exactly one destination):

- design_index_pin.rs clause-2 widened to accept crates/**/src/*.rs
  in-source #[cfg(test)] mod tests as first-class ratifiers.
- INDEX ratifier tokens resolved to recon-verified paths; memory-model
  -> uniqueness.rs (in-source), tail-calls -> lib.rs:5044
  tail_call_in_non_tail_position_is_rejected (NOT loop_recur — different
  construct), env-construction path corrected to ailang-check/tests.
- 3 ledger additions the 12-list under-counted: qualified-xref
  (source-link only), str-abi (prose+source; the one sanctioned
  sentence-level move, 2802 bold para), scope-boundaries (present-tense
  honesty-pinned). Net 15 contracts / 5 models / 1 decision-record journal.
- OQ7: lib.rs:103 dangling 'Iter 13b' cite is deleted not retargeted
  (no forward target exists; a pointer would be fiction).

grounding-check re-dispatched after the material edit: PASS, all 6
changed/new ratifiers resolve to named currently-green tests.
2026-05-19 12:11:53 +02:00
Brummel a64b2ccb2a spec: design-md-rolesplit — DESIGN.md role-split into design/ contracts+models ledger
Role-split the 3020-line docs/DESIGN.md into design/contracts/ (the
hot, test-linked invariant set), design/models/ (one whitepaper per
model), and design/INDEX.md as the sole addressable spine; decision-
records rehomed to docs/journals/. Clean cut (DESIGN.md deleted, no
stub), ###-subsection relocation granularity, build-atomic landing
(design_schema_drift.rs include_str! forces one commit), polymorphic
INDEX links (code-SoT contracts point at source //!, no prose dup),
typed-INDEX anti-regrowth pin (design_index_pin.rs). grounding-check
PASS 13/13 after one re-dispatch (commitment-4 pin-status claim
corrected: Decision-6 :242 audit-trail sentence is pinned by no test,
retired to the decision-record journal, no successor pin by design).

roadmap: DESIGN.md->design/ marked [~] in flight (spec landed).
2026-05-19 11:58:07 +02:00
Brummel dd5b18378f docs: DESIGN.md §Embedding ABI — current-state honesty (no general-ABI claim)
The §"Embedding ABI" prose presented the M3 contract as "the
embedding ABI" — a general boundary — and justified the layout
freeze with "this is what codegen emits today". Both overclaim:

- The accepted set (bare scalar | single-ctor all-scalar record)
  is the *whole* contract, narrow by construction. The host
  hand-constructs and hand-reads the box layout directly; there
  is no general value-marshalling layer. The text now says this
  plainly instead of framing the set as a subset of a wider ABI.
- The freeze is reframed as the consequence of that direct
  layout exposure (the host is the contract surface), not as
  "frozen because that is what lower_ctor emits".
- "MUST NOT move these offsets" is corrected to "for an exported
  type" — the prior wording overclaimed the freeze as binding
  every record layout; the commitment binds exported types and
  thereby constrains codegen repacking of exported records.

Present-tense Ist-state mirror only: no lockstep/wire-contract
design, no post-mortem, no aspirational "should become". Schema
section untouched. Verified green: design_schema_drift (8),
docs_honesty_pin (5), effect_doc_honesty_pin (4),
embed_record_layout_pin (1), embed_staticlib_lowering (3).
2026-05-19 11:22:25 +02:00
Brummel d160f2c977 roadmap: close Embedding ABI — M5 ([~]->[x]) — M1–M5 arc complete, audited+ratified+doc-honest; WhatsNew + P2 reframed
M5 done 2026-05-19: real backtest over real Pepperstone tick data
through the real external data-server into the shipped M3-frozen
kernel, across a per-thread-ctx thread swarm; symbol-fan (headline
existence proof) + time-shard (first actual proof of chunk-boundary
invisibility); globally leak-free, deterministic. Zero
language/compiler change; one runtime change (7bfa11e atomic
global RC-stats fallback — user-adjudicated bounce-back, RED-first,
+ a build-dep staleness root-caused & fixed). Audit: Invariant 1
PASS, M3 frozen-layout unmoved, gc_rss trio evidenced-RATIFY
(Boehm conservative-scan artefact, Decision-9 transitional), P2
noise causally exonerated baseline-pristine, doc-honesty tidy
clean. Friction-harvest: host-per-tick-FFI ~= ~206 ns/tick at real
EURUSD volume.

- roadmap M5 [~]->[x] with the done-state summary (stays one cycle
  for context, then prune).
- roadmap P2 "Flat array/slice primitive" reframed: the
  M4-retirement forward note's deferred reconciliation is now
  done — the obsolete "1024-cell cons-chain per chunk / M4 ships
  it" framing replaced with the real residual (host-per-tick-FFI
  vs. batch crossing) anchored on M5's measured ~206 ns/tick; the
  M5-dependency is now satisfied (the justification input exists).
- WhatsNew.md done-state entry (verbatim == the notify text):
  user-facing, no internals — the embedding arc is complete.

Done-state notification fired (user away per /boss). The next
roadmap item is a new milestone (the P2 flat-array decision or
another P0) — a deliberate user call, not autonomous.
2026-05-19 03:10:04 +02:00
Brummel 70f2a318e0 iter embedding-abi-m5.tidy (DONE 3/3): milestone-close doc-honesty drift — pin-safe, doc/comment-only
Resolves the M5 milestone-close audit DRIFT (audit journal
28ab56a). Single cohesive commit, M2.tidy a80d495 / M3.tidy
63d7d60 precedent (pins are the coverage, no RED, no
audit/fieldtest gate).

- docs/DESIGN.md edit-1 §"Free (host side)": dropped the retired-M4
  forward-reference ("an additive M4 concern, not a contradiction
  of this freeze"; M4 retired 2026-05-18) -> present-tense current
  fact (a boxed-field record is not an M3 embedding type,
  export-gate-rejected; the freeze covers exactly the all-scalar
  single-ctor record).
- docs/DESIGN.md edit-2 §"Embedding ABI": reconciled the now-
  inaccurate "(no shared mutable runtime state - ... data-race-free,
  sanitiser-verified)" blanket with the real post-7bfa11e state:
  the per-allocation hot path (per-ctx counters + per-object
  refcount header) is non-atomic by design and never shared
  (Ctx:!Send, single-thread-per-ctx); the one shared datum is the
  atomic-relaxed global RC-stats fallback counter; "data-race-free,
  sanitiser-verified" retained (still true).
- runtime/rc.c:88 comment-only: stale "two unconditional ++
  operations" -> accurate "one counter bump per alloc/free ...
  relaxed atomic add on the global null-ctx fallback, a plain ++
  on the per-ctx path" (consistent with the adjacent already-
  correct :93-106 atomic block + :45-55 Threading header).

Boss-verified independently: all pins green (design_schema_drift
8/0, docs_honesty_pin 5/0, effect_doc_honesty_pin 4/0,
embed_record_layout_pin 1/0) = mechanical proof no pinned/hashed
byte moved; the adjacent separately-pinned bare-scalar sentence is
byte-identical (shifted 2299->2305 by net-added lines; substring
pin, passes); rc.c strictly comment-only (filter empty);
cargo build --workspace Finished; git scope = only docs/DESIGN.md
+ runtime/rc.c + journal/stats. Clears the M5-audit doc-honesty
debt; no new debt.

FINAL M5 iteration. The M1-M5 Embedding ABI arc is functionally
complete, audited, ratified, and doc-honest. Includes the per-iter
journal, stats, and INDEX line.
2026-05-19 03:07:40 +02:00