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.
This commit is contained in:
@@ -43,7 +43,7 @@ it measurably improves correctness or removes redundancy.
|
|||||||
| `bench/` | Regression harnesses (`check.py`, `compile_check.py`, `cross_lang.py`) and the throughput-and-latency runner (`run.sh`); `bench/reference/` holds the hand-C corpus for cross-language ratios |
|
| `bench/` | Regression harnesses (`check.py`, `compile_check.py`, `cross_lang.py`) and the throughput-and-latency runner (`run.sh`); `bench/reference/` holds the hand-C corpus for cross-language ratios |
|
||||||
| `examples/` | AILang fixtures used by tests and benches |
|
| `examples/` | AILang fixtures used by tests and benches |
|
||||||
| `design/` | The canonical contract ledger — `design/INDEX.md` (sole addressable spine: a typed Contracts + Models table), `design/contracts/` (prose-authoritative test-linked invariants), `design/models/` (onboarding whitepapers) |
|
| `design/` | The canonical contract ledger — `design/INDEX.md` (sole addressable spine: a typed Contracts + Models table), `design/contracts/` (prose-authoritative test-linked invariants), `design/models/` (onboarding whitepapers) |
|
||||||
| `docs/` | Specs and roadmap — `docs/specs/` (per-milestone design specs), `docs/plans/` (per-iteration plans), `docs/roadmap.md` (forward queue), `docs/journal-archive.md` (pre-2026-05-11 history, content-frozen), `PROSE_ROUNDTRIP.md`. Per-iter history lives in `git log`. |
|
| `docs/` | Specs and roadmap — `docs/specs/` (per-milestone design specs), `docs/plans/` (per-iteration plans), `docs/roadmap.md` (forward queue), `PROSE_ROUNDTRIP.md`. Project history lives in `git log`. |
|
||||||
| `skills/` | Project-local skill definitions and their agents. See `skills/README.md` for the skill table, agent roster, and discovery layout. |
|
| `skills/` | Project-local skill definitions and their agents. See `skills/README.md` for the skill table, agent roster, and discovery layout. |
|
||||||
|
|
||||||
## Skill system
|
## Skill system
|
||||||
@@ -193,12 +193,11 @@ Work clusters into **milestones**, each subdivided into
|
|||||||
→ fieldtest`), skipping rules, and bench-exit-code gating live in
|
→ fieldtest`), skipping rules, and bench-exit-code gating live in
|
||||||
`skills/README.md` and the per-skill `SKILL.md` files.
|
`skills/README.md` and the per-skill `SKILL.md` files.
|
||||||
|
|
||||||
Vocabulary note: legacy entries in `docs/journal-archive.md`
|
Vocabulary note: pre-2026-05-09 git history uses "iter" / "family";
|
||||||
(pre-2026-05-09) use "iter" / "family"; current vocabulary is
|
current vocabulary is "iteration" / "milestone". Old commits are
|
||||||
"iteration" / "milestone". Existing archive entries are not
|
not retroactively renamed.
|
||||||
retroactively renamed.
|
|
||||||
|
|
||||||
## Roles of the `design/` ledger, `git log`, `docs/journal-archive.md`, `docs/roadmap.md`, `docs/specs/`, `docs/plans/`
|
## Roles of the `design/` ledger, `git log`, `docs/roadmap.md`, `docs/specs/`, `docs/plans/`
|
||||||
|
|
||||||
- **The `design/` ledger** is the canonical specification. It
|
- **The `design/` ledger** is the canonical specification. It
|
||||||
describes what AILang *is*: schema, semantics, invariants, runtime
|
describes what AILang *is*: schema, semantics, invariants, runtime
|
||||||
@@ -222,10 +221,6 @@ retroactively renamed.
|
|||||||
with `git log --oneline -30`; per-milestone scope with
|
with `git log --oneline -30`; per-milestone scope with
|
||||||
`git log <prev-milestone-close>..HEAD --format=full`.
|
`git log <prev-milestone-close>..HEAD --format=full`.
|
||||||
|
|
||||||
- **`docs/journal-archive.md`** is the archived monolithic
|
|
||||||
decisions log for everything pre-2026-05-11. Content-frozen.
|
|
||||||
Read it only when chasing long-tail history; do not append.
|
|
||||||
|
|
||||||
- **`docs/roadmap.md`** (since 2026-05-10): the priority-ordered
|
- **`docs/roadmap.md`** (since 2026-05-10): the priority-ordered
|
||||||
forward queue — milestones, features, todos, and ideas. The
|
forward queue — milestones, features, todos, and ideas. The
|
||||||
orchestrator owns this file and is responsible for keeping it
|
orchestrator owns this file and is responsible for keeping it
|
||||||
@@ -243,6 +238,5 @@ retroactively renamed.
|
|||||||
by `skills/implement`.
|
by `skills/implement`.
|
||||||
|
|
||||||
Together these answer three questions: "what is the language right
|
Together these answer three questions: "what is the language right
|
||||||
now?" (the `design/` ledger), "how did we get here?" (`git log`,
|
now?" (the `design/` ledger), "how did we get here?" (`git log`),
|
||||||
plus `docs/journal-archive.md` for pre-2026-05-11 context), and
|
and "what's next?" (roadmap).
|
||||||
"what's next?" (roadmap).
|
|
||||||
|
|||||||
@@ -3,11 +3,13 @@
|
|||||||
//! consumer of the per-module `env.ctor_index` rebuild in
|
//! consumer of the per-module `env.ctor_index` rebuild in
|
||||||
//! `check_in_workspace`. Env construction is code-SoT (the
|
//! `check_in_workspace`. Env construction is code-SoT (the
|
||||||
//! `env-construction` ledger row in design/INDEX.md is source-link
|
//! `env-construction` ledger row in design/INDEX.md is source-link
|
||||||
//! only); the why-two-overlays rationale this test rests on as the
|
//! only). The two overlays — typecheck-side rebuild + mono-side
|
||||||
//! asymmetry-with-mono-side argument lives in
|
//! narrowed — are deliberately asymmetric: the typecheck-side
|
||||||
//! docs/journals/2026-05-19-design-decision-records.md. If the
|
//! `ctor_index` half exists *for this very diagnostic*, while the
|
||||||
//! per-module rebuild is ever removed without an equivalent
|
//! mono-side overlay is narrowed to types-only because the runtime
|
||||||
//! replacement path, this test goes red.
|
//! ctor lookup is type-driven. If the per-module rebuild is ever
|
||||||
|
//! removed without an equivalent replacement path, this test goes
|
||||||
|
//! red.
|
||||||
|
|
||||||
use ailang_check::check_workspace;
|
use ailang_check::check_workspace;
|
||||||
use ailang_core::ast::{Ctor, Def, Module, TypeDef};
|
use ailang_core::ast::{Ctor, Def, Module, TypeDef};
|
||||||
|
|||||||
@@ -91,21 +91,16 @@ fn design_md_has_no_doc_archaeology() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn design_md_present_tense_anchors_present() {
|
fn design_md_present_tense_anchors_present() {
|
||||||
// Each anchor is read from its post-split home (spec Appendix
|
// Each anchor is read from its post-split home. Phrases that only
|
||||||
// relocation map). Three honest-reserved rationale anchors
|
// ever lived in the (now-removed) decision-records annex are
|
||||||
// ("Regions were considered and rejected", the "does not infer
|
// dropped — anything the design/ ledger truly needs lives in
|
||||||
// everything" bullet, the Form-B prose-placeholder state) sat in
|
// design/contracts/ or design/models/.
|
||||||
// `### What this Decision deliberately does not do` / `### What
|
|
||||||
// this decision does NOT commit to`, which the Appendix routes to
|
|
||||||
// the decision-record journal — so the journal is part of the
|
|
||||||
// canonical anchor corpus, not a contract surface.
|
|
||||||
let honesty = norm(&read("design/contracts/honesty-rule.md"));
|
let honesty = norm(&read("design/contracts/honesty-rule.md"));
|
||||||
let scope = norm(&read("design/contracts/scope-boundaries.md"));
|
let scope = norm(&read("design/contracts/scope-boundaries.md"));
|
||||||
let memory = norm(&read("design/contracts/memory-model.md"));
|
let memory = norm(&read("design/contracts/memory-model.md"));
|
||||||
let pipeline = norm(&read("design/models/pipeline.md"));
|
let pipeline = norm(&read("design/models/pipeline.md"));
|
||||||
let str_abi = norm(&read("design/contracts/str-abi.md"));
|
let str_abi = norm(&read("design/contracts/str-abi.md"));
|
||||||
let prelude_classes = norm(&read("design/contracts/prelude-classes.md"));
|
let prelude_classes = norm(&read("design/contracts/prelude-classes.md"));
|
||||||
let records = norm(&read("docs/journals/2026-05-19-design-decision-records.md"));
|
|
||||||
|
|
||||||
// the discriminator meta-subsection -> contracts/honesty-rule.md
|
// the discriminator meta-subsection -> contracts/honesty-rule.md
|
||||||
assert!(honesty.contains("the honesty rule it holds itself to"),
|
assert!(honesty.contains("the honesty rule it holds itself to"),
|
||||||
@@ -115,8 +110,6 @@ fn design_md_present_tense_anchors_present() {
|
|||||||
// protected honest-reserved exceptions — over-correction guard
|
// protected honest-reserved exceptions — over-correction guard
|
||||||
assert!(scope.contains("`Diverge` is a reserved effect name with no op and no codegen"),
|
assert!(scope.contains("`Diverge` is a reserved effect name with no op and no codegen"),
|
||||||
"the gold-form honest-reserved anchor must remain in scope-boundaries.md (do not over-strip)");
|
"the gold-form honest-reserved anchor must remain in scope-boundaries.md (do not over-strip)");
|
||||||
assert!(records.contains("Regions were considered and rejected"),
|
|
||||||
"the present-tense design-rationale exclusion must remain in the decision-records (do not over-strip)");
|
|
||||||
assert!(memory.contains("a tiebreaker, not a rationale"),
|
assert!(memory.contains("a tiebreaker, not a rationale"),
|
||||||
"the self-labelled tiebreaker is honest and stays in memory-model.md (do not over-strip)");
|
"the self-labelled tiebreaker is honest and stays in memory-model.md (do not over-strip)");
|
||||||
// corrected present-tense anchors
|
// corrected present-tense anchors
|
||||||
@@ -124,12 +117,8 @@ fn design_md_present_tense_anchors_present() {
|
|||||||
"models/pipeline.md must describe Boehm present-tense, not as 'on the path to retirement'");
|
"models/pipeline.md must describe Boehm present-tense, not as 'on the path to retirement'");
|
||||||
assert!(str_abi.contains("type-installed; codegen is reserved and not yet shipped"),
|
assert!(str_abi.contains("type-installed; codegen is reserved and not yet shipped"),
|
||||||
"float_to_str must be present-tense honest-reserved in str-abi.md");
|
"float_to_str must be present-tense honest-reserved in str-abi.md");
|
||||||
assert!(records.contains("AILang demands annotations *because* the LLM author can produce them effortlessly"),
|
|
||||||
"the 'does not infer everything' bullet must be present-tense in the decision-records (no doc-archaeology)");
|
|
||||||
assert!(prelude_classes.contains("`io/print_str` is the only built-in direct-output effect-op"),
|
assert!(prelude_classes.contains("`io/print_str` is the only built-in direct-output effect-op"),
|
||||||
"the print-op set must be stated present-tense in prelude-classes.md (post-split home), not as a retirement narrative");
|
"the print-op set must be stated present-tense in prelude-classes.md (post-split home), not as a retirement narrative");
|
||||||
assert!(records.contains("the render in `crates/ailang-prose/src/lib.rs` is a placeholder and informational only"),
|
|
||||||
"the Form-B class/instance prose-projection state must be present-tense in the decision-records");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -76,13 +76,11 @@
|
|||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
//! Notes on the form (the binding constraints are in
|
//! Notes on the form (the binding constraints are in
|
||||||
//! design/contracts/authoring-surface.md; the form-refinement
|
//! design/contracts/authoring-surface.md):
|
||||||
//! rationale is in docs/journals/2026-05-19-design-decision-records.md):
|
|
||||||
//!
|
//!
|
||||||
//! - The `lam` form carries `paramTypes`, a `ret` type, and an
|
//! - The `lam` form carries `paramTypes`, a `ret` type, and an
|
||||||
//! optional `effects` clause. The early form sketch left
|
//! optional `effects` clause. The AST stores them per-lambda and
|
||||||
//! these out; the AST stores them per-lambda and so the form must
|
//! so the form must round-trip them.
|
||||||
//! round-trip them.
|
|
||||||
//! - The `import` form admits an optional `as` alias to round-trip
|
//! - The `import` form admits an optional `as` alias to round-trip
|
||||||
//! [`ailang_core::ast::Import::alias`].
|
//! [`ailang_core::ast::Import::alias`].
|
||||||
|
|
||||||
|
|||||||
+3
-5
@@ -56,11 +56,9 @@ evolving in lockstep with the language:
|
|||||||
sole addressable spine for canonical state), `design/contracts/`
|
sole addressable spine for canonical state), `design/contracts/`
|
||||||
(test-linked invariants), `design/models/` (onboarding
|
(test-linked invariants), `design/models/` (onboarding
|
||||||
whitepapers).
|
whitepapers).
|
||||||
- **Docs** (`docs/`): `docs/journal-archive.md` (archived monolith
|
- **Docs** (`docs/`): `roadmap.md` (forward queue), `specs/`
|
||||||
for pre-2026-05-11 history, content-frozen), `roadmap.md` (forward
|
(per-milestone design specs), `plans/` (per-iteration
|
||||||
queue), `specs/` (per-milestone design specs), `plans/` (per-
|
implementation plans). Project history lives in `git log`.
|
||||||
iteration implementation plans). Current iter / audit history
|
|
||||||
lives in `git log`.
|
|
||||||
- **Tests**: unit tests per crate plus E2E in `crates/ail/tests/e2e.rs`. Every
|
- **Tests**: unit tests per crate plus E2E in `crates/ail/tests/e2e.rs`. Every
|
||||||
new compiler path needs a test, otherwise the feature does not count as done.
|
new compiler path needs a test, otherwise the feature does not count as done.
|
||||||
|
|
||||||
|
|||||||
-14578
File diff suppressed because it is too large
Load Diff
@@ -1,75 +0,0 @@
|
|||||||
# fieldtest — canonical-type-names
|
|
||||||
|
|
||||||
**Date:** 2026-05-11
|
|
||||||
**Branch:** main (orchestrator-direct dispatch; the or.1 branch-per-iter rule lands on the *next* implement run)
|
|
||||||
**Status:** DONE_WITH_CONCERNS
|
|
||||||
**Examples:** 5 of 5 authored + committed
|
|
||||||
**Findings:** 9 (4 working, 4 friction, 1 spec_gap, 0 bugs)
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
Standard skill-pipeline step after audit cleanly closed
|
|
||||||
canonical-type-names. The fieldtester wrote five `.ailx` examples
|
|
||||||
that exercise the milestone surface — one happy-path exhibit and
|
|
||||||
four diagnostic-triggers, one per new diagnostic plus a second
|
|
||||||
branch of the merged BadCrossModuleTypeRef case. Spec at
|
|
||||||
`docs/specs/2026-05-11-fieldtest-canonical-type-names.md`; fixtures
|
|
||||||
under `examples/ct_*`.
|
|
||||||
|
|
||||||
All four milestone-scoped findings landed as **working**: the new
|
|
||||||
diagnostics fire on the LLM-natural authoring mistakes, the
|
|
||||||
diagnostics carry actionable recipes (BareCrossModuleTypeRef lists
|
|
||||||
import candidates + names the migration tool), and Form-A
|
|
||||||
round-tripping stays byte-stable across cross-module type refs.
|
|
||||||
|
|
||||||
One milestone-internal friction: `BadCrossModuleTypeRef` collapses
|
|
||||||
two distinguishable cases (unknown owner vs. known owner / unknown
|
|
||||||
type name) into one diagnostic shape. A future tidy iter can split
|
|
||||||
these and, in the known-owner branch, list the available type names
|
|
||||||
the way `bare-cross-module-type-ref` lists candidates from imports.
|
|
||||||
|
|
||||||
Three orthogonal friction items surfaced as pre-existing UX gaps
|
|
||||||
that every fieldtest invocation will keep tripping on:
|
|
||||||
|
|
||||||
- Workspace search confined to the entry module's directory — no
|
|
||||||
way to organise fieldtest fixtures under
|
|
||||||
`examples/fieldtest/` while still importing prelude or std.
|
|
||||||
- `ail check foo.ailx` returns a misleading JSON-parse error
|
|
||||||
instead of either auto-parsing `.ailx` or pointing to `ail parse`.
|
|
||||||
- `(import prelude)` is rejected with a clear diagnostic but no
|
|
||||||
corpus example warns the author that prelude is auto-injected.
|
|
||||||
|
|
||||||
One spec_gap: the canonical happy-path exhibit shipped with this
|
|
||||||
milestone (`examples/compare_primitives_smoke.ail.json`) exists
|
|
||||||
only as JSON. Decision 6 makes Surface the LLM-author surface; the
|
|
||||||
milestone should ship a `.ailx` counterpart (or retire the JSON
|
|
||||||
fixture in favour of `ct_1_ordering_signum.{ailx,ail.json}`).
|
|
||||||
|
|
||||||
## Routing
|
|
||||||
|
|
||||||
| Finding | Class | Action |
|
|
||||||
|---|---|---|
|
|
||||||
| BareCrossModuleTypeRef recipe + JSON ctx | working | carry-on |
|
|
||||||
| BadCrossModuleTypeRef catches Surface | working | carry-on |
|
|
||||||
| QualifiedClassName fires on `(class prelude.Eq)` | working | carry-on |
|
|
||||||
| Form-A round-trip byte-stable | working | carry-on |
|
|
||||||
| BadCrossModuleTypeRef merges two cases | friction | roadmap P2 |
|
|
||||||
| Workspace search confined to entry dir | friction | roadmap P2 |
|
|
||||||
| `ail check` on `.ailx` misleading | friction | roadmap P2 |
|
|
||||||
| `(import prelude)` corpus doc gap | friction | carry-on (one-line DESIGN.md hint sufficient) |
|
|
||||||
| No `.ailx` for `compare_primitives_smoke.ail.json` | spec_gap | roadmap P3 |
|
|
||||||
|
|
||||||
No bug findings, so no debug dispatch. The five `.ailx` fixtures
|
|
||||||
stay in the corpus as live exhibits.
|
|
||||||
|
|
||||||
## Lesson — orthogonal UX gaps surface together
|
|
||||||
|
|
||||||
Three of four friction items are orthogonal to canonical-type-names
|
|
||||||
proper. They predate the milestone. None would have surfaced in a
|
|
||||||
brainstorm or planner pass because none touches the milestone's
|
|
||||||
substance. Fieldtest's role IS to surface this kind of cross-cutting
|
|
||||||
UX debt — DESIGN.md does not mention `.ailx` extension handling,
|
|
||||||
workspace-root flags, or the prelude doc-gap, but every downstream
|
|
||||||
LLM author trips on them within ten minutes of writing their first
|
|
||||||
program. The roadmap entries below are the persistent backlog this
|
|
||||||
fieldtest produced.
|
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
# iter 23.4-prep — checker prerequisites for prelude free fns
|
|
||||||
|
|
||||||
**Date:** 2026-05-11
|
|
||||||
**Branch:** iter/23.4-prep
|
|
||||||
**Status:** DONE
|
|
||||||
**Tasks completed:** 2 of 2
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
Closes two localised checker gaps that BLOCKED iter 23.4 mid-flight:
|
|
||||||
|
|
||||||
- **Gap 2 (Task 1):** `linearity::check_module`'s globals-construction
|
|
||||||
loop now iterates `Def::Class` methods and registers each method
|
|
||||||
type in the globals map. Previously, a borrow-mode fn that
|
|
||||||
forwarded its borrow-mode params into a class-method call fired
|
|
||||||
`consume-while-borrowed` false positives, because
|
|
||||||
`callee_arg_modes` defaulted to `Consume` when it could not find
|
|
||||||
the method's `param_modes`. `Def::Instance` stays a no-op:
|
|
||||||
instance method bodies are walked separately via `Def::Fn` after
|
|
||||||
monomorphisation, per the comment in the new arm.
|
|
||||||
|
|
||||||
- **Gap 1 (Task 2):** the `Term::Var { name }` lookup ladder in
|
|
||||||
`synth` grows one new branch between the class-method arm and the
|
|
||||||
dot-qualified arm. It iterates `env.imports.values()` to find a
|
|
||||||
free-fn match in any implicitly-imported module's flat-globals
|
|
||||||
table; on hit, it qualifies any owner-module-local Type::Cons via
|
|
||||||
the existing `qualify_local_types` helper (mirror of the
|
|
||||||
dot-qualified arm). Today the prelude is the only implicit
|
|
||||||
import, so single-match-wins is unambiguous; the code comment
|
|
||||||
names the future revisit point for multi-implicit-import
|
|
||||||
ambiguity.
|
|
||||||
|
|
||||||
Both fixes are intra-crate and additive — no schema change, no new
|
|
||||||
diagnostic, no new workspace plumbing. iter 23.4's five prelude
|
|
||||||
free fns (`ne`/`lt`/`le`/`gt`/`ge`) can now ship against the
|
|
||||||
existing plan unchanged.
|
|
||||||
|
|
||||||
## Per-task subjects
|
|
||||||
|
|
||||||
- iter 23.4-prep.1: linearity — register Def::Class method types in globals
|
|
||||||
- iter 23.4-prep.2: check — bare-name fall-through to implicit-imported free fns
|
|
||||||
|
|
||||||
## Concerns
|
|
||||||
|
|
||||||
- No E2E fixture added under `examples/`. The plan's own "Plan
|
|
||||||
self-review" identifies the two RED-first tests (one in
|
|
||||||
`linearity.rs`, one in `lib.rs`'s `check_workspace`-level test
|
|
||||||
module) as the iter-23.4-prep-shaped coverage. The natural E2E
|
|
||||||
surface (a fixture calling bare `ne 1 2` against the prelude)
|
|
||||||
cannot land until iter 23.4 ships the prelude free fns
|
|
||||||
themselves — both `examples/prelude.ail.json` and the consumer
|
|
||||||
fixture are out of scope for this iter. iter 23.4's E2E coverage
|
|
||||||
is the right place; flagging here so the next iter does not
|
|
||||||
inherit "no E2E for the prep iter" as unrecorded debt.
|
|
||||||
|
|
||||||
## Known debt
|
|
||||||
|
|
||||||
- The Task 2 branch's `mod_name` first-match-wins rule remains
|
|
||||||
ambiguous-by-design as long as `prelude` is the singleton
|
|
||||||
implicit import. If a second implicit import lands in a future
|
|
||||||
milestone, this branch needs a duplicate-detection diagnostic
|
|
||||||
("name `X` resolves through implicit imports `A` and `B`,
|
|
||||||
qualify the call"). The code comment names the revisit point.
|
|
||||||
|
|
||||||
## Blocked detail
|
|
||||||
|
|
||||||
N/A — DONE.
|
|
||||||
|
|
||||||
## Commits
|
|
||||||
|
|
||||||
c10acf0a..aef4ab84
|
|
||||||
|
|
||||||
## Stats
|
|
||||||
|
|
||||||
bench/orchestrator-stats/2026-05-11-iter-23.4-prep.json
|
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
# iter 23.4 — Mono-Pass Unification
|
|
||||||
|
|
||||||
**Date:** 2026-05-11
|
|
||||||
**Started from:** fab1685
|
|
||||||
**Status:** DONE
|
|
||||||
**Tasks completed:** 11 of 11
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
Restructured `crates/ailang-check/src/mono.rs::monomorphise_workspace`
|
|
||||||
into a single specialiser over every `Type::Forall`-quantified
|
|
||||||
`Def::Fn` in one fixpoint pass, covering both class-method residuals
|
|
||||||
and polymorphic free-fn call sites. Removed the codegen-time
|
|
||||||
specialiser entirely (`lower_polymorphic_call`, `module_polymorphic_fns`,
|
|
||||||
`mono_queue`, `mono_emitted`, `emit_specialised_fn`, plus the dead
|
|
||||||
helpers `apply_subst_to_term`, `descriptor_for_subst`, `type_descriptor`).
|
|
||||||
Codegen now sees only monomorphic `Def::Fn`s — no polymorphic call
|
|
||||||
sites, no codegen-time queue.
|
|
||||||
|
|
||||||
The unified pass produces bit-identical mono-symbol bodies for the
|
|
||||||
six existing primitive Eq/Ord symbols (hash-stability pin Task 1,
|
|
||||||
`crates/ail/tests/mono_hash_stability.rs`) and produces new free-fn
|
|
||||||
mono symbols on demand for fixtures like `cmp_max_smoke` (Tasks 2,
|
|
||||||
6, 9). All 73 e2e tests + 18 typeclass tests + 81 codegen tests pass
|
|
||||||
end-to-end against the new architecture.
|
|
||||||
|
|
||||||
## Per-task notes
|
|
||||||
|
|
||||||
- iter 23.4.1: hash-stability regression pin — `examples/mono_hash_pin_smoke.ail.json` + `mono_hash_stability.rs` with six pinned hashes (`eq__Int`/`Bool`/`Str`, `compare__Int`/`Bool`/`Str`).
|
|
||||||
- iter 23.4.2: `cmp_max_smoke` fixture + RED workspace-level acceptance gate.
|
|
||||||
- iter 23.4.3: `MonoTarget` struct → enum with `ClassMethod` / `FreeFn` variants; `mono_target_key` widened with `"class"` / `"free"` first component (guaranteed-disjoint keys).
|
|
||||||
- iter 23.4.4: free-fn arm in `collect_mono_targets`. NOTE: the plan's Step 4.3 sketch proposed a separate walker over `Term::App` plus a new `env.polymorphic_free_fns` index; instead I extended `synth` itself with a parallel `&mut Vec<FreeFnCall>` channel (mirroring the existing `&mut Vec<ResidualConstraint>` mechanism). One less data flow, one less helper; substitution tracking comes "for free" via fresh metavars + post-synth `subst.apply`. Plan's Step 4.4 (adding `polymorphic_free_fns` to `Env`) is therefore obsolete; not landed.
|
|
||||||
- iter 23.4.5: `synthesise_mono_fn_for_free_fn` + N-ary `mono_symbol_n`. One substantive deviation from the plan's sketch: I also substitute rigid vars in the BODY via a new `crate::substitute_rigids_in_term` helper. The plan said "passes through unchanged" mirroring the class-method arm — but free-fn bodies (e.g. `std_list.length`) carry inner `Term::Lam`s whose `param_tys` reference outer Forall vars; without body substitution, Phase 3's re-synth would fail unification on unbound rigid `a`.
|
|
||||||
- iter 23.4.6: `rewrite_class_method_calls` renamed to `rewrite_mono_calls`; new helper `poly_free_fn_names_for_module` builds the per-module set of bare + qualified poly-fn names; `collect_residuals_ordered` walks the AST emitting interleaved slots via new `interleave_slots` helper. The walker and slot collector share the same predicate so the cursor invariant survives across the two channels.
|
|
||||||
- iter 23.4.7: `workspace_has_typeclasses` → `workspace_has_specialisable_targets`. NOTE: the plan's expected "FAIL → PASS after generalisation" was incorrect because the auto-injected prelude (iter 23.1) already keeps the old predicate true for every user workspace. The generalisation is principled-correctness only; documented in the predicate docstring.
|
|
||||||
- iter 23.4.8: codegen cleanup — removed `lower_polymorphic_call`, both call sites, the Emitter fields (`module_polymorphic_fns`, `mono_queue`, `mono_emitted`), `emit_specialised_fn`, Pass-1 poly population, and the unused dead helpers (`apply_subst_to_term`, `descriptor_for_subst`, `type_descriptor`). `is_static_callee` collapsed to its mono-only path. Two follow-on fixes were required beyond the plan: (a) Unit-default fallback for unpinned forall vars in the mono pass — mirrors pre-iter-23.4 codegen behaviour for sites like `is_empty(Nil)` where the elem type is unobservable from the args alone; (b) updated the `ail emit-ir` command pipeline to include `lift_letrecs` + `monomorphise_workspace` (pre-iter-23.4 the codegen-time poly path was masking this dependency — `emit-ir` was broken for poly fixtures without the explicit mono call now that codegen no longer handles it).
|
|
||||||
- iter 23.4.9: end-to-end verification — `cmp_max_smoke_runs_end_to_end` proves `cmp_max(3, 7)` at Int prints 7; the three existing poly fixtures (`polymorphic_id_at_int_and_bool`, `polymorphic_apply_with_fn_param`, `poly_rec_capture_demo`) still run.
|
|
||||||
- iter 23.4.10: `docs/DESIGN.md` §"Monomorphisation (post-typecheck, pre-codegen)" amended to describe the two-arm fixpoint, the dedup key with disjoint `"class"` / `"free"` prefixes, the cursor-aligned walker, and the removed codegen-time path.
|
|
||||||
- iter 23.4.11: bench-regression check — `bench/check.py`, `bench/compile_check.py`, `bench/cross_lang.py` all exit 0; no metrics regressed.
|
|
||||||
|
|
||||||
## Concerns
|
|
||||||
|
|
||||||
- Plan Step 4.4 was bypassed (no `env.polymorphic_free_fns` field added). The synth-channel approach is structurally cleaner; the plan's index is unneeded. If a future iter needs an O(1) "is this name a poly free fn" predicate at non-synth call sites, the helper `poly_free_fn_names_for_module` (in mono.rs) builds the set on demand.
|
|
||||||
- Plan Steps 5.3 / 7's "body unchanged" / "early-out fires for class-free workspaces" assumptions were both wrong against the actual codebase shape (free-fn bodies carry rigid vars in inner Lams; prelude is auto-injected so no workspace is class-free). Both were caught and adapted during implementation — no spec defect, just plan-draft drift against the live codebase.
|
|
||||||
|
|
||||||
## Known debt
|
|
||||||
|
|
||||||
- None tracked; all out-of-scope items from the plan (five prelude free fns ne/lt/le/gt/ge, `examples/prelude.ail.json` edits, the negative E2E fixtures `eq_ord_polymorphic` / `eq_ord_user_adt`, DESIGN.md §"Decision 11" amendment, Float-NoInstance diagnostic wording, roadmap update) remain owned by iter 23.5 as planned.
|
|
||||||
|
|
||||||
## Files touched
|
|
||||||
|
|
||||||
- `crates/ailang-check/src/lib.rs` — new `FreeFnCall` struct; `synth` signature gains `&mut Vec<FreeFnCall>`; Var arm tracks `(owner_module, unqualified_name)` and pushes `FreeFnCall` for `Type::Forall`-resolved non-local refs; new `substitute_rigids_in_term` helper.
|
|
||||||
- `crates/ailang-check/src/mono.rs` — `MonoTarget` struct → enum (`ClassMethod` / `FreeFn`); `mono_target_key` widened; new `mono_symbol_n` (N-ary); new `synthesise_mono_fn_for_free_fn`; new `poly_free_fn_names_for_module`; new `interleave_slots`; rename `rewrite_class_method_calls` → `rewrite_mono_calls`; `workspace_has_typeclasses` → `workspace_has_specialisable_targets`; fixpoint loop matches on target variants.
|
|
||||||
- `crates/ailang-check/src/lift.rs`, `crates/ailang-check/src/builtins.rs` — synth call sites pass new `&mut free_fn_calls` Vec.
|
|
||||||
- `crates/ailang-codegen/src/lib.rs` — deletions per Task 8 (Emitter fields, `lower_polymorphic_call`, `emit_specialised_fn`, mono drain loop, Pass-1 poly population, `is_static_callee` poly arm). `use` import shrunk.
|
|
||||||
- `crates/ailang-codegen/src/subst.rs` — `apply_subst_to_term` and `descriptor_for_subst` removed (dead post-Task-8).
|
|
||||||
- `crates/ailang-codegen/src/synth.rs` — `type_descriptor` removed (dead post-Task-8).
|
|
||||||
- `crates/ail/src/main.rs` — `emit-ir` command gains `lift_letrecs` + `monomorphise_workspace` pre-passes (matches `build` shape).
|
|
||||||
- `crates/ail/tests/typeclass_22b3.rs` — updated three test sites to `MonoTarget::ClassMethod` enum form.
|
|
||||||
- `crates/ail/tests/mono_hash_stability.rs` (new) — regression pin for six primitive Eq/Ord mono-symbol body hashes.
|
|
||||||
- `crates/ail/tests/mono_unification.rs` (new) — six tests covering variant key disjointness, free-fn target emission, free-fn synthesis with rigid substitution, rewrite walker on free fns, identity for poly-free-fn-only workspaces, and end-to-end `cmp_max_smoke` runtime correctness.
|
|
||||||
- `examples/cmp_max_smoke.ail.json` (new) — `cmp_max : forall a. Ord a => (a, a) -> a` composition fixture.
|
|
||||||
- `examples/mono_hash_pin_smoke.ail.json` (new) — fixture exercising the six primitive Eq/Ord mono symbols for hash-stability pinning.
|
|
||||||
- `docs/DESIGN.md` — §"Monomorphisation (post-typecheck, pre-codegen)" amended.
|
|
||||||
|
|
||||||
## Stats
|
|
||||||
|
|
||||||
bench/orchestrator-stats/2026-05-11-iter-23.4.json
|
|
||||||
@@ -1,104 +0,0 @@
|
|||||||
# iter cadence — audit/fieldtest/docwriter cadence restructure
|
|
||||||
|
|
||||||
**Date:** 2026-05-11
|
|
||||||
**Branch:** main
|
|
||||||
**Status:** DONE
|
|
||||||
**Tasks completed:** 5 of 5
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
This iter sorts the milestone-close stretch of the pipeline into
|
|
||||||
three cadence buckets, packaged by stability-window rather than by
|
|
||||||
milestone-grain:
|
|
||||||
|
|
||||||
1. **Per-milestone-mandatory — `audit`.** Runs every milestone close.
|
|
||||||
Architect drift review against DESIGN.md plus the three regression
|
|
||||||
scripts (`bench/check.py`, `bench/compile_check.py`,
|
|
||||||
`bench/cross_lang.py`). Step 3 ("Optional rustdoc audit") and the
|
|
||||||
`ailang-docwriter` cross-reference are removed; the rustdoc
|
|
||||||
warning line is gone from the Handoff Contract and the Common
|
|
||||||
Rationalisations table. Audit is now purely drift + bench.
|
|
||||||
2. **Boss-judgment post-audit — `fieldtest`.** Fires only when the
|
|
||||||
orchestrator decides the iteration is correct and wants a field
|
|
||||||
test, after audit closes clean (or with `ratify`-d drift only).
|
|
||||||
Trigger is reframed from "audit closes" to "Boss judgment call";
|
|
||||||
the expected finding shape (simple bugs route via `debug`,
|
|
||||||
catastrophic architecture problems route via next `brainstorm`)
|
|
||||||
is named explicitly. Cross-references add the ordering rule:
|
|
||||||
fieldtest runs *before* docwriter.
|
|
||||||
3. **Boss-judgment post-fieldtest — `docwriter`.** New skill at
|
|
||||||
`skills/docwriter/SKILL.md`. Boss-dispatched only, never
|
|
||||||
triggered by audit closing. Pre-condition is clean audit plus any
|
|
||||||
pending fieldtest. The agent (`ailang-docwriter`) carries the
|
|
||||||
substantive rustdoc rules; the SKILL.md only governs trigger and
|
|
||||||
dispatch. Symlinks added at `.claude/skills/docwriter` and
|
|
||||||
`.claude/agents/docwriter`.
|
|
||||||
|
|
||||||
`skills/README.md` is the one place that visibly mirrors the new
|
|
||||||
three-bucket taxonomy: the skill table grew from six rows to seven,
|
|
||||||
the pipeline diagram extends past `audit --(clean)-->` into
|
|
||||||
fieldtest and docwriter as two distinct Boss-decision points, and
|
|
||||||
the agent roster moves `ailang-docwriter` into its own dispatch
|
|
||||||
group.
|
|
||||||
|
|
||||||
## Per-task subjects
|
|
||||||
|
|
||||||
- iter cadence.1: skills/docwriter — new Boss-dispatched skill (split from audit)
|
|
||||||
- iter cadence.2: skills/audit — drop docwriter step (now own skill)
|
|
||||||
- iter cadence.3: skills/fieldtest — Boss-dispatched framing + ordering note
|
|
||||||
- iter cadence.3-fix: address quality-review minors (frontmatter desc + cross-ref path)
|
|
||||||
- iter cadence.4: skills/README — three-bucket cadence taxonomy
|
|
||||||
- iter cadence.4-fix: drop stale audit-dispatches-docwriter parenthetical (README Conventions)
|
|
||||||
- iter cadence.4-fix: address quality-review importants + German-loanword sweep (English-only in-tree)
|
|
||||||
- iter cadence.5: per-iter journal + INDEX
|
|
||||||
|
|
||||||
## Lesson
|
|
||||||
|
|
||||||
**Packaging cadence by stability-window outperforms packaging by
|
|
||||||
milestone-grain.** The previous arrangement bundled rustdoc cleanup
|
|
||||||
into `audit` as an "optional Step 3", which papered over the real
|
|
||||||
mismatch: rustdoc rot is a *post-stability* concern (documenting
|
|
||||||
something that gets rewritten three iterations later is waste),
|
|
||||||
while drift review and bench regression are per-iteration concerns
|
|
||||||
(letting them slip even one milestone compounds). Bundling those
|
|
||||||
two cadences inside one skill made the optional step easy to skip
|
|
||||||
on every milestone, which is exactly the failure mode the skill
|
|
||||||
discipline exists to prevent. Splitting docwriter out into its own
|
|
||||||
Boss-dispatched skill makes the cadence honest: audit is
|
|
||||||
non-negotiable per milestone, docwriter is non-negotiable per
|
|
||||||
*stability window*. The fieldtest framing flip — from "fires when
|
|
||||||
audit closes" to "Boss judgment after audit closes" — is the same
|
|
||||||
refactor at a smaller scale: the milestone clock was not the right
|
|
||||||
trigger; the orchestrator's judgement that the iteration is
|
|
||||||
*complete* is.
|
|
||||||
|
|
||||||
## Concerns
|
|
||||||
|
|
||||||
None worth carrying forward. Three quality-review-fix follow-ups on
|
|
||||||
cadence.3 and cadence.4 (frontmatter description drift, stale
|
|
||||||
audit-dispatches-docwriter parenthetical, one German-loanword in
|
|
||||||
in-tree text) — all caught by the standard implement-skill
|
|
||||||
two-stage review and resolved inline.
|
|
||||||
|
|
||||||
## Known debt
|
|
||||||
|
|
||||||
- The agent file `skills/docwriter/agents/ailang-docwriter.md` was
|
|
||||||
moved from `skills/audit/agents/` by the user before this iter
|
|
||||||
began. Its standing reading list still names `skills/audit` as
|
|
||||||
its home in one or two phrasings; a sweep of the agent's
|
|
||||||
cross-references is a candidate tidy iteration but did not
|
|
||||||
block this iter's narrower SKILL.md reshuffle.
|
|
||||||
- `docs/specs/2026-05-11-audit-fieldtest-docwriter-cadence.md` is a
|
|
||||||
dated artefact — the spec describes the design at time of
|
|
||||||
writing and is not retroactively updated as the implementation
|
|
||||||
shipped. Per the or.1 convention, the per-iter journal is the
|
|
||||||
running annotation.
|
|
||||||
|
|
||||||
## Blocked detail
|
|
||||||
|
|
||||||
N/A — DONE.
|
|
||||||
|
|
||||||
## Commits
|
|
||||||
|
|
||||||
`e351969^..HEAD` (8 commits including 3 quality-review-fix
|
|
||||||
follow-ups across cadence.3 and cadence.4).
|
|
||||||
@@ -1,190 +0,0 @@
|
|||||||
# iter disc.1 — boss-only commits and main-as-quarantine
|
|
||||||
|
|
||||||
**Date:** 2026-05-11
|
|
||||||
**Started from:** 841d65d
|
|
||||||
**Status:** DONE
|
|
||||||
**Tasks completed:** 10 of 10 (Boss-direct implementation; no orchestrator dispatch)
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
Skill-system-wide discipline update. Two project-wide rules are now
|
|
||||||
explicit:
|
|
||||||
|
|
||||||
1. **Only the Boss commits.** No skill agent — implementer,
|
|
||||||
brainstormer, planner, debugger, fieldtester, docwriter,
|
|
||||||
architect, bencher — performs `git commit`. Agents write all
|
|
||||||
their artefacts into the working tree as unstaged changes; the
|
|
||||||
Boss inspects, decides commit shape (typically one cohesive iter
|
|
||||||
commit, occasionally a few logical commits), and commits.
|
|
||||||
2. **main HEAD is sacrosanct.** No actor — Boss or agent — runs
|
|
||||||
`git reset` or `git revert` on main. Bad work stays in the
|
|
||||||
working tree where it is still discardable via
|
|
||||||
`git checkout -- <paths>`; main moves forward only via Boss
|
|
||||||
commits.
|
|
||||||
|
|
||||||
The two rules together force a working-tree-as-quarantine
|
|
||||||
discipline: nothing half-baked enters main, because nothing can
|
|
||||||
be taken back off main. The implement skill loses its
|
|
||||||
`iter/<iter_id>` branch-per-iter mechanic entirely; per-task agent
|
|
||||||
commits are also gone — Phase 4 of the orchestrator-agent now
|
|
||||||
writes the per-iter journal and the stats file into the working
|
|
||||||
tree (Write tool) but does not commit them.
|
|
||||||
|
|
||||||
## Per-task notes
|
|
||||||
|
|
||||||
- disc.1.1: `skills/implement/SKILL.md` — Iron Law rewritten
|
|
||||||
("IMPLEMENTER NEVER COMMITS" replaces "ONE BRANCH PER ITER";
|
|
||||||
"MAIN HEAD IS SACROSANCT" added). Step 3 "Boss merge step"
|
|
||||||
renamed and rewritten as "Boss inspect + commit step" (no
|
|
||||||
fast-forward; Boss reads `git status` / `git diff`, decides
|
|
||||||
commit shape, commits). Step 4 (BLOCKED) rewritten: Working-Tree
|
|
||||||
inspect, Repair re-dispatch or Discard via
|
|
||||||
`git checkout -- .`, no branch deletion. Description, Overview,
|
|
||||||
Rationalisations, Red Flags updated.
|
|
||||||
- disc.1.2: `skills/implement/agents/ailang-implement-orchestrator.md`
|
|
||||||
rewritten — Phase 0 is now a clean-tree check
|
|
||||||
(`git status --porcelain` must be empty), records
|
|
||||||
`start_sha` informationally (not as reset target), refuses to
|
|
||||||
proceed on a dirty tree. All `git commit` invocations removed.
|
|
||||||
Phase 4 (journal) and Phase 5 (stats) write to the working tree
|
|
||||||
via the Write tool, no commit. End-report Branch-field replaced
|
|
||||||
by `Working tree: dirty (N files)` and `(uncommitted)` markers
|
|
||||||
on file paths.
|
|
||||||
- disc.1.3: `skills/implement/agents/ailang-implementer.md`,
|
|
||||||
`ailang-tester.md` — commit steps in The Process removed.
|
|
||||||
Red-Flag "commit on red tests" replaced by the broader "never
|
|
||||||
commits, ever". Self-review now operates on `git diff HEAD`
|
|
||||||
with `git checkout -- <path>` for undo.
|
|
||||||
- disc.1.4: `skills/implement/agents/ailang-spec-reviewer.md`,
|
|
||||||
`ailang-quality-reviewer.md` — Carrier fields `pre_task_sha` /
|
|
||||||
`head_sha` replaced by `diff_command` (default `git diff HEAD`).
|
|
||||||
The full working-tree diff accumulates across tasks; reviewers
|
|
||||||
focus on the current task's footprint via the task block's file
|
|
||||||
list (or an optional `task_footprint_hint`).
|
|
||||||
- disc.1.5: `skills/brainstorm/SKILL.md` — Step 8 spec-commit
|
|
||||||
block removed; brainstorm writes the spec, user reviews, Boss
|
|
||||||
commits. The Step-7.5 retire-on-BLOCK procedure was rewritten
|
|
||||||
too: it now deletes the working-tree spec file (Bash `rm`, not
|
|
||||||
`git rm`, since the file was never committed) and appends a
|
|
||||||
roadmap entry; Boss commits the roadmap edit.
|
|
||||||
- disc.1.6: `skills/planner/SKILL.md` — Step 6 plan-commit
|
|
||||||
block removed; planner writes the plan, hand-off carries the
|
|
||||||
filepath, Boss commits. Plan-template tasks lose their
|
|
||||||
per-task "Step 5: Commit" — tasks are now 4 steps (write-failing-
|
|
||||||
test, verify-fail, write-impl, verify-pass) and leave work in
|
|
||||||
the working tree for the iter-level Boss commit. The "Bundle
|
|
||||||
Tasks 3 and 4" rationalisation reframed: tasks are units of
|
|
||||||
review, not units of commit.
|
|
||||||
- disc.1.7: `skills/debug/SKILL.md`, `skills/debug/agents/ailang-debugger.md`
|
|
||||||
— debugger writes the RED test to the working tree (uncommitted)
|
|
||||||
and hands off. The Boss chooses between two flows for
|
|
||||||
implement-mini: commit the RED separately for an audit-trail
|
|
||||||
before dispatching mini-mode (clean-tree mini gets the RED via
|
|
||||||
HEAD diff), or commit RED+GREEN together at the end (Boss
|
|
||||||
decides before dispatching).
|
|
||||||
- disc.1.8: `skills/fieldtest/SKILL.md`,
|
|
||||||
`skills/fieldtest/agents/ailang-fieldtester.md` — Phase 5
|
|
||||||
commit block removed; fieldtester writes `.ailx` files,
|
|
||||||
`.ail.json` files, and the spec to the working tree; Boss
|
|
||||||
commits with the suggested subject
|
|
||||||
`fieldtest: <milestone> — <N> examples, <K> findings`.
|
|
||||||
- disc.1.9: `skills/docwriter/agents/ailang-docwriter.md` —
|
|
||||||
Iron Law extended with "YOU NEVER COMMIT"; Red-Flag added.
|
|
||||||
`skills/audit/SKILL.md` — Step 4 reframed to name the Boss as
|
|
||||||
the actor who commits tidy fixes / ratify baselines / clean
|
|
||||||
closes. Other audit-agent files (architect, bencher) had no
|
|
||||||
commit invocations and are unchanged in this dispatch.
|
|
||||||
- disc.1.10: `skills/README.md` — Conventions section gained
|
|
||||||
two new bullets ("Only the Boss commits", "main HEAD is
|
|
||||||
sacrosanct"). Skill-table Output column updated for implement,
|
|
||||||
docwriter, fieldtest, debug to say "uncommitted in the working
|
|
||||||
tree". `CLAUDE.md` Direction-freedom subsection rewritten
|
|
||||||
(recovery is no longer about reverting commits; it is about
|
|
||||||
discarding bad working-tree state). New
|
|
||||||
"Commit discipline and main-branch sanctity" subsection added,
|
|
||||||
stating both rules in CLAUDE.md form.
|
|
||||||
|
|
||||||
## Concerns
|
|
||||||
|
|
||||||
(empty)
|
|
||||||
|
|
||||||
## Known debt
|
|
||||||
|
|
||||||
- Three historical files describe the obsolete branch-per-iter
|
|
||||||
discipline: `docs/specs/2026-05-11-implement-orchestrator-agent.md`,
|
|
||||||
`docs/journals/2026-05-11-iter-pr.1.md`,
|
|
||||||
`docs/plans/2026-05-11-orchestrator-refactor.md`. They are
|
|
||||||
zeitliche records and are left intact; this journal entry is the
|
|
||||||
forward-pointer that supersedes them. A future tidy iter could
|
|
||||||
add "Superseded by" banners, but the journal-archive convention is
|
|
||||||
append-only history.
|
|
||||||
- The implement orchestrator's Phase 0 refuses to run on a dirty
|
|
||||||
tree. The first real dispatch under the new discipline may
|
|
||||||
surface friction around this (e.g. Boss prepared scratch files
|
|
||||||
before dispatch). Capture in that iter's journal.
|
|
||||||
|
|
||||||
## Blocked detail
|
|
||||||
|
|
||||||
(none — DONE)
|
|
||||||
|
|
||||||
## Files touched
|
|
||||||
|
|
||||||
- `CLAUDE.md`
|
|
||||||
- `skills/README.md`
|
|
||||||
- `skills/audit/SKILL.md`
|
|
||||||
- `skills/brainstorm/SKILL.md`
|
|
||||||
- `skills/debug/SKILL.md`
|
|
||||||
- `skills/debug/agents/ailang-debugger.md`
|
|
||||||
- `skills/docwriter/agents/ailang-docwriter.md`
|
|
||||||
- `skills/fieldtest/SKILL.md`
|
|
||||||
- `skills/fieldtest/agents/ailang-fieldtester.md`
|
|
||||||
- `skills/implement/SKILL.md`
|
|
||||||
- `skills/implement/agents/ailang-implement-orchestrator.md`
|
|
||||||
- `skills/implement/agents/ailang-implementer.md`
|
|
||||||
- `skills/implement/agents/ailang-quality-reviewer.md`
|
|
||||||
- `skills/implement/agents/ailang-spec-reviewer.md`
|
|
||||||
- `skills/implement/agents/ailang-tester.md`
|
|
||||||
- `skills/planner/SKILL.md`
|
|
||||||
- `docs/journals/2026-05-11-iter-disc.1.md` (this file)
|
|
||||||
- `docs/journals/INDEX.md`
|
|
||||||
|
|
||||||
## Motivation
|
|
||||||
|
|
||||||
On 2026-05-11 iter 23.4 (eq/ord prelude — Free-Fns) BLOCKED three
|
|
||||||
times. A retrospective brainstorm produced a corrected spec; the
|
|
||||||
implement-orchestrator was dispatched on the revised plan and
|
|
||||||
immediately BLOCKED at Phase 0 with a branch-state mismatch: an
|
|
||||||
existing `iter/23.4` branch carried prep2 + prep3 commits that
|
|
||||||
the spec falsely claimed lived on main. They had never been
|
|
||||||
fast-forwarded; they were stranded on the iter-branch when the
|
|
||||||
iter was abandoned. The grounding-check agent had passed the
|
|
||||||
spec because it verified main-behaviour-via-green-tests, not
|
|
||||||
git-reachability of named commit SHAs.
|
|
||||||
|
|
||||||
The user identified the structural root cause: branch-per-iter
|
|
||||||
plus manual Boss-merge plus iter-stacking allows work to strand
|
|
||||||
on a branch that nobody integrates. The discipline correction is
|
|
||||||
this iter:
|
|
||||||
|
|
||||||
- Drop the branch mechanic in implement entirely.
|
|
||||||
- Centralise commit authority on the Boss across every skill.
|
|
||||||
- Make main forward-only by policy: no reset, no revert, by
|
|
||||||
anyone.
|
|
||||||
|
|
||||||
Per-task agent commits were eh too many (user phrasing: "es wird
|
|
||||||
eh viel zu viel commitet") — the new policy eliminates them as
|
|
||||||
a side-effect.
|
|
||||||
|
|
||||||
## Forward pointer
|
|
||||||
|
|
||||||
Step 5 of the 2026-05-11 recovery plan: re-attempt the eq/ord
|
|
||||||
prelude mono-pass unification (iter 23.4 in the corrected spec
|
|
||||||
`docs/specs/2026-05-11-23-eq-ord-prelude.md`), now under the
|
|
||||||
discipline this iter installs. A fresh plan is written first;
|
|
||||||
the implement-orchestrator is dispatched against it; the Boss
|
|
||||||
inspects + commits at the end. No branches, no agent commits.
|
|
||||||
|
|
||||||
## Stats
|
|
||||||
|
|
||||||
None — this iter was Boss-direct implementation (no orchestrator
|
|
||||||
dispatch; no stats file written).
|
|
||||||
@@ -1,126 +0,0 @@
|
|||||||
# iter gc.1 — Grounding-Check Agent, brainstorm Step 7.5 hard-gate
|
|
||||||
|
|
||||||
**Date:** 2026-05-11
|
|
||||||
**Branch:** iter/gc.1
|
|
||||||
**Status:** DONE
|
|
||||||
**Tasks completed:** 5 of 5
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
This iter ships `ailang-grounding-check`, a read-only spec reviewer
|
|
||||||
dispatched between brainstorm Step 7 (self-review) and Step 8
|
|
||||||
(user-approval). The agent reads a freshly-written spec with no
|
|
||||||
sunk-cost bias, extracts the spec's load-bearing assumptions about
|
|
||||||
current compiler / checker / codegen / schema behaviour, and for
|
|
||||||
each one searches the workspace for a currently-green test that
|
|
||||||
ratifies it. PASS or BLOCK; no PARTIAL credit. The output is the
|
|
||||||
hybrid format the agent template prescribes: a ratified-assumptions
|
|
||||||
table for fast Boss scanning plus a per-assumption prose block for
|
|
||||||
the unratified set.
|
|
||||||
|
|
||||||
Wiring is markdown-only. `skills/brainstorm/SKILL.md` gets a new
|
|
||||||
Step 7.5 hard-gate section, two new Handoff-Contract rows
|
|
||||||
(brainstorm → grounding-check, brainstorm → roadmap on no-override
|
|
||||||
BLOCK), and a Cross-references rewrite that splits the old "No
|
|
||||||
private agents" bullet into "Private agent" (the new grounding-check
|
|
||||||
dispatch) plus "Ad-hoc dispatch" (the existing plan-recon
|
|
||||||
opt-in). `skills/README.md` gets one new agent-roster row and one
|
|
||||||
new symlink-discovery line. `.claude/agents/brainstorm` is the
|
|
||||||
matching symlink. Spec-23 (Eq/Ord prelude) is retired because the
|
|
||||||
session that produced gc.1's spec is the same session that
|
|
||||||
discovered Spec-23's polymorphic-free-fn-monomorphisation assumption
|
|
||||||
was unratified — a fact gc.1's whole machinery exists to catch from
|
|
||||||
now on. The roadmap P1 Post-22 Prelude entry is amended to record
|
|
||||||
the retirement, name the unratified mechanism, and point forward to
|
|
||||||
the re-brainstorm.
|
|
||||||
|
|
||||||
The bootstrap irony stands: the spec for `ailang-grounding-check`
|
|
||||||
did not itself run through `ailang-grounding-check`. The agent did
|
|
||||||
not exist when its own spec was written, so per Spec §Architecture's
|
|
||||||
bootstrap-exclusion the gc.1 plan was implemented without the
|
|
||||||
gating mechanism it introduces. The first real run of the new
|
|
||||||
pipeline is the re-brainstormed Spec-23 in a separate session, not
|
|
||||||
this iter.
|
|
||||||
|
|
||||||
## Per-task subjects
|
|
||||||
|
|
||||||
- iter gc.1.1: agent — ailang-grounding-check definition
|
|
||||||
- iter gc.1.2: brainstorm SKILL — insert Step 7.5 (grounding-check hard-gate)
|
|
||||||
- iter gc.1.3: skills README — roster + symlink for ailang-grounding-check
|
|
||||||
- iter gc.1.4: symlink .claude/agents/brainstorm -> skills/brainstorm/agents
|
|
||||||
- iter gc.1.5: retire Spec-23, roadmap forward-pointer to re-brainstorm
|
|
||||||
|
|
||||||
## Concerns
|
|
||||||
|
|
||||||
- Plan §Step 2.4 and §F.2 expect `grep -c "^### Step " == 9` after
|
|
||||||
the 7.5 splice, but the brainstorm SKILL has nine *original* step
|
|
||||||
headings (Step 1 through Step 9), so adding 7.5 lands at 10. The
|
|
||||||
plan's parenthetical comment in §F.2 enumerates ten labels (1, 2,
|
|
||||||
3, 4, 5, 6, 7, 7.5, 8, 9) while asserting `wc -l = 9` — internally
|
|
||||||
inconsistent. Substantive intent (Step 7.5 between 7 and 8, leave
|
|
||||||
Step 9 in place) is unambiguous and honored; the off-by-one is a
|
|
||||||
plan-template miscount, not an implementation bug. Surfaced as a
|
|
||||||
cadence-flow signal to the Boss in the end-report.
|
|
||||||
|
|
||||||
## Cadence-flow notes (for skillsystem postmortem)
|
|
||||||
|
|
||||||
Two observations from the orchestrator's end-report, surfaced here
|
|
||||||
for the Boss's future skillsystem-postmortem entry — NOT in scope
|
|
||||||
for gc.1 itself:
|
|
||||||
|
|
||||||
1. **Planner verify-step counts derived from offsets become stale
|
|
||||||
at plan-freeze time.** The §Step 2.4 and §F.2 miscount above is
|
|
||||||
one instance. The orchestrator suggested two fixes: (a) planner
|
|
||||||
re-runs grep counts against the current file state at
|
|
||||||
plan-freeze time, or (b) planner expresses such counts as
|
|
||||||
`≥ N` inequalities rather than exact-match equalities. The
|
|
||||||
latter is cheaper and survives subsequent edits to the same
|
|
||||||
file.
|
|
||||||
|
|
||||||
2. **Markdown-only verbatim-content iters bottom out cleanly in the
|
|
||||||
spec-check + quality-check phases.** Zero review-loop fixups
|
|
||||||
landed this iter; all five tasks went DONE on first pass through
|
|
||||||
both phases. The spec-check phase effectively reduced to "is
|
|
||||||
the verbatim text from the plan in the diff?" and the
|
|
||||||
quality-check phase had little to flag beyond whitespace
|
|
||||||
hygiene. Both passes still ran (discipline preserved), but the
|
|
||||||
value they added was low. This is expected for verbatim-content
|
|
||||||
tasks and not a concern about the discipline itself — captured
|
|
||||||
here so it is not misread, in retrospect, as evidence that the
|
|
||||||
phases are too lenient.
|
|
||||||
|
|
||||||
3. **First real run of grounding-check (2026-05-11 same-day, on
|
|
||||||
the revised Spec-23): PASS, 15 assumptions ratified.** The
|
|
||||||
agent's output format leaked an internal-thinking preamble
|
|
||||||
before the structured `GROUNDING-CHECK REPORT` block — the
|
|
||||||
Iron Law should be tightened to forbid any preamble before the
|
|
||||||
structured report. Substantively unproblematic (the judgement
|
|
||||||
and test pins were clear) but a real prompt-discipline gap.
|
|
||||||
|
|
||||||
4. **Behaviour change requested for impending-BLOCK paths.** On a
|
|
||||||
miss (assumption with no green test), the agent should attempt
|
|
||||||
to write the missing GREEN test itself — strictly test code,
|
|
||||||
never `src/`-side app code — and only BLOCK if test creation
|
|
||||||
fails. This is two changes: (a) tools — add `Write` (or `Edit`)
|
|
||||||
to the agent frontmatter, today `Read, Glob, Grep, Bash` only;
|
|
||||||
(b) discipline — Iron Law gains "you may write tests under
|
|
||||||
`crates/*/tests/` or `#[cfg(test)] mod tests`; you may NOT touch
|
|
||||||
any file outside test scope." The first real PASS run did not
|
|
||||||
exercise this path, so the constraint surfaces here as a future
|
|
||||||
tightening rather than as a tested behaviour.
|
|
||||||
|
|
||||||
## Known debt
|
|
||||||
|
|
||||||
None.
|
|
||||||
|
|
||||||
## Blocked detail
|
|
||||||
|
|
||||||
N/A — DONE.
|
|
||||||
|
|
||||||
## Commits
|
|
||||||
|
|
||||||
`606a2eb..HEAD` (5 task commits, no review-loop fixups).
|
|
||||||
|
|
||||||
## Stats
|
|
||||||
|
|
||||||
bench/orchestrator-stats/2026-05-11-iter-gc.1.json
|
|
||||||
@@ -1,151 +0,0 @@
|
|||||||
# iter or.1 — orchestrator-refactor
|
|
||||||
|
|
||||||
**Date:** 2026-05-11
|
|
||||||
**Branch:** iter/or.1
|
|
||||||
**Status:** DONE
|
|
||||||
**Tasks completed:** 7 of 7
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
This iter lands the three coupled changes from
|
|
||||||
`docs/specs/2026-05-11-implement-orchestrator-agent.md`:
|
|
||||||
|
|
||||||
1. **Boss-context offload.** A new agent
|
|
||||||
`skills/implement/agents/ailang-implement-orchestrator.md` owns
|
|
||||||
one full `/implement` iter end-to-end — per-task implementer →
|
|
||||||
spec-reviewer → quality-reviewer loop, with the canonical
|
|
||||||
re-loop limits — running in its own context. The Boss now
|
|
||||||
dispatches one subagent and receives a ≤500-token end-report.
|
|
||||||
2. **Iter isolation via branches.** Each `/implement` run owns a
|
|
||||||
dedicated `iter/<iter_id>` branch, created from `origin/main`
|
|
||||||
in Phase 0 of the orchestrator-agent. Integration is a Boss-side
|
|
||||||
`git switch main && git merge --ff-only` after reading the
|
|
||||||
end-report.
|
|
||||||
3. **Per-iter journals.** `docs/JOURNAL.md` (14k lines) is renamed
|
|
||||||
to `docs/journal-archive.md` with an archived-status header.
|
|
||||||
Going forward, each iter writes its own file under
|
|
||||||
`docs/journals/<YYYY-MM-DD>-iter-<id>.md`; the Boss appends a
|
|
||||||
one-line pointer to `docs/journals/INDEX.md` at merge time.
|
|
||||||
This file is the first such per-iter journal.
|
|
||||||
|
|
||||||
The orchestrator-agent goes live on the *next* `/implement` run after
|
|
||||||
this iter lands. This iter itself was executed in the pre-refactor
|
|
||||||
mode (Boss dispatched workers directly) — a bootstrap necessity since
|
|
||||||
the new agent did not yet exist while creating it.
|
|
||||||
|
|
||||||
Model selection was unchanged: every dispatch this iter used Opus 4.7.
|
|
||||||
The token savings come from token-volume relocation, not from
|
|
||||||
intelligence downgrade.
|
|
||||||
|
|
||||||
## Per-task subjects
|
|
||||||
|
|
||||||
- iter or.1.1: migrate JOURNAL.md to journal-archive.md + docs/journals/
|
|
||||||
- iter or.1.2: repoint top-level docs to docs/journals/ + journal-archive.md
|
|
||||||
- iter or.1.2-fix: address quality-review minor + nit
|
|
||||||
- iter or.1.3: repoint skill + agent reading lists to docs/journals/
|
|
||||||
- iter or.1.3-fix: address quality-review minors (architect / audit phrasing)
|
|
||||||
- iter or.1.4: carrier switch task_text -> task_text_path
|
|
||||||
- iter or.1.5: new agent ailang-implement-orchestrator
|
|
||||||
- iter or.1.6: rewrite skills/implement/SKILL.md to delegate to orchestrator-agent
|
|
||||||
- iter or.1.6-fix: correct git rebase syntax + README cross-ref wording
|
|
||||||
- iter or.1.6-fix2: SKILL.md description drift + self-contained exception ref
|
|
||||||
- iter or.1.7: document ailang-implement-orchestrator as named exception
|
|
||||||
|
|
||||||
## Concerns
|
|
||||||
|
|
||||||
Three `DONE_WITH_CONCERNS` observations across the iter, all
|
|
||||||
plan-template issues rather than scope deviations:
|
|
||||||
|
|
||||||
- Task 1: the plan's Step 6 listed `git add docs/JOURNAL.md` after
|
|
||||||
a `git mv`; this errors because the path is already gone from the
|
|
||||||
working tree. The implementer dropped the dead path. Plan defect.
|
|
||||||
- Task 4: the plan's per-step instructions enumerated only the
|
|
||||||
carrier-contract row, but the plan's Step 6 verification clause
|
|
||||||
(`grep -n "task_text\b" → empty`) required visiting every body-prose
|
|
||||||
mention of `task_text` too. The implementer correctly expanded
|
|
||||||
scope to satisfy the verification; rewrites preserved semantics.
|
|
||||||
- Task 6: the plan's Step 1 verbatim content is 194 lines; Step 2's
|
|
||||||
sanity check expected 110–160. Step 1 is canonical; the sanity
|
|
||||||
range was my optimistic estimate when writing the plan. The
|
|
||||||
implementer correctly followed Step 1.
|
|
||||||
- Task 7: the plan's Step 3 grep expected 1 match for
|
|
||||||
`ailang-implement-orchestrator`, but the new exception paragraph
|
|
||||||
itself names the agent, so the actual count is 2. Same shape of
|
|
||||||
plan-template error.
|
|
||||||
|
|
||||||
None of these affect correctness; all are notes for the next plan
|
|
||||||
template revision.
|
|
||||||
|
|
||||||
## Known debt
|
|
||||||
|
|
||||||
- Two bare `JOURNAL` uppercase mentions remain in `CLAUDE.md` at
|
|
||||||
lines 285/287 (the "answer three questions" closing paragraph).
|
|
||||||
They refer conceptually to the historical log, but the surface
|
|
||||||
reading is slightly stale now that JOURNAL is no longer a file.
|
|
||||||
Out of scope for Task 2's enumerated sites; carry-on observation.
|
|
||||||
- "Boss" / "Boss-Orchestrator" vocabulary is introduced in the new
|
|
||||||
`skills/implement/SKILL.md` and `ailang-implement-orchestrator.md`
|
|
||||||
to distinguish from "the orchestrator-agent". `CLAUDE.md` uses
|
|
||||||
"orchestrator" throughout. The distinction is intentional, but a
|
|
||||||
one-line glossary entry in `CLAUDE.md` would prevent term drift
|
|
||||||
in future docs.
|
|
||||||
- Verification ladder steps 1–5 (real-orchestrator-agent dispatch
|
|
||||||
exercise) are deferred to the first real `/implement` run after
|
|
||||||
this iter merges. Steps 6–8 (intentional-BLOCKED, intentional
|
|
||||||
re-loop, manual Boss-merge replay) get checked off when those
|
|
||||||
failure modes arise organically.
|
|
||||||
|
|
||||||
## Blocked detail
|
|
||||||
|
|
||||||
N/A — DONE.
|
|
||||||
|
|
||||||
## Commits
|
|
||||||
|
|
||||||
`1891030..92e39bb` (11 commits including 4 quality-review-fix
|
|
||||||
follow-ups; the per-task pattern was DONE/DONE-WITH-CONCERNS →
|
|
||||||
spec-reviewer compliant → quality-reviewer approved-or-fixed-then-
|
|
||||||
approved, per the implement skill's two-stage review).
|
|
||||||
|
|
||||||
## Stats
|
|
||||||
|
|
||||||
Not produced this iter — `bench/orchestrator-stats/` is a new
|
|
||||||
artefact introduced by the orchestrator-agent (Phase 5 of its
|
|
||||||
process). This iter was executed in pre-refactor mode by the Boss
|
|
||||||
directly, so no stats file exists. The first stats file will be
|
|
||||||
written by the orchestrator-agent on the next real `/implement` run.
|
|
||||||
|
|
||||||
## Audit / milestone close
|
|
||||||
|
|
||||||
Audit ran post-iter (skill-system mandatory at milestone close).
|
|
||||||
|
|
||||||
- **Architect drift review:** `drift_found`. Live docs and a handful
|
|
||||||
of concept-noun sites still mentioned `JOURNAL.md` as if it were a
|
|
||||||
current path. Highest-impact misses were `docs/WhatsNew.md` (live
|
|
||||||
changelog framing-text — wrongly carved out of the Task 3 plan as
|
|
||||||
"dated artifact"), `docs/roadmap.md` lines 25-27 (live convention
|
|
||||||
text), and four bare `JOURNAL` mentions in `CLAUDE.md` (lines 108,
|
|
||||||
267, 285, 287). Five concept-noun spots in
|
|
||||||
`skills/audit/agents/ailang-architect.md` (Iron Law line 100 was
|
|
||||||
already correct; the others were process descriptions). All
|
|
||||||
resolved inline via `iter or.1.tidy` (single commit, four files).
|
|
||||||
Spec-vs-shipped divergences (the spec still prescribes the invalid
|
|
||||||
`git rebase ... onto main` and lists 3 blocked reasons where the
|
|
||||||
agent ships 5) were intentionally left as-is — specs are dated
|
|
||||||
artefacts capturing design at time of writing; the per-iter journal
|
|
||||||
is the running annotation.
|
|
||||||
- **Bench-regression check:** `bench/check.py` 63 metrics stable
|
|
||||||
(exit 0); `bench/compile_check.py` 24 metrics stable (exit 0);
|
|
||||||
`bench/cross_lang.py` 25 metrics stable (exit 0). No language code
|
|
||||||
changed; the green result was expected and confirms it.
|
|
||||||
- **Rustdoc:** 18 pre-existing warnings (2 in `ailang-core`, 16 in
|
|
||||||
`ailang-check`) about private-item links and unresolved cross-refs.
|
|
||||||
None caused by this iter. Deferred to a future docwriter sweep.
|
|
||||||
|
|
||||||
Milestone close: clean after `or.1.tidy`. Three roadmap follow-ups
|
|
||||||
worth noting:
|
|
||||||
- WhatsNew.md should NOT be carved out as "dated artifact" in any
|
|
||||||
future doc-sweep plan; it is a live changelog.
|
|
||||||
- A separate docwriter sweep for the 18 pre-existing rustdoc warnings.
|
|
||||||
- Once a real `/implement` run goes through the new orchestrator-agent,
|
|
||||||
ladders 1–5 of the verification ladder in `docs/specs/
|
|
||||||
2026-05-11-implement-orchestrator-agent.md` are checked off.
|
|
||||||
@@ -1,189 +0,0 @@
|
|||||||
# iter or.2 — orchestrator-agent design correction
|
|
||||||
|
|
||||||
**Date:** 2026-05-11
|
|
||||||
**Branch:** none (trivial-mechanical carve-out — doc-only revision, single commit on main)
|
|
||||||
**Status:** DONE
|
|
||||||
**Tasks completed:** N/A (doc revision, not a plan-driven iter)
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
This entry corrects a factual error in the `or.1` architecture that
|
|
||||||
surfaced during the first real dispatch (pr.1, same day). The
|
|
||||||
revision lands as one direct-on-main commit because the change is
|
|
||||||
doc-only: no code, no tests, no behaviour shift in any compiler
|
|
||||||
crate. The Iron-Law mini-fix from earlier today (`origin/main` →
|
|
||||||
`main`) is preserved unchanged — that fix remains correct.
|
|
||||||
|
|
||||||
### What was wrong
|
|
||||||
|
|
||||||
`or.1` and `pr.1` shipped the `ailang-implement-orchestrator` agent
|
|
||||||
with frontmatter `tools: Read, Edit, Write, Bash, Glob, Grep, Agent`
|
|
||||||
and an architecture that depended on the orchestrator-agent
|
|
||||||
spawning further subagents per task (implementer → spec-reviewer →
|
|
||||||
quality-reviewer → tester, each in fresh context). The architecture
|
|
||||||
was framed in `skills/README.md` as "agents do not call other
|
|
||||||
agents — one named exception" (the orchestrator-agent being the
|
|
||||||
exception).
|
|
||||||
|
|
||||||
The exception never existed in Claude Code. The platform forbids
|
|
||||||
**all** nested-subagent dispatch, categorically:
|
|
||||||
|
|
||||||
> "Subagents cannot spawn other subagents, so `Agent(agent_type)`
|
|
||||||
> has no effect in subagent definitions."
|
|
||||||
> — https://code.claude.com/docs/en/sub-agents
|
|
||||||
|
|
||||||
> "Subagents cannot spawn their own subagents. Don't include
|
|
||||||
> `Agent` in a subagent's `tools` array."
|
|
||||||
> — https://code.claude.com/docs/en/agent-sdk/subagents
|
|
||||||
|
|
||||||
The `Agent` tool was silently dropped from the orchestrator-agent's
|
|
||||||
tool set at dispatch time (no error, no warning). The pr.1 run
|
|
||||||
caught this empirically; the diagnosis closed the question today.
|
|
||||||
|
|
||||||
### Why the failure took the form it did
|
|
||||||
|
|
||||||
The Boss-Orchestrator (me) designed `or.1` on the false premise
|
|
||||||
that the project's house rule "agents do not call other agents"
|
|
||||||
was a local convention with a documented exception. In reality the
|
|
||||||
house rule was a *restatement of an external constraint* — and the
|
|
||||||
"named exception" was incompatible with the constraint itself.
|
|
||||||
This is a verification-of-capability failure: the architecture was
|
|
||||||
locked in before checking the platform docs for what subagents can
|
|
||||||
do. The right order is the other way around. (Memorised as a
|
|
||||||
feedback memory for future architecture decisions.)
|
|
||||||
|
|
||||||
### The corrected architecture
|
|
||||||
|
|
||||||
Same skill body, same skill triggers, same branch-and-merge
|
|
||||||
discipline, same Boss-side end-report contract. What changes is
|
|
||||||
*inside* the orchestrator-agent's run:
|
|
||||||
|
|
||||||
- Per-task phases (implementer → spec-compliance check → quality
|
|
||||||
check) run sequentially as **role-switches in the orchestrator-
|
|
||||||
agent's own context**, not as nested subagents.
|
|
||||||
- The `ailang-implementer.md` / `ailang-spec-reviewer.md` /
|
|
||||||
`ailang-quality-reviewer.md` / `ailang-tester.md` files now serve
|
|
||||||
as **phase reference files** — the orchestrator-agent consults
|
|
||||||
them at each role-switch to inhale the discipline of that role,
|
|
||||||
but does not dispatch them as separate subagents.
|
|
||||||
- The Boss-context offload property is preserved (the Boss still
|
|
||||||
sees only the ≤500-token end-report; per-task chatter stays
|
|
||||||
inside the orchestrator-agent's context).
|
|
||||||
- The fresh-per-phase context property — which was the design goal
|
|
||||||
that drove `or.1` to attempt nested dispatch — is **given up**.
|
|
||||||
Pure role-switching inside one context is a weaker discipline
|
|
||||||
than fresh-context-per-phase. The trade-off is named openly: the
|
|
||||||
cost is the loss of fresh-eyes review per phase; the benefit is
|
|
||||||
that the architecture is buildable in Claude Code at all.
|
|
||||||
- The pr.1 "degraded mode" was not actually degraded — it was the
|
|
||||||
only mode this agent can run in. The pr.1 journal's degraded-
|
|
||||||
mode caveat is preserved as-is (historical record of how the
|
|
||||||
constraint was discovered).
|
|
||||||
|
|
||||||
### Why option (A), not (B) or (C)
|
|
||||||
|
|
||||||
Three options were on the table:
|
|
||||||
|
|
||||||
- **(A)** Single-context inline phases (what landed) — preserves
|
|
||||||
Boss-context offload, gives up fresh-per-phase context.
|
|
||||||
- **(B)** Roll back to pre-or.1: Boss dispatches each per-task
|
|
||||||
subagent itself. Preserves fresh-per-phase context, gives up
|
|
||||||
Boss-context offload.
|
|
||||||
- **(C)** Hybrid: orchestrator-agent for setup / branch / journal,
|
|
||||||
Boss for per-task dispatches in between. Preserves both
|
|
||||||
theoretically but multiplies moving parts.
|
|
||||||
|
|
||||||
(A) was chosen because the Boss-context offload is the scarce
|
|
||||||
resource (user chat budget). Empirically, the pr.1 run with the
|
|
||||||
plan template's verbatim per-task content blocks made fresh-per-
|
|
||||||
phase context contribute little additional signal beyond a careful
|
|
||||||
single-context phase loop. The decision was made by the user
|
|
||||||
2026-05-11 after the diagnosis was presented; the rationale belongs
|
|
||||||
in the user's hands, not the agent's.
|
|
||||||
|
|
||||||
### Files changed
|
|
||||||
|
|
||||||
- `skills/implement/agents/ailang-implement-orchestrator.md` —
|
|
||||||
frontmatter (`Agent` removed from tools, description rewritten),
|
|
||||||
Iron Law (sequential role-switches instead of fresh subagents),
|
|
||||||
Phase 2.1 / 2.2 / 2.3 rewritten as inline phases, Phase 3 likewise,
|
|
||||||
common rationalisations and red flags updated.
|
|
||||||
- `skills/implement/SKILL.md` — frontmatter description, Iron Law,
|
|
||||||
per-task sub-status mechanics note (vocabulary is preserved but
|
|
||||||
now describes inline role-phase status), one new
|
|
||||||
Common-Rationalisation row, Cross-references rewritten (role-files
|
|
||||||
recast as phase references, with a "Why inline phases" subsection
|
|
||||||
pointing here).
|
|
||||||
- `skills/README.md` — Conventions: "Agents do not call other
|
|
||||||
agents — one named exception" → "Agents do not call other agents"
|
|
||||||
(the exception is gone, with an explanation of why). Agent roster
|
|
||||||
rows for implementer / spec-reviewer / quality-reviewer / tester
|
|
||||||
recast as phase references for the orchestrator-agent.
|
|
||||||
- `docs/roadmap.md` — P1 entry "`Agent` tool wiring for the
|
|
||||||
dispatched orchestrator-agent" removed (resolved as
|
|
||||||
categorically-not-fixable).
|
|
||||||
- `docs/journals/INDEX.md` — this entry appended.
|
|
||||||
- `docs/WhatsNew.md` — user-facing correction entry appended.
|
|
||||||
|
|
||||||
### Files NOT changed (and why)
|
|
||||||
|
|
||||||
- `CLAUDE.md` — no stale references; the file does not encode the
|
|
||||||
named-exception story.
|
|
||||||
- `skills/implement/agents/ailang-implementer.md`,
|
|
||||||
`ailang-spec-reviewer.md`, `ailang-quality-reviewer.md`,
|
|
||||||
`ailang-tester.md` — these remain unchanged. Their content
|
|
||||||
(discipline, reading list, output format) is correct in the new
|
|
||||||
architecture; their *role* in the system has shifted from
|
|
||||||
"dispatched subagent" to "phase reference for the orchestrator-
|
|
||||||
agent". The shift is documented in the README roster row and the
|
|
||||||
SKILL.md Cross-references; no in-file edits are required for
|
|
||||||
correctness. A future cleanup iter may rewrite their frontmatter
|
|
||||||
to drop the "subagent" framing if it begins to mislead — for now
|
|
||||||
the cost of touching four files outweighs the marginal clarity
|
|
||||||
gain.
|
|
||||||
- `docs/specs/2026-05-11-implement-orchestrator-agent.md` and
|
|
||||||
`docs/specs/2026-05-11-plan-recon-subagent.md` — specs are
|
|
||||||
historical records of the design as conceived; they remain
|
|
||||||
accurate to that moment. Drift between spec and the corrected
|
|
||||||
architecture is acknowledged here, not by editing the specs.
|
|
||||||
|
|
||||||
## Concerns
|
|
||||||
|
|
||||||
None. The revision is deliberate and self-contained.
|
|
||||||
|
|
||||||
## Known debt
|
|
||||||
|
|
||||||
- **Phase reference files frame themselves as dispatched subagents.**
|
|
||||||
`ailang-implementer.md` and its three siblings still read like
|
|
||||||
files describing a subagent receiving a carrier from a dispatcher.
|
|
||||||
In the corrected architecture, the orchestrator-agent IS the
|
|
||||||
dispatcher and the worker for those phases. If the next
|
|
||||||
`/implement` run shows the orchestrator-agent confused by this
|
|
||||||
framing during phase reads, a follow-up cleanup iter rewrites
|
|
||||||
the four files. Not pre-emptive — wait for an empirical signal.
|
|
||||||
- **No verification ladder for the corrected design.** `or.1`'s
|
|
||||||
verification ladder (items 1–5 in
|
|
||||||
`docs/specs/2026-05-11-implement-orchestrator-agent.md`) was
|
|
||||||
written for the nested-dispatch architecture. Items 1–3
|
|
||||||
(Boss-side dispatch, branch creation, journal write) carry over
|
|
||||||
unchanged. Items 4–5 (intentional BLOCKED, intentional re-loop)
|
|
||||||
are weaker in the inline-phase world — re-loop is a same-context
|
|
||||||
retry, not a fresh-context re-dispatch — and a new ladder entry
|
|
||||||
for the inline phase-switch should be added the next time
|
|
||||||
`/implement` runs against a non-trivial plan. Tracked
|
|
||||||
informally; no ticket.
|
|
||||||
|
|
||||||
## Blocked detail
|
|
||||||
|
|
||||||
N/A — DONE.
|
|
||||||
|
|
||||||
## Commits
|
|
||||||
|
|
||||||
This entry's revision lands as a single trivial-mechanical commit
|
|
||||||
on `main` (no iter branch, no plan template). The commit subject is
|
|
||||||
`or.2: orchestrator-agent design correction — no nested subagent dispatch`.
|
|
||||||
|
|
||||||
## Stats
|
|
||||||
|
|
||||||
N/A — no orchestrator-agent dispatch (this is a doc-only revision
|
|
||||||
made directly by the Boss-Orchestrator).
|
|
||||||
@@ -1,171 +0,0 @@
|
|||||||
# iter pr.1 — plan-recon subagent
|
|
||||||
|
|
||||||
**Date:** 2026-05-11
|
|
||||||
**Branch:** iter/pr.1
|
|
||||||
**Status:** DONE
|
|
||||||
**Tasks completed:** 5 of 5
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
This iter lands the changes from
|
|
||||||
`docs/specs/2026-05-11-plan-recon-subagent.md`: a new read-only
|
|
||||||
subagent `ailang-plan-recon` that the `planner` skill dispatches at
|
|
||||||
Step 2 (file-structure mapping), moving the read-heavy code-recon out
|
|
||||||
of Boss context. `brainstorm` gets a one-paragraph note in its
|
|
||||||
Cross-references allowing ad-hoc dispatch of the same agent when
|
|
||||||
entering unfamiliar code territory; it does NOT receive a dedicated
|
|
||||||
agent. Five sites changed: one new agent file, one new symlink, three
|
|
||||||
SKILL/README edits. No Rust, no benchmark impact.
|
|
||||||
|
|
||||||
The recon agent goes live the next time `planner` runs after this
|
|
||||||
iter merges. This plan itself was written in pre-recon mode (Boss did
|
|
||||||
the file-mapping in-context) — a bootstrap necessity since the recon
|
|
||||||
agent did not yet exist while creating it.
|
|
||||||
|
|
||||||
**Degraded-mode dispatch note (orchestrator-agent).** This was the
|
|
||||||
first real dispatch of `ailang-implement-orchestrator` after milestone
|
|
||||||
`or.1` (verification ladder steps 1–3). The dispatch was launched
|
|
||||||
without the `Agent` tool wired through to the orchestrator-agent's
|
|
||||||
tool set, so the canonical per-task implementer → spec-reviewer →
|
|
||||||
quality-reviewer sub-loop could not run. Two paths were available:
|
|
||||||
return `BLOCKED` with `reason: infra`, or execute the tasks inline
|
|
||||||
within the orchestrator's context as a degraded mode. The latter was
|
|
||||||
chosen because (a) every task in this plan carries verbatim content
|
|
||||||
blocks pre-written, leaving no judgement work for an implementer; (b)
|
|
||||||
the Iron-Law goal of *Boss-context offload* is still preserved (the
|
|
||||||
Boss sees one end-report regardless of how the per-task work was
|
|
||||||
executed inside the orchestrator); (c) Auto Mode is active and the
|
|
||||||
user explicitly named this run as a verification exercise, so
|
|
||||||
bailing on infra forces a re-dispatch for a tool-wiring oversight at
|
|
||||||
the dispatch layer, not at the agent-design layer. The per-task
|
|
||||||
discipline (RED check → write → GREEN check → commit) was preserved;
|
|
||||||
the fresh-subagent-per-task isolation was not. This degraded path is
|
|
||||||
*one-time*, not a precedent: the orchestrator-agent's standing tool
|
|
||||||
set must include `Agent`, and the next dispatch should restore the
|
|
||||||
canonical loop. Recorded for the verification ladder.
|
|
||||||
|
|
||||||
## Per-task subjects
|
|
||||||
|
|
||||||
- iter pr.1.1: new agent ailang-plan-recon
|
|
||||||
- iter pr.1.2: symlink .claude/agents/planner
|
|
||||||
- iter pr.1.3: planner Step 2 dispatches ailang-plan-recon
|
|
||||||
- iter pr.1.4: brainstorm allows ad-hoc plan-recon dispatch
|
|
||||||
- iter pr.1.5: roster + discovery entries for ailang-plan-recon
|
|
||||||
|
|
||||||
## Concerns
|
|
||||||
|
|
||||||
One `DONE_WITH_CONCERNS` observation, a plan-template issue rather
|
|
||||||
than a scope deviation:
|
|
||||||
|
|
||||||
- Task 4's Step 1 RED-check regex is malformed:
|
|
||||||
`grep -q "No private agents.\*\*\* This skill is dialogue-driven\."`
|
|
||||||
expects three literal stars (`\*\*\*`) after the period, but the
|
|
||||||
file content has only two (markdown bold `**`). The check exits
|
|
||||||
non-zero against a correct pre-state. The implementer (the
|
|
||||||
orchestrator-agent in this degraded mode) verified RED by intent —
|
|
||||||
the line exists, "ad-hoc dispatch" does not — and proceeded. Plan
|
|
||||||
defect; note for the next plan template revision.
|
|
||||||
|
|
||||||
## Known debt
|
|
||||||
|
|
||||||
- Verification ladder items 1–3 from
|
|
||||||
`docs/specs/2026-05-11-implement-orchestrator-agent.md` were
|
|
||||||
exercised in degraded mode (see Summary). Restoring the full
|
|
||||||
per-task sub-loop is a precondition for ladder items 4–5
|
|
||||||
(intentional-BLOCKED, intentional re-loop) to be meaningfully
|
|
||||||
testable. The Boss should verify `Agent` is in the
|
|
||||||
orchestrator-agent's tool wiring on the next `/implement` dispatch.
|
|
||||||
- The plan-recon agent's standing reading list points at
|
|
||||||
`docs/journals/INDEX.md` and "the latest entries" — once this iter
|
|
||||||
lands and INDEX is updated, the agent's reading list naturally
|
|
||||||
picks up the pr.1 entry.
|
|
||||||
|
|
||||||
### Boss-side addendum
|
|
||||||
|
|
||||||
Two follow-ups surfaced during this dispatch that are not pr.1's
|
|
||||||
own scope but were exposed by being the first real run of the
|
|
||||||
or.1-orchestrator-agent. Both go to the post-pr.1 fix queue:
|
|
||||||
|
|
||||||
- **or.1 Iron-Law wording is wrong.** The orchestrator-agent's
|
|
||||||
Phase 0 Iron Law says `git switch -c iter/<iter_id> origin/main`,
|
|
||||||
which presupposes that the plan-commit is on `origin/main`. The
|
|
||||||
Boss does not push between plan-commit and dispatch, so the
|
|
||||||
branch is created without the plan file. The agent worked around
|
|
||||||
this by mid-flight ff-merging local `main` into `iter/pr.1`,
|
|
||||||
which got the right end-state but rationalised itself through a
|
|
||||||
"branch already exists" clause that was actually meant for repair
|
|
||||||
re-dispatch. Correct fix: `git switch -c iter/<iter_id> main`
|
|
||||||
(drop `origin/`). Iron Law's purpose is iter-isolation from main,
|
|
||||||
not push-state coupling. Touches
|
|
||||||
`skills/implement/agents/ailang-implement-orchestrator.md` Phase
|
|
||||||
0 and the corresponding mention in `skills/implement/SKILL.md`
|
|
||||||
if any. One-commit mini-fix iter (no plan template needed,
|
|
||||||
trivial-mechanical carve-out).
|
|
||||||
- **`Agent` tool not reaching the orchestrator-agent.** Frontmatter
|
|
||||||
declares `tools: Read, Edit, Write, Bash, Glob, Grep, Agent`, but
|
|
||||||
the dispatched orchestrator-agent reports `Agent` was not in its
|
|
||||||
actual tool set, forcing the degraded-mode execution described
|
|
||||||
in Summary. Root-cause investigation needed: either a Claude
|
|
||||||
Code restriction on nested-subagent dispatch, or a frontmatter
|
|
||||||
parsing detail, or a deferred-tool issue at the harness layer.
|
|
||||||
This blocks the entire or.1 design goal (Boss-context offload
|
|
||||||
via fresh-subagent-per-task isolation), so it dominates the
|
|
||||||
post-pr.1 queue. Not fixable by a doc edit; needs diagnosis
|
|
||||||
before a fix can be scoped.
|
|
||||||
- **Plan-template RED-check regex.** Task 4 Step 1 used
|
|
||||||
`\*\*\*` (three literal stars) where the file content has two
|
|
||||||
(`**…**` markdown bold). Three plan iterations have now hit
|
|
||||||
plan-template defects (or.1 Tasks 1/4/6/7, pr.1 Task 4) —
|
|
||||||
cumulative signal that the plan-template needs a small revision
|
|
||||||
pass before the next planner run, or the planner skill needs
|
|
||||||
a self-test step.
|
|
||||||
|
|
||||||
## Audit / milestone close
|
|
||||||
|
|
||||||
Audit ran post-iter (skill-system mandatory at milestone close).
|
|
||||||
|
|
||||||
- **Architect drift review:** `clean`. Spec → code mapping is 1:1
|
|
||||||
across all five acceptance criteria; the new agent file is
|
|
||||||
template-compliant; `docs/DESIGN.md` was not touched; all four
|
|
||||||
`architect_sweeps.sh` sweeps are clean (the one sweep-1 hit at
|
|
||||||
`docs/DESIGN.md:50` is a pre-existing benign cross-reference last
|
|
||||||
modified in `or.1.2`, not pr.1 regrowth). The two follow-up items
|
|
||||||
in this journal's "Boss-side addendum" (or.1 Iron-Law wording;
|
|
||||||
`Agent` tool not reaching the orchestrator-agent) are confirmed as
|
|
||||||
fix-queue items, not DESIGN-level drift. A mild duplication
|
|
||||||
observation: the plan-template RED-check regex defect appears in
|
|
||||||
both "Concerns" and "Boss-side addendum" sections — accepted as-is
|
|
||||||
(different audiences read each section).
|
|
||||||
- **Bench-regression check:** `bench/check.py` 63 metrics stable
|
|
||||||
(exit 0); `bench/compile_check.py` 24 metrics stable (exit 0);
|
|
||||||
`bench/cross_lang.py` 25 metrics stable (exit 0). No language code
|
|
||||||
changed; the green result was expected and confirms it.
|
|
||||||
- **Rustdoc:** 20 pre-existing-style warnings (or.1 audit recorded
|
|
||||||
18; the +2 are not caused by pr.1, which touched no Rust). All are
|
|
||||||
private-item links or unresolved cross-refs. Same `[P2] Rustdoc
|
|
||||||
warning sweep` roadmap entry still applies; the count will be
|
|
||||||
re-checked when that sweep runs.
|
|
||||||
|
|
||||||
Milestone close: clean. Two post-pr.1 fix items already named in the
|
|
||||||
Boss-side addendum above:
|
|
||||||
|
|
||||||
1. or.1 Iron-Law `origin/main` → `main` mini-fix (trivial one-commit
|
|
||||||
iter, no plan template needed; trivial-mechanical carve-out).
|
|
||||||
2. `Agent` tool wiring for the dispatched orchestrator-agent — needs
|
|
||||||
diagnosis before fix-scope can be set. Until resolved, the or.1
|
|
||||||
design goal (per-task fresh-subagent isolation) is degraded; the
|
|
||||||
Boss-context-offload aspect still works because the per-task
|
|
||||||
detail collapses into one end-report regardless.
|
|
||||||
|
|
||||||
## Blocked detail
|
|
||||||
|
|
||||||
N/A — DONE.
|
|
||||||
|
|
||||||
## Commits
|
|
||||||
|
|
||||||
`14ac4ae..f06911d` (5 commits, one per task, no fix-passes).
|
|
||||||
|
|
||||||
## Stats
|
|
||||||
|
|
||||||
`bench/orchestrator-stats/2026-05-11-iter-pr.1.json` (created by
|
|
||||||
Phase 5 of this run; first stats file in the directory).
|
|
||||||
@@ -1,134 +0,0 @@
|
|||||||
# audit-23 — Milestone 23 close
|
|
||||||
|
|
||||||
**Date:** 2026-05-12
|
|
||||||
**Started from:** 3fed372
|
|
||||||
**Status:** DONE — milestone 23 closed (1 ratify + 1 nit-fix)
|
|
||||||
**Skill:** audit (architect + bencher)
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
Milestone-close audit for Milestone 23 (Eq/Ord prelude). Architect
|
|
||||||
clean on substance — all 8 acceptance criteria of
|
|
||||||
`docs/specs/2026-05-11-23-eq-ord-prelude.md` met, no out-of-scope
|
|
||||||
creep, DESIGN.md two-arm-fixpoint description (line 1513-1556)
|
|
||||||
matches `mono.rs::collect_mono_targets` observable behaviour, roadmap
|
|
||||||
split internally consistent. Bencher found and ratified one
|
|
||||||
structural compile-time shift; the iter-23.5 journal's "one sub-ms
|
|
||||||
noise-band metric" framing was wrong on both counts (4 metrics, not
|
|
||||||
1; consistent +18-20% cohort shift, not noise).
|
|
||||||
|
|
||||||
## Architect — drift items
|
|
||||||
|
|
||||||
- **[important]** `docs/DESIGN.md:1692-1693` — sentence collision:
|
|
||||||
"Milestone 22 ships **no built-in Prelude classes**. The original
|
|
||||||
An earlier draft committed to …". Pre-existing since commit
|
|
||||||
`613d4d8` (2026-05-10, design-md-consolidation 1.4 fixup) which
|
|
||||||
edited `22a draft` → `An earlier draft` and left the dangling
|
|
||||||
fragment `The original` on the previous line. NOT introduced in
|
|
||||||
iter 23.5; architect's attribution corrected. Boss-fixed inline:
|
|
||||||
the dangling fragment removed.
|
|
||||||
- **[minor]** `crates/ailang-check/src/lib.rs:884-894` — workspace-
|
|
||||||
sibling linearity construction is O(N²) over module count. Bounded
|
|
||||||
for today's 2-module workspace; named here so a future Nx-module
|
|
||||||
workspace doesn't re-discover it. Bencher confirmed below that the
|
|
||||||
iter-23.5 H1 cost is negligible at N=2. No action.
|
|
||||||
- **[minor]** `crates/ailang-check/src/mono.rs:703-712` —
|
|
||||||
`contains_rigid_var` discriminates rigid vs. metavar by
|
|
||||||
`Type::Var.name.starts_with("$m")`. The journal already named this
|
|
||||||
as a future-fragility; a structural discriminator (separate
|
|
||||||
`Type::Meta` variant) would be the principled fix. Carry-on; not
|
|
||||||
blocking.
|
|
||||||
- **[nit]** `crates/ailang-check/src/lib.rs:709-726` — Float-NoInstance
|
|
||||||
addendum mutates `d.message` directly. The codebase has no
|
|
||||||
`with_hint` / `extend_message` convention yet, so this isn't
|
|
||||||
pre-existing drift, but the next class-specific addendum (e.g.
|
|
||||||
partial-Float `compare`) would compound `to_diagnostic` into a
|
|
||||||
conditional chain. Carry-on; refactor when a second addendum
|
|
||||||
arrives.
|
|
||||||
|
|
||||||
## Bencher — compile_check regression diagnosis
|
|
||||||
|
|
||||||
Bench-rerun at HEAD `3fed372` showed 4/12 check_ms metrics above
|
|
||||||
the 25% tolerance, with all 12 metrics in the +7 to +45% band — a
|
|
||||||
coherent cohort shift, not the journal-claimed "one sub-ms noise
|
|
||||||
metric".
|
|
||||||
|
|
||||||
Two hypotheses tested via 5-run medians over 60 samples each across
|
|
||||||
four states (23.4 HEAD, 23.5 HEAD, 23.4 checker + 23.5 prelude,
|
|
||||||
23.5 checker + 23.4 prelude):
|
|
||||||
|
|
||||||
- **H1** (linearity workspace-visibility + `contains_rigid_var`):
|
|
||||||
refuted. 23.5-checker + 23.4-prelude returns to baseline noise
|
|
||||||
(median +1.84% vs. baseline noise floor).
|
|
||||||
- **H2** (5 new polymorphic prelude `Def::Fn` bodies cost real
|
|
||||||
typecheck work): confirmed. 23.4-checker + 23.5-prelude reproduces
|
|
||||||
the full HEAD regression (median +18.62% vs. HEAD's +18.74%; gap
|
|
||||||
bounds the H1 interaction at <1%).
|
|
||||||
|
|
||||||
Cost source: each workspace check now typechecks 5 additional
|
|
||||||
polymorphic `Def::Fn`s (each carrying `forall` + 1 class constraint
|
|
||||||
+ a `match` or small `not (eq …)` body) plus their constraint
|
|
||||||
resolution. ~0.2-0.3ms median additional work per `ail check`
|
|
||||||
subprocess; the ~5-10ms subprocess-spawn floor inflates the
|
|
||||||
relative-percent figure.
|
|
||||||
|
|
||||||
**Verdict:** Ratify. The regression is the user-visible feature iter
|
|
||||||
23.5 shipped — five prelude helpers — accurately surfacing in the
|
|
||||||
bench as "typecheck does more work because the prelude has more
|
|
||||||
work to do".
|
|
||||||
|
|
||||||
## Resolution
|
|
||||||
|
|
||||||
- **Ratify path applied:** `python3 bench/compile_check.py
|
|
||||||
--update-baseline` re-measured (1 run, default 5 samples) and
|
|
||||||
wrote `bench/baseline_compile.json` with the new check_ms numbers.
|
|
||||||
Post-update rerun: 0/24 regressed, 0/24 improved beyond tolerance.
|
|
||||||
- **Important-drift fix applied:** the dangling `The original`
|
|
||||||
fragment removed from `docs/DESIGN.md:1692`. One-line tidy edit;
|
|
||||||
no behavioural change.
|
|
||||||
- **Minor / nit drift items:** carry-on. Linearity O(N²) is
|
|
||||||
observed-not-blocking; mono name-prefix discriminator is journal-
|
|
||||||
flagged; NoInstance addendum mutation pattern revisit when a
|
|
||||||
second class-specific addendum arrives.
|
|
||||||
|
|
||||||
## Concerns
|
|
||||||
|
|
||||||
- The iter-23.5 journal's bench framing was incorrect. The
|
|
||||||
implementer asserted "one sub-ms metric, two others stayed inside
|
|
||||||
on rerun" — fresh-rerun showed 4 above tolerance with consistent
|
|
||||||
cohort shift. Implementer-phase bench rerun count was likely too
|
|
||||||
small to see the cohort; orchestrator-audit pattern of running
|
|
||||||
bench 5× back-to-back at milestone close is the correct
|
|
||||||
granularity, and the 1-sample bench-check inside `implement` is
|
|
||||||
not.
|
|
||||||
- Implication for future iters: when `compile_check.py` exits 1
|
|
||||||
during implement, the implementer should NOT classify it as
|
|
||||||
noise-band without doing the same multi-run, multi-state isolation
|
|
||||||
the bencher did here. Defer to audit if the framing is uncertain.
|
|
||||||
This is a soft convention — encoding it as a hard skill rule would
|
|
||||||
slow implement when the regression genuinely is noise; the
|
|
||||||
weaker form is "if bench exits 1, the journal Concerns must
|
|
||||||
describe what additional evidence was gathered, not just give a
|
|
||||||
hypothesis".
|
|
||||||
|
|
||||||
## Known debt
|
|
||||||
|
|
||||||
- DESIGN.md sentence-collision had been on main for two days
|
|
||||||
unnoticed. No drift-review ran between `613d4d8` (2026-05-10)
|
|
||||||
and this audit (2026-05-12); the implement-skill diff scope
|
|
||||||
(per-iter, narrow) does not catch unrelated pre-existing
|
|
||||||
fragments. Acceptable: drift-review at milestone close is exactly
|
|
||||||
the catch mechanism, and it caught it.
|
|
||||||
|
|
||||||
## Files touched
|
|
||||||
|
|
||||||
- `bench/baseline_compile.json` — re-measured baseline; numbers
|
|
||||||
reflect new prelude cost.
|
|
||||||
- `docs/DESIGN.md:1692` — dangling fragment removed.
|
|
||||||
- `docs/journals/2026-05-12-audit-23.md` (this file).
|
|
||||||
- `docs/journals/INDEX.md` — append (Boss-side).
|
|
||||||
|
|
||||||
## Stats
|
|
||||||
|
|
||||||
No stats file — audit is a Boss-direct skill, not an
|
|
||||||
implement-orchestrator dispatch.
|
|
||||||
@@ -1,122 +0,0 @@
|
|||||||
# audit-cma — Milestone close: Cross-model authoring-form test
|
|
||||||
|
|
||||||
**Date:** 2026-05-12
|
|
||||||
**Milestone:** Cross-model authoring-form test (cma.1 + cma.2 + cma.3)
|
|
||||||
**Status:** Closed, clean (after two `[low]` tidy fixes)
|
|
||||||
|
|
||||||
## Architect drift review
|
|
||||||
|
|
||||||
`ailang-architect` reported `drift_found` with two items over
|
|
||||||
`44c6e56..HEAD`:
|
|
||||||
|
|
||||||
- `[low]` `docs/journals/INDEX.md` — commit `90512d5` ("brainstorm
|
|
||||||
Step 7.5 re-dispatch on any post-PASS spec edit") removed the
|
|
||||||
corresponding P1 todo from roadmap.md but did not add an INDEX
|
|
||||||
mirror. CLAUDE.md's roadmap convention requires a one-line mirror
|
|
||||||
in the journals when a roadmap entry is removed.
|
|
||||||
- `[low]` `experiments/.../README.md` — the "Total 8 passed" sentence
|
|
||||||
inherited from the cma.2 plan understates the actual harness
|
|
||||||
test sweep (13 passed across `--lib` unit + 4 integration suites).
|
|
||||||
Surface cosmetic; flagged in cma.3 known-debt.
|
|
||||||
|
|
||||||
Both items confined to record-keeping, neither blocks milestone
|
|
||||||
close. Architect recommendation: carry on as planned.
|
|
||||||
|
|
||||||
## Tidy fixes (boss-direct, inline)
|
|
||||||
|
|
||||||
1. **INDEX backfill for `90512d5`.** Added a single-line entry in
|
|
||||||
`docs/journals/INDEX.md` pointing at the commit and noting that
|
|
||||||
no per-iter journal accompanies it (a small skill-discipline
|
|
||||||
tweak rather than an iter). The format mirrors existing entries
|
|
||||||
so future agents can navigate from INDEX to the commit history
|
|
||||||
without reconstructing.
|
|
||||||
|
|
||||||
2. **README test-count correction.** The five-suite layout under
|
|
||||||
`experiments/2026-05-12-cross-model-authoring/README.md`'s
|
|
||||||
"Running the harness" section now lists all five suites
|
|
||||||
explicitly (inline `--lib` unit tests for strip_locations, plus
|
|
||||||
the four integration files) and reports the correct total
|
|
||||||
(13 passed).
|
|
||||||
|
|
||||||
Both edits are ≤30 LOC and require no design judgement (CLAUDE.md
|
|
||||||
"trivial mechanical edits" carve-out); no planner+implement
|
|
||||||
dispatch needed.
|
|
||||||
|
|
||||||
## Bench-regression check
|
|
||||||
|
|
||||||
`bench/check.py && bench/compile_check.py && bench/cross_lang.py`:
|
|
||||||
|
|
||||||
- `check.py`: exit 0; **63 metrics; 0 regressed, 5 improved beyond
|
|
||||||
tolerance, 58 stable**.
|
|
||||||
- `compile_check.py`: exit 0; 24 metrics; 0 regressed, 0 improved
|
|
||||||
beyond tolerance, 24 stable.
|
|
||||||
- `cross_lang.py`: exit 0; 25 metrics; 0 regressed, 0 improved
|
|
||||||
beyond tolerance, 25 stable.
|
|
||||||
|
|
||||||
All three exit-code gates green. The five improvements on
|
|
||||||
`check.py` cluster on `latency.explicit_at_rc` (p99 -36.5%, p99_9
|
|
||||||
-41.8%, max -41.7%, p99_over_median -38.0%) plus
|
|
||||||
`throughput.bench_list_sum.gc_over_bump` -8.7%.
|
|
||||||
|
|
||||||
**Decision: no baseline update (carry-on).** Rationale:
|
|
||||||
|
|
||||||
- The cma milestone touched no in-workspace crate. Every line of
|
|
||||||
code change lives under `experiments/2026-05-12-cross-model-authoring/`
|
|
||||||
which is out of the root Cargo workspace by design. No mechanism
|
|
||||||
in the milestone could plausibly improve runtime latency or
|
|
||||||
GC-over-bump throughput.
|
|
||||||
- The improvements therefore reflect either (a) runtime variance
|
|
||||||
/ scheduler / clang version drift between the prior audit-rt
|
|
||||||
baseline measurement and now, or (b) genuine system-level
|
|
||||||
improvement uncorrelated with the milestone.
|
|
||||||
- Ratifying an unexplained improvement would lock it into the
|
|
||||||
baseline and lose the ability to detect a future regression
|
|
||||||
back to the prior level. Better to leave the baseline pristine;
|
|
||||||
if the improvement holds across the next two or three audits,
|
|
||||||
the next audit can ratify with confidence.
|
|
||||||
- This is the audit-rt pattern carried forward: don't ratify what
|
|
||||||
the milestone didn't intentionally move.
|
|
||||||
|
|
||||||
## Spec acceptance criteria — final check
|
|
||||||
|
|
||||||
Reviewing the parent spec `docs/specs/2026-05-12-cross-model-authoring-form-test.md`
|
|
||||||
§"Acceptance criteria" against landed work:
|
|
||||||
|
|
||||||
1. ✓ Directory `experiments/2026-05-12-cross-model-authoring/`
|
|
||||||
exists with all four sub-components (master/, render/, harness/,
|
|
||||||
runs/) plus top-level README. (cma.1 + cma.2)
|
|
||||||
2. ✓ `master/spec.md` is vollumfänglich; `spec_completeness.rs`
|
|
||||||
gate green; 34/34 AST variants covered by the curated subset.
|
|
||||||
(cma.1)
|
|
||||||
3. ✓ Both `rendered/json.md` and `rendered/ailx.md` are checked
|
|
||||||
in; all `render/` tests green. (cma.1)
|
|
||||||
4. ✓ `harness/` builds, passes 13/13 tests including the
|
|
||||||
mock_full_run end-to-end. (cma.2)
|
|
||||||
5. ✓ Four task definitions + reference solutions exist; the
|
|
||||||
`verify_references` integration test confirms each reaches
|
|
||||||
green through the real ail+clang pipeline locally. (cma.2)
|
|
||||||
6. ✓ Live run executed against IONOS (Qwen3-Coder-Next,
|
|
||||||
temperature=0, top_p=1, max-turns=5) and recorded under
|
|
||||||
`runs/2026-05-12-df7531/` with RUN_STATUS=ok, full per-turn raw
|
|
||||||
artefacts for all eight (cohort × task) combinations,
|
|
||||||
scores.csv, and summary.md. (cma.3)
|
|
||||||
7. ✓ DESIGN.md §"Decision 6" gained an "Empirical addendum
|
|
||||||
(2026-05-12)" subsection — 27 lines, model id + run date +
|
|
||||||
per-cohort means table + illustrative t3 contrast + explicit
|
|
||||||
scope note deferring the universal claim to a multi-subject
|
|
||||||
expansion. (cma.3)
|
|
||||||
8. ✓ Journal entry `2026-05-12-iter-cma.3.md` linked from
|
|
||||||
INDEX.md summarises the data point. (cma.3)
|
|
||||||
9. ✓ Roadmap P2 entry removed; P3 entry "Multi-subject expansion
|
|
||||||
(cross-model authoring-form follow-up)" added pointing at the
|
|
||||||
run as baseline. (cma.3)
|
|
||||||
|
|
||||||
All nine criteria green.
|
|
||||||
|
|
||||||
## Outcome
|
|
||||||
|
|
||||||
Milestone closed clean after the two record-keeping tidies above.
|
|
||||||
The cross-model authoring-form test ships its harness, four MVP
|
|
||||||
tasks with verified reference solutions, a single-subject baseline
|
|
||||||
dataset, and a calibrated DESIGN.md addendum. The next step
|
|
||||||
(P3 roadmap: multi-subject expansion) is queued but not started.
|
|
||||||
@@ -1,163 +0,0 @@
|
|||||||
# audit-ct-tidy — Milestone close: ct-tidy (closing iter ctt.3)
|
|
||||||
|
|
||||||
**Date:** 2026-05-12
|
|
||||||
**Milestone:** ct-tidy (ctt.1 + ctt.2 + ctt.3)
|
|
||||||
**Status:** Closed, three doc-drift items fixed inline (`ctt.tidy`)
|
|
||||||
|
|
||||||
## Architect drift review
|
|
||||||
|
|
||||||
`ailang-architect` reported `drift_found` across the commit range
|
|
||||||
`805bba3..0d3f44b`. Code-side changes match DESIGN.md /
|
|
||||||
spec / iters; three doc-side drift items surfaced (two high,
|
|
||||||
one low; one medium is acceptable per spec).
|
|
||||||
|
|
||||||
**[high — spec acceptance]** `docs/DESIGN.md:1735-1736` —
|
|
||||||
`KindMismatch` listed as a live class-schema diagnostic. Stale
|
|
||||||
since ctt.3.
|
|
||||||
|
|
||||||
**[high — spec acceptance]** `docs/DESIGN.md:1761-1763` —
|
|
||||||
`KindMismatch` cited as the rejection mechanism for
|
|
||||||
higher-kinded class params. Stale since ctt.3.
|
|
||||||
|
|
||||||
**[high — spec acceptance]** `docs/roadmap.md:91-97` — the
|
|
||||||
`KindMismatch` retire todo unchecked with no forward-reference
|
|
||||||
to ctt.3. Spec acceptance criterion called for strikes on the
|
|
||||||
relevant todo lines; ctt.3 struck none.
|
|
||||||
|
|
||||||
**[medium — record-keeping]** `examples/test_22b2_kind_mismatch.ail.json`
|
|
||||||
keeps the retired-diagnostic-name in its filename. Spec is silent
|
|
||||||
(acceptable as-is); rename is a future candidate, not in this
|
|
||||||
audit's scope.
|
|
||||||
|
|
||||||
**[low — numerical drift]** Spec claim "`grep type_def_module`
|
|
||||||
returns 13 hits" is now 18 hits post-ctt.2. Intent honoured (every
|
|
||||||
consumer threaded); numerical figure is historical.
|
|
||||||
|
|
||||||
### Tidy fixes applied inline (`ctt.tidy`)
|
|
||||||
|
|
||||||
All three high-severity items addressed in this audit commit
|
|
||||||
(Boss-mechanical edits — three doc edits, no separate plan /
|
|
||||||
implement dispatch warranted):
|
|
||||||
|
|
||||||
1. `docs/DESIGN.md:1733-1740` — dropped the `KindMismatch` bullet
|
|
||||||
from the class-schema diagnostics list; added a successor
|
|
||||||
paragraph naming `BareCrossModuleTypeRef` as the structural
|
|
||||||
rejection path for the malformed class-param-in-applied-position
|
|
||||||
shape; cross-references iter ctt.3.
|
|
||||||
|
|
||||||
2. `docs/DESIGN.md:1761-1763` — rewrote the "Higher-kinded class
|
|
||||||
params" bullet under "What this typeclass design explicitly does
|
|
||||||
NOT support" to name `BareCrossModuleTypeRef` from canonical-form
|
|
||||||
validation instead of `KindMismatch` at class declaration.
|
|
||||||
|
|
||||||
3. `docs/roadmap.md:91-97` — struck `[x]` with a forward-reference
|
|
||||||
to ctt.3 (canonical-form-rejection test stays green asserting
|
|
||||||
`BareCrossModuleTypeRef`). Adjacent ctt.2 todo at lines 98-105
|
|
||||||
also struck (mechanical follow-up — ctt.2 closed the re-key it
|
|
||||||
tracks).
|
|
||||||
|
|
||||||
Medium / low items skipped: filename rename + spec numerical claim
|
|
||||||
both fall outside ctt.tidy's scope (spec acceptance was on
|
|
||||||
intent-honour, not figure-precision; filename rename is a future
|
|
||||||
candidate the spec already permits as-is).
|
|
||||||
|
|
||||||
## Bench-regression check
|
|
||||||
|
|
||||||
Sequence run as Boss (the `&&` chain tripped at `check.py`'s exit
|
|
||||||
1; `compile_check.py` and `cross_lang.py` then ran separately):
|
|
||||||
|
|
||||||
- `check.py`: **exit 1** — 63 metrics; 3 regressed, 3 improved
|
|
||||||
beyond tolerance, 57 stable.
|
|
||||||
- Regressions:
|
|
||||||
- `bench_list_sum.bump_s` +12.23% (tol 10%) —
|
|
||||||
**second consecutive sighting** (audit-eob first-sighted
|
|
||||||
this at +12.60%, this audit at +12.23%).
|
|
||||||
- `latency.explicit_at_rc.max_us` +44.31% (tol 25%) —
|
|
||||||
**first sighting**, rc-cohort tail-latency.
|
|
||||||
- `latency.implicit_at_rc.max_us` +35.07% (tol 30%) —
|
|
||||||
**first sighting**, rc-cohort tail-latency.
|
|
||||||
- Improvements:
|
|
||||||
- `bench_list_sum.gc_over_bump` -10.40% — mirror of the
|
|
||||||
bump_s regression (gc/bump ratio falls because bump rose).
|
|
||||||
- `bench_closure_chain.gc_over_bump` -15.05% — new improvement.
|
|
||||||
- `bench_list_sum_explicit.gc_over_bump` -8.30% — new improvement.
|
|
||||||
- `compile_check.py`: exit 0; 24 metrics, all stable within ±25%.
|
|
||||||
- `cross_lang.py`: exit 0; 25 metrics, all stable within ±15%.
|
|
||||||
|
|
||||||
### Classification
|
|
||||||
|
|
||||||
**bump_s persistence** (`bench_list_sum.bump_s`): second
|
|
||||||
consecutive sighting (audit-eob → audit-ct-tidy) at near-identical
|
|
||||||
magnitude (+12.60% → +12.23%). The audit-eob classification was
|
|
||||||
"first-sighting rule — observe in the next audit; if it persists,
|
|
||||||
investigate". This audit IS that next audit, and it persists.
|
|
||||||
|
|
||||||
ctt.* milestone made zero codegen-relevant edits (the three iters
|
|
||||||
touched env-overlay doc + check-side registry key shape +
|
|
||||||
workspace-load-time diagnostic deletion). No plausible attribution
|
|
||||||
to ctt.*. The persistence is real but uncaptured — the underlying
|
|
||||||
cause predates this milestone and is independent of the work in
|
|
||||||
the audit window.
|
|
||||||
|
|
||||||
Conservative call (same standing rule as previous three audits):
|
|
||||||
**do not** baseline-update. The persistence is now documented
|
|
||||||
across two audits; if it persists into the next audit
|
|
||||||
unchanged, ratify-without-attribution becomes the right call
|
|
||||||
(at three consecutive sightings the noise hypothesis is
|
|
||||||
unsupported). The latency tail-cluster regressions are first
|
|
||||||
sightings — observe.
|
|
||||||
|
|
||||||
**Latency max_us regressions** (`explicit_at_rc.max_us`,
|
|
||||||
`implicit_at_rc.max_us`): first sighting in any recent audit.
|
|
||||||
Both metrics are rc-cohort tail-latency (the extreme-outlier
|
|
||||||
window). The bench harness runs ~50ms workloads; a single
|
|
||||||
outlier of +183µs (explicit) or +167µs (implicit) absolute
|
|
||||||
moves the max_us proportionally. ctt.* made zero codegen edits,
|
|
||||||
so the noise hypothesis is the most parsimonious explanation
|
|
||||||
on first sighting.
|
|
||||||
|
|
||||||
Conservative call: first-sighting rule — **do not**
|
|
||||||
baseline-update; observe in the next audit.
|
|
||||||
|
|
||||||
**Recurring latency.explicit_at_rc improvement cluster**: the
|
|
||||||
three-audit-long improvement cluster on `p99_us` / `p99_9_us` /
|
|
||||||
`p99_over_median` (previously at ~-37%, classified as
|
|
||||||
ratify-candidate after one more audit at audit-eob) now reports
|
|
||||||
within tolerance this audit:
|
|
||||||
- `p99_us` -24.87% (tol 25%) — stable (was: improvement)
|
|
||||||
- `p99_over_median` -24.10% (tol 25%) — stable (was: improvement)
|
|
||||||
- `p99_9_us` not flagged (within tolerance)
|
|
||||||
|
|
||||||
The cluster has narrowed from ~-37% improvements to ~-24%
|
|
||||||
near-tolerance stable. The improvements partially reverted —
|
|
||||||
the metrics are still on the improvement side, but no longer
|
|
||||||
beyond tolerance. The audit-eob recommendation ("if the cluster
|
|
||||||
persists into the next two audits unchanged, ratify; if it
|
|
||||||
shifts or shrinks meaningfully, that is itself the attribution
|
|
||||||
signal") has fired: the cluster shrank meaningfully this audit.
|
|
||||||
This is itself attribution-evidence — the underlying cause is
|
|
||||||
variable rather than persistent. The ratify-pending plan is
|
|
||||||
withdrawn; the cluster is now noise-class, not signal-class.
|
|
||||||
|
|
||||||
**Baseline left pristine on every script** for the fourth
|
|
||||||
consecutive audit. No `--update-baseline` invocation.
|
|
||||||
|
|
||||||
## Status
|
|
||||||
|
|
||||||
Closed. Ct-tidy milestone fully landed:
|
|
||||||
|
|
||||||
- env-overlay shape ratified (ctt.1: DESIGN.md `## Env construction`
|
|
||||||
section + DuplicateCtor pinning test + 2 P2 todos closed).
|
|
||||||
- `Registry.type_def_module` re-keyed to `(owning_module, bare_name)`
|
|
||||||
with `caller_module` threaded through every consumer (ctt.2:
|
|
||||||
3-module fixture + regression test).
|
|
||||||
- `KindMismatch` retired (ctt.3: variant + helper + dispatch +
|
|
||||||
Display arm deleted; test stays green via canonical-form
|
|
||||||
successor `BareCrossModuleTypeRef`).
|
|
||||||
- DESIGN.md tracks all three iters' anchors (ctt.tidy in this audit:
|
|
||||||
class-schema diagnostics list updated, higher-kinded params bullet
|
|
||||||
updated, ctt.3 retirement marker pattern).
|
|
||||||
- Roadmap reflects closure (ctt.tidy: two P2 todos struck `[x]`).
|
|
||||||
|
|
||||||
Working tree exits audit clean. main HEAD advances by exactly
|
|
||||||
this audit commit.
|
|
||||||
@@ -1,143 +0,0 @@
|
|||||||
# audit-eob — Milestone close: heap-str-abi (closing iter eob.1)
|
|
||||||
|
|
||||||
**Date:** 2026-05-12
|
|
||||||
**Milestone:** heap-str-abi (hs.1 + hs.2 + hs.3 + hs.4 + eob.1)
|
|
||||||
**Status:** Closed, three DESIGN.md tidies applied inline (`eob.tidy`)
|
|
||||||
|
|
||||||
## Architect drift review
|
|
||||||
|
|
||||||
`ailang-architect` reported `drift_found` across the commit range
|
|
||||||
`750f97e..78e8338`. Lockstep invariants and codegen ABI all
|
|
||||||
intact; three high-severity items in DESIGN.md plus one medium
|
|
||||||
record-keeping note.
|
|
||||||
|
|
||||||
**[high — spec acceptance]** `docs/DESIGN.md:2338-2342` —
|
|
||||||
`float_to_str` described as "type-installed but codegen-deferred"
|
|
||||||
with `CodegenError::Internal`. Stale since hs.4.
|
|
||||||
|
|
||||||
**[high — spec acceptance]** `docs/DESIGN.md:2387` — same caveat
|
|
||||||
inside the "What is supported" inventory; `int_to_str` was absent
|
|
||||||
from the inventory entirely.
|
|
||||||
|
|
||||||
**[high — spec acceptance]** `docs/DESIGN.md` (missing) — no
|
|
||||||
"Str ABI" anchor documenting the heap-Str / static-Str dual
|
|
||||||
realisation, shared consumer ABI, or codegen-elision invariant
|
|
||||||
for static-Str-in-RC-paths.
|
|
||||||
|
|
||||||
**[medium — record-keeping]** `docs/journals/2026-05-12-iter-hs.4.md`
|
|
||||||
closed `DONE_WITH_CONCERNS` with weakened RC assertions; eob.1
|
|
||||||
retroactively restored the strict invariants. No forward-pointer
|
|
||||||
appended; readers cross-walk via INDEX.md.
|
|
||||||
|
|
||||||
### Tidy fixes applied inline (`eob.tidy`)
|
|
||||||
|
|
||||||
All three high-severity DESIGN.md drift items addressed in this
|
|
||||||
audit commit (Boss-mechanical edits — no separate plan / implement
|
|
||||||
dispatch warranted; the spec amend was small, the Str-ABI-anchor
|
|
||||||
content was already fully known from the just-closed milestone):
|
|
||||||
|
|
||||||
1. `docs/DESIGN.md:2338-2343` (float_to_str caveat block): replaced
|
|
||||||
the codegen-deferred prose with a one-paragraph statement that
|
|
||||||
`float_to_str` and `int_to_str` are fully wired, allocate a
|
|
||||||
heap-Str slab, and carry `ret_mode: Own`. Forward-references the
|
|
||||||
new Str-ABI anchor.
|
|
||||||
|
|
||||||
2. `docs/DESIGN.md:2387-2392` (inventory bullet): dropped the
|
|
||||||
codegen-deferred parenthetical from `float_to_str`; added
|
|
||||||
`int_to_str : (Int) -> Str` to the inventory; pointer to the
|
|
||||||
Str-ABI section for the dual realisation.
|
|
||||||
|
|
||||||
3. `docs/DESIGN.md` (new "Str ABI" subsection placed after the
|
|
||||||
Float-semantics block, before "What is not (yet) supported"):
|
|
||||||
documents the two realisations (static-Str packed-struct
|
|
||||||
`<{ i64, [N+1 x i8] }>` globals in `.rodata`; heap-Str slabs
|
|
||||||
allocated via `ailang_rc_alloc(8 + len + 1)`); the shared
|
|
||||||
consumer ABI (Str pointer at len-field offset 0, bytes at
|
|
||||||
`payload + 8`, inherited `strcmp` semantics); and the
|
|
||||||
codegen-level non-RC invariant for static-Str (move-tracking
|
|
||||||
partial-drop + non-escape lowering + `Type::Con { name: "Str" }`
|
|
||||||
carve-outs at `field_drop_call` and `drop_symbol_for_binder`'s
|
|
||||||
App arm).
|
|
||||||
|
|
||||||
Medium-severity hs.4-journal forward-pointer: skipped. INDEX.md
|
|
||||||
already links hs.4 → eob.1 chronologically; the eob.1 journal
|
|
||||||
explicitly cross-references hs.4's deferred fix as the iter's
|
|
||||||
motivation. A forward-pointer in hs.4's own file would push the
|
|
||||||
append-only convention; the cross-walk from INDEX is sufficient.
|
|
||||||
|
|
||||||
## Bench-regression check
|
|
||||||
|
|
||||||
Sequence run as Boss (the `&&` chain in audit-SKILL.md tripped at
|
|
||||||
`check.py`'s exit 1; `compile_check.py` and `cross_lang.py` then
|
|
||||||
run separately to read their exit codes cleanly):
|
|
||||||
|
|
||||||
- `check.py`: **exit 1** — 63 metrics; 2 regressed, 5 improved
|
|
||||||
beyond tolerance, 56 stable.
|
|
||||||
- Regressions: `bench_list_sum.bump_s` +12.60% (tol 10%);
|
|
||||||
`bench_hof_pipeline.bump_s` +11.65% (tol 10%). Both only
|
|
||||||
marginally over tolerance; the other four `bump_s` metrics
|
|
||||||
(`bench_tree_walk`, `bench_closure_chain`,
|
|
||||||
`bench_compute_collatz`, `bench_list_sum_explicit`) are stable
|
|
||||||
within ±7%.
|
|
||||||
- Improvements: three on the `latency.explicit_at_rc` cluster
|
|
||||||
(`p99_us` -37.37%, `p99_9_us` -40.14%, `p99_over_median`
|
|
||||||
-37.35%); two `gc_over_bump` mirrors on `bench_list_sum` and
|
|
||||||
`bench_hof_pipeline` (ratio falls because the denominator —
|
|
||||||
`bump_s` — rose; same physical signal as the bump regressions).
|
|
||||||
- `compile_check.py`: exit 0; 24 metrics, all stable within ±25%.
|
|
||||||
- `cross_lang.py`: exit 0; 25 metrics, all stable within ±15%.
|
|
||||||
|
|
||||||
### Classification
|
|
||||||
|
|
||||||
**Latency cluster** (`latency.explicit_at_rc.p99_us` /
|
|
||||||
`p99_9_us` / `p99_over_median`): third consecutive sighting
|
|
||||||
across `audit-cma` → `audit-ms` → `audit-eob`. `audit-ms` flagged
|
|
||||||
this as "one more audit before considering ratification"; this is
|
|
||||||
that one more audit.
|
|
||||||
|
|
||||||
eob.1's only codegen-side touchpoints are the `drop.rs` Str
|
|
||||||
carve-out (8 LOC) and the `ret_mode: Own` flip at four signature
|
|
||||||
sites; none of those should plausibly shift latency p99 by 37%.
|
|
||||||
The signal predates this milestone (audit-cma was three milestones
|
|
||||||
ago). Conservative call: still **do not** baseline-update on this
|
|
||||||
audit. The cluster is real and persistent, but the underlying
|
|
||||||
cause is unknown, and a baseline update without an identified
|
|
||||||
attribution would obscure the next signal that *does* have an
|
|
||||||
attributable cause.
|
|
||||||
|
|
||||||
Recommendation forward: if the cluster persists into the next
|
|
||||||
two audits unchanged, ratify with `--update-baseline` and a
|
|
||||||
journal entry naming the persistence as the ratify rationale
|
|
||||||
(absence of identified cause being the standing fact). If the
|
|
||||||
cluster shifts or shrinks meaningfully, that is itself the
|
|
||||||
attribution signal.
|
|
||||||
|
|
||||||
**bump_s cluster** (`bench_list_sum.bump_s`,
|
|
||||||
`bench_hof_pipeline.bump_s`): **first sighting** in any recent
|
|
||||||
audit. Both metrics only marginally over the 10% tolerance.
|
|
||||||
The other four `bump_s` measurements are stable, which makes
|
|
||||||
"shared underlying bump-allocator change" unlikely; per-fixture
|
|
||||||
system noise (12.6% on a ~50ms benchmark is ~6ms of jitter) is
|
|
||||||
the most parsimonious explanation.
|
|
||||||
|
|
||||||
eob.1 made zero changes to bump-allocator codepaths. The
|
|
||||||
implementer's end-report flagged this same cluster as appearing
|
|
||||||
during the workspace bench sweep before commit. Conservative call:
|
|
||||||
first-sighting rule — **do not** baseline-update; observe in the
|
|
||||||
next audit; if it persists, investigate.
|
|
||||||
|
|
||||||
**Baseline left pristine on every script** for the third
|
|
||||||
consecutive audit. No `--update-baseline` invocation.
|
|
||||||
|
|
||||||
## Status
|
|
||||||
|
|
||||||
Closed. Heap-str-abi milestone fully landed:
|
|
||||||
- Static-Str layout migrated (hs.1, hs.2).
|
|
||||||
- Heap-Str runtime additions wired (hs.3, hs.4).
|
|
||||||
- Effect-op-borrow rule + RC-discipline closes (eob.1).
|
|
||||||
- DESIGN.md anchors both arg-position rules + Str-ABI dual
|
|
||||||
realisation (eob.1 + eob.tidy in this audit).
|
|
||||||
- WhatsNew entry + roadmap P1 close (eob.1).
|
|
||||||
|
|
||||||
Working tree exits audit clean. main HEAD advances by exactly
|
|
||||||
this audit commit.
|
|
||||||
@@ -1,107 +0,0 @@
|
|||||||
# audit-ms — Milestone close: Multi-subject Authoring-Form Test (CodeLlama Replication)
|
|
||||||
|
|
||||||
**Date:** 2026-05-12
|
|
||||||
**Milestone:** Multi-subject Authoring-Form Test — CodeLlama Replication (ms.1 + ms.2)
|
|
||||||
**Status:** Closed, clean (no tidy fixes needed)
|
|
||||||
|
|
||||||
## Architect drift review
|
|
||||||
|
|
||||||
`ailang-architect` reported `clean` over the working tree
|
|
||||||
(`8689f7a..HEAD`, no commits yet). Working tree matches DESIGN.md
|
|
||||||
(the §Decision-6 addendum extension is the only DESIGN.md touch,
|
|
||||||
intentional and spec-mandated), matches the spec's seven acceptance
|
|
||||||
criteria, and matches the boss-only-commit / append-only-INDEX /
|
|
||||||
main-sacrosanct discipline.
|
|
||||||
|
|
||||||
Two `[low]` items flagged, both expected:
|
|
||||||
|
|
||||||
- The spec mandates an `audit-ms` INDEX entry on milestone close;
|
|
||||||
this audit pass is the one that produces it — written below.
|
|
||||||
- `docs/specs/2026-05-12-multi-subject-codellama.md:154` references
|
|
||||||
"the cma.3 baseline" (`runs/2026-05-12-df7531/`) which DESIGN.md
|
|
||||||
no longer cites after the addendum rewrite to a two-subject
|
|
||||||
view. The spec text is correct (the new runs are distinct from
|
|
||||||
cma.3) and the baseline dir is still on disk under the cma run
|
|
||||||
tree, so the asymmetry is purely cosmetic — ratified, no edit.
|
|
||||||
|
|
||||||
Architect recommendation: carry on as planned.
|
|
||||||
|
|
||||||
## Bench-regression check
|
|
||||||
|
|
||||||
`bench/check.py && bench/compile_check.py && bench/cross_lang.py`:
|
|
||||||
|
|
||||||
- `check.py`: exit 0; **63 metrics; 0 regressed, 5 improved beyond
|
|
||||||
tolerance, 58 stable**.
|
|
||||||
- `compile_check.py`: exit 0; 24 metrics; 0 regressed, 0 improved
|
|
||||||
beyond tolerance, 24 stable.
|
|
||||||
- `cross_lang.py`: exit 0; 25 metrics; 0 regressed, 0 improved
|
|
||||||
beyond tolerance, 25 stable.
|
|
||||||
|
|
||||||
All three exit-code gates green. The five improvements on
|
|
||||||
`check.py` cluster on `latency.explicit_at_rc` (p99, p99_9, max,
|
|
||||||
p99_over_median) plus `throughput.bench_list_sum.gc_over_bump` —
|
|
||||||
the exact same cluster `audit-cma` (this morning's milestone close)
|
|
||||||
flagged and left unratified for the same reason. The ms milestone
|
|
||||||
touched only `experiments/2026-05-12-cross-model-authoring/harness/src/`
|
|
||||||
(plus docs); no in-workspace runtime / codegen / RC path changed.
|
|
||||||
The improvements therefore reflect runtime variance or
|
|
||||||
system-level drift uncorrelated with the milestone.
|
|
||||||
|
|
||||||
**Decision: no baseline update (carry-on).** Same rationale as
|
|
||||||
audit-cma: ratifying an unexplained improvement would lock it into
|
|
||||||
the baseline and lose the ability to detect a future regression
|
|
||||||
back to the prior level. If the same improvement cluster persists
|
|
||||||
across the next two or three audits, it ratifies with confidence.
|
|
||||||
This is now the second audit in a row showing the same five-metric
|
|
||||||
improvement cluster; one more clean audit and the next audit can
|
|
||||||
ratify.
|
|
||||||
|
|
||||||
## Spec acceptance criteria — final check
|
|
||||||
|
|
||||||
Reviewing `docs/specs/2026-05-12-multi-subject-codellama.md`
|
|
||||||
§"Acceptance criteria" against landed work:
|
|
||||||
|
|
||||||
1. ✓ `pipeline.rs:118` reads `format!("check: {e:#}")`; RED→GREEN
|
|
||||||
pinning unit test `check_format_preserves_anyhow_chain` present
|
|
||||||
in the same file. (ms.1)
|
|
||||||
2. ✓ Existing 13 harness tests still pass; suite now 14/14 with
|
|
||||||
the new pin test. (ms.1, re-confirmed at ms.2 close)
|
|
||||||
3. ✓ `meta-llama/CodeLlama-13b-Instruct-hf` ran to completion
|
|
||||||
against IONOS (8 cells, RUN_STATUS=ok at run level; 2 cells
|
|
||||||
`api_failure` on turn 1 disclosed in addendum). Chosen model id
|
|
||||||
recorded in ms.2 journal. (ms.2)
|
|
||||||
4. ✓ Qwen3-Coder-Next re-run with fixed pipeline completed (8
|
|
||||||
cells, RUN_STATUS=ok). New dir `runs/2026-05-12-080864/`
|
|
||||||
distinct from cma.3 baseline `runs/2026-05-12-df7531/`; both new
|
|
||||||
runs checked into the working tree. (ms.2)
|
|
||||||
5. ✓ `docs/DESIGN.md` §"Decision 6 / Empirical addendum
|
|
||||||
(2026-05-12)" extended: framing paragraph + four-column per-cell
|
|
||||||
table (Qwen-JSON, Qwen-AILX, CodeLlama-JSON, CodeLlama-AILX)
|
|
||||||
covering prompt_tokens, completion_tokens, pass-rate, turn-count
|
|
||||||
distribution, top error class; api_failure anomaly disclosed;
|
|
||||||
scope paragraph updated to "two subjects, n=1 each". (ms.2)
|
|
||||||
6. ✓ Roadmap P3 multi-subject entry removed; one-line mirror in
|
|
||||||
`2026-05-12-iter-ms.2.md`. (ms.2)
|
|
||||||
7. ✓ Per-iter journal entries (ms.1, ms.2) linked from INDEX.md;
|
|
||||||
this audit-ms entry being added by this audit pass. (ms.{1,2,
|
|
||||||
audit})
|
|
||||||
|
|
||||||
All seven criteria green.
|
|
||||||
|
|
||||||
## Outcome
|
|
||||||
|
|
||||||
Milestone closed clean — no tidy fixes needed (both architect-flagged
|
|
||||||
`[low]` items are expected by-products of the audit step itself,
|
|
||||||
not drift). The multi-subject expansion ships: pipeline-error
|
|
||||||
formatting fixed (JSON-cohort now sees the full anyhow cause
|
|
||||||
chain), Qwen retroactive re-run plus first CodeLlama-13b-Instruct
|
|
||||||
run recorded, DESIGN.md §Decision-6 addendum now spans two
|
|
||||||
subjects with consistent direction (AILX cheaper + ≥ green for
|
|
||||||
both), roadmap P3 entry closed. Token spend across the milestone:
|
|
||||||
prompt 501,985 + completion 23,391 = 525,376.
|
|
||||||
|
|
||||||
Decision 6 still pending universal ratification — the spec was
|
|
||||||
explicitly scoped to two subjects, not the ≥3-subjects + statistics
|
|
||||||
that a ratification would need. A future iteration may re-open the
|
|
||||||
queue if additional subjects are wanted; for now the second data
|
|
||||||
point is recorded and the queue is back to user-direction.
|
|
||||||
@@ -1,99 +0,0 @@
|
|||||||
# audit-rt — Milestone close: Roundtrip Invariant
|
|
||||||
|
|
||||||
**Date:** 2026-05-12
|
|
||||||
**Milestone:** Roundtrip Invariant (rt.1 + rt.2)
|
|
||||||
**Status:** Closed, clean (after rt.tidy fixes)
|
|
||||||
|
|
||||||
## Architect drift review
|
|
||||||
|
|
||||||
`ailang-architect` reported `drift_found` with four items over
|
|
||||||
`daf9f4f..HEAD`:
|
|
||||||
|
|
||||||
- `[high]` DESIGN.md §"Roundtrip Invariant" Direction 2 (text →
|
|
||||||
JSON → text idempotency) is stated as a property but not
|
|
||||||
directly enforced by any of the four listed tests. Test 1 covers
|
|
||||||
Direction 1; test 2 covers `.ailx` → JSON ≡ counterpart; test 4
|
|
||||||
covers CLI render → parse. None tests `parse → print → parse`
|
|
||||||
idempotency on a hand-authored `.ailx` text directly. Architect
|
|
||||||
recommendation: claim soften OR add a fifth test.
|
|
||||||
- `[high]` `docs/roadmap.md` P1 entry "Round-trip completeness
|
|
||||||
invariant" still `- [ ]` (open) after the milestone closed.
|
|
||||||
Stale per roadmap convention (finished entries → `[x]` then
|
|
||||||
removal with one-line journal mirror).
|
|
||||||
- `[low]` DESIGN.md wording: `<16-hex>` in new section vs
|
|
||||||
`<16-lowercase-hex>` in §"Data model". Casing constraint is
|
|
||||||
load-bearing for canonical-byte equality; should be stated
|
|
||||||
identically.
|
|
||||||
- `[low]` rt.1 plan errata note (planner emitted ast.rs-mismatching
|
|
||||||
destructure scaffold). Single occurrence absorbed cleanly by the
|
|
||||||
Step-3.4 escape hatch. Not a deeper drift pattern; informational.
|
|
||||||
|
|
||||||
## Bench-regression check
|
|
||||||
|
|
||||||
`bench/check.py && bench/compile_check.py && bench/cross_lang.py`:
|
|
||||||
|
|
||||||
- `check.py`: exit 0; 63 metrics; 0 regressed, 0 improved beyond
|
|
||||||
tolerance, 63 stable.
|
|
||||||
- `compile_check.py`: exit 0; 24 metrics; 0 regressed, 0 improved
|
|
||||||
beyond tolerance, 24 stable.
|
|
||||||
- `cross_lang.py`: exit 0; 25 metrics; 0 regressed, 0 improved
|
|
||||||
beyond tolerance, 25 stable.
|
|
||||||
|
|
||||||
All three bench gates green. Bench was the most plausible source
|
|
||||||
of a regression given rt.1 added three new tests but the tests
|
|
||||||
are pure readers and are not exercised by the bench harness; the
|
|
||||||
green signal matches that expectation.
|
|
||||||
|
|
||||||
## Resolution (rt.tidy, boss-direct edits)
|
|
||||||
|
|
||||||
Three fixes, mechanical, no plan/implement dispatch:
|
|
||||||
|
|
||||||
1. **Direction-2 enforcement** —
|
|
||||||
`crates/ailang-surface/tests/round_trip.rs` gains a third test
|
|
||||||
`parse_then_print_then_parse_is_idempotent_on_every_ailx_fixture`.
|
|
||||||
For every `.ailx` fixture asserts
|
|
||||||
`canonical_bytes(parse(t)) == canonical_bytes(parse(print(parse(t))))`.
|
|
||||||
For the 57 fixtures with a JSON counterpart this is logically
|
|
||||||
derivable from tests 1 + 2; the dedicated test stays robust for
|
|
||||||
future `.ailx` fixtures without a counterpart. DESIGN.md
|
|
||||||
§"Roundtrip Invariant" "Enforcement" list updated from four to
|
|
||||||
five tests; module doc-comment updated from "two complementary
|
|
||||||
checks" to "three complementary checks". Test PASS first-shot
|
|
||||||
across all 57 fixtures.
|
|
||||||
|
|
||||||
2. **Roadmap P1 entry removed.** The "Round-trip completeness
|
|
||||||
invariant" milestone entry deleted; this journal entry is the
|
|
||||||
one-line mirror per roadmap convention. The P1 todo "Brainstorm
|
|
||||||
Step 7.5" remains; the P1 Show + print-rewire milestone remains.
|
|
||||||
|
|
||||||
3. **Wording fix.** DESIGN.md §"Roundtrip Invariant" Float-bits-hex
|
|
||||||
description changed from `<16-hex>` to `<16-lowercase-hex>` to
|
|
||||||
match §"Data model".
|
|
||||||
|
|
||||||
The `[low]` planner-errata carry-on item is retained in the rt.1
|
|
||||||
journal "Concerns" section; it will surface again only if a future
|
|
||||||
tests-against-AST iteration repeats the pattern.
|
|
||||||
|
|
||||||
## Spec acceptance criteria — final check
|
|
||||||
|
|
||||||
1. ✓ Top-level `## Roundtrip Invariant` section in DESIGN.md;
|
|
||||||
Decision 6 Constraint 2 carries upward cross-reference. (rt.2)
|
|
||||||
2. ✓ `every_ailx_fixture_matches_its_json_counterpart` dynamic in
|
|
||||||
`crates/ailang-surface/tests/round_trip.rs`. (rt.1)
|
|
||||||
3. ✓ `crates/ailang-core/tests/schema_coverage.rs` with
|
|
||||||
exhaustive-match visitor; 34/34 variants observed. (rt.1)
|
|
||||||
4. ✓ `crates/ail/tests/roundtrip_cli.rs` with BLAKE3 identity
|
|
||||||
over `ail render` → tempfile → `ail parse`. (rt.1)
|
|
||||||
5. ✓ `cargo test --workspace` green with all four (now five) tests.
|
|
||||||
(rt.1 + rt.2 + rt.tidy)
|
|
||||||
6. ✓ No roundtrip gaps surfaced; no render/parse code changes
|
|
||||||
needed in this milestone. (rt.1)
|
|
||||||
7. ✓ Tests are pure readers; the CLI test's only filesystem side
|
|
||||||
effect is `tempfile::TempDir`. (rt.1)
|
|
||||||
|
|
||||||
## Outcome
|
|
||||||
|
|
||||||
Milestone closed clean. Five workspace-wide tests now anchor the
|
|
||||||
`.ail.json` ↔ `.ailx` bijection plus AST-variant coverage; the
|
|
||||||
property has a dedicated top-level section in DESIGN.md. No
|
|
||||||
follow-up iterations queued in this milestone.
|
|
||||||
@@ -1,200 +0,0 @@
|
|||||||
# iter 23.5 — Prelude free fns + E2E (Eq/Ord milestone close)
|
|
||||||
|
|
||||||
**Date:** 2026-05-12
|
|
||||||
**Started from:** 35c6eb5
|
|
||||||
**Status:** DONE
|
|
||||||
**Tasks completed:** 8 of 8
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
Closes milestone 23. Ships the five polymorphic prelude free fns
|
|
||||||
`ne` / `lt` / `le` / `gt` / `ge` (Tasks 1-2), three end-to-end
|
|
||||||
fixtures covering primitive composition + user-ADT integration +
|
|
||||||
the Float negative case (Tasks 3-5), the Float-aware `NoInstance`
|
|
||||||
diagnostic addendum (Task 5), the DESIGN.md §"Prelude classes"
|
|
||||||
amendment (Task 6), and the roadmap split between shipped Eq/Ord
|
|
||||||
and the pending Show + print-rewire half (Task 7). Bench check
|
|
||||||
runs clean except a noisy sub-millisecond `check_ms.local_rec_capture`
|
|
||||||
regression that sits just over the 25% tolerance — ratifiable per
|
|
||||||
the audit-tolerance rule (the prelude grew by 5 polymorphic
|
|
||||||
`Def::Fn`s, so the typecheck workload per workspace measurably
|
|
||||||
shifts).
|
|
||||||
|
|
||||||
Two implementer-phase fixes were needed beyond the plan, both
|
|
||||||
symmetric to existing precedent (iter 23.4-prep). They surfaced
|
|
||||||
when the natural test fixtures (user-defined `at_most x y = not (gt
|
|
||||||
x y)` composing prelude poly fns) hit two distinct false-positive
|
|
||||||
shapes:
|
|
||||||
|
|
||||||
1. **Linearity check needed workspace-wide visibility** for
|
|
||||||
imported polymorphic free fns + class methods. Previously the
|
|
||||||
linearity pass only saw the current module's globals, so a
|
|
||||||
user `at_most` forwarding its borrow params into `gt` (defined
|
|
||||||
in the prelude) hit `callee_arg_modes("gt", 2) → []` →
|
|
||||||
default-Consume → `consume-while-borrowed` false positive. Fix
|
|
||||||
extends `linearity::check_module` to a `check_module_with_visible`
|
|
||||||
entry that takes additional Module references whose top-level fns
|
|
||||||
+ class methods are registered in globals; the workspace-side
|
|
||||||
driver passes all sibling modules. Pure precedent extension of
|
|
||||||
23.4-prep, which added class-method visibility for the same
|
|
||||||
reason.
|
|
||||||
|
|
||||||
2. **Mono pass needed to skip rigid-var-bearing free-fn targets.**
|
|
||||||
When a polymorphic free fn's body calls another polymorphic
|
|
||||||
free fn (e.g. `at_most`'s body calls `gt x y` with x, y of
|
|
||||||
type rigid `a`), the synth-channel observer recorded a
|
|
||||||
FreeFnCall whose type args were rigid `Type::Var { name: "a"
|
|
||||||
}`. The existing Unit-default fallback (correct for
|
|
||||||
`is_empty(Nil)`-shape unobservable metavars) was unsound here:
|
|
||||||
it produced `gt__Unit` whose body referenced `compare`, which
|
|
||||||
has no Unit instance → "unknown variable: compare" at the
|
|
||||||
synthesised body. Fix discriminates rigid vars
|
|
||||||
(Type::Var without `$m` prefix) from unbound metavars (`$m`
|
|
||||||
prefix) — rigids skip (they get re-collected with concrete
|
|
||||||
substitution when the enclosing fn itself monomorphises),
|
|
||||||
metavars keep their Unit-default.
|
|
||||||
|
|
||||||
One existing fixture (`examples/test_22b1_missing_method.ail.json`)
|
|
||||||
had to rename its class method `ne → tne` because `ne` is now a
|
|
||||||
top-level prelude free fn and the workspace registry's
|
|
||||||
method-name uniqueness check rejected the collision. The
|
|
||||||
fixture's docstring already foreshadowed this case ("Class is
|
|
||||||
`TEq` rather than `Eq` to avoid colliding…"); extended to also
|
|
||||||
cover the `ne → tne` rename rationale.
|
|
||||||
|
|
||||||
## Per-task notes
|
|
||||||
|
|
||||||
- iter 23.5.1: `ne` free fn — added `Def::Fn ne` to prelude (body
|
|
||||||
`not (eq x y)`); created `crates/ail/tests/prelude_free_fns.rs`
|
|
||||||
with helpers `fixture()` and `mono_symbol_names()` + first test;
|
|
||||||
smoke fixture `examples/ne_at_int_smoke.ail.json`. RED→GREEN.
|
|
||||||
Adjusted `mono.modules.iter()` → `.values()` per the actual
|
|
||||||
`BTreeMap<String, Module>` shape (Boss pre-flight flagged).
|
|
||||||
- iter 23.5.2: `lt` / `le` / `gt` / `ge` free fns — four
|
|
||||||
`Def::Fn`s in prelude (each pattern-matches on `compare x y`
|
|
||||||
against one of LT/GT plus a wildcard); four extending tests in
|
|
||||||
the same `prelude_free_fns.rs`; four smoke fixtures. RED→GREEN
|
|
||||||
per fn.
|
|
||||||
- iter 23.5.3: positive E2E — `examples/eq_ord_polymorphic.ail.json`
|
|
||||||
with user-defined `at_most a a → Bool` composing prelude `not`
|
|
||||||
+ `gt`; `crates/ail/tests/eq_ord_e2e.rs` with helpers
|
|
||||||
`fixture_path()` + `build_and_run()` plus
|
|
||||||
`eq_ord_polymorphic_runs_end_to_end`. Two implementer-phase
|
|
||||||
fixes (linearity + mono rigid-skip) landed during this task to
|
|
||||||
unblock the GREEN. Final stdout pinned at `"1\n0\n1"` (matches
|
|
||||||
`io/print_int`'s actual newline-separator behaviour; plan text
|
|
||||||
had `"101"` based on an incorrect concatenation assumption).
|
|
||||||
- iter 23.5.4: user-ADT E2E — `examples/eq_ord_user_adt.ail.json`
|
|
||||||
with `type IntBox = MkIntBox Int` + user-written
|
|
||||||
`instance Eq IntBox` + `instance Ord IntBox` + `main` calling
|
|
||||||
`eq` twice. Two extending tests:
|
|
||||||
`eq_ord_user_adt_runs_end_to_end` (stdout `"1\n0"`) and
|
|
||||||
`eq_ord_user_adt_eq_intbox_hash_stable` (record-then-pin).
|
|
||||||
Mono symbol name is `eq__cde77856` (8-hex-prefix mangling for
|
|
||||||
compound types per DESIGN.md §"Mangling scheme"); body hash
|
|
||||||
pinned at `9daaffa7528d2a1c`. The Ord-IntBox instance's
|
|
||||||
retType needed qualifying as `prelude.Ordering` per the
|
|
||||||
canonical-type-names rule.
|
|
||||||
- iter 23.5.5: Float-aware `NoInstance` addendum — implemented in
|
|
||||||
`CheckError::to_diagnostic()` (the per-iter spec-§"NoInstance"
|
|
||||||
conversion path; option-a from the plan's structural-choice
|
|
||||||
list). Addendum fires when `class ∈ {Eq, Ord}` AND `at_type ==
|
|
||||||
"Float"`: appends `— Float has no Eq/Ord instance by design
|
|
||||||
(partial orderability per IEEE-754); see DESIGN.md §"Float
|
|
||||||
semantics".` New `examples/eq_float_noinstance.ail.json` +
|
|
||||||
`crates/ail/tests/eq_float_noinstance.rs`. RED→GREEN.
|
|
||||||
- iter 23.5.6: DESIGN.md amendment — inserted Milestone-23
|
|
||||||
amendment paragraph in §"Prelude (built-in) classes". Grep
|
|
||||||
gate RED→GREEN.
|
|
||||||
- iter 23.5.7: roadmap split — replaced the single Post-22
|
|
||||||
Prelude entry with two: `[x]` Eq/Ord (shipped 23.5) and `[ ]`
|
|
||||||
Show + print rewire (gated on heap-`Str` ABI). Also
|
|
||||||
disambiguated the `depends on: Post-22 Prelude` reference at
|
|
||||||
line 73 to point specifically at the Eq/Ord half (Op routing's
|
|
||||||
actual dependency).
|
|
||||||
- iter 23.5.8: bench gate — `bench/check.py` exit 0 (clean),
|
|
||||||
`bench/compile_check.py` exit 1 (one sub-ms `check_ms`
|
|
||||||
regression at the tolerance line — see Concerns), `bench/cross_lang.py`
|
|
||||||
exit 0 (clean).
|
|
||||||
|
|
||||||
## Concerns
|
|
||||||
|
|
||||||
- `bench/compile_check.py` exited 1 with one regression:
|
|
||||||
`check_ms.local_rec_capture` measured +26.36% over baseline
|
|
||||||
(1.1ms → 1.4ms, tolerance 25.0%). This is a sub-millisecond
|
|
||||||
noise-band metric, and the prelude grew by 5 polymorphic
|
|
||||||
`Def::Fn`s in this iter so a measurable typecheck-workload
|
|
||||||
shift is expected per the Boss-noted audit tolerance. Two
|
|
||||||
other check_ms metrics in the first run (`borrow_own_demo`,
|
|
||||||
`bench_list_sum`) crossed the line on one sample but stayed
|
|
||||||
inside on the rerun, confirming this is noise-band fluctuation
|
|
||||||
rather than a structural regression. Recommend ratifying at
|
|
||||||
iter close via `--update-baseline`; flagged as a Boss
|
|
||||||
judgement call.
|
|
||||||
- The mono-pass rigid-var skip (Task 3 inline fix) preserves
|
|
||||||
the existing Unit-default for unbound metavars exactly (the
|
|
||||||
`is_empty(Nil)` shape from iter 23.4.8 is untouched). The
|
|
||||||
discrimination is by name prefix (`$m`) which is the same
|
|
||||||
convention `Subst::meta_id` uses; a future schema refactor
|
|
||||||
that drops the prefix convention would silently break the
|
|
||||||
skip rule. Code comment names this.
|
|
||||||
- The linearity-side `check_module(&Module)` standalone entry
|
|
||||||
is now `#[cfg(test)]`-gated because production code uses
|
|
||||||
`check_module_with_visible(m, &visible_extra)` instead. Unit
|
|
||||||
tests still call the no-extra path. Acceptable; flagging in
|
|
||||||
case a future caller wants the standalone form back.
|
|
||||||
|
|
||||||
## Known debt
|
|
||||||
|
|
||||||
- The `eq_ord_user_adt` mono-symbol name (`eq__cde77856`) is
|
|
||||||
pinned by literal string match in the test. If the canonical
|
|
||||||
type-bytes encoding for `IntBox` ever changes (e.g. via a new
|
|
||||||
schema field on `TypeDef`), the test fails with a clear
|
|
||||||
type-mangling-regression message. That is the intended drift
|
|
||||||
alarm; no immediate debt.
|
|
||||||
|
|
||||||
## Files touched
|
|
||||||
|
|
||||||
- `crates/ailang-check/src/lib.rs` — Float-aware `NoInstance`
|
|
||||||
addendum in `to_diagnostic()`; linearity call site updated to
|
|
||||||
pass workspace siblings as visible-extra.
|
|
||||||
- `crates/ailang-check/src/linearity.rs` — new
|
|
||||||
`check_module_with_visible(m, visible_extra)` entry;
|
|
||||||
`check_module(&Module)` made test-only.
|
|
||||||
- `crates/ailang-check/src/mono.rs` — free-fn target collection
|
|
||||||
now distinguishes rigid `Type::Var` from `$m`-prefixed
|
|
||||||
metavars; rigid-bearing targets are skipped; new helper
|
|
||||||
`contains_rigid_var`.
|
|
||||||
- `crates/ailang-core/src/workspace.rs` — docstring update on
|
|
||||||
the `iter22b1_missing_method` test naming the new `ne` reason
|
|
||||||
for the `TEq`/`tne` rename.
|
|
||||||
- `crates/ail/tests/prelude_free_fns.rs` (new) — five workspace-level
|
|
||||||
tests, one per prelude free fn, plus helpers.
|
|
||||||
- `crates/ail/tests/eq_ord_e2e.rs` (new) — three E2E tests:
|
|
||||||
polymorphic composition, user-ADT integration, mono-symbol
|
|
||||||
hash stability.
|
|
||||||
- `crates/ail/tests/eq_float_noinstance.rs` (new) — Float-aware
|
|
||||||
NoInstance pin.
|
|
||||||
- `examples/prelude.ail.json` — five new `Def::Fn` entries
|
|
||||||
(ne / lt / le / gt / ge).
|
|
||||||
- `examples/ne_at_int_smoke.ail.json` (new), `lt_at_int_smoke.ail.json`
|
|
||||||
(new), `le_at_str_smoke.ail.json` (new), `gt_at_bool_smoke.ail.json`
|
|
||||||
(new), `ge_at_int_smoke.ail.json` (new) — five workspace-level
|
|
||||||
smoke fixtures.
|
|
||||||
- `examples/eq_ord_polymorphic.ail.json` (new) — positive E2E:
|
|
||||||
polymorphic helper composing prelude fns at Int / Bool / Str.
|
|
||||||
- `examples/eq_ord_user_adt.ail.json` (new) — user-ADT E2E:
|
|
||||||
`IntBox` with user-written Eq + Ord instances.
|
|
||||||
- `examples/eq_float_noinstance.ail.json` (new) — negative-Float
|
|
||||||
fixture firing the addended diagnostic.
|
|
||||||
- `examples/test_22b1_missing_method.ail.json` — `ne → tne`
|
|
||||||
rename to avoid collision with the new prelude `ne` free fn.
|
|
||||||
- `docs/DESIGN.md` — Milestone-23 amendment paragraph inserted
|
|
||||||
in §"Prelude (built-in) classes".
|
|
||||||
- `docs/roadmap.md` — Post-22 Prelude entry split into shipped
|
|
||||||
Eq/Ord + pending Show; Op-routing depends-on reference
|
|
||||||
disambiguated.
|
|
||||||
|
|
||||||
## Stats
|
|
||||||
|
|
||||||
bench/orchestrator-stats/2026-05-12-iter-23.5.json
|
|
||||||
@@ -1,216 +0,0 @@
|
|||||||
# iter 24.1 — `bool_to_str` + `str_clone` runtime + codegen wiring
|
|
||||||
|
|
||||||
**Date:** 2026-05-12
|
|
||||||
**Started from:** 8bfa09adc7774a1c94af609fc4c78a70ba77be48
|
|
||||||
**Status:** DONE_WITH_CONCERNS
|
|
||||||
**Tasks completed:** 6 of 6
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
First iter of milestone 24 ("Show + print rewire"). Wires two new
|
|
||||||
heap-Str-producing primitives — `bool_to_str : (Bool) -> Str` and
|
|
||||||
`str_clone : (Str borrow) -> Str` — through the runtime C code, type
|
|
||||||
checker, codegen IR lowering, and IR-header preamble, mechanically
|
|
||||||
mirror-imaging the hs.4 wiring for `int_to_str` / `float_to_str`. The
|
|
||||||
two new symbols install lockstep in `builtins.rs::install` and
|
|
||||||
`synth.rs::builtin_value_type` with `ret_mode: Own`; codegen gains
|
|
||||||
two new IR-header `declare` lines, two `Emitter::lower_app` arms, and
|
|
||||||
two `is_static_callee` whitelist entries. Plan's pre-existing-drift
|
|
||||||
fix landed alongside: `int_to_str` row added to `builtins.rs::list()`
|
|
||||||
(hs.4 added the `env.globals` entry but missed the `list()` row).
|
|
||||||
|
|
||||||
Nine new tests landed per plan: 2 builtins-install unit tests
|
|
||||||
(`install_bool_to_str_signature`, `install_str_clone_signature`),
|
|
||||||
2 IR-shape unit pins (`bool_to_str_emits_call_to_ailang_bool_to_str`,
|
|
||||||
`str_clone_emits_call_to_ailang_str_clone`), 5 E2E tests
|
|
||||||
(`bool_to_str_drop_balances_rc_stats`, `bool_to_str_emits_true_branch`,
|
|
||||||
`bool_to_str_emits_false_branch`, `str_clone_drop_balances_rc_stats`,
|
|
||||||
`str_clone_cross_realisation_uniform_abi`). Four `.ail.json` fixtures
|
|
||||||
shipped. Five IR snapshots regenerated for the two new declare lines.
|
|
||||||
|
|
||||||
One substantive deviation from the plan's "purely-additive wiring"
|
|
||||||
framing surfaced during Task 6 fixture validation: the
|
|
||||||
`str_clone_cross_realisation_uniform_abi` test's literal assertion
|
|
||||||
`allocs == 3, frees == 3, live == 0` could not hold under the
|
|
||||||
pre-24.1 uniqueness analyser because the analyser's `globals` map
|
|
||||||
does not contain builtin signatures — `str_clone`'s `param_modes:
|
|
||||||
[Borrow]` is invisible to `callee_arg_modes`, so a `src_heap` let-
|
|
||||||
binder consumed by `str_clone(src_heap)` walks as `Position::Consume`,
|
|
||||||
incrementing `consume_count` and gating off the scope-close
|
|
||||||
`ailang_rc_dec`. Fixed inline by registering builtin signatures in
|
|
||||||
both `uniqueness.rs::infer_module` and
|
|
||||||
`linearity.rs::check_module_with_visible` (8 lines × 2 files,
|
|
||||||
symmetric to the existing class-method registration the linearity
|
|
||||||
pass added in iter 23.4-prep). The fix is substantively correct (it
|
|
||||||
applies a pre-existing pattern to a new class of callees) but is
|
|
||||||
NOT in the plan's literal Edit text — see Concerns for the
|
|
||||||
discussion.
|
|
||||||
|
|
||||||
Acceptance verified: full `cargo test --workspace` green (513 tests
|
|
||||||
passing, 0 failures); the five new E2E tests pass with the plan's
|
|
||||||
literal numeric assertions; the two new IR-shape pins pass; the five
|
|
||||||
IR snapshot tests pass after `UPDATE_SNAPSHOTS=1` regen (diff is
|
|
||||||
exactly the two new `declare ptr @ailang_bool_to_str(i1)` and
|
|
||||||
`declare ptr @ailang_str_clone(ptr)` lines). `bench/compile_check.py`
|
|
||||||
24/24 stable, 0 regressions. `bench/cross_lang.py` 25/25 stable, 0
|
|
||||||
regressions. Grep verification: 44 hits across runtime/str.c +
|
|
||||||
builtins.rs + synth.rs + lib.rs (well above the plan's ≥ 10
|
|
||||||
threshold), 10 hits in snapshots (2 declares × 5 files), 0 build
|
|
||||||
warnings.
|
|
||||||
|
|
||||||
## Per-task notes
|
|
||||||
|
|
||||||
- iter 24.1.1: Appended `ailang_bool_to_str(bool b) -> char *` and
|
|
||||||
`ailang_str_clone(const char *src) -> char *` to `runtime/str.c`
|
|
||||||
after `ailang_float_to_str` (str.c:128-143). Both use the existing
|
|
||||||
static `str_alloc(uint64_t)` helper for heap-Str slab allocation;
|
|
||||||
`bool_to_str` writes either "true" (4 bytes) or "false" (5 bytes);
|
|
||||||
`str_clone` reads `len` from offset 0 of source and memcpy's bytes
|
|
||||||
+ trailing NUL to a fresh slab. Standalone clang -c compile green.
|
|
||||||
- iter 24.1.2: Installed `bool_to_str : (Bool) -> Str` and
|
|
||||||
`str_clone : (Str borrow) -> Str` in `builtins.rs::install`
|
|
||||||
(builtins.rs:213-232, between the existing `int_to_str` block and
|
|
||||||
the `is_nan` block), both with `ret_mode: ParamMode::Own`. Mirror-
|
|
||||||
installed the same two signatures in `synth.rs::builtin_value_type`
|
|
||||||
(synth.rs:181-194, lockstep). Added three rows to
|
|
||||||
`builtins.rs::list()` (`int_to_str` drift fix from hs.4 +
|
|
||||||
`bool_to_str` + `str_clone`). Two new unit tests
|
|
||||||
(`install_bool_to_str_signature`, `install_str_clone_signature`)
|
|
||||||
appended to `builtins.rs::tests` using the fallback explicit
|
|
||||||
`Term::Lit` shape (plan-noted: `lit_bool` / `lit_str` helpers do
|
|
||||||
not exist; only `lit_int` / `lit_float` are defined). Both new
|
|
||||||
unit tests PASS.
|
|
||||||
- iter 24.1.3: Codegen IR wiring. (a) Extended the IR-header preamble
|
|
||||||
at `lib.rs:539-548` with two unconditional declares
|
|
||||||
`declare ptr @ailang_bool_to_str(i1)` and
|
|
||||||
`declare ptr @ailang_str_clone(ptr)`. (b) Inserted two new arms in
|
|
||||||
`Emitter::lower_app` at `lib.rs:1934-1969` (after the existing
|
|
||||||
`float_to_str` arm), each emitting a direct call to the runtime
|
|
||||||
C glue with arity check + `lower_term`+`fresh_ssa`+call triple
|
|
||||||
matching `int_to_str`'s structural shape. (c) Extended the
|
|
||||||
`is_static_callee` builtin-callee whitelist at `lib.rs:2133-2135`
|
|
||||||
with `bool_to_str` and `str_clone` (strictly required so
|
|
||||||
`Term::Var { name: "bool_to_str" }` reaches `lower_app`'s chain
|
|
||||||
instead of the UnknownVar fallback). Full workspace build clean.
|
|
||||||
- iter 24.1.4: Two IR-shape unit pin tests
|
|
||||||
(`bool_to_str_emits_call_to_ailang_bool_to_str`,
|
|
||||||
`str_clone_emits_call_to_ailang_str_clone`) appended at the bottom
|
|
||||||
of `lib.rs::mod tests`, mirroring the
|
|
||||||
`int_to_str_lowers_to_ailang_int_to_str_call` template. Both PASS.
|
|
||||||
- iter 24.1.5: Five IR snapshots (`hello.ll`, `list.ll`, `max3.ll`,
|
|
||||||
`sum.ll`, `ws_main.ll`) regenerated via `UPDATE_SNAPSHOTS=1`. RED
|
|
||||||
observed first as expected (5 ir_snapshot_* tests failed with
|
|
||||||
"run UPDATE_SNAPSHOTS=1 cargo test ir_snapshot_ to refresh");
|
|
||||||
GREEN after regen. Diff is exactly the two new declare lines.
|
|
||||||
Each .ll file shows 2 matches for the new symbols.
|
|
||||||
- iter 24.1.6: Four `.ail.json` fixtures created
|
|
||||||
(`bool_to_str_drop_rc.ail.json`, `bool_to_str_smoke_false.ail.json`,
|
|
||||||
`str_clone_drop_rc.ail.json`, `str_clone_cross_realisation.ail.json`)
|
|
||||||
per the plan's verbatim JSON. Five new E2E tests appended to
|
|
||||||
`crates/ail/tests/e2e.rs` after `heap_str_repeated_print_balances_rc_stats`.
|
|
||||||
The `str_clone_cross_realisation_uniform_abi` test initially
|
|
||||||
FAILED with `frees=2, expected=3`; root-caused to the
|
|
||||||
uniqueness-analyser-globals scope gap (see Concerns); fixed by
|
|
||||||
registering builtin signatures in `uniqueness.rs::infer_module`
|
|
||||||
and `linearity.rs::check_module_with_visible`. After the fix all
|
|
||||||
five new E2E tests PASS with the plan's literal numeric
|
|
||||||
assertions. Full `cargo test --workspace`: 513 passed, 0 failed.
|
|
||||||
`bench/compile_check.py` 24/24 stable. `bench/cross_lang.py` 25/25
|
|
||||||
stable. Grep verification per plan Step 9 met: 44 code-side hits,
|
|
||||||
10 snapshot hits, 0 build warnings.
|
|
||||||
|
|
||||||
## Concerns
|
|
||||||
|
|
||||||
- **Analyser-globals registration extends the plan's "purely-additive"
|
|
||||||
scope.** The plan framed 24.1 as "Pure-additive wiring across four
|
|
||||||
crates / files." The actual landed change extends to two more
|
|
||||||
files (`uniqueness.rs`, `linearity.rs`) with 8 lines each — a fix
|
|
||||||
to the uniqueness / linearity analyser-globals scope so the
|
|
||||||
`str_clone` builtin's `param_modes: [Borrow]` is visible to the
|
|
||||||
`callee_arg_modes` walker. Without the fix, the
|
|
||||||
`str_clone_cross_realisation_uniform_abi` test's plan-literal
|
|
||||||
assertion `frees == 3` does not hold (one heap-Str slab leaks
|
|
||||||
because the App-arg walker can't see the Borrow mode → walks
|
|
||||||
`src_heap` as Consume → `consume_count == 1` → drop emission
|
|
||||||
gated off).
|
|
||||||
|
|
||||||
The fix is **symmetric** to the iter 23.4-prep extension that
|
|
||||||
registered class-method signatures in the same `globals` maps for
|
|
||||||
the same reason (a builtin/class-method callee carrying
|
|
||||||
`param_modes` would not be visible to the analyser, false-firing
|
|
||||||
`consume-while-borrowed` lints AND, in this iter, leaking heap-Str
|
|
||||||
slabs). The plan's verbatim Edit text did not include the fix; the
|
|
||||||
plan's verbatim test assertion required it. Per the orchestrator-
|
|
||||||
agent's "make the reasonable call and continue" mandate I applied
|
|
||||||
the fix in-place; it is the substantively-correct repair, not a
|
|
||||||
design departure.
|
|
||||||
|
|
||||||
The hs.4 journal's "Known debt" item on this is partially closed
|
|
||||||
by this iter (the App-side of the Borrow-walker visibility is now
|
|
||||||
fixed for builtin callees). The eob.1 fix (effect-op args walk as
|
|
||||||
Borrow regardless of callee `param_modes`) covered the `Term::Do`
|
|
||||||
side; the corresponding `Term::App` side is now covered for
|
|
||||||
builtins. User-fn `Term::App` callees still benefit from their
|
|
||||||
own def being registered in the analyser's `globals` from the
|
|
||||||
module's own def list (lines 113-115 in uniqueness.rs); imported
|
|
||||||
user-fn callees benefit from the iter 23.5 visible-extra
|
|
||||||
registration in linearity.rs (lines 220-238). So the only
|
|
||||||
remaining unknown-callee case is one this iter does not encounter.
|
|
||||||
|
|
||||||
## Known debt
|
|
||||||
|
|
||||||
- **`str_clone` could rc_inc on heap-Str input** instead of allocating
|
|
||||||
a fresh copy, but this requires distinguishing heap-Str from
|
|
||||||
static-Str at runtime — a tagging scheme the codebase deliberately
|
|
||||||
dropped post-hs.2 (UINT64_MAX sentinel removal). Out of scope per
|
|
||||||
the parent spec's "`str_clone` as rc_header bump" entry.
|
|
||||||
|
|
||||||
- **`bool_to_str` as schema-`if`** with two static-Str literals would
|
|
||||||
avoid the per-call heap allocation but requires phi-of-static-Str
|
|
||||||
through the drop-elision pipeline that is not load-bearing-ratified
|
|
||||||
today. Open commitment per the parent spec.
|
|
||||||
|
|
||||||
## Files touched
|
|
||||||
|
|
||||||
- Modified (12 files):
|
|
||||||
- `runtime/str.c` — appended `ailang_bool_to_str` (16 LOC incl.
|
|
||||||
comment) and `ailang_str_clone` (18 LOC incl. comment)
|
|
||||||
- `crates/ailang-check/src/builtins.rs` — two new
|
|
||||||
`env.globals.insert` blocks (`bool_to_str` + `str_clone`), three
|
|
||||||
new rows in `list()` (drift fix + two new), two new unit tests
|
|
||||||
- `crates/ailang-codegen/src/synth.rs` — two new arms in
|
|
||||||
`builtin_value_type` (lockstep partner of the checker insert)
|
|
||||||
- `crates/ailang-codegen/src/lib.rs` — IR-header preamble extends
|
|
||||||
with 2 new `declare` lines; 2 new `lower_app` arms; 2-entry
|
|
||||||
`is_static_callee` whitelist extension; 2 new IR-shape pin tests
|
|
||||||
- `crates/ailang-check/src/uniqueness.rs` — register builtin
|
|
||||||
signatures in `infer_module`'s `globals` map so `callee_arg_modes`
|
|
||||||
resolves builtin `param_modes` correctly
|
|
||||||
- `crates/ailang-check/src/linearity.rs` — register builtin
|
|
||||||
signatures in `check_module_with_visible`'s `globals` map for the
|
|
||||||
same reason
|
|
||||||
- `crates/ail/tests/e2e.rs` — 5 new E2E tests appended
|
|
||||||
- `crates/ail/tests/snapshots/hello.ll`,
|
|
||||||
`crates/ail/tests/snapshots/list.ll`,
|
|
||||||
`crates/ail/tests/snapshots/max3.ll`,
|
|
||||||
`crates/ail/tests/snapshots/sum.ll`,
|
|
||||||
`crates/ail/tests/snapshots/ws_main.ll` — golden-IR regen via
|
|
||||||
`UPDATE_SNAPSHOTS=1`; diff is exactly the two new
|
|
||||||
`declare ptr @ailang_bool_to_str(i1)` and
|
|
||||||
`declare ptr @ailang_str_clone(ptr)` lines
|
|
||||||
|
|
||||||
- Created (4 files):
|
|
||||||
- `examples/bool_to_str_drop_rc.ail.json` — RC-stats fixture for
|
|
||||||
`bool_to_str true` (4-byte slab, single drop)
|
|
||||||
- `examples/bool_to_str_smoke_false.ail.json` — stdout-smoke
|
|
||||||
fixture for `bool_to_str false` (5-byte slab)
|
|
||||||
- `examples/str_clone_drop_rc.ail.json` — RC-stats fixture for
|
|
||||||
`str_clone "hello"` (static-Str input → fresh heap-Str clone)
|
|
||||||
- `examples/str_clone_cross_realisation.ail.json` — cross-
|
|
||||||
realisation invariant: clones one heap-Str (output of
|
|
||||||
`int_to_str 42`) AND one static-Str (literal `"abc"`), printing
|
|
||||||
both; pins the "uniform consumer ABI" claim
|
|
||||||
|
|
||||||
## Stats
|
|
||||||
|
|
||||||
bench/orchestrator-stats/2026-05-12-iter-24.1.json
|
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
# 2026-05-12 — iter boss: `/boss` skill — autonomous-mode discipline gated to user invocation
|
|
||||||
|
|
||||||
## What shipped
|
|
||||||
|
|
||||||
- New skill at `skills/boss/SKILL.md`. Consolidates the three
|
|
||||||
mode-specific subsections that previously lived in `CLAUDE.md`:
|
|
||||||
Direction freedom, Notifications, Done-state notifications:
|
|
||||||
WhatsNew.md.
|
|
||||||
- `CLAUDE.md` refactored. Three subsections removed (~95 lines).
|
|
||||||
Skill-system pointer paragraph extended with one sentence
|
|
||||||
naming `/boss` as the autonomous-mode gate.
|
|
||||||
- `.claude/skills/boss` symlink created (`../../skills/boss`),
|
|
||||||
matching the discoverability convention codified in
|
|
||||||
`skills/README.md`.
|
|
||||||
- `skills/README.md` updated: skill table extended with a `boss`
|
|
||||||
row, heading shifted from "seven skills" to "eight skills",
|
|
||||||
pipeline diagram captioned with a `/boss`-wraps note.
|
|
||||||
- Two cross-references repointed: `skills/implement/SKILL.md`
|
|
||||||
and `skills/implement/agents/ailang-implement-orchestrator.md`
|
|
||||||
both named the "Done-state notifications" subsection of
|
|
||||||
CLAUDE.md by sub-heading; both now point at `skills/boss/SKILL.md`.
|
|
||||||
|
|
||||||
## Why
|
|
||||||
|
|
||||||
CLAUDE.md is always loaded into context. Mixing universal facts
|
|
||||||
(agent role boundaries, commit discipline, design rationale, file
|
|
||||||
roles, TDD-for-bugs) with mode-specific autonomy rules (direction
|
|
||||||
freedom, notifications, WhatsNew procedure) was OK while
|
|
||||||
autonomous-by-default was the operating assumption — but it
|
|
||||||
conflicted with the user's intent that a fresh session should be
|
|
||||||
collaborative-interactive unless explicitly elevated.
|
|
||||||
|
|
||||||
The mode-switch needs to be explicit. `/boss` is the explicit
|
|
||||||
elevation, and only the three genuinely mode-specific subsections
|
|
||||||
move with it. Universal orchestrator discipline stays in CLAUDE.md
|
|
||||||
because it applies whether `/boss` is active or not: agents need
|
|
||||||
to know role boundaries when dispatched in any session, design
|
|
||||||
discipline applies whenever a design choice is made (including a
|
|
||||||
`brainstorm` invoked outside `/boss`), and the orchestrator can
|
|
||||||
edit skills at any time.
|
|
||||||
|
|
||||||
## Decisions
|
|
||||||
|
|
||||||
- **Tightened the move-list from seven items to three.** The
|
|
||||||
initial spec proposed moving "My role: orchestrator", "Authority
|
|
||||||
over skills/", "Design rationale ≠ effort", "Feature acceptance",
|
|
||||||
"When NOT to delegate", plus the three mode-specific subsections.
|
|
||||||
Planner recon surfaced that the first five are universal, not
|
|
||||||
mode-specific — they apply in any session. The move-list was
|
|
||||||
narrowed to the three genuinely mode-specific items. The
|
|
||||||
brainstorm Step 7.5 grounding-check was re-dispatched on the
|
|
||||||
edited spec and returned PASS.
|
|
||||||
- **No private agents under `skills/boss/agents/`.** `/boss` is an
|
|
||||||
orchestration layer, not a worker. It dispatches the existing
|
|
||||||
skills (and their agents). The skill table reflects this by
|
|
||||||
listing `boss` as a meta-skill without an agent-roster row.
|
|
||||||
- **Cross-reference repoints kept tight.** Recon found 11+
|
|
||||||
agent/skill files that reference "CLAUDE.md — orchestrator
|
|
||||||
framing". Most of those resolve correctly to the universal
|
|
||||||
content that stays in CLAUDE.md. Only two files name the moved
|
|
||||||
subsections by sub-heading (`implement/SKILL.md` and the
|
|
||||||
orchestrator-agent's standing reading list); only those two
|
|
||||||
were repointed.
|
|
||||||
|
|
||||||
## Status
|
|
||||||
|
|
||||||
Iter closed. No bench regressions to check (markdown-only change,
|
|
||||||
no compiler touch). No audit needed (single-iter milestone, no
|
|
||||||
DESIGN.md change). Acceptance criteria from the spec all met.
|
|
||||||
@@ -1,128 +0,0 @@
|
|||||||
# iter cma.1 — Master mini-spec + renderer for the cross-model authoring experiment
|
|
||||||
|
|
||||||
**Date:** 2026-05-12
|
|
||||||
**Started from:** fd6efdbc
|
|
||||||
**Status:** DONE
|
|
||||||
**Tasks completed:** 9 of 9
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
Stands up the master mini-spec source and the renderer binary under a
|
|
||||||
new top-level `experiments/2026-05-12-cross-model-authoring/` tree
|
|
||||||
that lives outside the root Cargo workspace. The renderer projects
|
|
||||||
one form-agnostic `master/spec.md` into two form-specific files
|
|
||||||
(`rendered/json.md` and `rendered/ailx.md`) by walking three directive
|
|
||||||
forms (`{form-only: json}`, `{form-only: ailx}`, `{example: <id>}`)
|
|
||||||
and emitting per-example fenced blocks from 13 curated `.ail.json`
|
|
||||||
fixtures. Four test gates land green: a 7-test splitter unit suite,
|
|
||||||
a `spec_completeness` test borrowing the AST-variant visitor verbatim
|
|
||||||
from `crates/ailang-core/tests/schema_coverage.rs` (34/34 variants
|
|
||||||
observed across the curated subset), an `example_roundtrip` test
|
|
||||||
mirroring the surface roundtrip contract, and a `token_balance` test
|
|
||||||
that gates form-only block tokens to ±5% under `tiktoken-rs::cl100k_base`
|
|
||||||
(final ratio: json=1800, ailx=1742, ratio 3.22%). Two follow-on iters
|
|
||||||
(cma.2: harness, cma.3: live IONOS run + DESIGN.md addendum) are out
|
|
||||||
of scope per the spec.
|
|
||||||
|
|
||||||
## Per-task notes
|
|
||||||
|
|
||||||
- cma.1.1: Bootstrap experiment dir + Cargo skeleton — created
|
|
||||||
`experiments/.../{README.md,render/Cargo.toml,render/src/main.rs}`
|
|
||||||
plus the four target directories. Spec-pinned dependency paths
|
|
||||||
resolve `ailang-core` and `ailang-surface` via relative
|
|
||||||
`../../../crates/...` references.
|
|
||||||
- cma.1.2: Directive splitter (TDD) — 7 unit tests RED first, then
|
|
||||||
implementation; added a `[lib]` target so the integration tests
|
|
||||||
can reach `xmodel_render::splitter`. GREEN on first impl run.
|
|
||||||
- cma.1.3: spec_completeness test — visitor + EXPECTED_VARIANTS
|
|
||||||
list lifted verbatim from `crates/ailang-core/tests/schema_coverage.rs`;
|
|
||||||
only `examples_dir()` body and the test name changed. Confirmed RED
|
|
||||||
on the empty master/examples/ directory (failure mode: "no .ail.json
|
|
||||||
fixtures found").
|
|
||||||
- cma.1.4: 13 curated `.ail.json` fixtures (one per file as planned).
|
|
||||||
The plan's 13 fixture topics naturally covered 28 of the 34 AST
|
|
||||||
variants; the remaining 6 (Def::Const, Term::Let, Term::LetRec,
|
|
||||||
Term::If, Term::Clone, Term::ReuseAs) were folded into existing
|
|
||||||
fixtures per the plan's Step 4.14 instruction ("Extend the fixture
|
|
||||||
closest in scope … do not weaken the test"). Concrete absorptions:
|
|
||||||
Let + Clone into `fn_calls_prelude`; LetRec into `data_with_match`
|
|
||||||
(as a recursive `count_via_letrec` helper); If into
|
|
||||||
`match_literal_pattern` (as a `sign_if` helper); ReuseAs into
|
|
||||||
`data_simple` (the `wrap_one` body uses `(reuse-as src ...)`);
|
|
||||||
Const into `floats` (a `const pi : Float = 3.14`). All 34 variants
|
|
||||||
green on first run.
|
|
||||||
- cma.1.5: example_roundtrip test — verbatim from plan; all 13
|
|
||||||
fixtures roundtrip through `ailang_surface::print` and
|
|
||||||
`ailang_surface::parse` to the same canonical bytes on first
|
|
||||||
implementation run.
|
|
||||||
- cma.1.6: master/spec.md — 13 sections of form-agnostic prose with
|
|
||||||
parallel JSON/AILX form-only blocks in §2-5, §7, §9, §10 plus the
|
|
||||||
prelude table in §11 (no form-only there; the table itself is the
|
|
||||||
content). Section §11's prelude tabulation reflects the live
|
|
||||||
`crates/ailang-check/src/builtins.rs` symbol set: `+`, `-`, `*`,
|
|
||||||
`/`, `%`, `==`, `!=`, `<`, `<=`, `>`, `>=`, `not`, `neg`,
|
|
||||||
`int_to_float`, `float_to_int_truncate`, `float_to_str`, `is_nan`,
|
|
||||||
`nan`/`inf`/`neg_inf`, `__unreachable__`, plus the effect ops
|
|
||||||
`io/print_{int,bool,str,float}`. `int_to_str` is explicitly named
|
|
||||||
as OUT (heap-Str ABI milestone).
|
|
||||||
- cma.1.7: Wire renderer end-to-end — examples.rs and main.rs filled
|
|
||||||
in verbatim from the plan. Renderer ran clean on first invocation;
|
|
||||||
json.md (33409 bytes) and ailx.md (23964 bytes) emit with the
|
|
||||||
expected 13 fenced blocks each (and zero of the wrong fence type).
|
|
||||||
- cma.1.8: token_balance test — first run reported json=1800,
|
|
||||||
ailx=1692, ratio 6.00% (above the 5% threshold). One substantive
|
|
||||||
AILX-side extension to §4 (mode annotations) added authoring
|
|
||||||
guidance on `over-strict-mode` and hash stability under future
|
|
||||||
default shifts; final ratio json=1800, ailx=1742, 3.22%. The
|
|
||||||
rebalancing is content, not filler.
|
|
||||||
- cma.1.9: Final render + full sweep — `cargo test --manifest-path
|
|
||||||
experiments/.../render/Cargo.toml` ends with 10 passed; 0 failed
|
|
||||||
across four test suites; `git status` shows the entire experiment
|
|
||||||
tree as untracked.
|
|
||||||
|
|
||||||
## Concerns
|
|
||||||
|
|
||||||
- Task 1: Cargo's automatic workspace discovery hits the root
|
|
||||||
manifest, so the experiment crate refuses to build on its own. The
|
|
||||||
fix is the standard Cargo idiom for a nested-but-out-of-workspace
|
|
||||||
crate: add an empty `[workspace]` table to the experiment's
|
|
||||||
`Cargo.toml`. The plan did not enumerate this; I added it with a
|
|
||||||
comment pointing at the spec authority (§Architecture lines 90-95)
|
|
||||||
for the standalone constraint. Boss should confirm this is the
|
|
||||||
intended mechanism vs. e.g. adding the experiment to a
|
|
||||||
`workspace.exclude` list in the root manifest (the spec is explicit
|
|
||||||
that the root Cargo.toml is not touched, which the chosen mechanism
|
|
||||||
respects).
|
|
||||||
- Task 1 ancillary: a `.gitignore` was added under
|
|
||||||
`experiments/.../render/` ignoring `/target` and `Cargo.lock`. Not
|
|
||||||
enumerated in the plan but a structural necessity to keep cargo's
|
|
||||||
build output and lockfile out of the repo. Boss can keep, replace
|
|
||||||
with a top-level rule, or move to the root `.gitignore`.
|
|
||||||
|
|
||||||
## Known debt
|
|
||||||
|
|
||||||
- None surfaced inside the iter. Two named deferrals remain out of
|
|
||||||
scope (cma.2 harness, cma.3 live run + DESIGN.md addendum), per
|
|
||||||
the spec.
|
|
||||||
|
|
||||||
## Files touched
|
|
||||||
|
|
||||||
27 new files, all under `experiments/2026-05-12-cross-model-authoring/`:
|
|
||||||
|
|
||||||
- `README.md`
|
|
||||||
- `master/spec.md`
|
|
||||||
- `master/examples/*.ail.json` × 13
|
|
||||||
- `render/Cargo.toml`
|
|
||||||
- `render/.gitignore`
|
|
||||||
- `render/src/{main,lib,splitter,examples}.rs`
|
|
||||||
- `render/tests/{splitter_unit,spec_completeness,example_roundtrip,token_balance}.rs`
|
|
||||||
- `rendered/json.md`
|
|
||||||
- `rendered/ailx.md`
|
|
||||||
|
|
||||||
`git diff --name-only HEAD | wc -l`: 0 (untracked tree; use
|
|
||||||
`git status --porcelain --untracked-files=all experiments/...` to
|
|
||||||
enumerate).
|
|
||||||
|
|
||||||
## Stats
|
|
||||||
|
|
||||||
`bench/orchestrator-stats/2026-05-12-iter-cma.1.json`
|
|
||||||
@@ -1,212 +0,0 @@
|
|||||||
# iter cma.2 — Harness binary + four tasks + reference solutions + three integration tests
|
|
||||||
|
|
||||||
**Date:** 2026-05-12
|
|
||||||
**Started from:** 21c2d2cfaa999980f727a069792e3bd073c1f1e7
|
|
||||||
**Status:** DONE
|
|
||||||
**Tasks completed:** 13 of 13
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
Stands up the sibling `harness/` standalone Cargo crate under
|
|
||||||
`experiments/2026-05-12-cross-model-authoring/`, mirroring the
|
|
||||||
out-of-workspace idiom from cma.1's `render/`. Six modules under
|
|
||||||
`harness/src/`: `strip_locations` (regex pass for form-asymmetric
|
|
||||||
location info), `pipeline` (subprocess wrapper around
|
|
||||||
`ail parse|check|build` plus the model's compiled binary, with a
|
|
||||||
fail-fast preflight on `ail` + `clang`), `ionos` (blocking reqwest
|
|
||||||
client with retry policy), `mock` (canned-response loader keyed by
|
|
||||||
cohort/task/turn), `scoring` (CSV + summary.md writers), `tasks`
|
|
||||||
(task-definition struct + loader). `main.rs` ties them into the
|
|
||||||
per-(cohort, task) loop. Four task definitions (`t1_add_three`,
|
|
||||||
`t2_length`, `t3_main_prints`, `t4_count_zeros`) with reference
|
|
||||||
solutions that all reach green through the actual ail+clang
|
|
||||||
pipeline. 13/13 tests pass: 5 inline unit tests under `--lib`
|
|
||||||
(strip_locations), 5 strip_locations integration tests against
|
|
||||||
verbatim recon-captured stderr fixtures, 1 verify_references
|
|
||||||
integration test driving every reference through the real pipeline,
|
|
||||||
1 mock_full_run end-to-end with eight rows and per-cohort artefact
|
|
||||||
trees, 1 budget_abort with the harness aborting cleanly under a
|
|
||||||
1500-token cap.
|
|
||||||
|
|
||||||
## Per-task notes
|
|
||||||
|
|
||||||
- cma.2.1: Bootstrap harness Cargo project + skeleton — created
|
|
||||||
`harness/{Cargo.toml,.gitignore,src/lib.rs,src/main.rs}` plus
|
|
||||||
six stub module files. Empty `[workspace]` table at the top of
|
|
||||||
Cargo.toml keeps the crate out of the root workspace. Cargo.lock
|
|
||||||
gitignored per spec. First `cargo build` succeeded with the
|
|
||||||
expected unused-import warnings on the skeleton.
|
|
||||||
- cma.2.2: strip_locations module — written verbatim from the
|
|
||||||
plan with 5 inline unit tests (json_pointer, byte_offset, line/
|
|
||||||
column, Caused-by chain, passthrough). All 5 green on first run.
|
|
||||||
- cma.2.3: pipeline module — written verbatim from the plan plus
|
|
||||||
a small implementer-phase repair: `ail check` enforces filename
|
|
||||||
stem == module name, so `pipeline::run_pipeline` extracts the
|
|
||||||
top-level `"name"` field from the JSON via a new
|
|
||||||
`module_name_from_json` helper and renames the working file to
|
|
||||||
`<module_name>.ail.json` before invoking `ail check`. Without
|
|
||||||
this, every reference would have failed at the `ail check` step
|
|
||||||
with "module name in file does not match expected name from
|
|
||||||
path". See Concerns.
|
|
||||||
- cma.2.4: ionos module — written verbatim from the plan; added
|
|
||||||
`thiserror = "1"` to `[dependencies]`. Compile-time warnings
|
|
||||||
about three unused things on the `IonosClient` path (Auth, Usage,
|
|
||||||
`last_err` assignment) are expected and flagged in the plan.
|
|
||||||
- cma.2.5: mock module — written verbatim; compile clean.
|
|
||||||
- cma.2.6: scoring module — written verbatim; compile clean.
|
|
||||||
- cma.2.7: tasks module — written verbatim; compile clean.
|
|
||||||
- cma.2.8: Author four task definitions + reference solutions —
|
|
||||||
task.json files written verbatim from the plan text. Reference
|
|
||||||
`.ail.json` files authored fresh against the cma.1 templates
|
|
||||||
(hello.ail.json shape for t3; gc_stress.ail.json's polymorphic
|
|
||||||
`List a` shape for t2 and t4; canonical chained `+` for t1's
|
|
||||||
three-arg sum; canonical `eq` invocation pattern + branched `if`
|
|
||||||
for t4's count_zeros). All four references compile clean and
|
|
||||||
produce the expected stdout. Reference solutions intentionally
|
|
||||||
omit `param_modes` because every parameter is Implicit and the
|
|
||||||
canonical AILang form omits the field in that case — consistent
|
|
||||||
with hello.ail.json, list.ail.json, eq_primitives_smoke.ail.json,
|
|
||||||
and the explanatory doc in `master/examples/param_modes_all.ail.json`.
|
|
||||||
See Concerns. Added `tempfile = "3"` to `[dev-dependencies]` for
|
|
||||||
the `verify_references.rs` integration test. The test runs every
|
|
||||||
reference through `pipeline::run_pipeline(Cohort::Json, ...)`
|
|
||||||
end-to-end (ail parse-check-build-execute); passes when
|
|
||||||
`AIL_BIN` is set to the release binary.
|
|
||||||
- cma.2.9: Wire main.rs end-to-end — written verbatim from the
|
|
||||||
plan; added `tempfile = "3"` to `[dependencies]` (not only
|
|
||||||
dev-deps) because `main.rs::run_one` uses it for per-turn
|
|
||||||
workdir. Compile clean (modulo the three flagged warnings from
|
|
||||||
ionos).
|
|
||||||
- cma.2.10: Five stderr fixtures + strip_locations integration
|
|
||||||
test — all five fixture files written verbatim from the plan
|
|
||||||
(which itself captured them from recon's run against current HEAD).
|
|
||||||
Integration test asserts strip behaviour against each fixture;
|
|
||||||
5/5 green on first run.
|
|
||||||
- cma.2.11: mock_full_run integration test + mock_full_run.json
|
|
||||||
fixture — fixture authored by inlining each
|
|
||||||
`master/tasks/<id>.reference.ail.json` as the `content` field
|
|
||||||
for the green-path entries, plus `ail render` for the AILX-cohort
|
|
||||||
inlines (verbatim AILX serialisations of the same references).
|
|
||||||
Test runs the harness binary against the fixture; 8 rows total
|
|
||||||
(4 tasks × 2 cohorts), `(json, t3_main_prints)` reaches green on
|
|
||||||
turn 2 (matches the plan's intended cycle: broken-then-fixed),
|
|
||||||
`(ailx, t1_add_three)` runs to the 5-turn limit (`(module garbage)`
|
|
||||||
repeated). All artefacts (turn_N_program.ext,
|
|
||||||
turn_N_request.json, turn_N_response.json,
|
|
||||||
turn_N_check_stderr.txt, turn_N_run_stdout.txt) land in
|
|
||||||
`per_cohort/<cohort>/<task>/`. 1/1 green.
|
|
||||||
- cma.2.12: budget_abort integration test — test verbatim from
|
|
||||||
the plan, plus an implementer-phase repair to `main.rs`: with a
|
|
||||||
tiny budget (1500), the outer loop broke out after consuming
|
|
||||||
~2400 tokens across tasks 1+2 (both green on turn 1), but
|
|
||||||
neither row carried `final_status = budget_abort` because the
|
|
||||||
inner `run_one` only marks BudgetAbort if the budget is exhausted
|
|
||||||
*during* the turn loop. The plan's test assertion expects at
|
|
||||||
least one row with `budget_abort`. Repair: after the outer loop
|
|
||||||
breaks with `run_status="budget_exceeded"`, fill in the not-yet-run
|
|
||||||
(cohort, task) pairs with synthetic `BudgetAbort` rows. mock_full_run
|
|
||||||
remains green (the new code only runs on budget_exceeded).
|
|
||||||
1/1 green. See Concerns.
|
|
||||||
- cma.2.13: README update + final sweep — appended "Running the
|
|
||||||
harness" section to `experiments/.../README.md` verbatim from the
|
|
||||||
plan. Full `cargo test` sweep: 5 (lib unit, strip_locations) + 5
|
|
||||||
(integ, strip_locations) + 1 (verify_references) + 1
|
|
||||||
(mock_full_run) + 1 (budget_abort) = 13/13 green. `git status`
|
|
||||||
shows the README modification + the new harness/ tree + the new
|
|
||||||
master/tasks/ files, all unstaged.
|
|
||||||
|
|
||||||
## Concerns
|
|
||||||
|
|
||||||
- **Pipeline filename-matching: minimal repair landed in `pipeline.rs`,
|
|
||||||
not flagged in the plan.** `ail check` enforces filename stem ==
|
|
||||||
module name; the plan's `run_pipeline` writes the model's program
|
|
||||||
to `prog.{ext}`, which would have been rejected at every `ail
|
|
||||||
check` invocation. The repair adds a 9-line helper
|
|
||||||
`module_name_from_json` (reads JSON, returns top-level `name`) plus
|
|
||||||
14 lines in `run_pipeline` that rename `prog.ail.json` ->
|
|
||||||
`<module_name>.ail.json` between parse and check. Behaviour
|
|
||||||
outside that rename is unchanged. The structural alternative
|
|
||||||
(telling the model to name its module `prog`) would have been
|
|
||||||
semantically wrong — the task description prompts say "module
|
|
||||||
named t1_add_three" etc. Recommend a forward-fix sweep in cma.3
|
|
||||||
if the structural decision (whether the pipeline or the prompt
|
|
||||||
owns module naming) warrants spec attention.
|
|
||||||
- **`param_modes` omitted from every reference solution.** The plan's
|
|
||||||
Step 8.2 text says "mode annotations on every parameter", but the
|
|
||||||
templates the plan references (hello.ail.json, list.ail.json,
|
|
||||||
eq_primitives_smoke.ail.json, etc.) all omit `param_modes`, and
|
|
||||||
the `param_modes_all.ail.json` master example's own doc string
|
|
||||||
documents "Implicit mode is the legacy default — `param_modes` is
|
|
||||||
omitted from canonical JSON when every entry is Implicit". The
|
|
||||||
references stay in canonical form. If the plan's intent was
|
|
||||||
explicit `["implicit", "implicit", ...]` annotations on every
|
|
||||||
reference, that is a follow-on amendment.
|
|
||||||
- **Budget-abort row-filling, minimal repair landed in `main.rs`,
|
|
||||||
not flagged in the plan.** Without it, Task 12's test fails on
|
|
||||||
the assertion `expected at least one budget_abort row`. Repair
|
|
||||||
is gated on `run_status == "budget_exceeded"` so the green path
|
|
||||||
is unchanged.
|
|
||||||
- **README "Total 8 passed" text mismatches Step 13.2's "13 passed"
|
|
||||||
expectation.** The plan's README text (Step 13.1) advertises four
|
|
||||||
test suites with "Total 8 passed" — counts the four integration
|
|
||||||
suites but skips the 5 inline unit tests under `--lib`. Step 13.2
|
|
||||||
expects 13 across `--lib` + 4 integration tests. The README text
|
|
||||||
is reproduced verbatim per plan; the actual sweep produces 13.
|
|
||||||
Recommend a docfix in cma.3 ("Total 13 passed across --lib + 4
|
|
||||||
integration tests"), but the cma.2 plan locked the README copy.
|
|
||||||
- **`AIL_BIN` env required for `verify_references.rs` and
|
|
||||||
`mock_full_run.rs` to run from a fresh shell.** These tests shell
|
|
||||||
out to `ail`; without `ail` on PATH the preflight bails. The
|
|
||||||
README documents this. CI integration is out of scope.
|
|
||||||
|
|
||||||
## Known debt
|
|
||||||
|
|
||||||
- The harness has not yet been live-fired against IONOS (deferred
|
|
||||||
to cma.3 per spec). The retry/backoff path in
|
|
||||||
`ionos::IonosClient::post` has no test coverage in cma.2 (mock
|
|
||||||
mode bypasses it entirely).
|
|
||||||
- Three compile warnings in `ionos.rs` (`Auth` and one `Usage` are
|
|
||||||
read in tests/other code; `last_err` second assignment is
|
|
||||||
intentional dead-store for retry-loop symmetry). Not addressed.
|
|
||||||
|
|
||||||
## Files touched
|
|
||||||
|
|
||||||
Modified (1):
|
|
||||||
- `experiments/2026-05-12-cross-model-authoring/README.md`
|
|
||||||
|
|
||||||
New — harness crate (8 source, 4 integration tests, 6 fixtures, 1
|
|
||||||
manifest, 1 .gitignore):
|
|
||||||
- `experiments/2026-05-12-cross-model-authoring/harness/Cargo.toml`
|
|
||||||
- `experiments/2026-05-12-cross-model-authoring/harness/.gitignore`
|
|
||||||
- `experiments/2026-05-12-cross-model-authoring/harness/src/lib.rs`
|
|
||||||
- `experiments/2026-05-12-cross-model-authoring/harness/src/main.rs`
|
|
||||||
- `experiments/2026-05-12-cross-model-authoring/harness/src/strip_locations.rs`
|
|
||||||
- `experiments/2026-05-12-cross-model-authoring/harness/src/pipeline.rs`
|
|
||||||
- `experiments/2026-05-12-cross-model-authoring/harness/src/ionos.rs`
|
|
||||||
- `experiments/2026-05-12-cross-model-authoring/harness/src/mock.rs`
|
|
||||||
- `experiments/2026-05-12-cross-model-authoring/harness/src/scoring.rs`
|
|
||||||
- `experiments/2026-05-12-cross-model-authoring/harness/src/tasks.rs`
|
|
||||||
- `experiments/2026-05-12-cross-model-authoring/harness/tests/strip_locations.rs`
|
|
||||||
- `experiments/2026-05-12-cross-model-authoring/harness/tests/verify_references.rs`
|
|
||||||
- `experiments/2026-05-12-cross-model-authoring/harness/tests/mock_full_run.rs`
|
|
||||||
- `experiments/2026-05-12-cross-model-authoring/harness/tests/budget_abort.rs`
|
|
||||||
- `experiments/2026-05-12-cross-model-authoring/harness/tests/fixtures/check_unbound_var.stderr`
|
|
||||||
- `experiments/2026-05-12-cross-model-authoring/harness/tests/fixtures/check_type_mismatch.stderr`
|
|
||||||
- `experiments/2026-05-12-cross-model-authoring/harness/tests/fixtures/check_bare_xmod.stderr`
|
|
||||||
- `experiments/2026-05-12-cross-model-authoring/harness/tests/fixtures/check_schema_missing_field.stderr`
|
|
||||||
- `experiments/2026-05-12-cross-model-authoring/harness/tests/fixtures/parse_unclosed.stderr`
|
|
||||||
- `experiments/2026-05-12-cross-model-authoring/harness/tests/fixtures/mock_full_run.json`
|
|
||||||
|
|
||||||
New — master tasks (4 task.json + 4 reference.ail.json):
|
|
||||||
- `experiments/2026-05-12-cross-model-authoring/master/tasks/t1_add_three.task.json`
|
|
||||||
- `experiments/2026-05-12-cross-model-authoring/master/tasks/t1_add_three.reference.ail.json`
|
|
||||||
- `experiments/2026-05-12-cross-model-authoring/master/tasks/t2_length.task.json`
|
|
||||||
- `experiments/2026-05-12-cross-model-authoring/master/tasks/t2_length.reference.ail.json`
|
|
||||||
- `experiments/2026-05-12-cross-model-authoring/master/tasks/t3_main_prints.task.json`
|
|
||||||
- `experiments/2026-05-12-cross-model-authoring/master/tasks/t3_main_prints.reference.ail.json`
|
|
||||||
- `experiments/2026-05-12-cross-model-authoring/master/tasks/t4_count_zeros.task.json`
|
|
||||||
- `experiments/2026-05-12-cross-model-authoring/master/tasks/t4_count_zeros.reference.ail.json`
|
|
||||||
|
|
||||||
## Stats
|
|
||||||
|
|
||||||
bench/orchestrator-stats/2026-05-12-iter-cma.2.json
|
|
||||||
@@ -1,103 +0,0 @@
|
|||||||
# iter cma.3 — Live run + DESIGN.md addendum + milestone close
|
|
||||||
|
|
||||||
**Date:** 2026-05-12
|
|
||||||
**Started from:** fe1fb6b
|
|
||||||
**Status:** DONE (milestone Cross-Model Authoring-Form Test closed)
|
|
||||||
**Mode:** Boss-direct (no implement dispatch per spec)
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
Live IONOS run against Qwen3-Coder-Next executed; raw dataset
|
|
||||||
under `experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/`.
|
|
||||||
AILX cohort reached green on 2/4 tasks at ~98k total tokens; JSON
|
|
||||||
cohort reached green on 1/4 tasks at ~207k total tokens. The only
|
|
||||||
first-attempt success was AILX on `t3_main_prints`. JSON-cohort
|
|
||||||
failures clustered in the typecheck stage (check×4); AILX-cohort
|
|
||||||
failures clustered in the parse stage (parse×3). DESIGN.md
|
|
||||||
§"Decision 6" gains a 27-line "Empirical addendum (2026-05-12)"
|
|
||||||
recording the data, the illustrative t3 case, and an explicit
|
|
||||||
single-subject single-run scope note. Multi-subject expansion
|
|
||||||
queued as a fresh roadmap P3 entry.
|
|
||||||
|
|
||||||
## Run details
|
|
||||||
|
|
||||||
- Endpoint: `https://openai.inference.de-txl.ionos.com/v1/chat/completions`
|
|
||||||
- Model: `Qwen/Qwen3-Coder-Next`
|
|
||||||
- Parameters: temperature=0, top_p=1, max-turns=5,
|
|
||||||
token-budget=500000.
|
|
||||||
- RUN_STATUS: `ok`. Total tokens consumed: 305,148 of 500,000
|
|
||||||
budget (39% headroom).
|
|
||||||
- 40 raw artefact files (8 runs × 5 per-turn files on average)
|
|
||||||
preserved under `runs/2026-05-12-df7531/per_cohort/<cohort>/<task>/`.
|
|
||||||
|
|
||||||
## Per-task notes
|
|
||||||
|
|
||||||
- `json/t1_add_three`: turn_limit. Cohort consumed 5 turns trying
|
|
||||||
to satisfy the schema constraints around `do` / `seq` / effect
|
|
||||||
positioning. Top error class `check` throughout.
|
|
||||||
- `json/t2_length`: turn_limit. ADT + polymorphism stress; the
|
|
||||||
model emitted plausible-shape JSON that the typechecker
|
|
||||||
rejected.
|
|
||||||
- `json/t3_main_prints`: green on turn 2 (the only green in the
|
|
||||||
JSON cohort). Turn 1 was ~23 lines of structured JSON with a
|
|
||||||
small structural mistake the check loop pointed out (with
|
|
||||||
location info stripped); turn 2 was correct.
|
|
||||||
- `json/t4_count_zeros`: turn_limit. Typeclass surface + Eq
|
|
||||||
constraint shape; the model never converged.
|
|
||||||
- `ailx/t1_add_three`: green on turn 3. Got the structure right
|
|
||||||
but needed two correction turns to align with the prelude
|
|
||||||
function naming.
|
|
||||||
- `ailx/t2_length`: turn_limit. Errors mixed `check` and `parse`
|
|
||||||
(model occasionally produced malformed s-expressions on
|
|
||||||
correction turns).
|
|
||||||
- `ailx/t3_main_prints`: green on turn 1, ~5 lines of tagged
|
|
||||||
s-expr. Only first-attempt success in the entire run.
|
|
||||||
- `ailx/t4_count_zeros`: turn_limit. Parse-stage failures on
|
|
||||||
every turn; the model struggled with the typeclass-constraint
|
|
||||||
shape in AILX.
|
|
||||||
|
|
||||||
## DESIGN.md edit
|
|
||||||
|
|
||||||
§"Decision 6: authoring surface" gains a new
|
|
||||||
"Empirical addendum (2026-05-12)" subsection (~27 lines after the
|
|
||||||
existing prose, before §"Decision 8"). Records: model id, run
|
|
||||||
parameters, the 6-row metric table, an illustrative paragraph
|
|
||||||
naming the t3 contrast, and the explicit scope note that the
|
|
||||||
data is one subject + one deterministic run and that the
|
|
||||||
universal claim of Decision 6 stays pending multi-subject
|
|
||||||
follow-up.
|
|
||||||
|
|
||||||
## Roadmap
|
|
||||||
|
|
||||||
- Removed: P2 entry "Cross-model authoring-form test" (closed).
|
|
||||||
- Added: P3 entry "Multi-subject expansion
|
|
||||||
(cross-model authoring-form follow-up)" pointing at this run as
|
|
||||||
baseline.
|
|
||||||
|
|
||||||
## Concerns
|
|
||||||
|
|
||||||
- AILX cohort's high parse-error count is partly model-side
|
|
||||||
(small grammar mistakes the typechecker would have forgiven)
|
|
||||||
and partly the strip_locations regex erasing `at byte N`
|
|
||||||
offsets, which leaves the model without precise localisation
|
|
||||||
during correction turns. Symmetric degradation is the fairness
|
|
||||||
anchor; calibration trade-off is documented in the spec.
|
|
||||||
- The JSON cohort's mini-spec at ~15k system tokens vs AILX's
|
|
||||||
~7k is an inherent form asymmetry (JSON is wordier per
|
|
||||||
construct). Token costs are partly a measurement of *the form*,
|
|
||||||
not just of how easy each is to author. This is part of what
|
|
||||||
the experiment measures, but worth naming.
|
|
||||||
- Only 3/8 (cohort, task) pairs reached green. The dataset's
|
|
||||||
signal-to-noise ratio is modest; the multi-subject expansion
|
|
||||||
needs more tasks and/or more permissive prelude coverage so
|
|
||||||
the failure clusters are less dominant.
|
|
||||||
|
|
||||||
## Known debt
|
|
||||||
|
|
||||||
- Multi-subject expansion (≥2 additional foreign LLMs, ≥4 more
|
|
||||||
tasks, repetitions for statistical robustness) — queued as the
|
|
||||||
P3 roadmap entry. Not started.
|
|
||||||
- README "Total 8 passed" inherited from the cma.2 plan still
|
|
||||||
understates the actual 13/13 test count; the prose was carried
|
|
||||||
verbatim per cma.2 plan discipline. Cosmetic; can be fixed in
|
|
||||||
a future tidy iter.
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
# iter ctt.1 — Env-overlay shape ratification
|
|
||||||
|
|
||||||
**Date:** 2026-05-12
|
|
||||||
**Started from:** 0a36294e61f08d8de85c084415d694d3736e0c67
|
|
||||||
**Status:** DONE
|
|
||||||
**Tasks completed:** 3 of 3
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
Ratifies the two-overlay shape of the check environment (`env.types`
|
|
||||||
+ `env.ctor_index`) and pins its load-bearing consumer. The split
|
|
||||||
decision now has a DESIGN.md anchor (§"Env construction") explaining
|
|
||||||
the owning-index / reverse-index roles, the rationale for not
|
|
||||||
collapsing into one variant-keyed map, and the intentional asymmetry
|
|
||||||
with the mono side (narrowed to types-only at ct.3.2, because its
|
|
||||||
runtime-ctor-lookup consumer story differs from the check side's
|
|
||||||
in-band `DuplicateCtor` diagnostic). A new behavioural-pin test
|
|
||||||
(`crates/ailang-check/tests/duplicate_ctor_pin.rs`) asserts
|
|
||||||
`code == "duplicate-ctor"` plus message-substring coverage for a
|
|
||||||
two-ADT-same-ctor fixture, going red if the per-module rebuild at
|
|
||||||
`crates/ailang-check/src/lib.rs:1342-1384` is ever silently removed.
|
|
||||||
Two P2 roadmap todos struck `[x]` with one-line forward-references to
|
|
||||||
this iter. No production-code edits. `cargo test --workspace` green.
|
|
||||||
|
|
||||||
## Per-task notes
|
|
||||||
|
|
||||||
- iter ctt.1.1: New pinning test `crates/ailang-check/tests/duplicate_ctor_pin.rs` — builds a `Workspace` inline with one module declaring `type Cat = Twins` and `type Dog = Twins`, calls `check_workspace`, asserts a `Diagnostic` with `code == "duplicate-ctor"` plus message substring for `Twins` / `Cat` / `Dog`. PASS on first run, as the plan predicted (behaviour pre-exists).
|
|
||||||
- iter ctt.1.2: Inserted new top-level section `## Env construction` in `docs/DESIGN.md` between `## Convention: qualified cross-module references` (line 1941) and `## Data model` (now line 1988, was 1959 pre-edit). Three paragraphs: split definition, rationale, check-side / mono-side asymmetry. Cross-references the pinning test.
|
|
||||||
- iter ctt.1.3: Two `[ ]` → `[x]` strikes in `docs/roadmap.md` at lines 79 (overlay shape question) and 183 (per-module overlay narrowing). Each gains a one-line forward-reference suffix on its `context:` line pointing at iter ctt.1 and the closing artefact.
|
|
||||||
|
|
||||||
## Concerns
|
|
||||||
|
|
||||||
(none)
|
|
||||||
|
|
||||||
## Known debt
|
|
||||||
|
|
||||||
(none)
|
|
||||||
|
|
||||||
## Files touched
|
|
||||||
|
|
||||||
- `crates/ailang-check/tests/duplicate_ctor_pin.rs` (new)
|
|
||||||
- `docs/DESIGN.md` (modified — `## Env construction` section inserted)
|
|
||||||
- `docs/roadmap.md` (modified — two strikes)
|
|
||||||
|
|
||||||
## Stats
|
|
||||||
|
|
||||||
bench/orchestrator-stats/2026-05-12-iter-ctt.1.json
|
|
||||||
@@ -1,142 +0,0 @@
|
|||||||
# iter ctt.2 — `Registry.type_def_module` re-key to tuple-keyed map
|
|
||||||
|
|
||||||
**Date:** 2026-05-12
|
|
||||||
**Started from:** 548ebf8d51758ab9f156b266e0baa59c78b14034
|
|
||||||
**Status:** DONE
|
|
||||||
**Tasks completed:** 3 of 3
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
`Registry.type_def_module` re-keyed from `BTreeMap<String, String>` to
|
|
||||||
`BTreeMap<(String, String), String>` keyed by `(owning_module,
|
|
||||||
bare_name)`. A new `caller_module: &str` parameter threads through
|
|
||||||
`normalize_type_for_registry` and `Registry::normalize_type_for_lookup`,
|
|
||||||
and all four `ailang-check` consumer sites pass the correct
|
|
||||||
module-context per the plan's Design Notes block. A regression test
|
|
||||||
plus a three-module fixture exercise the bare-name collision the
|
|
||||||
re-key fixes — two modules each declaring `type Foo` now register
|
|
||||||
under distinct canonical keys instead of tripping the silent-overwrite
|
|
||||||
defect. `cargo test --workspace` green (16 binary-test suites,
|
|
||||||
~600 tests total, zero failures).
|
|
||||||
|
|
||||||
## Per-task notes
|
|
||||||
|
|
||||||
- iter ctt.2.1: RED-first — three fixture JSON files
|
|
||||||
(`ctt2_collision_{cls,lib,main}.ail.json`) plus
|
|
||||||
`crates/ailang-core/tests/ctt2_registry_rekey.rs`. Test goes
|
|
||||||
RED today. Failure shape was `OrphanInstance` rather than the
|
|
||||||
plan's predicted `DuplicateInstance` (a) or missing-hash (b) —
|
|
||||||
see Concerns.
|
|
||||||
- iter ctt.2.2: GREEN — atomic re-key edit pass. Touched the field
|
|
||||||
type + doc-comment, `Registry::normalize_type_for_lookup`
|
|
||||||
signature + body, pass-1 declaration + insert,
|
|
||||||
pass-2 coherence-check lookup + canonical-form hashing call,
|
|
||||||
`normalize_type_for_registry` signature + doc-comment + body
|
|
||||||
(recursive calls thread caller_module everywhere), the inline
|
|
||||||
`ct1_5a_normalize_recurses_into_forall_constraints` test
|
|
||||||
at workspace.rs:2543, and all four `ailang-check` consumer sites
|
|
||||||
at the module-context dictated by the plan's Design Notes
|
|
||||||
(lib.rs:1640 → `env.current_module.as_str()`, mono.rs:121 →
|
|
||||||
`defining_module.as_str()`, mono.rs:624 and :1175 → `module_name`).
|
|
||||||
Clean `cargo build --workspace` at the end of the pass.
|
|
||||||
- iter ctt.2.3: workspace-wide test gate. New test goes GREEN.
|
|
||||||
Full `cargo test --workspace` green, no test in any crate
|
|
||||||
regressed. The plan's Step 2 hypothesis ("a fixture under
|
|
||||||
`examples/` might legitimately exercise a bare cross-module
|
|
||||||
type reference") did not materialise — the existing corpus
|
|
||||||
uses canonical-form (qualified) cross-module type refs
|
|
||||||
exclusively, so the tightening had no surface.
|
|
||||||
|
|
||||||
## Concerns
|
|
||||||
|
|
||||||
- Task 1 RED-failure shape diverged from the plan's predicted
|
|
||||||
shapes (a) `DuplicateInstance` and (b) missing-`contains_key`.
|
|
||||||
The plan's verbatim two-module fixture pair (lib imports main +
|
|
||||||
main imports lib + lib-side instance with qualified class
|
|
||||||
`ctt2_collision_main.MyC`) had two independent structural
|
|
||||||
defects against the loaded codebase invariants:
|
|
||||||
1. The workspace loader rejects import cycles
|
|
||||||
(`WorkspaceLoadError::Cycle`) at module-name granularity —
|
|
||||||
the plan's claim "Two-way import is supported; workspace
|
|
||||||
loader DFS handles cycles at module-name granularity" is
|
|
||||||
empirically false. Confirmed by inspection at
|
|
||||||
`workspace.rs:1410-1413` and `:1451-1454`.
|
|
||||||
2. `validate_canonical_type_names` rejects qualified
|
|
||||||
`InstanceDef.class` with `QualifiedClassName` (see test
|
|
||||||
`ct1_validator_rejects_qualified_instancedef_class` at
|
|
||||||
workspace.rs:2321) — the plan's lib-side instance with
|
|
||||||
`class: "ctt2_collision_main.MyC"` would have been rejected
|
|
||||||
even without the cycle.
|
|
||||||
|
|
||||||
Implementer-phase repair: introduced a third module
|
|
||||||
`ctt2_collision_cls.ail.json` carrying `class MyC`, broke the
|
|
||||||
cycle (both lib and main import cls only; main also imports
|
|
||||||
lib so the loader DFS reaches it), and dropped the qualifier
|
|
||||||
from both instance heads (both declare bare `class: "MyC"`
|
|
||||||
satisfying coherence via the type's module). The plan's
|
|
||||||
*core* RED-condition is preserved: two modules each declaring
|
|
||||||
`type Foo` with `instance MyC Foo` collide on the pre-ctt.2
|
|
||||||
bare-name `type_def_module` key.
|
|
||||||
|
|
||||||
The actually-observed RED shape was `OrphanInstance` rather
|
|
||||||
than `DuplicateInstance` because the pass-2 coherence check
|
|
||||||
(`workspace.rs:656-659`) uses the bare-name lookup *before*
|
|
||||||
the duplicate-uniqueness check fires — lib's `Foo` and main's
|
|
||||||
`Foo` both resolve to whichever module won the alphabetical
|
|
||||||
race (`ctt2_collision_main` beats `ctt2_collision_lib`
|
|
||||||
lexicographically), so lib's instance is no longer coherent
|
|
||||||
("type lives in main, class lives in cls, instance lives in
|
|
||||||
lib — neither matches"). The re-key fixes this consumer site
|
|
||||||
simultaneously with the duplicate-check site because both go
|
|
||||||
through the same `type_def_module` lookup, now tuple-keyed.
|
|
||||||
|
|
||||||
The deviation from the plan's verbatim fixtures is an
|
|
||||||
implementer-phase NEEDS_CONTEXT repair that the
|
|
||||||
user-no-stop-instruction permitted in-line; the plan's *intent*
|
|
||||||
(RED-first against the bare-name collision) is achieved and
|
|
||||||
the GREEN-state matches the plan's acceptance criteria
|
|
||||||
unchanged. Boss may want to back-edit the plan or amend the
|
|
||||||
spec to note the multi-module fixture shape if this kind of
|
|
||||||
collision regression returns later.
|
|
||||||
|
|
||||||
## Known debt
|
|
||||||
|
|
||||||
- The `OrphanInstance` consumer site at `workspace.rs:656-659`
|
|
||||||
is fixed by ctt.2 but the diagnostic-shape change (orphan
|
|
||||||
instead of duplicate-on-loser) is not separately tested. The
|
|
||||||
new test asserts the GREEN state directly (both entries land
|
|
||||||
under distinct keys) which transitively exercises both
|
|
||||||
consumer sites, but neither the orphan nor the duplicate
|
|
||||||
diagnostic shapes are pinned by a dedicated RED test. Adding
|
|
||||||
one is plausibly out-of-scope for ctt.2.
|
|
||||||
|
|
||||||
## Files touched
|
|
||||||
|
|
||||||
Modified (3):
|
|
||||||
- `crates/ailang-check/src/lib.rs` — single call-site update at
|
|
||||||
the NoInstance check inside `check_fn`.
|
|
||||||
- `crates/ailang-check/src/mono.rs` — three call-site updates
|
|
||||||
(monomorphise_workspace at :121, collect_mono_targets at :624,
|
|
||||||
collect_residuals_ordered at :1175).
|
|
||||||
- `crates/ailang-core/src/workspace.rs` — field re-key,
|
|
||||||
`Registry::normalize_type_for_lookup` signature,
|
|
||||||
`normalize_type_for_registry` signature + body,
|
|
||||||
pass-1 declaration + insert,
|
|
||||||
pass-2 coherence-check lookup + canonical-form hashing call,
|
|
||||||
inline test `ct1_5a_normalize_recurses_into_forall_constraints`
|
|
||||||
at :2543; doc-comments at field-decl and at both
|
|
||||||
`normalize_…` fns.
|
|
||||||
|
|
||||||
Added (4):
|
|
||||||
- `crates/ailang-core/tests/ctt2_registry_rekey.rs` — regression test.
|
|
||||||
- `examples/ctt2_collision_cls.ail.json` — class-bearing module
|
|
||||||
(not in plan; required to break the cycle in the plan's
|
|
||||||
verbatim fixture design).
|
|
||||||
- `examples/ctt2_collision_lib.ail.json` — `type Foo = MkLib` +
|
|
||||||
bare `instance MyC Foo`.
|
|
||||||
- `examples/ctt2_collision_main.ail.json` — `type Foo = MkMain` +
|
|
||||||
bare `instance MyC Foo`, plus imports of both `cls` and `lib`.
|
|
||||||
|
|
||||||
## Stats
|
|
||||||
|
|
||||||
bench/orchestrator-stats/2026-05-12-iter-ctt.2.json
|
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
# iter ctt.3 — `KindMismatch` retire (pure deletion across 3 files)
|
|
||||||
|
|
||||||
**Date:** 2026-05-12
|
|
||||||
**Started from:** 0e556f085c14993441b7d25dcdc69077b77c83b0
|
|
||||||
**Status:** DONE
|
|
||||||
**Tasks completed:** 2 of 2
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
`WorkspaceLoadError::KindMismatch`, its dispatch site in
|
|
||||||
`validate_classdefs`, its `walk_kind_mismatch` helper, its Display
|
|
||||||
arm in `crates/ail/src/main.rs:workspace_error_to_diagnostic`, and
|
|
||||||
the "dead-but-defensive" doc-comment on the canonical-form-rejection
|
|
||||||
test are all retired. Two adjacent textual-consistency edits ride
|
|
||||||
along: the `validate_classdefs` doc-comment narrows from "Three
|
|
||||||
diagnostics fire from here" to "Two" with a successor sentence
|
|
||||||
naming the new path, and the `crates/ailang-check/src/lib.rs:1428`
|
|
||||||
22b.1-era archaeology comment drops the `KindMismatch` token from
|
|
||||||
its enumeration of class-schema diagnostics and gains a one-line
|
|
||||||
ctt.3 retirement marker. The existing test
|
|
||||||
`class_param_in_applied_position_fires_canonical_form_rejection`
|
|
||||||
stays green unchanged — it asserts `BareCrossModuleTypeRef` on the
|
|
||||||
malformed fixture, which is the canonical-form-validator successor
|
|
||||||
the plan named. `cargo build --workspace` clean, no orphan match
|
|
||||||
arms, no dead-code warnings. `cargo test --workspace` green
|
|
||||||
(~600 tests across 16 binary-test suites, zero failures).
|
|
||||||
|
|
||||||
## Per-task notes
|
|
||||||
|
|
||||||
- iter ctt.3.1: atomic deletion pass — 6 Edits across 3 files. workspace.rs ×4 (variant delete; `validate_classdefs` doc + dispatch-loop delete; `walk_kind_mismatch` helper delete; "dead-but-defensive" doc delete). main.rs ×1 (`W::KindMismatch` arm in `workspace_error_to_diagnostic`). lib.rs ×1 (22b.1 archaeology comment). Build green at Step 7.
|
|
||||||
- iter ctt.3.2: workspace-wide test gate — grep substantively clean in `crates/` (one residual `KindMismatch` token at lib.rs:1431 is the ctt.3 retirement marker introduced by Step 6 itself, not a live consumer); canonical-form-rejection test PASS; full `cargo test --workspace` PASS.
|
|
||||||
|
|
||||||
## Concerns
|
|
||||||
|
|
||||||
- Plan-internal contradiction surfaced during Task 2 Step 1: the
|
|
||||||
plan's Step 6 `new_string` deliberately introduces a new
|
|
||||||
`KindMismatch` token in the ctt.3 retirement marker
|
|
||||||
(`// ctt.3: KindMismatch retired — ...`), but Task 2 Step 1's
|
|
||||||
verification text says "Expected: zero hits in `crates/`". The
|
|
||||||
parent spec at `docs/specs/2026-05-12-ct-tidy.md:384` is more
|
|
||||||
permissive ("`grep KindMismatch` returns the three deletion sites
|
|
||||||
only" — accepting the diff itself as the only mention, but
|
|
||||||
silent on a successor archaeology marker). Substantive intent
|
|
||||||
(retire the diagnostic) is met: variant, helper, dispatch, and
|
|
||||||
Display arm are all gone. Treated as `compliant` with this
|
|
||||||
concern recorded; Boss may choose to either tighten the
|
|
||||||
spec/plan rule for future tidies or accept the retirement-marker
|
|
||||||
pattern (which is already common in the codebase — cf. ctt.1
|
|
||||||
successor-rationale comments in workspace.rs).
|
|
||||||
- Mid-deletion repair on workspace.rs:773 — the dispatch-loop
|
|
||||||
deletion in Step 2 orphaned the `mod_name` binding that
|
|
||||||
destructured `for (mod_name, m) in modules`. Plan Step 7's
|
|
||||||
acceptance criterion is "no dead-code warnings" — left as
|
|
||||||
`for (mod_name, m)` the build would warn `unused variable:
|
|
||||||
mod_name`. Minimal cleanup applied: rewrote to
|
|
||||||
`for m in modules.values()`. Same body, same `m: &Module` type
|
|
||||||
via `.values()` instead of tuple-destructure. Not in the plan's
|
|
||||||
verbatim edit list but a foreseeable side-effect of the
|
|
||||||
deletion and entirely mechanical.
|
|
||||||
|
|
||||||
## Known debt
|
|
||||||
|
|
||||||
- None new. The "future tidy may retire" promise in the deleted
|
|
||||||
doc-comment is now this iter; no successor obligation.
|
|
||||||
|
|
||||||
## Files touched
|
|
||||||
|
|
||||||
- `crates/ailang-core/src/workspace.rs` (variant delete + doc adjust + dispatch-loop delete + helper delete + test-doc delete + `mod_name` binding cleanup)
|
|
||||||
- `crates/ail/src/main.rs` (Display arm delete)
|
|
||||||
- `crates/ailang-check/src/lib.rs` (archaeology comment edit)
|
|
||||||
|
|
||||||
## Stats
|
|
||||||
|
|
||||||
bench/orchestrator-stats/2026-05-12-iter-ctt.3.json
|
|
||||||
@@ -1,123 +0,0 @@
|
|||||||
# iter eob.1 — Effect-op args walked as Borrow; heap-Str RC discipline closes
|
|
||||||
|
|
||||||
**Date:** 2026-05-12
|
|
||||||
**Started from:** b5b0c2d7dcd8bec1819801257de7731edc542e0f
|
|
||||||
**Status:** DONE
|
|
||||||
**Tasks completed:** 7 of 7
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
Land the language rule that `Term::Do.args[*]` are walked in
|
|
||||||
`Position::Borrow` by the uniqueness and linearity passes, plus the
|
|
||||||
two heap-Str-specific shape fixes (`ret_mode: Own` for `int_to_str`
|
|
||||||
/ `float_to_str`; Str carve-out in the App arm of
|
|
||||||
`drop_symbol_for_binder`) that make the existing RED tests at
|
|
||||||
`crates/ail/tests/e2e.rs` flip to GREEN under `--alloc=rc`. Both
|
|
||||||
arg-position rules (Ctor = Consume, Do = Borrow) are anchored in
|
|
||||||
DESIGN.md §Decision 10. The heap-str-abi milestone closes here:
|
|
||||||
WhatsNew entry shipped, roadmap P1 entry checked off, downstream
|
|
||||||
Post-22-Prelude `depends on:` line removed.
|
|
||||||
|
|
||||||
The five plan layers landed first-shot in lockstep — no review
|
|
||||||
re-loops fired on any of the 7 tasks. The pre-existing RED tests
|
|
||||||
(`int_to_str_drop_balances_rc_stats`,
|
|
||||||
`str_field_in_adt_drops_heap_str_correctly`) flipped to GREEN with
|
|
||||||
the predicted concrete numbers (`allocs == 1, frees == 1, live == 0`
|
|
||||||
and `allocs == 2, frees == 2, live == 0`). Two new positive tests
|
|
||||||
pin the rule's coverage at the primitive boundary (Int through
|
|
||||||
`io/print_int`: zero RC traffic) and at the linearity boundary
|
|
||||||
(heap-Str through two sequential `io/print_str` calls: allocs == 1,
|
|
||||||
frees == 1, live == 0). Workspace test --workspace + cross_lang.py
|
|
||||||
+ compile_check.py all green. check.py shows the same noisy
|
|
||||||
latency-max + bump_s cluster the audit-cma and audit-ms journals
|
|
||||||
both left pristine ("not coupled to milestone, baseline left
|
|
||||||
untouched for second consecutive audit") — same precedent applies
|
|
||||||
here; baseline left as-is.
|
|
||||||
|
|
||||||
## Per-task notes
|
|
||||||
|
|
||||||
- iter eob.1.1: Term::Do arg-walks flip Consume → Borrow in both
|
|
||||||
uniqueness.rs:289 and linearity.rs:506; linearity.rs:42 doc
|
|
||||||
comment split into two bullets matching the new code shape.
|
|
||||||
Inline clarifying comment added on the linearity-side flip
|
|
||||||
mirroring the Ctor-arm convention. cargo build + workspace
|
|
||||||
check-crate tests green.
|
|
||||||
- iter eob.1.2: builtins.rs `float_to_str` (:200) + `int_to_str`
|
|
||||||
(:210) and synth.rs `float_to_str` (:172) + `int_to_str` (:179)
|
|
||||||
all flip `ret_mode: Implicit` → `ret_mode: Own` in lockstep.
|
|
||||||
Workspace build clean. Target test now reports the predicted
|
|
||||||
link-error shape "undefined value @drop_int_to_str_drop_rc_Str"
|
|
||||||
— exactly the gap Task 3 closes.
|
|
||||||
- iter eob.1.3: drop.rs App arm of `drop_symbol_for_binder` gets
|
|
||||||
the Str carve-out `if name == "Str" return ailang_rc_dec`,
|
|
||||||
symmetric to `field_drop_call`'s existing Str arm. The
|
|
||||||
comment block mirrors `field_drop_call`'s wording. cargo build
|
|
||||||
clean.
|
|
||||||
- iter eob.1.4 (observational): both RED tests flip to GREEN with
|
|
||||||
the predicted concrete numbers (allocs == 1, frees == 1 / allocs
|
|
||||||
== 2, frees == 2; live == 0 on both). Full e2e suite (79 tests)
|
|
||||||
all green — no other fixture's RC trace shifted unexpectedly.
|
|
||||||
- iter eob.1.5: two new fixtures
|
|
||||||
(`examples/int_to_print_int_borrow.ail.json`,
|
|
||||||
`examples/heap_str_repeated_print_borrow.ail.json`) + two new
|
|
||||||
e2e tests (`int_arg_to_effect_op_does_not_rc_track`,
|
|
||||||
`heap_str_repeated_print_balances_rc_stats`) appended near
|
|
||||||
e2e.rs:2625. Both new tests PASS first-shot with exact predicted
|
|
||||||
RC stats.
|
|
||||||
- iter eob.1.6: DESIGN.md §Decision 10 gets a new subsection
|
|
||||||
`#### Arg-position policy for compound AST nodes` after the
|
|
||||||
`ret_mode` block. The two arg-position rules (Ctor = Consume,
|
|
||||||
Do = Borrow) are anchored together as language rules, not
|
|
||||||
per-op annotations. One added linking paragraph explains how
|
|
||||||
the Do = Borrow rule cooperates with the `ret_mode == Own`
|
|
||||||
letbinder-trackability rule above to balance heap-Str RC
|
|
||||||
discipline — within the plan's "Boss judgement on placement"
|
|
||||||
carve-out.
|
|
||||||
- iter eob.1.7: WhatsNew.md entry appended at bottom (user-facing
|
|
||||||
wording, no internals). roadmap.md heap-Str ABI entry flipped
|
|
||||||
`[ ]` → `[x]`. Post-22 Prelude `depends on:` line removed (the
|
|
||||||
only dep was Heap-Str, which has shipped). cargo test workspace
|
|
||||||
+ cross_lang.py + compile_check.py all green. check.py noisy
|
|
||||||
latency cluster left pristine per audit-cma/audit-ms precedent.
|
|
||||||
|
|
||||||
## Concerns
|
|
||||||
|
|
||||||
(none — all 7 tasks DONE without review re-loops)
|
|
||||||
|
|
||||||
## Known debt
|
|
||||||
|
|
||||||
- check.py shows recurring noisy `latency.implicit_at_rc.max_us`
|
|
||||||
/ `latency.explicit_at_rc.max_us` tail-jitter, plus
|
|
||||||
`throughput.bench_list_sum.bump_s` swinging +11-15% across
|
|
||||||
back-to-back runs (with reciprocal `gc_over_bump` improvement
|
|
||||||
on the same fixture). Neither family is reachable from this
|
|
||||||
iter's changes (bump_s on bench_list_sum has no effect-ops; the
|
|
||||||
list-sum hot path has no int_to_str / float_to_str calls). The
|
|
||||||
audit-cma and audit-ms journals already document the same
|
|
||||||
noise cluster as "not coupled to milestone, baseline left
|
|
||||||
pristine for two consecutive audits" — same precedent applies;
|
|
||||||
baseline untouched.
|
|
||||||
|
|
||||||
## Files touched
|
|
||||||
|
|
||||||
Code:
|
|
||||||
- crates/ailang-check/src/uniqueness.rs (Term::Do Consume → Borrow)
|
|
||||||
- crates/ailang-check/src/linearity.rs (Term::Do Consume → Borrow + doc-block split)
|
|
||||||
- crates/ailang-check/src/builtins.rs (int_to_str / float_to_str ret_mode → Own)
|
|
||||||
- crates/ailang-codegen/src/synth.rs (int_to_str / float_to_str ret_mode → Own)
|
|
||||||
- crates/ailang-codegen/src/drop.rs (App-arm Str carve-out)
|
|
||||||
|
|
||||||
Tests + fixtures:
|
|
||||||
- crates/ail/tests/e2e.rs (2 new positive tests appended)
|
|
||||||
- examples/int_to_print_int_borrow.ail.json (new)
|
|
||||||
- examples/heap_str_repeated_print_borrow.ail.json (new)
|
|
||||||
|
|
||||||
Docs:
|
|
||||||
- docs/DESIGN.md (Decision 10: arg-position policy subsection)
|
|
||||||
- docs/WhatsNew.md (heap-str-abi milestone close entry)
|
|
||||||
- docs/roadmap.md (Heap-Str ABI P1 closed; Post-22 Prelude
|
|
||||||
depends-on line removed)
|
|
||||||
|
|
||||||
## Stats
|
|
||||||
|
|
||||||
bench/orchestrator-stats/2026-05-12-iter-eob.1.json
|
|
||||||
@@ -1,137 +0,0 @@
|
|||||||
# iter ext-cli.1 — CLI accepts `.ail` (Form A) source files alongside `.ail.json`
|
|
||||||
|
|
||||||
**Date:** 2026-05-12
|
|
||||||
**Started from:** efecbaa
|
|
||||||
**Status:** DONE
|
|
||||||
**Tasks completed:** 4 of 4
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
Closes the loop opened by this morning's `.ailx → .ail` rename. Every
|
|
||||||
path-taking `ail` subcommand (`check`, `build`, `run`, `manifest`,
|
|
||||||
`render`, `prose`, `merge-prose`, `describe`, `emit-ir`, `diff`,
|
|
||||||
`workspace`, `deps`) now accepts a `.ail` (Form A) input as well as
|
|
||||||
`.ail.json` (Form B). For `.ail` paths the subcommand parses through
|
|
||||||
`ailang_surface::parse` in-line and proceeds with the same loaded
|
|
||||||
`Module` value `.ail.json` would have produced; the binary semantics
|
|
||||||
are extension-agnostic.
|
|
||||||
|
|
||||||
Architecture: a new `ailang_surface::{load_module, load_workspace}`
|
|
||||||
pair dispatches on file extension. A new injection point in
|
|
||||||
`ailang_core::workspace::load_workspace_with<F>` lets the surface
|
|
||||||
crate plug in an extension-aware loader without `ailang-core` having
|
|
||||||
to import `ailang-surface` (which would close a crate cycle). Import
|
|
||||||
resolution in `workspace::visit` now prefers a `.ail` sibling over a
|
|
||||||
`.ail.json` sibling of the same module name. The CLI binary's 18
|
|
||||||
`ailang_core::load_*` callsites all switched to the new
|
|
||||||
`ailang_surface::load_*`. Surface parse failures route through the
|
|
||||||
existing `workspace_error_to_diagnostic` channel as a new
|
|
||||||
`SurfaceParse` variant of `WorkspaceLoadError`, so
|
|
||||||
`ail check --json foo.ail` on a malformed `.ail` returns a parseable
|
|
||||||
JSON diagnostic with code `surface_parse_error` instead of the
|
|
||||||
pre-iter misleading `json: expected value at line 1 column 1`
|
|
||||||
fall-through.
|
|
||||||
|
|
||||||
## Per-task notes
|
|
||||||
|
|
||||||
- iter ext-cli.1.1: `WorkspaceLoadError::SurfaceParse { path,
|
|
||||||
message }` variant added to `ailang_core::workspace`;
|
|
||||||
`load_workspace` refactored into a thin wrapper around a new
|
|
||||||
`pub fn load_workspace_with<F>(entry, loader: F)` with `F:
|
|
||||||
Fn(&Path) -> Result<Module, WorkspaceLoadError> + Copy`; `visit`'s
|
|
||||||
signature gained the loader parameter and its import-path
|
|
||||||
construction now does `.ail`-first / `.ail.json`-fallback; new
|
|
||||||
test `load_workspace_with_custom_loader_is_called` pins the
|
|
||||||
injection. `tempfile` added as `ailang-core` dev-dependency.
|
|
||||||
- iter ext-cli.1.2: new `crates/ailang-surface/src/loader.rs` with
|
|
||||||
`pub fn load_module` (extension-dispatching: `.ail` → `parse`,
|
|
||||||
else delegate to `ailang_core::load_module`) and
|
|
||||||
`pub fn load_workspace` (calls `load_workspace_with(entry,
|
|
||||||
load_module)`); `lib.rs` re-exports both. Three integration tests
|
|
||||||
in `crates/ailang-surface/tests/loader.rs` pin the dispatch on
|
|
||||||
each form individually + the mixed-extension workspace case.
|
|
||||||
`tempfile` added as `ailang-surface` dev-dependency.
|
|
||||||
- iter ext-cli.1.3: every `ailang_core::load_module` /
|
|
||||||
`ailang_core::load_workspace` callsite in `crates/ail/src/main.rs`
|
|
||||||
(18 sites total) replaced with `ailang_surface::load_module` /
|
|
||||||
`ailang_surface::load_workspace`. Workspace-build placeholder
|
|
||||||
`W::SurfaceParse { .. } => None` arm added to
|
|
||||||
`workspace_error_to_diagnostic` (replaced in Task 4) so the
|
|
||||||
exhaustive match compiles. Two new E2E tests in
|
|
||||||
`crates/ail/tests/e2e.rs`: `ail_check_accepts_ail_source` and
|
|
||||||
`ail_run_accepts_ail_source_with_same_stdout_as_ail_json`.
|
|
||||||
- iter ext-cli.1.4: placeholder arm replaced with proper
|
|
||||||
`W::SurfaceParse { path, message } => Some(Diagnostic::error(
|
|
||||||
"surface_parse_error", message).with_ctx({"path": ...}))` —
|
|
||||||
mirroring the `SchemaMismatch` arm's builder pattern. DESIGN.md
|
|
||||||
§Decision 6 / "CLI" bullet gained the one-paragraph addendum
|
|
||||||
(verbatim from the plan, dated). New ct1 test
|
|
||||||
`ail_check_json_on_ail_with_syntax_error_returns_structured_diagnostic`
|
|
||||||
pins the JSON-diagnostic shape.
|
|
||||||
|
|
||||||
Plus one E2E coverage test added during Phase 3:
|
|
||||||
`load_workspace_prefers_ail_when_both_extensions_exist` —
|
|
||||||
protects the `.ail`-first precedence when both extensions are
|
|
||||||
present for the same module (the existing 1.2 test only exercised
|
|
||||||
the fallback half).
|
|
||||||
|
|
||||||
## Concerns
|
|
||||||
|
|
||||||
- **Diagnostic code convention drift (Boss-corrected pre-commit).**
|
|
||||||
The plan-specified code `surface_parse_error` used snake_case;
|
|
||||||
every other diagnostic code in the codebase is kebab-case
|
|
||||||
(`type-mismatch`, `unbound-var`, `over-strict-mode`,
|
|
||||||
`reuse-as-shape-mismatch`, ...). Boss verified the convention
|
|
||||||
via `git grep` over `crates/ailang-check/src` and
|
|
||||||
`crates/ail/src` before commit and aligned the literal to
|
|
||||||
`surface-parse-error` at three sites: the `SurfaceParse` arm
|
|
||||||
in `workspace_error_to_diagnostic` (`crates/ail/src/main.rs`)
|
|
||||||
and the two matching strings in
|
|
||||||
`crates/ail/tests/ct1_check_cli.rs` (the rustdoc on the test +
|
|
||||||
the `assert_eq!` against `first["code"]`). The plan file is
|
|
||||||
left at the snake_case form as historical record; the source
|
|
||||||
of truth is now the corrected code.
|
|
||||||
- **bench/check.py first run noisy.** First execution showed 4
|
|
||||||
regressions on `latency.{explicit,implicit}_at_rc.{p99_9,max}_us`
|
|
||||||
(tail-latency tier). Re-runs (×2) returned 0 regressed
|
|
||||||
(one even showed 4 improvements beyond tolerance). This is the
|
|
||||||
same `latency.explicit_at_rc.*` cluster audit-cma and audit-ms
|
|
||||||
already documented as nondeterministic; the iter's actual code
|
|
||||||
change introduces ONE extra `surface_path.is_file()` syscall per
|
|
||||||
import-resolution step plus a single-byte extension check per
|
|
||||||
load, neither of which can move us-scale tail latencies. Net:
|
|
||||||
baseline left pristine, consistent with the last two audits.
|
|
||||||
|
|
||||||
## Known debt
|
|
||||||
|
|
||||||
- The `ail parse` subcommand is still the only way to materialise
|
|
||||||
a stable `.ail.json` snapshot from a `.ail` source. Now that
|
|
||||||
every subcommand silently dispatches `.ail` through the parser,
|
|
||||||
one might argue for a "did you mean `ail parse`?" hint on paths
|
|
||||||
with non-`.ail`/non-`.ail.json` extensions. The roadmap note for
|
|
||||||
ext-cli.1 mentions this as a possible same-iter or follow-up
|
|
||||||
scope; nothing surfaced as load-bearing during impl, deferring.
|
|
||||||
|
|
||||||
## Files touched
|
|
||||||
|
|
||||||
Code:
|
|
||||||
- `crates/ailang-core/Cargo.toml` (+ tempfile dev-dep)
|
|
||||||
- `crates/ailang-core/src/workspace.rs` (SurfaceParse variant,
|
|
||||||
load_workspace_with, visit threading, .ail-first precedence,
|
|
||||||
new unit test)
|
|
||||||
- `crates/ailang-surface/Cargo.toml` (+ tempfile dev-dep)
|
|
||||||
- `crates/ailang-surface/src/lib.rs` (mod loader + re-exports)
|
|
||||||
- `crates/ailang-surface/src/loader.rs` (NEW)
|
|
||||||
- `crates/ailang-surface/tests/loader.rs` (NEW; 4 tests)
|
|
||||||
- `crates/ail/src/main.rs` (18 callsite rewires + SurfaceParse
|
|
||||||
diagnostic arm)
|
|
||||||
- `crates/ail/tests/e2e.rs` (2 new E2E tests)
|
|
||||||
- `crates/ail/tests/ct1_check_cli.rs` (1 new diagnostic-shape test)
|
|
||||||
|
|
||||||
Spec / metadata:
|
|
||||||
- `docs/DESIGN.md` (§Decision 6 / CLI bullet addendum)
|
|
||||||
- `Cargo.lock` (tempfile dev-dep transitive)
|
|
||||||
|
|
||||||
## Stats
|
|
||||||
|
|
||||||
bench/orchestrator-stats/2026-05-12-iter-ext-cli.1.json
|
|
||||||
@@ -1,107 +0,0 @@
|
|||||||
# iter ext-rename — `.ailx` extension renamed to `.ail` across the live toolchain
|
|
||||||
|
|
||||||
**Date:** 2026-05-12
|
|
||||||
**Started from:** 17b370b (post-audit-ms close, working tree clean)
|
|
||||||
**Status:** DONE
|
|
||||||
**Tasks completed:** 1 of 1
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
The surface-form file extension changes from `.ailx` to `.ail`.
|
|
||||||
AILang's authoring surface now uses the same `.ail` stem as its
|
|
||||||
canonical JSON form (`.ail.json`), giving the language a single
|
|
||||||
coherent extension family: `.ail` is the LLM-authored Form A;
|
|
||||||
`.ail.json` is the canonical JSON-AST Form B that
|
|
||||||
`ail check` / `build` / `run` actually loads today.
|
|
||||||
|
|
||||||
The earlier `.ailx` extension carried an unmotivated `x`. The
|
|
||||||
cross-model authoring-form test (cma + ms milestones, closed
|
|
||||||
earlier today) treated `.ailx` purely as a cohort identifier;
|
|
||||||
once the empirical case for keeping a separate authoring surface
|
|
||||||
had been ratified (DESIGN.md §Decision-6 addendum), the awkward
|
|
||||||
extension was the last asymmetry between Form A and Form B.
|
|
||||||
User-initiated rename.
|
|
||||||
|
|
||||||
## Scope
|
|
||||||
|
|
||||||
In scope (touched):
|
|
||||||
|
|
||||||
- 61 example files `examples/**/*.ailx → .ail` (57 top-level +
|
|
||||||
4 `examples/fieldtest/`) renamed via `git mv`.
|
|
||||||
- `experiments/.../rendered/ailx.md → ail.md` rename.
|
|
||||||
- All live toolchain prose and code: `crates/ail/`,
|
|
||||||
`crates/ailang-surface/`, `crates/ailang-core/specs/form_a.md`,
|
|
||||||
`docs/DESIGN.md`, `docs/roadmap.md`, `docs/PROSE_ROUNDTRIP.md`,
|
|
||||||
`bench/reference/*.c`, all of `skills/`, the Cross-Model
|
|
||||||
Authoring experiment crates under
|
|
||||||
`experiments/.../{render,harness,master,rendered,README.md}`.
|
|
||||||
- Cohort identifier `ailx → ail` in the experiment crate (Rust
|
|
||||||
`Cohort::Ailx → Cohort::Ail`, `Form::Ailx → Form::Ail`,
|
|
||||||
per-cohort path component, `{form-only: ailx}` marker,
|
|
||||||
```ailx codefence language tag).
|
|
||||||
|
|
||||||
Out of scope (untouched, by design):
|
|
||||||
|
|
||||||
- `docs/journal-archive.md` (content-frozen per CLAUDE.md).
|
|
||||||
- All `docs/journals/`, `docs/specs/`, `docs/plans/`,
|
|
||||||
`bench/orchestrator-stats/` — historical records describing
|
|
||||||
states as they were.
|
|
||||||
- `experiments/.../runs/` — frozen LLM-output artefacts; the
|
|
||||||
models in those runs actually saw `.ailx`, and renaming the
|
|
||||||
artefacts would falsify the experimental record.
|
|
||||||
|
|
||||||
The exclusion preserves the journals as honest history. Anyone
|
|
||||||
chasing why a 2026-05-11 fieldtest names `.ailx` files will find
|
|
||||||
that the renaming happened today and that the names were correct
|
|
||||||
at the time of writing.
|
|
||||||
|
|
||||||
## Why boss-direct edit (no planner / implement dispatch)
|
|
||||||
|
|
||||||
Same rationale as iter rt.2: the user fully specified the
|
|
||||||
transformation (rename `.ailx → .ail`, rename cohort
|
|
||||||
`ailx → ail`, historical records untouched), down to the three
|
|
||||||
open questions that were resolved up-front via AskUserQuestion
|
|
||||||
(cohort name; treatment of frozen runs; treatment of historical
|
|
||||||
docs). The execution was two `git mv` passes plus one
|
|
||||||
heavily-scoped `sed` over an explicit 41-file whitelist (plus
|
|
||||||
two follow-up files surfaced by the negative-grep). No design
|
|
||||||
judgement deferred to execution time.
|
|
||||||
|
|
||||||
## Files touched (working-tree counts)
|
|
||||||
|
|
||||||
- 61 renames under `examples/`.
|
|
||||||
- 1 rename `experiments/.../rendered/ailx.md → ail.md`.
|
|
||||||
- 35 content-edited files: the 41-file SCOPE whitelist minus
|
|
||||||
the experiment-crate files that had no `ailx` content, plus
|
|
||||||
two follow-up files surfaced by the negative-grep
|
|
||||||
(`examples/bench_list_sum_explicit.ail`'s in-source comment;
|
|
||||||
`experiments/.../rendered/json.md` for three `AILX` acronyms
|
|
||||||
in prose).
|
|
||||||
|
|
||||||
## Verification
|
|
||||||
|
|
||||||
- `cargo build --workspace` clean.
|
|
||||||
- `cargo test --workspace` green; the renamed identifiers
|
|
||||||
`every_ail_fixture_matches_its_json_counterpart` and
|
|
||||||
`parse_then_print_then_parse_is_idempotent_on_every_ail_fixture`
|
|
||||||
in `crates/ailang-surface/tests/round_trip.rs` were run
|
|
||||||
explicitly and pass.
|
|
||||||
- Experiment crate `cargo test` (out-of-workspace) green.
|
|
||||||
- `bench/check.py`, `bench/compile_check.py`, `bench/cross_lang.py`:
|
|
||||||
0 regressed across all three; 6 improved-beyond-tolerance on
|
|
||||||
`latency.explicit_at_rc` (same cluster the previous two audits
|
|
||||||
today already observed; decoupled from this rename — baseline
|
|
||||||
left pristine).
|
|
||||||
- Negative grep: `git grep -nIi 'ailx|Ailx|AILX'` outside the
|
|
||||||
out-of-scope paths returns no matches.
|
|
||||||
|
|
||||||
## Roadmap implications
|
|
||||||
|
|
||||||
The `.ail` extension is now canonical authoring surface but the
|
|
||||||
CLI does not yet accept it — `ail check foo.ail` still produces
|
|
||||||
the misleading JSON-parse error. The existing P2 todo
|
|
||||||
"`ail check`/`build`/`run` accept `.ail` extension" (roadmap.md
|
|
||||||
line 185) is the natural next iteration: it closes the loop
|
|
||||||
opened by today's rename and removes the largest first-command
|
|
||||||
friction for any LLM-author writing in Form A. Promoting that
|
|
||||||
todo to the immediate next dispatch.
|
|
||||||
@@ -1,118 +0,0 @@
|
|||||||
# iter hs.1 — Heap-Str ABI: Static-Str layout migration
|
|
||||||
|
|
||||||
**Date:** 2026-05-12
|
|
||||||
**Started from:** 69bb5f9952fb51af7cdf4e36f77cff1b6cd73c40
|
|
||||||
**Status:** DONE
|
|
||||||
**Tasks completed:** 5 of 5
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
First iter of the heap-Str ABI milestone. Pure codegen-side refactor:
|
|
||||||
language `Str` literals now emit as packed-struct LLVM globals
|
|
||||||
`<{ i64, i64, [N+1 x i8] }> <{ i64 -1, i64 N, [N+1 x i8] c"...\00" }>`
|
|
||||||
carrying a `UINT64_MAX` sentinel rc-header in the first slot, an
|
|
||||||
explicit byte length in the second, and the bytes plus trailing NUL
|
|
||||||
in the array tail. The IR-Str-pointer convention is now uniform: a
|
|
||||||
language `Str` value flowing through the IR is a `ptr` landing on the
|
|
||||||
`len`-field (offset +8 from the header, -8 from the bytes). Every
|
|
||||||
codegen site that hands a `Str` pointer to a `NUL`-terminated-bytes
|
|
||||||
C-API consumer emits a `getelementptr inbounds i8, ptr <v>, i64 8`
|
|
||||||
immediately before the call. The sentinel slot is hardcoded in the
|
|
||||||
global initialiser and not yet observed by any caller — `hs.2` will
|
|
||||||
add the `UINT64_MAX` short-circuit to `ailang_rc_inc` /
|
|
||||||
`ailang_rc_dec` in `runtime/rc.c` to exploit it. Format strings
|
|
||||||
(`%lld\n`, `%g\n`, `true\n`, `false\n`) are unchanged — they are
|
|
||||||
runtime-internal scaffolding, never flow as language `Str` values,
|
|
||||||
and remain in the raw `[N x i8]` shape via the original
|
|
||||||
`intern_string` / `all_strings` path. Acceptance verified
|
|
||||||
end-to-end: full `cargo test --workspace` green (no regressions
|
|
||||||
across 74 e2e fixtures), `bench/compile_check.py` exit 0,
|
|
||||||
`bench/cross_lang.py` exit 0 (byte-identical stdout across all
|
|
||||||
ail/c benchmark pairs).
|
|
||||||
|
|
||||||
## Per-task notes
|
|
||||||
|
|
||||||
- iter hs.1.1: introduced parallel `str_literals` interning table on
|
|
||||||
`Emitter`, `intern_str_literal` fn, workspace-level
|
|
||||||
`all_str_literals` aggregation, and a parallel global-emission
|
|
||||||
loop emitting the packed-struct shape. Switched both
|
|
||||||
`Literal::Str` arms (`emit_const_def`, `lower_term`) to call
|
|
||||||
`intern_str_literal` and materialise a constexpr
|
|
||||||
`getelementptr inbounds (<{ i64, i64, [N x i8] }>, ptr @g, i32 0, i32 1)`
|
|
||||||
landing on the `len`-field. Two pinning tests added.
|
|
||||||
- iter hs.1.2: `io/print_str` arm now emits a `+8 GEP` to land on
|
|
||||||
the bytes pointer before calling `@puts`. One pinning test added.
|
|
||||||
- iter hs.1.3: `eq__Str` arm of `try_emit_primitive_instance_body`
|
|
||||||
now emits `+8 GEP`s on both operands before calling
|
|
||||||
`@ail_str_eq`. One pinning test added.
|
|
||||||
- iter hs.1.4: `compare__Str` arm of `try_emit_primitive_instance_body`
|
|
||||||
now emits `+8 GEP`s on both operands before calling
|
|
||||||
`@ail_str_compare`. One pinning test added.
|
|
||||||
- iter hs.1.5: regenerated `crates/ail/tests/snapshots/hello.ll`
|
|
||||||
via `UPDATE_SNAPSHOTS=1`. Full `cargo test --workspace` revealed
|
|
||||||
a regression in `eq_demo` traced to a fourth codegen site not
|
|
||||||
enumerated in the plan: `lower_eq`'s `"Str"` arm calls `@strcmp`
|
|
||||||
inline (separate from the `eq__Str` instance-method intercept)
|
|
||||||
to lower `==` on `Str` literals and Str-pattern desugaring in
|
|
||||||
`classify_str`. Fixed inline with the same `+8 GEP` shape; one
|
|
||||||
additional pinning test added.
|
|
||||||
|
|
||||||
## Concerns
|
|
||||||
|
|
||||||
- The plan claimed three codegen sites pass a `Str` pointer to a
|
|
||||||
`NUL`-terminated-bytes C-API consumer (`@puts`, `@ail_str_eq`,
|
|
||||||
`@ail_str_compare`); in fact a fourth site exists in
|
|
||||||
`lower_eq`'s `"Str"` arm, calling `@strcmp` directly. This is
|
|
||||||
the dispatch path for the surface-level `==` operator on
|
|
||||||
`Str` and for `(pat-lit "...")` desugar against a `Str`
|
|
||||||
scrutinee. The fourth site is required to meet the iter's
|
|
||||||
stated acceptance criterion (byte-identical stdout); fixing it
|
|
||||||
was discovered via the e2e regression in `eq_demo` rather than
|
|
||||||
via the per-task IR-shape checks. The fix has the same shape
|
|
||||||
as Tasks 2-4 and the corresponding pinning test
|
|
||||||
(`lower_eq_str_calls_strcmp_with_bytes_pointer`) is added. The
|
|
||||||
plan's "Files this plan creates or modifies" enumeration
|
|
||||||
should list `crates/ailang-codegen/src/lib.rs:2520-2529` as a
|
|
||||||
fifth modify site for any future re-execution.
|
|
||||||
- The plan's two test fixtures in Task 1 contained schema-syntax
|
|
||||||
errors that prevented compilation: `ConstDef.body` (actual:
|
|
||||||
`value`), `Term::App { f, args }` (actual: `callee, args, tail`),
|
|
||||||
and `Term::App { Term::Var "io/print_str", ... }` for what is
|
|
||||||
actually a `Term::Do` effect op. Tests were adapted to compile
|
|
||||||
while preserving the substantive assertions verbatim. Same
|
|
||||||
applies to the test in Task 2 (`Term::App` → `Term::Do` for the
|
|
||||||
`io/print_str` invocation and added `effects: ["IO"]` on the
|
|
||||||
main fn so the typecheck-side dispatch finds the effect).
|
|
||||||
These were small per-test fixture adaptations, not changes to
|
|
||||||
the asserted IR shape.
|
|
||||||
|
|
||||||
## Known debt
|
|
||||||
|
|
||||||
- The sentinel value `-1` (`UINT64_MAX`) is hardcoded in the
|
|
||||||
global initialiser. `hs.2` adds the matching short-circuit
|
|
||||||
check in `ailang_rc_inc` / `ailang_rc_dec` so static-Str
|
|
||||||
pointers can flow through generic RC paths without
|
|
||||||
ref-count traffic. Until `hs.2` lands, no caller actually
|
|
||||||
observes the sentinel — static `Str` slabs are still
|
|
||||||
invisible to the RC runtime by construction (the call sites
|
|
||||||
for `inc` / `dec` against `Str` values are still gated by
|
|
||||||
the same uniqueness-inference pass that elided them
|
|
||||||
pre-iter).
|
|
||||||
|
|
||||||
## Files touched
|
|
||||||
|
|
||||||
- `crates/ailang-codegen/src/lib.rs` — Emitter field, intern fn,
|
|
||||||
workspace aggregation, global emission, two `Literal::Str` arm
|
|
||||||
switches, four C-API-callsite `+8 GEP` adjustments
|
|
||||||
(`io/print_str`, `eq__Str`, `compare__Str`, `lower_eq` Str arm),
|
|
||||||
six new IR-shape pinning tests in the test module.
|
|
||||||
- `crates/ail/tests/snapshots/hello.ll` — regenerated snapshot
|
|
||||||
showing the new packed-struct global and the `+8 GEP` before
|
|
||||||
`@puts`. No other snapshot under
|
|
||||||
`crates/ail/tests/snapshots/` changed (other snapshots contain
|
|
||||||
only format-string globals which kept the raw `[N x i8]`
|
|
||||||
shape).
|
|
||||||
|
|
||||||
## Stats
|
|
||||||
|
|
||||||
`bench/orchestrator-stats/2026-05-12-iter-hs.1.json`
|
|
||||||
@@ -1,81 +0,0 @@
|
|||||||
# iter hs.2 — Static-Str sentinel-slot retrofit
|
|
||||||
|
|
||||||
**Date:** 2026-05-12
|
|
||||||
**Started from:** 51a096cf8354362783d8caf2ec4b35179d935a7a
|
|
||||||
**Status:** DONE
|
|
||||||
**Tasks completed:** 3 of 3
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
Second iter of the heap-Str ABI milestone. Surgical codegen-side
|
|
||||||
retrofit of hs.1's static-`Str` global layout: per the amended parent
|
|
||||||
spec (`docs/specs/2026-05-12-heap-str-abi.md`) the sentinel rc-header
|
|
||||||
slot drops. Language `Str` literals now emit as
|
|
||||||
`<{ i64, [N+1 x i8] }> <{ i64 N, [N+1 x i8] c"…\00" }>` instead of
|
|
||||||
the hs.1 shape `<{ i64, i64, [N+1 x i8] }> <{ i64 -1, i64 N, …}>`. The
|
|
||||||
explicit `len` field becomes the first (and only non-byte) field on
|
|
||||||
which the IR-`Str` pointer lands via constexpr-GEP `i32 0, i32 0`. The
|
|
||||||
`+8 GEP` consumer-side calls (`@puts`, `@ail_str_eq`, `@ail_str_compare`,
|
|
||||||
inline `@strcmp`) do **not** change — the bytes still sit 8 bytes past
|
|
||||||
the `len`-field. Static-Str pointers are kept out of `ailang_rc_dec`
|
|
||||||
at the codegen level via move-tracking (iter 18d.3) and non-escape
|
|
||||||
lowering (iter 18b), so no header slot is needed at the global.
|
|
||||||
|
|
||||||
Change surface: three format-string sites and two doc-comments in
|
|
||||||
`crates/ailang-codegen/src/lib.rs`, rename + assertion update of two
|
|
||||||
IR-shape tests in the same crate's test module, plus snapshot
|
|
||||||
regeneration of `crates/ail/tests/snapshots/hello.ll`. No runtime
|
|
||||||
changes, no checker changes, no new fixtures. Static-Str globals are
|
|
||||||
now 8 bytes smaller per literal.
|
|
||||||
|
|
||||||
Acceptance verified end-to-end: `cargo test --workspace` green (every
|
|
||||||
test result `ok`, 0 failures across the workspace), `bench/cross_lang.py`
|
|
||||||
exit 0 (byte-identical stdout across all `ail/c` benchmark pairs),
|
|
||||||
`bench/compile_check.py` exit 0, `bench/check.py` exit 0 within the
|
|
||||||
known `latency.explicit_at_rc.*` nondeterminism tolerance.
|
|
||||||
|
|
||||||
## Per-task notes
|
|
||||||
|
|
||||||
- iter hs.2.1: renamed `static_str_global_uses_packed_struct_with_sentinel_and_len`
|
|
||||||
→ `static_str_global_uses_packed_struct_with_len`; updated its
|
|
||||||
doc-comment and assertion substring (struct loses leading `i64, `,
|
|
||||||
initialiser loses leading `i64 -1, `). Updated assertion in
|
|
||||||
`static_str_callsite_pointer_is_payload_via_constexpr_gep` (name
|
|
||||||
preserved — "payload" still describes the `len`-field-as-pointer-target;
|
|
||||||
struct loses leading `i64, `, GEP indices `i32 0, i32 1` → `i32 0, i32 0`).
|
|
||||||
RED confirmed: both tests fail against hs.1 emission with the expected
|
|
||||||
assertion-substring mismatch. The four `+8 GEP` tests stayed green.
|
|
||||||
- iter hs.2.2: flipped the global-emission loop format string at
|
|
||||||
`lib.rs:471` to the 2-field shape; flipped both `Literal::Str` arms
|
|
||||||
(`emit_const_def` ~ `lib.rs:933`, `lower_term` ~ `lib.rs:1255`) to
|
|
||||||
emit constexpr-GEP `i32 0, i32 0` onto the 2-field struct type;
|
|
||||||
updated the two doc-comments on the `str_literals` field
|
|
||||||
(`lib.rs:561`) and on `intern_str_literal` (`lib.rs:2596`) to the
|
|
||||||
amended-spec wording. All five edits exactly per plan text. Static_str_
|
|
||||||
tests GREEN, `_calls_` tests GREEN, full `cargo test -p ailang-codegen`
|
|
||||||
GREEN (33 tests).
|
|
||||||
- iter hs.2.3: regenerated `crates/ail/tests/snapshots/hello.ll` via
|
|
||||||
`UPDATE_SNAPSHOTS=1 cargo test -p ail --test ir_snapshot hello`. The
|
|
||||||
two affected lines (the `@.str_hello_str_0` global and its constexpr-
|
|
||||||
GEP at `%v1`) flipped to the new shape; the outer `i64 8` byte offset
|
|
||||||
preserved. Workspace `cargo test --workspace` green; `cross_lang.py`,
|
|
||||||
`compile_check.py`, `check.py` all green.
|
|
||||||
|
|
||||||
## Concerns
|
|
||||||
|
|
||||||
(none)
|
|
||||||
|
|
||||||
## Known debt
|
|
||||||
|
|
||||||
(none)
|
|
||||||
|
|
||||||
## Files touched
|
|
||||||
|
|
||||||
- `crates/ailang-codegen/src/lib.rs` — three format strings, two
|
|
||||||
doc-comments, two test names + assertions + doc-comments
|
|
||||||
- `crates/ail/tests/snapshots/hello.ll` — regenerated snapshot
|
|
||||||
(2 lines content change, byte offsets preserved)
|
|
||||||
|
|
||||||
## Stats
|
|
||||||
|
|
||||||
bench/orchestrator-stats/2026-05-12-iter-hs.2.json
|
|
||||||
@@ -1,111 +0,0 @@
|
|||||||
# iter hs.3 — Heap-Str runtime additions
|
|
||||||
|
|
||||||
**Date:** 2026-05-12
|
|
||||||
**Started from:** 187d04814d4485bee1bb85f6f7433c2f3d146fb7
|
|
||||||
**Status:** DONE
|
|
||||||
**Tasks completed:** 2 of 2
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
Third iter of the heap-Str ABI milestone. Single-file additive change
|
|
||||||
to `runtime/str.c`: three new symbols (private `static char
|
|
||||||
*str_alloc(uint64_t)` slab helper plus two extern formatters
|
|
||||||
`ailang_int_to_str(int64_t)` and `ailang_float_to_str(double)`), the
|
|
||||||
supporting `<stdint.h>` / `<stdio.h>` / `<stdlib.h>` includes, and an
|
|
||||||
`extern void *ailang_rc_alloc(size_t)` declaration. No IR-side caller
|
|
||||||
is wired yet — hs.4 will land codegen + checker + linker work that
|
|
||||||
makes the formatters live builtins.
|
|
||||||
|
|
||||||
One deviation from the plan: the `extern` declaration of
|
|
||||||
`ailang_rc_alloc` was promoted from plain-external to
|
|
||||||
`__attribute__((weak))`. The plan's assertion that the new symbols
|
|
||||||
sit "present-but-unreferenced" across all `--alloc` strategies turned
|
|
||||||
out to be wrong at the link layer — `ailang_int_to_str` /
|
|
||||||
`ailang_float_to_str` are publicly-visible `T` symbols, so the linker
|
|
||||||
preserves them and their transitive callees (including
|
|
||||||
`ailang_rc_alloc`) in every binary regardless of program-level usage.
|
|
||||||
Under `--alloc=gc` and `--alloc=bump` the build pipeline does not
|
|
||||||
link `rc.c`, so the unresolved-symbol error fires at link time and
|
|
||||||
took out 11 e2e tests on the first regression sweep. The weak
|
|
||||||
attribute makes the reference link-safe in isolation: under gc/bump
|
|
||||||
it resolves to NULL (never dereferenced — no IR caller exists yet);
|
|
||||||
under rc the strong definition wins as usual. The attribute is
|
|
||||||
retained after hs.4 unconditionally links rc.c (where it becomes a
|
|
||||||
no-op) to keep `runtime/str.c` link-safe in isolation, matching the
|
|
||||||
spec's "single-file abstraction" framing of the runtime split.
|
|
||||||
|
|
||||||
Acceptance verified: `cargo test --workspace` (zero failures across
|
|
||||||
the full test surface), `bench/cross_lang.py` (25/25 metrics stable,
|
|
||||||
exit 0), `bench/compile_check.py` (24/24 metrics stable, exit 0),
|
|
||||||
`bench/check.py` (zero regressed, exit 0; same five
|
|
||||||
`latency.explicit_at_rc.*` improvements outside tolerance that
|
|
||||||
audit-cma and audit-ms have already documented as
|
|
||||||
not-coupled-to-milestone — baseline left untouched per established
|
|
||||||
practice). `clang -c -O2 -Wall -Werror` on `runtime/str.c` standalone
|
|
||||||
also exits 0 with no warnings.
|
|
||||||
|
|
||||||
## Per-task notes
|
|
||||||
|
|
||||||
- iter hs.3.1: appended three `#include` lines (`stdint.h`,
|
|
||||||
`stdio.h`, `stdlib.h`), one weak `extern void *ailang_rc_alloc`
|
|
||||||
declaration, and three new function bodies (`str_alloc`,
|
|
||||||
`ailang_int_to_str`, `ailang_float_to_str`) to `runtime/str.c`.
|
|
||||||
Compile-check via `clang -c -O2 -Wall -Werror` green; nm
|
|
||||||
symbol-table inspection: 4 public `T` symbols (`ail_str_eq`,
|
|
||||||
`ail_str_compare`, `ailang_int_to_str`, `ailang_float_to_str`),
|
|
||||||
`str_alloc` absent at `-O2` (inlined into both callers — only
|
|
||||||
visible as `t str_alloc` at `-O0`; static-linkage invariant
|
|
||||||
preserved), `ailang_rc_alloc` shown as `w` (weak undefined).
|
|
||||||
- iter hs.3.2: ran the four regression gates. First-pass sweep
|
|
||||||
surfaced the 11-test linker failure under `--alloc=gc`/`bump`
|
|
||||||
caused by the plan's "rc_alloc reference is harmless if
|
|
||||||
unreferenced" assumption; weak-attribute repair re-ran clean. All
|
|
||||||
four gates green on the re-sweep.
|
|
||||||
|
|
||||||
## Concerns
|
|
||||||
|
|
||||||
- The plan's Task 1 Step 7 nm-output expected-line block ("`U
|
|
||||||
ailang_rc_alloc`" and "`0000000000000000 t str_alloc`") is
|
|
||||||
inaccurate at the optimisation level the plan itself specifies
|
|
||||||
(`-O2`): the grep regex does not match `ailang_rc_alloc`, and
|
|
||||||
`clang -O2` inlines the 2-line `str_alloc` helper into both
|
|
||||||
callers so no `t str_alloc` symbol survives the compilation unit.
|
|
||||||
Both deviations are `-O2` consequences, not bugs — the
|
|
||||||
semantically meaningful invariants (`str_alloc` is static and
|
|
||||||
absent from `T`; `ailang_rc_alloc` is a non-`T` undefined
|
|
||||||
reference) both hold. A future plan author writing nm-output
|
|
||||||
checks should inspect at `-O0` for symbol presence and at `-O2`
|
|
||||||
for what actually ships, or relax the expected text from
|
|
||||||
literal-match to invariant-check (e.g. "no `T str_alloc`",
|
|
||||||
"`ailang_rc_alloc` is `U` or `w`").
|
|
||||||
- The plan's core "no build-pipeline change in hs.3" boundary held,
|
|
||||||
but the supporting claim "symbols are present in every binary
|
|
||||||
regardless of allocator choice" was false at the link layer. The
|
|
||||||
weak-attribute repair makes it true at the cost of one extra
|
|
||||||
attribute and a long-form doc-comment explaining the cross-
|
|
||||||
allocator linking story. A future plan that defers a build-
|
|
||||||
pipeline change should explicitly call out cross-translation-unit
|
|
||||||
symbol dependencies as a gating concern.
|
|
||||||
|
|
||||||
## Known debt
|
|
||||||
|
|
||||||
- hs.4 will wire `ailang_int_to_str` / `ailang_float_to_str` from
|
|
||||||
the IR side and unconditionally link `rc.c`; at that point the
|
|
||||||
`__attribute__((weak))` on the `ailang_rc_alloc` declaration
|
|
||||||
becomes a no-op (strong definitions always satisfy weak
|
|
||||||
references). The attribute is intentionally retained — see the
|
|
||||||
doc-comment in `runtime/str.c` — to keep the translation unit
|
|
||||||
link-safe in isolation, matching the parent spec's framing of
|
|
||||||
`runtime/str.c` as the heap-Str ABI's single point of contact.
|
|
||||||
|
|
||||||
## Files touched
|
|
||||||
|
|
||||||
- `runtime/str.c` — three new functions appended after
|
|
||||||
`ail_str_compare`, plus three new `#include` lines and one weak
|
|
||||||
`extern` declaration after the existing include block. Existing
|
|
||||||
`ail_str_eq` / `ail_str_compare` bodies and the file's top
|
|
||||||
comment block untouched.
|
|
||||||
|
|
||||||
## Stats
|
|
||||||
|
|
||||||
bench/orchestrator-stats/2026-05-12-iter-hs.3.json
|
|
||||||
@@ -1,260 +0,0 @@
|
|||||||
# iter hs.4 — IR + checker + linker wiring for int_to_str / float_to_str
|
|
||||||
|
|
||||||
**Date:** 2026-05-12
|
|
||||||
**Started from:** 1f832c028a419665ecc7ddbcbdd5dd1a16d35e83
|
|
||||||
**Status:** DONE_WITH_CONCERNS
|
|
||||||
**Tasks completed:** 4 of 4
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
Fourth iter of the heap-Str ABI milestone. Wires the hs.3 runtime
|
|
||||||
symbols `ailang_int_to_str` and `ailang_float_to_str` into live
|
|
||||||
AILang builtins observable at every layer of the toolchain. After
|
|
||||||
this iter `do io/print_str(int_to_str(42))` builds and prints
|
|
||||||
`"42\n"` — the originating acceptance goal of the milestone is met.
|
|
||||||
Four pipeline layers landed in lockstep: (1) checker installs
|
|
||||||
`int_to_str : (Int) -> Str` next to the existing `float_to_str`,
|
|
||||||
synth.rs gets the parallel arm; (2) codegen IR-header unconditionally
|
|
||||||
declares both runtime externs, `Emitter::lower_app` gains an
|
|
||||||
`int_to_str` arm and replaces the `CodegenError::Internal` body of
|
|
||||||
`float_to_str` with the actual call emission, `is_static_callee`'s
|
|
||||||
builtin-callee whitelist extends to recognise `int_to_str`; (3) the
|
|
||||||
build pipeline hoists `runtime/rc.c`'s clang-compile-and-link out of
|
|
||||||
the `AllocStrategy::Rc` arm into the unconditional path next to
|
|
||||||
`runtime/str.c` (the `__attribute__((weak))` on str.c's
|
|
||||||
`ailang_rc_alloc` extern becomes the documented no-op the hs.3
|
|
||||||
journal anticipated); (4) two IR-shape tests pin the new lowerings,
|
|
||||||
four E2E tests cover stdout + RC-stats, four `.ail.json` fixtures
|
|
||||||
exercise them, the stale Str-arm comment in `drop.rs`'s
|
|
||||||
`field_drop_call` is refreshed to the dual-realisation framing.
|
|
||||||
|
|
||||||
One internal contradiction in the plan surfaced during Task 4
|
|
||||||
fixture validation: the plan's literal `ret_mode: Implicit` for
|
|
||||||
both `int_to_str` and `float_to_str` (Task 1 Step 1 code block,
|
|
||||||
copied verbatim from the pre-hs.4 `float_to_str` stub) prevents the
|
|
||||||
plan's literal RC-stats assertion `allocs == frees && live == 0`
|
|
||||||
(Task 4 Step 5) from holding — under Implicit ret-mode the
|
|
||||||
let-binder for an `App`-shape value is not trackable in
|
|
||||||
`is_rc_heap_allocated` (drop.rs:464-475), so no scope-close
|
|
||||||
`ailang_rc_dec` is emitted. Per the carrier's "make the reasonable
|
|
||||||
call and continue" mandate, shipped is the conservative form
|
|
||||||
(plan's literal `Implicit`) with weakened test assertions
|
|
||||||
(`allocs >= 1` instead of `allocs == frees && live == 0`) and the
|
|
||||||
substantive design call queued as known debt. See Concerns for the
|
|
||||||
full failure mode + the speculative-fix attempt + revert sequence.
|
|
||||||
|
|
||||||
Acceptance verified: full `cargo test --workspace` green; the four
|
|
||||||
new E2E tests pass; the two new IR-shape pins pass; the five IR
|
|
||||||
snapshot tests pass after `UPDATE_SNAPSHOTS=1` regen (the new
|
|
||||||
unconditional `declare ptr @ailang_int_to_str(i64)` / `declare ptr
|
|
||||||
@ailang_float_to_str(double)` lines now appear in every snapshot);
|
|
||||||
`bench/cross_lang.py` 25/25 stable; `bench/compile_check.py` 24/24
|
|
||||||
stable; `bench/check.py` 0 regressed first run / 1 flapping
|
|
||||||
`bench_list_sum.bump_s` +10.51% vs 10.0% tolerance on second run,
|
|
||||||
matching the same noise-cluster pattern the hs.3 journal + audit-cma
|
|
||||||
audit-ms have documented as not-coupled-to-milestone, baseline left
|
|
||||||
pristine. Hello.ail builds and runs identically under all three
|
|
||||||
`--alloc` strategies post-rc.c-hoist.
|
|
||||||
|
|
||||||
## Per-task notes
|
|
||||||
|
|
||||||
- iter hs.4.1: installed `int_to_str : (Int) -> Str` in the checker
|
|
||||||
(builtins.rs:203-212, immediately after the existing `float_to_str`
|
|
||||||
entry); added the parallel `install_int_to_str_signature` unit
|
|
||||||
test (builtins.rs:461-466); added the lockstep arm in
|
|
||||||
`crates/ailang-codegen/src/synth.rs:174-180`. Both signature tests
|
|
||||||
PASS. Diff vs `float_to_str`: only the name and the param type
|
|
||||||
(`Type::int()` vs `Type::float()`) differ. `ret_mode: Implicit` kept
|
|
||||||
per the plan's literal text.
|
|
||||||
- iter hs.4.2: extended the IR-header preamble at
|
|
||||||
`crates/ailang-codegen/src/lib.rs:530-538` with two unconditional
|
|
||||||
`declare` lines for `@ailang_int_to_str(i64) -> ptr` and
|
|
||||||
`@ailang_float_to_str(double) -> ptr`; replaced the
|
|
||||||
`CodegenError::Internal` body of the `float_to_str` arm at
|
|
||||||
`lib.rs:1907-1925` with a `lower_term`+`fresh_ssa`+call-emission
|
|
||||||
triple matching the `int_to_float` arm's structural shape; added
|
|
||||||
the new `int_to_str` arm immediately above (lib.rs:1890-1906) so
|
|
||||||
both heap-Str formatters are co-located; extended the
|
|
||||||
`is_static_callee` builtin-callee whitelist at lib.rs:2124-2138
|
|
||||||
with `"int_to_str"` (strictly required so `Term::Var { name:
|
|
||||||
"int_to_str" }` reaches lower_app's chain instead of the
|
|
||||||
UnknownVar fallback — the existing `float_to_str` entry was already
|
|
||||||
in the whitelist from iter 22-floats.3); appended the two IR-shape
|
|
||||||
tests at the bottom of `mod tests` (lib.rs:4089-4170), confirmed
|
|
||||||
RED-for-the-right-reason (UnknownVar then Internal-not-implemented)
|
|
||||||
before the lowering arms were wired, GREEN after; updated the
|
|
||||||
stale Str-arm comment in `drop.rs`'s `field_drop_call` at lines
|
|
||||||
374-393 to the dual-realisation framing (heap-Str + static-Str
|
|
||||||
share consumer ABI; static-Str pointers kept out of `ailang_rc_dec`
|
|
||||||
via codegen-level move-tracking from iter 18d.3 + non-escape
|
|
||||||
lowering from iter 18b). Full `cargo test -p ailang-codegen` green
|
|
||||||
(35 tests).
|
|
||||||
- iter hs.4.3: hoisted the `runtime/rc.c` compile-and-link block out
|
|
||||||
of the `AllocStrategy::Rc` arm into the unconditional path next to
|
|
||||||
`runtime/str.c` at `crates/ail/src/main.rs:2294-2321`. The
|
|
||||||
`Rc`-arm body is now an empty doc-commented stub (retaining the
|
|
||||||
arm so `--alloc=rc` remains a valid CLI flag — it still drives
|
|
||||||
codegen's `@ailang_rc_alloc`-vs-`@GC_malloc` selection in the IR
|
|
||||||
header). Smoke-verified hello.ail builds + prints "Hello, AILang."
|
|
||||||
under all three alloc strategies. Five IR snapshot tests
|
|
||||||
regenerated via `UPDATE_SNAPSHOTS=1` to reflect the two new
|
|
||||||
unconditional `declare` lines added in Task 2; the snapshot diff
|
|
||||||
is exactly the two added lines (verified by inspecting hello.ll).
|
|
||||||
Full workspace re-sweep green afterwards.
|
|
||||||
- iter hs.4.4: created four fixtures (`int_to_str_smoke.ail.json`,
|
|
||||||
`float_to_str_smoke.ail.json`, `int_to_str_drop_rc.ail.json`,
|
|
||||||
`str_field_in_adt_heap.ail.json`) and appended four E2E tests at
|
|
||||||
the end of `crates/ail/tests/e2e.rs`. The float-literal JSON form
|
|
||||||
is `{"kind":"float","bits":"<16-char hex>"}` per the actual
|
|
||||||
`Literal::Float` schema (the plan's `value: <ieee_bits_as_u64>`
|
|
||||||
was a Rust-side description; the canonical wire form is the
|
|
||||||
hex-string per the `hex_u64` serde helper). 3.5_f64.to_bits() =
|
|
||||||
0x400c000000000000. The two RC-stats tests assert
|
|
||||||
`allocs >= 1` / `allocs >= 2 && frees >= 1` instead of the plan's
|
|
||||||
literal `allocs == frees && live == 0` — the discovery + revert
|
|
||||||
+ weakening sequence is in Concerns. Stdout pins: int → `"42\n"`,
|
|
||||||
float → `"3.5\n"` (libc %g, C locale default, glibc dev target).
|
|
||||||
All four E2E PASS. `bench/cross_lang.py` 25/25 stable;
|
|
||||||
`bench/compile_check.py` 24/24 stable; `bench/check.py` 0
|
|
||||||
regressed first run, 1 flapping bench-noise second run within
|
|
||||||
documented variability.
|
|
||||||
|
|
||||||
## Concerns
|
|
||||||
|
|
||||||
- **Plan's literal assertions in Task 4 Step 5 cannot all hold
|
|
||||||
simultaneously given the plan's other choices.** The plan
|
|
||||||
installs `int_to_str` with `ret_mode: ailang_core::ast::ParamMode::
|
|
||||||
Implicit` (Task 1 Step 1 code block), but `is_rc_heap_allocated`
|
|
||||||
at `crates/ailang-codegen/src/drop.rs:464-475` only fires on App
|
|
||||||
arms whose callee carries `ret_mode == Own`. With Implicit, the
|
|
||||||
let-binder `let s = int_to_str(42)` is not trackable, no
|
|
||||||
scope-close `ailang_rc_dec` is emitted, and the heap-Str slab
|
|
||||||
leaks. RC-stats observed on the plan-literal pipeline:
|
|
||||||
`int_to_str_drop_rc` produces `allocs=1 frees=0 live=1`;
|
|
||||||
`str_field_in_adt_heap` produces `allocs=2 frees=1 live=1` (the
|
|
||||||
inner heap-Str is freed via the ADT's match-arm pattern walk
|
|
||||||
through `field_drop_call`'s Str arm, but the outer ADT cell
|
|
||||||
leaks via the same Implicit-ret-mode story applied to its own
|
|
||||||
let-binder). The plan's literal Task 4 Step 5 asserts
|
|
||||||
`allocs == frees && live == 0` for both — over-strict given the
|
|
||||||
rest of the plan's literal choices.
|
|
||||||
|
|
||||||
**Speculative-fix attempt + revert.** I tentatively switched both
|
|
||||||
`int_to_str` and `float_to_str` to `ret_mode: ParamMode::Own` in
|
|
||||||
both the checker (`builtins.rs`) and the codegen synth-arm
|
|
||||||
(`synth.rs`), and added a Str-primitive carve-out in
|
|
||||||
`drop_symbol_for_binder` at `drop.rs` (returning `ailang_rc_dec`
|
|
||||||
directly for `Type::Con { name: "Str" }` instead of the
|
|
||||||
`drop_<m>_Str` fallthrough that resolves to a non-existent
|
|
||||||
symbol). After rebuild the leak counts were unchanged.
|
|
||||||
Root cause is upstream: the uniqueness analyser at
|
|
||||||
`crates/ailang-check/src/uniqueness.rs:289-292` walks every
|
|
||||||
`Term::Do` arg in `Position::Consume`, so `consume_count` for
|
|
||||||
`s` in `do io/print_str(s)` is 1, and the let-arm dec at
|
|
||||||
`lib.rs:1440` is gated off by `consume_count == 0`. The
|
|
||||||
uniqueness analyser is treating an effect-op arg as a Consume
|
|
||||||
(transfer-of-ownership) when in fact `io/print_str` only reads
|
|
||||||
the pointer through libc's `@puts` and never dec's. Refining the
|
|
||||||
effect-op arg-mode story (do-args of pure-read effect-ops should
|
|
||||||
be Borrow for ptr-typed args) is a design call that goes beyond
|
|
||||||
hs.4's wiring scope.
|
|
||||||
|
|
||||||
Reverted the three speculative edits to match the plan's literal
|
|
||||||
`Implicit` (more conservative, matches the existing `float_to_str`
|
|
||||||
signature convention). Weakened the test assertions to the
|
|
||||||
achievable invariant. Substantive fix is queued as known debt;
|
|
||||||
see below.
|
|
||||||
|
|
||||||
- **bench/check.py flapping `bench_list_sum.bump_s` `+10.51%` on
|
|
||||||
the second run** (tolerance `10.0%`, overshoot 0.5pp). First run
|
|
||||||
was 0-regressed. The bump-allocator path was not touched by
|
|
||||||
hs.4. The hs.3 journal and audit-cma / audit-ms have documented
|
|
||||||
the same noise-cluster pattern (multi-metric +/- swings on
|
|
||||||
`latency.explicit_at_rc.*` and various `*.bump_s` /
|
|
||||||
`*.gc_over_bump` metrics) as not-coupled-to-milestone across the
|
|
||||||
past three iters + two audits, baseline left pristine. Treating
|
|
||||||
this as the same noise; baseline left pristine again.
|
|
||||||
|
|
||||||
- **`drop_symbol_for_binder` Str-primitive fallthrough is wrong if
|
|
||||||
any future iter switches `int_to_str` to Own.** The current
|
|
||||||
fallback at `crates/ailang-codegen/src/drop.rs:534-549` for an
|
|
||||||
`App` whose ret-type is `Type::Con { name: "Str" }` (no dot)
|
|
||||||
produces `format!("drop_{m}_Str", m = self.module_name)` —
|
|
||||||
e.g. `drop_int_to_str_drop_rc_Str`, a non-existent symbol.
|
|
||||||
Currently dead code because Implicit-ret-mode gates the path off.
|
|
||||||
The known-debt item below covers the matched fix.
|
|
||||||
|
|
||||||
## Known debt
|
|
||||||
|
|
||||||
- **Refine the uniqueness-walker's effect-op arg-mode** so
|
|
||||||
`Term::Do` ptr-typed args of pure-read effect-ops (initially
|
|
||||||
`io/print_str` — only effect-op consuming ptr-typed Str) are
|
|
||||||
classified as Borrow rather than Consume. Pre-hs.4, no fixture
|
|
||||||
exercised the path "Own-returning App bound to a let, then
|
|
||||||
consumed by io/print_str" — `int_to_str`'s landing is the first
|
|
||||||
case. The wider question is which effect-ops carry which arg-modes
|
|
||||||
(most existing effect-ops take prim-typed args where the
|
|
||||||
consume/borrow distinction is moot; io/print_str + io/print_float
|
|
||||||
+ io/print_bool + io/print_int all take by-value primitives;
|
|
||||||
io/print_str is the lone ptr-typed slot). A spec-level decision
|
|
||||||
is needed before the analyser change.
|
|
||||||
|
|
||||||
- **Switch `int_to_str` / `float_to_str` to `ret_mode: Own` once the
|
|
||||||
uniqueness fix lands**, AND fix `drop_symbol_for_binder` to route
|
|
||||||
primitive-typed ret-cons (`Str` at a minimum) to `ailang_rc_dec`
|
|
||||||
shallow free — the speculative-fix attempt above showed both
|
|
||||||
pieces are needed in lockstep. Then strengthen the two RC-stats
|
|
||||||
test assertions from `allocs >= N` to `allocs == frees && live == 0`
|
|
||||||
per the plan's original Task 4 Step 5 intent. Either a follow-up
|
|
||||||
hs-tidy iter or a fresh hs.5+ slot.
|
|
||||||
|
|
||||||
- **Single-value smokes only (per plan).** Edge cases for
|
|
||||||
`int_to_str` (0, -1, i64::MAX, i64::MIN) and `float_to_str` (NaN,
|
|
||||||
+Inf, -Inf, subnormals, exact-integer-doubles) are deferred to a
|
|
||||||
follow-up tidy iter if hs.4's regression sweep does not surface a
|
|
||||||
need. The current single-value smokes are sufficient to
|
|
||||||
demonstrate the codegen + runtime path is wired end-to-end.
|
|
||||||
|
|
||||||
- **The `__attribute__((weak))` on `ailang_rc_alloc` in
|
|
||||||
`runtime/str.c`** is now a permanent no-op (strong definition
|
|
||||||
from rc.c always wins post-hs.4). Retained intentionally per the
|
|
||||||
hs.3 journal Concerns note + the doc-comment in str.c, to keep
|
|
||||||
the translation unit link-safe in isolation as the heap-Str ABI's
|
|
||||||
single point of contact.
|
|
||||||
|
|
||||||
## Files touched
|
|
||||||
|
|
||||||
- Modified (11 files):
|
|
||||||
- `crates/ailang-check/src/builtins.rs` — `int_to_str` signature
|
|
||||||
install + unit test
|
|
||||||
- `crates/ailang-codegen/src/synth.rs` — `int_to_str` type-replay
|
|
||||||
arm (lockstep partner of the checker insert)
|
|
||||||
- `crates/ailang-codegen/src/lib.rs` — IR-header preamble (2 new
|
|
||||||
declares); `is_static_callee` whitelist extension; `lower_app`
|
|
||||||
`int_to_str` arm + rewritten `float_to_str` arm; 2 new IR-shape
|
|
||||||
tests at the bottom of `mod tests`
|
|
||||||
- `crates/ailang-codegen/src/drop.rs` — `field_drop_call`'s
|
|
||||||
Str-arm comment refreshed to the dual-realisation framing
|
|
||||||
- `crates/ail/src/main.rs` — `runtime/rc.c` compile-and-link
|
|
||||||
hoisted to the unconditional path; Rc-arm body emptied with
|
|
||||||
doc comment
|
|
||||||
- `crates/ail/tests/e2e.rs` — 4 new E2E tests appended
|
|
||||||
- `crates/ail/tests/snapshots/hello.ll`,
|
|
||||||
`crates/ail/tests/snapshots/list.ll`,
|
|
||||||
`crates/ail/tests/snapshots/max3.ll`,
|
|
||||||
`crates/ail/tests/snapshots/sum.ll`,
|
|
||||||
`crates/ail/tests/snapshots/ws_main.ll` — golden-IR regen via
|
|
||||||
`UPDATE_SNAPSHOTS=1`; diff is exactly the two new unconditional
|
|
||||||
`declare` lines from the IR-header extension
|
|
||||||
|
|
||||||
- Created (4 files):
|
|
||||||
- `examples/int_to_str_smoke.ail.json` — `do io/print_str(int_to_str(42))`
|
|
||||||
- `examples/float_to_str_smoke.ail.json` — `do io/print_str(float_to_str(3.5))`
|
|
||||||
- `examples/int_to_str_drop_rc.ail.json` — `let s = int_to_str(42) in
|
|
||||||
do io/print_str(s)` (RC-stats fixture)
|
|
||||||
- `examples/str_field_in_adt_heap.ail.json` — `type Boxed = | Box(Str)`
|
|
||||||
carrying an `int_to_str` result, pattern-matched + printed
|
|
||||||
|
|
||||||
## Stats
|
|
||||||
|
|
||||||
bench/orchestrator-stats/2026-05-12-iter-hs.4.json
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
# iter ms.1 — Pipeline anyhow-chain preservation
|
|
||||||
|
|
||||||
**Date:** 2026-05-12
|
|
||||||
**Started from:** 8689f7a2386e0d7993609c374d91984bc28d215c
|
|
||||||
**Status:** DONE
|
|
||||||
**Tasks completed:** 3 of 3
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
Two-character fix at `pipeline.rs:118`: `format!("check: {e}")` →
|
|
||||||
`format!("check: {e:#}")`. Alternate-Display on `anyhow::Error` joins
|
|
||||||
the full cause chain with `: ` on one line, restoring the
|
|
||||||
`serde_json` parse-error leaf that plain Display dropped. Survives
|
|
||||||
`strip_locations` (regex only collapses `\nCaused by:` blocks).
|
|
||||||
|
|
||||||
The bug surfaced during ms.2 planning recon: JSON-cohort feedback in
|
|
||||||
the cma.3 baseline showed uninformative top-line `"parsing JSON in
|
|
||||||
/tmp/..."` while AILX cohort saw the full `ail parse` diagnostic, an
|
|
||||||
asymmetry that flattered AILX. ms.1 lands the fix before ms.2's live
|
|
||||||
IONOS re-runs so the comparative dataset is internally consistent.
|
|
||||||
|
|
||||||
A pinning unit test (`check_format_preserves_anyhow_chain`) asserts
|
|
||||||
both layers survive `{:#}`. Per the plan's Reviewer note, no helper
|
|
||||||
extracted for direct RED-first — two-character fix doesn't justify
|
|
||||||
the refactor.
|
|
||||||
|
|
||||||
Test count: 14 (was 13). `cargo check` clean.
|
|
||||||
|
|
||||||
## Concerns
|
|
||||||
|
|
||||||
- Harness integration tests (`budget_abort`, `mock_full_run`,
|
|
||||||
`verify_references`) need `AIL_BIN` set or `ail` on PATH. The
|
|
||||||
14-green count assumes `AIL_BIN=target/release/ail`. Pre-existing
|
|
||||||
cma.2 harness behaviour.
|
|
||||||
|
|
||||||
## Files touched
|
|
||||||
|
|
||||||
- `experiments/2026-05-12-cross-model-authoring/harness/src/pipeline.rs`
|
|
||||||
|
|
||||||
## Stats
|
|
||||||
|
|
||||||
bench/orchestrator-stats/2026-05-12-iter-ms.1.json
|
|
||||||
@@ -1,124 +0,0 @@
|
|||||||
# iter ms.2 — Multi-subject run + DESIGN.md addendum extension
|
|
||||||
|
|
||||||
**Date:** 2026-05-12
|
|
||||||
**Started from:** 8689f7a2386e0d7993609c374d91984bc28d215c
|
|
||||||
**Status:** DONE
|
|
||||||
**Tasks completed:** 6 of 6
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
Two live IONOS harness invocations executed with the ms.1 fix in
|
|
||||||
place: a Qwen3-Coder-Next retroactive re-run
|
|
||||||
(`runs/2026-05-12-080864/`, RUN_STATUS=ok) and a first-ever
|
|
||||||
CodeLlama-13b-Instruct run (`runs/2026-05-12-9197fd/`, RUN_STATUS=ok
|
|
||||||
at the run level; 2 of 8 cells terminated as api_failure on turn 1
|
|
||||||
with zero tokens — transient IONOS-side terminal error, disclosed
|
|
||||||
in the addendum and below). DESIGN.md §"Decision 6 / Empirical
|
|
||||||
addendum (2026-05-12)" replaced with a four-column per-cell table
|
|
||||||
plus a framing paragraph; roadmap P3 "Multi-subject expansion"
|
|
||||||
entry removed.
|
|
||||||
|
|
||||||
Both subjects point the same way on every measured axis: AILX
|
|
||||||
cohort is cheaper than JSON cohort on prompt tokens (Qwen 61%,
|
|
||||||
CodeLlama 80%) and on completion tokens (Qwen 23%, CodeLlama 82%),
|
|
||||||
and AILX reached-green ≥ JSON reached-green for each subject (Qwen
|
|
||||||
ties at 1/4; CodeLlama strictly dominates 2/4 vs 0/4). Three of the
|
|
||||||
four first-attempt-green cells across the two subjects are AILX.
|
|
||||||
Decision 6 is not ratified — n=1 per subject across two subjects is
|
|
||||||
not statistical robustness — but the second data point points
|
|
||||||
consistently. The roadmap entry was removed because the brainstorm
|
|
||||||
spec was scoped to two subjects, not because the universal claim is
|
|
||||||
closed; a future ratification iteration would re-open it.
|
|
||||||
|
|
||||||
Anomaly disclosure: the Qwen re-run shifted by one cell vs the cma.3
|
|
||||||
baseline (AILX 2/4 → 1/4, JSON 1/4 → 1/4) at identical
|
|
||||||
temperature=0; this is IONOS-side state-of-day noise, not an effect
|
|
||||||
of the ms.1 feedback fix, which only changes JSON-cohort prompt
|
|
||||||
content. The two CodeLlama JSON api_failure cells (`t2_length`,
|
|
||||||
`t4_count_zeros`) were not re-run — the carrier constraint forbids
|
|
||||||
exploratory token spend, and the addendum discloses the partial
|
|
||||||
sample directly.
|
|
||||||
|
|
||||||
Roadmap P3 "Multi-subject expansion" entry removed in this iter.
|
|
||||||
|
|
||||||
## Per-task notes
|
|
||||||
|
|
||||||
- iter ms.2.1: Qwen retroactive re-run via harness — new run dir
|
|
||||||
`experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/`,
|
|
||||||
RUN_STATUS=ok, 8/8 cells populated. AIL_BIN set to
|
|
||||||
`target/release/ail` (no `~/.local/bin/ail`); harness picks it up
|
|
||||||
via env-var resolution at `pipeline.rs:42`.
|
|
||||||
- iter ms.2.2: CodeLlama-13b-Instruct first run via harness — new
|
|
||||||
run dir
|
|
||||||
`experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/`,
|
|
||||||
model id `meta-llama/CodeLlama-13b-Instruct-hf` (Boss-pre-flighted
|
|
||||||
IONOS catalogue id), RUN_STATUS=ok. 6/8 cells informative; 2/8
|
|
||||||
json cells (`t2_length`, `t4_count_zeros`) `api_failure` on turn 1
|
|
||||||
zero tokens.
|
|
||||||
- iter ms.2.3: Per-cell metrics computed via direct scores.csv
|
|
||||||
read — see addendum table. Total milestone token spend: prompt
|
|
||||||
501,985 + completion 23,391 = 525,376.
|
|
||||||
- iter ms.2.4: DESIGN.md lines 604-636 replaced with new
|
|
||||||
four-column addendum block (~50 lines including the framing
|
|
||||||
paragraph). Subjects named with canonical IONOS model ids and
|
|
||||||
run-dir pointers; framing paragraph reports direction agreement,
|
|
||||||
failure-class symmetry, first-attempt tally, api_failure anomaly,
|
|
||||||
and Qwen-rerun noise. Scope paragraph extended to "two subjects,
|
|
||||||
n=1 each".
|
|
||||||
- iter ms.2.5: roadmap.md lines 253-268 removed (P3 multi-subject
|
|
||||||
entry + context note); journal file written. INDEX.md is NOT
|
|
||||||
edited by this agent per the Iron Law; the Boss appends the
|
|
||||||
ms.2 line at commit time. Suggested INDEX line (per plan Task 5
|
|
||||||
Step 3):
|
|
||||||
`- 2026-05-12 — iter ms.2: Qwen retroactive re-run + first CodeLlama-13b-Instruct run; DESIGN.md §Decision-6 addendum extended to four cells; roadmap P3 removed → 2026-05-12-iter-ms.2.md`
|
|
||||||
- iter ms.2.6: Spec acceptance criteria sweep (1-7 all green); 14
|
|
||||||
harness tests still pass.
|
|
||||||
|
|
||||||
## Spec acceptance sweep
|
|
||||||
|
|
||||||
1. ms.1 `pipeline.rs:118` reads `format!("check: {e:#}")` + RED→GREEN unit test → green (ms.1 working tree).
|
|
||||||
2. ms.1 existing 14 harness tests still pass → green (verified Task 6 Step 2).
|
|
||||||
3. CodeLlama-13b-Instruct-hf runs to completion against IONOS endpoint, RUN_STATUS=ok at run level → green; model id recorded above.
|
|
||||||
4. Qwen3-Coder-Next re-run completes, RUN_STATUS=ok, 8 cells; distinct dir from cma.3 baseline → green.
|
|
||||||
5. DESIGN.md §"Decision 6 / Empirical addendum" extended with follow-up paragraph + four-cell-shaped table → green.
|
|
||||||
6. Roadmap P3 entry removed; one-line mirror above → green.
|
|
||||||
7. Per-iter journal (this file) written → green. INDEX.md ms.2 entry is Boss-side per the Iron Law; suggested line is in the end-report.
|
|
||||||
|
|
||||||
## Concerns
|
|
||||||
|
|
||||||
- 2 of 4 CodeLlama JSON cells are `api_failure` (terminal IONOS
|
|
||||||
error on turn 1). Per the Plan's branch (c) and the carrier's
|
|
||||||
"no exploratory re-runs" constraint, not re-attempted. The
|
|
||||||
addendum and journal disclose this directly; the
|
|
||||||
CodeLlama-JSON column's prompt-token average is therefore over 2
|
|
||||||
informative cells, not 4. Effect on the AILX-cheaper conclusion:
|
|
||||||
none in direction; the prompt-token gap is preserved over the
|
|
||||||
two informative JSON cells. If a future iteration re-runs
|
|
||||||
CodeLlama at lower IONOS load, this should resolve.
|
|
||||||
- Qwen-re-run vs cma.3-baseline drift (1 cell) at identical
|
|
||||||
temperature=0 is IONOS noise on a single deterministic call. A
|
|
||||||
future ratification iteration should add ≥3 repetitions per
|
|
||||||
subject to bound this noise, not rely on a single
|
|
||||||
temperature-zero call.
|
|
||||||
|
|
||||||
## Known debt
|
|
||||||
|
|
||||||
- The pre-existing 3 warnings in `harness/src/ionos.rs` lines 86,
|
|
||||||
125, 137 (`last_err` assigned but never read) survived ms.1
|
|
||||||
and ms.2. Not in scope here; cma.2 inheritance.
|
|
||||||
- Decision 6 universal-claim status is still "second data point,
|
|
||||||
not verdict". A future ratification iteration would need ≥3
|
|
||||||
subjects with statistical robustness. Out of scope for ms.
|
|
||||||
|
|
||||||
## Files touched
|
|
||||||
|
|
||||||
- `docs/DESIGN.md` (addendum extended)
|
|
||||||
- `docs/roadmap.md` (P3 entry removed)
|
|
||||||
- `experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/` (created by Qwen run)
|
|
||||||
- `experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/` (created by CodeLlama run)
|
|
||||||
- `docs/journals/2026-05-12-iter-ms.2.md` (this file)
|
|
||||||
- `bench/orchestrator-stats/2026-05-12-iter-ms.2.json` (stats)
|
|
||||||
|
|
||||||
## Stats
|
|
||||||
|
|
||||||
bench/orchestrator-stats/2026-05-12-iter-ms.2.json
|
|
||||||
@@ -1,128 +0,0 @@
|
|||||||
# iter rt.1 — Roundtrip-Invariant audit tests
|
|
||||||
|
|
||||||
**Date:** 2026-05-12
|
|
||||||
**Started from:** 10ccd1406c886270d105cd7d5a08ec4b447030cd
|
|
||||||
**Status:** DONE
|
|
||||||
**Tasks completed:** 4 of 4
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
Three new tests landed; one existing test (handwritten exhibits)
|
|
||||||
was replaced with a dynamic counterpart. No production code or
|
|
||||||
DESIGN.md changes (those would be later iterations if any gap
|
|
||||||
surfaced). All three new tests passed on first observation —
|
|
||||||
the corpus carries enough breadth that the spec-intended audit
|
|
||||||
deliverable is the clean signal "no current gap" rather than a
|
|
||||||
defect list.
|
|
||||||
|
|
||||||
## Per-task notes
|
|
||||||
|
|
||||||
- iter rt.1.1: `crates/ailang-surface/tests/round_trip.rs` —
|
|
||||||
replaced static three-pair `handwritten_exhibits_match_json_fixtures`
|
|
||||||
with dynamic `every_ailx_fixture_matches_its_json_counterpart`
|
|
||||||
(and helper `list_ailx_fixtures`). Module doc rewritten to drop
|
|
||||||
the "three hand-written exhibits" phrasing.
|
|
||||||
- iter rt.1.2: `Cargo.toml` — `tempfile = "3"` added to
|
|
||||||
`[workspace.dependencies]`. `crates/ail/Cargo.toml` — new
|
|
||||||
`[dev-dependencies]` section with `tempfile.workspace = true`
|
|
||||||
and `blake3.workspace = true`.
|
|
||||||
- iter rt.1.3: `crates/ailang-core/tests/schema_coverage.rs` — new
|
|
||||||
file. Exhaustive AST visitor (`Def`, `Term`, `Pattern`, `Literal`,
|
|
||||||
`Type`, `ParamMode`) against the fixture corpus; flat `VariantTag`
|
|
||||||
enum + `EXPECTED_VARIANTS` array; assertion that every variant is
|
|
||||||
observed at least once. No `_` wildcards in outer match arms —
|
|
||||||
AST drift fails the build, not the test.
|
|
||||||
- iter rt.1.4: `crates/ail/tests/roundtrip_cli.rs` — new file.
|
|
||||||
Subprocess pipeline `ail render → tempfile → ail parse`, BLAKE3
|
|
||||||
hash identity on canonical bytes, for every `examples/*.ail.json`.
|
|
||||||
|
|
||||||
## Audit findings
|
|
||||||
|
|
||||||
- `every_ailx_fixture_matches_its_json_counterpart`:
|
|
||||||
PASS. `.ailx cross-check ok (57 paired, 0 parse-only)`.
|
|
||||||
All 57 `.ailx` fixtures parse and produce canonical bytes
|
|
||||||
byte-equal to their same-stem `.ail.json` counterpart. Today
|
|
||||||
no `.ailx` exists without a JSON counterpart.
|
|
||||||
|
|
||||||
- `every_ast_variant_is_observed_in_the_fixture_corpus`:
|
|
||||||
PASS. `schema coverage ok: 34 variants observed across 136 fixtures`.
|
|
||||||
All 34 expected `VariantTag` entries (5 `Def` + 13 `Term` + 4
|
|
||||||
`Pattern` + 5 `Literal` + 4 `Type` + 3 `ParamMode`) are exercised
|
|
||||||
by at least one fixture. No load failures across the 136-file
|
|
||||||
corpus.
|
|
||||||
|
|
||||||
- `cli_render_then_parse_preserves_canonical_bytes_on_every_fixture`:
|
|
||||||
PASS. `CLI roundtrip ok for 136 fixtures`. Every `examples/*.ail.json`
|
|
||||||
survives `ail render → tempfile → ail parse` with BLAKE3 hash
|
|
||||||
identity on canonical bytes.
|
|
||||||
|
|
||||||
- `cargo test --workspace --quiet` (Step 4.5): PASS, no
|
|
||||||
regressions in the pre-existing test suite.
|
|
||||||
|
|
||||||
## Concerns
|
|
||||||
|
|
||||||
- Plan-text errata in Task 3's visitor scaffold. The plan in
|
|
||||||
`docs/plans/rt.1.md` Step 3.2 destructured AST nodes with field
|
|
||||||
names that do not match `crates/ailang-core/src/ast.rs` (the
|
|
||||||
authoritative source). Step 3.4 of the plan explicitly anticipated
|
|
||||||
this and prescribed inline correction by reading `ast.rs` and
|
|
||||||
adjusting the destructuring. The corrections applied:
|
|
||||||
- `Def::Fn(fd)`: dropped the `for m in &fd.params { visit_param_mode(m, …) }`
|
|
||||||
loop (`fd.params: Vec<String>` is parameter names, not modes).
|
|
||||||
- `Def::Const(cd)`: walks `cd.value` (the actual field name), not
|
|
||||||
`cd.body`.
|
|
||||||
- `Def::Class(cd)`: walks `m.ty` plus optional `m.default: Option<Term>`
|
|
||||||
on each `ClassMethod`; no `m.body`/`m.params` fields exist on
|
|
||||||
`ClassMethod`.
|
|
||||||
- `Def::Instance(id)`: walks `id.type_: Type` (singular) and
|
|
||||||
`id.methods`; no `id.args: Vec<Type>` field exists.
|
|
||||||
`InstanceMethod` has only `name` and `body`.
|
|
||||||
- `Term::App`: `{ callee, args, .. }` (not `{ f, args }`).
|
|
||||||
- `Term::Let`: `{ value, body, .. }` (not `{ bound, body }`).
|
|
||||||
- `Term::LetRec`: `{ ty, body, in_term, .. }` (per actual AST:
|
|
||||||
`name, ty, params, body, in_term`), not the plan's
|
|
||||||
`{ bindings, body }`.
|
|
||||||
- `Term::If`: `{ cond, then, else_ }` (not `{ cond, then_b, else_b }`).
|
|
||||||
- `Term::Do`: `{ args, .. }` only (no `stmts`/`ret` fields).
|
|
||||||
- `Term::Lam`: `{ param_tys, ret_ty, body, .. }` (no `modes` field
|
|
||||||
exists on `Lam`).
|
|
||||||
- `Term::Seq`: `{ lhs, rhs }` (not `{ stmts, ret }`).
|
|
||||||
- ParamMode discovery routed through `visit_type` on
|
|
||||||
`Type::Fn { param_modes, ret_mode, .. }` — the only place
|
|
||||||
`ParamMode` appears in the AST. The plan tried to visit
|
|
||||||
ParamMode from `Def::Fn.params` (wrong type) and `Term::Lam.modes`
|
|
||||||
(non-existent field).
|
|
||||||
|
|
||||||
The spec-compliance intent of Step 3.2 ("every AST enum variant is
|
|
||||||
visited at least once via an exhaustive match with no `_` wildcard")
|
|
||||||
is preserved; only the destructuring patterns adapted to the actual
|
|
||||||
AST. All 34 expected variants observe coverage in the corpus, and
|
|
||||||
the build remains a tripwire for future AST additions.
|
|
||||||
|
|
||||||
- Plan template note: a future `planner`/`brainstorm` pass on
|
|
||||||
tests-against-AST might cheaply read `ast.rs` and emit a
|
|
||||||
destructuring-correct scaffold instead of relying on the
|
|
||||||
implementer to repair under the Step 3.4 escape hatch. Not
|
|
||||||
rt.1 work.
|
|
||||||
|
|
||||||
## Known debt
|
|
||||||
|
|
||||||
- None added by this iter. The three new tests are pure readers
|
|
||||||
and add no production-code surface; their existence pins three
|
|
||||||
invariants that future iters can use to detect regression
|
|
||||||
rather than rediscover.
|
|
||||||
|
|
||||||
## Files touched
|
|
||||||
|
|
||||||
- `Cargo.toml` — workspace dep `tempfile = "3"`.
|
|
||||||
- `Cargo.lock` — auto-updated by cargo for `tempfile`.
|
|
||||||
- `crates/ail/Cargo.toml` — new `[dev-dependencies]` section.
|
|
||||||
- `crates/ailang-surface/tests/round_trip.rs` — module doc
|
|
||||||
rewrite; `handwritten_exhibits_match_json_fixtures` →
|
|
||||||
`every_ailx_fixture_matches_its_json_counterpart` (+ helper).
|
|
||||||
- `crates/ailang-core/tests/schema_coverage.rs` — new file.
|
|
||||||
- `crates/ail/tests/roundtrip_cli.rs` — new file.
|
|
||||||
|
|
||||||
## Stats
|
|
||||||
|
|
||||||
bench/orchestrator-stats/2026-05-12-iter-rt.1.json
|
|
||||||
@@ -1,71 +0,0 @@
|
|||||||
# iter rt.2 — Roundtrip Invariant anchored at top level in DESIGN.md
|
|
||||||
|
|
||||||
**Date:** 2026-05-12
|
|
||||||
**Started from:** b6046bbf (post-rt.1, working tree clean)
|
|
||||||
**Status:** DONE
|
|
||||||
**Tasks completed:** 1 of 1
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
DESIGN.md gains a new top-level section `## Roundtrip Invariant`,
|
|
||||||
positioned between Decision 11 and `## Mangling scheme`. The
|
|
||||||
section states both directions of the `.ail.json` ↔ `.ailx`
|
|
||||||
bijection, names the Float-bits-hex encoding as the mechanism
|
|
||||||
that keeps Floats *inside* the invariant, lists the four
|
|
||||||
enforcement tests by path, and explains why the property is
|
|
||||||
top-level (load-bearing on language identity, not on Decision 6's
|
|
||||||
surface-design rationale).
|
|
||||||
|
|
||||||
Decision 6 Constraint 2 is rewritten as an upward cross-reference
|
|
||||||
to the new top-level section. The substantive content of the
|
|
||||||
constraint now lives at the anchor; Decision 6 records that its
|
|
||||||
surface design must satisfy the anchored invariant.
|
|
||||||
|
|
||||||
## Why boss-direct edit (no planner / implement dispatch)
|
|
||||||
|
|
||||||
The spec's Architecture #1 fully specified the content (statement
|
|
||||||
both directions, Float-mechanism subsection, enforcement-points
|
|
||||||
list, why-top-level justification, Decision 6 cross-reference).
|
|
||||||
No design judgement was deferred to execution time; the iter is a
|
|
||||||
deterministic projection of the spec onto DESIGN.md prose. Plan +
|
|
||||||
implement dispatch for a pure docs edit with all content
|
|
||||||
pre-specified is overhead without value, per CLAUDE.md "trivial
|
|
||||||
mechanical edits" carve-out (the LOC budget is exceeded but the
|
|
||||||
judgement budget is zero).
|
|
||||||
|
|
||||||
## Files touched
|
|
||||||
|
|
||||||
- `docs/DESIGN.md`:
|
|
||||||
- Inserted new `## Roundtrip Invariant` section (Z. 1753+, just
|
|
||||||
before `## Mangling scheme`).
|
|
||||||
- Rewrote Decision 6 Constraint 2 (Z. 249-253) as an upward
|
|
||||||
cross-reference to the new section.
|
|
||||||
|
|
||||||
## Verification
|
|
||||||
|
|
||||||
`cargo test --workspace` — all tests green, no regressions. The
|
|
||||||
schema-drift tests in `crates/ailang-core/tests/design_schema_drift.rs`
|
|
||||||
scan DESIGN.md for anchors and were the most likely place for the
|
|
||||||
DESIGN.md edit to surface false-positive failures; they remain
|
|
||||||
green.
|
|
||||||
|
|
||||||
## Milestone status
|
|
||||||
|
|
||||||
The roundtrip-invariant milestone has now satisfied acceptance
|
|
||||||
criteria 1-7 from `docs/specs/2026-05-12-roundtrip-invariant.md`:
|
|
||||||
|
|
||||||
1. ✓ Top-level `## Roundtrip Invariant` section in DESIGN.md;
|
|
||||||
Decision 6 Constraint 2 carries upward cross-reference. (rt.2)
|
|
||||||
2. ✓ `every_ailx_fixture_matches_its_json_counterpart` dynamic in
|
|
||||||
`crates/ailang-surface/tests/round_trip.rs`. (rt.1)
|
|
||||||
3. ✓ `crates/ailang-core/tests/schema_coverage.rs` with
|
|
||||||
exhaustive-match visitor; 34/34 variants observed. (rt.1)
|
|
||||||
4. ✓ `crates/ail/tests/roundtrip_cli.rs` with BLAKE3 identity
|
|
||||||
over `ail render` → tempfile → `ail parse`. (rt.1)
|
|
||||||
5. ✓ `cargo test --workspace` green with all four tests. (rt.1 + rt.2)
|
|
||||||
6. ✓ No roundtrip gaps surfaced; no render/parse code changes
|
|
||||||
needed in this milestone. (rt.1)
|
|
||||||
7. ✓ Tests are pure readers; the CLI test's only filesystem side
|
|
||||||
effect is `tempfile::TempDir`. (rt.1)
|
|
||||||
|
|
||||||
Audit skill follows for milestone close.
|
|
||||||
@@ -1,244 +0,0 @@
|
|||||||
# audit-24 — milestone close (Show + print rewire)
|
|
||||||
|
|
||||||
**Date:** 2026-05-13
|
|
||||||
**Milestone:** Show + print rewire (number-24 family)
|
|
||||||
**Iters covered:** 24.1 (`f38bad8`, shipped 2026-05-12) → 24.2 (`3286117`) → 24.3 (`246b5c7`)
|
|
||||||
**Range from previous close:** `0dcdaab..0cfb3f6` (audit-ct-tidy → iter-24.3 INDEX)
|
|
||||||
**Status:** drift_found — `24.tidy` queued
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
The Show + print milestone shipped in three iters: 24.1 installed
|
|
||||||
`bool_to_str` + `str_clone` runtime + codegen primitives at heap-Str
|
|
||||||
ABI level (pre-mq audit lineage); 24.2 added `class Show` + four
|
|
||||||
primitive instances (Int/Bool/Str/Float) to the prelude and migrated
|
|
||||||
the 22b user-Show fixture corpus to `TShow`/`tshow` to eliminate
|
|
||||||
post-prelude.Show ambiguity; 24.3 added `fn print` with explicit-let
|
|
||||||
body, three E2E fixtures (positive 4-prim, user-ADT, negative
|
|
||||||
NoInstance), an IR-shape pin on `print__Int.body`, a Show-aware
|
|
||||||
NoInstance diagnostic, plus three compiler-path repairs surfaced by
|
|
||||||
the user-ADT E2E (canonical-form normalisation of
|
|
||||||
`MonoTarget::FreeFn::type_args`, codegen cross-module reference
|
|
||||||
fallback, FreeFnCall constraint-residual push).
|
|
||||||
|
|
||||||
Architect drift review surfaces 7 items: three [high] (missing DESIGN.md
|
|
||||||
documentation of the three iter-24.3 strengthenings: canonical-form
|
|
||||||
`type_args`, post-mono cross-module-ref invariant, FreeFnCall constraint-
|
|
||||||
residual push; missing codegen-level pin for the import_map-fallback
|
|
||||||
path; constraint-residual push currently fires only in the dot-qualified
|
|
||||||
FreeFnCall branch), three [medium] (`unwrap_or_default` in the
|
|
||||||
method-name lookup masks class-index drift; `normalize_type_for_lookup`
|
|
||||||
duplicated across two `mono.rs` sites with manual-lockstep invariant;
|
|
||||||
negative-test coverage is single-shape — only `f : Int -> Int`, not
|
|
||||||
user-type-without-instance), and one [low] (bench `architect_sweeps.sh`
|
|
||||||
noise on three pre-milestone-24 DESIGN.md lines — not new drift).
|
|
||||||
|
|
||||||
Bench: `bench/check.py` exit 1 with 2 regressed / 1 improved metrics
|
|
||||||
(metric identities migrate between runs — first run hit
|
|
||||||
`latency.implicit_at_rc.max_us +209%`, second run hit
|
|
||||||
`latency.explicit_at_rc.max_us +66%` + `bench_list_sum.bump_s +11.9%`).
|
|
||||||
`bench/compile_check.py` exit 1 with 3-5 regressed metrics across
|
|
||||||
runs (metric identities migrate: first run hit `build_O0_ms.*`
|
|
||||||
cluster within tolerance, second run hit `check_ms.*` cluster with
|
|
||||||
several +25-34% deltas). `bench/cross_lang.py` exit 0, 25/25 stable.
|
|
||||||
**9th consecutive audit observation** of the documented noise envelope
|
|
||||||
covering `latency.implicit_at_rc.*` / `latency.explicit_at_rc.*` /
|
|
||||||
`bench_list_sum.bump_s` / `check_ms.*` metric-cluster migration
|
|
||||||
across consecutive runs. Conservative-call convention has held the
|
|
||||||
baseline pristine across 8 prior audits since `audit-cma`; this
|
|
||||||
audit extends the lineage to 9 with the same conservative call —
|
|
||||||
the metric-migration-between-runs is itself attribution evidence
|
|
||||||
that the signal is variance, not regression.
|
|
||||||
|
|
||||||
Resolution path: `24.tidy` iteration covers 5 actionable drift
|
|
||||||
items (three [high] + two [medium] documentable/refactorable). The
|
|
||||||
[medium] "negative-test coverage single-shape" item moves to the
|
|
||||||
roadmap P3 backlog. The [low] bench_sweep noise is carry-on.
|
|
||||||
|
|
||||||
## Architect drift items
|
|
||||||
|
|
||||||
### [high-1] Three iter-24.3 strengthenings undocumented in DESIGN.md
|
|
||||||
|
|
||||||
**Paths:** `crates/ailang-check/src/mono.rs:687-696` + `:1287-1294`,
|
|
||||||
`crates/ailang-codegen/src/lib.rs:1980-2000` + `:2200-2244` +
|
|
||||||
`:2776-2816`, `crates/ailang-check/src/lib.rs:2820-2865`,
|
|
||||||
`docs/DESIGN.md` §"Monomorphisation" or new subsection.
|
|
||||||
|
|
||||||
Iter 24.3 silently strengthened three load-bearing mono / codegen /
|
|
||||||
synth invariants without documenting them in DESIGN.md:
|
|
||||||
|
|
||||||
- (a) `MonoTarget::FreeFn::type_args` carries canonical (`<owner>.<bare>`)
|
|
||||||
types post-collection via `normalize_type_for_lookup`. Pre-iter
|
|
||||||
invariant was "subst.apply result"; new invariant is
|
|
||||||
"subst.apply result normalised to registry-lookup form".
|
|
||||||
- (b) Post-mono synthesised bodies may carry cross-module references
|
|
||||||
to modules their source template didn't import. Codegen falls back
|
|
||||||
to direct `module_user_fns` / `module_def_ail_types` lookup when
|
|
||||||
the prefix isn't in the current module's `import_map`.
|
|
||||||
- (c) FreeFnCall synth pushes one `ResidualConstraint` per declared
|
|
||||||
forall-constraint with rigid vars substituted by fresh metavars.
|
|
||||||
Pre-iter, the arm had `constraints: _` (constraints ignored);
|
|
||||||
latent for milestone-23 fixtures because all primitive instances
|
|
||||||
discharged silently.
|
|
||||||
|
|
||||||
Future planner / implementer work that touches these surfaces will
|
|
||||||
not see the strengthenings without a doc anchor.
|
|
||||||
|
|
||||||
**Outcome:** fix in `24.tidy` (doc-only).
|
|
||||||
|
|
||||||
### [high-2] Codegen import_map fallback path has no unit-level pin
|
|
||||||
|
|
||||||
**Path:** `crates/ailang-codegen/src/lib.rs:2200-2244` + `:1980-2000` +
|
|
||||||
`:2776-2816`.
|
|
||||||
|
|
||||||
The three cross-module-fallback arms (`resolve_top_level_fn`,
|
|
||||||
`lower_app` cross-module arm, `synth_with_extras` Var arm) introduce
|
|
||||||
a new dispatch path: try `import_map`, fall back to direct
|
|
||||||
`module_user_fns` / `module_def_ail_types` lookup. The fallback is
|
|
||||||
**only** justified by post-mono synthesised cross-module references.
|
|
||||||
No codegen-level unit test pins the failure mode; the only test that
|
|
||||||
would catch a regression is the E2E `show_user_adt.ail.json`. If a
|
|
||||||
future refactor tightens `resolve_top_level_fn` back to import_map-only,
|
|
||||||
the regression surfaces only at E2E (slow to bisect).
|
|
||||||
|
|
||||||
**Outcome:** fix in `24.tidy` — add a unit pin asserting the fallback
|
|
||||||
resolves for a synthesised `prelude.print__T` → `<user_module>.show__T`
|
|
||||||
pair without going through `import_map`.
|
|
||||||
|
|
||||||
### [high-3] FreeFnCall constraint-residual push covers only dot-qualified branch
|
|
||||||
|
|
||||||
**Path:** `crates/ailang-check/src/lib.rs:2820-2865`.
|
|
||||||
|
|
||||||
The constraint-residual push happens **only** in the dot-qualified
|
|
||||||
`prefix.suffix` synth branch (the only branch that populates
|
|
||||||
`free_fn_owner = Some(...)`). Bare-name references to polymorphic free
|
|
||||||
fns that resolve through other branches (locals shadow,
|
|
||||||
`env.module_globals` direct hit at the current module, etc.) do not
|
|
||||||
push residuals for declared constraints. For prelude poly fns this is
|
|
||||||
fine because the auto-injected prelude call-path always lands in the
|
|
||||||
dot-qualified branch — but the invariant is implicit and undocumented.
|
|
||||||
The same "silent unknown variable from codegen" failure mode existed
|
|
||||||
pre-iter-24.3 for any milestone-23 negative case (`ne` on a type
|
|
||||||
without `Eq` instance, etc.); the iter-24.3 fix covers Show only
|
|
||||||
because that's what the negative E2E exercised.
|
|
||||||
|
|
||||||
**Outcome:** fix in `24.tidy` — generalise the constraint-residual push
|
|
||||||
to all FreeFnCall synth branches, OR document the invariant
|
|
||||||
"poly free fns always reach synth via the dot-qualified branch" and
|
|
||||||
pin it with a unit test.
|
|
||||||
|
|
||||||
### [medium-1] `unwrap_or_default` in method-name lookup masks class-index drift
|
|
||||||
|
|
||||||
**Path:** `crates/ailang-check/src/lib.rs:2852-2858`.
|
|
||||||
|
|
||||||
Method-name lookup in the FreeFnCall constraint-residual push uses
|
|
||||||
`unwrap_or_default()` (empty string fallback) when `class_methods` has
|
|
||||||
no entry for the residual class. A user constraint referencing a class
|
|
||||||
that does not exist would silently render `NoInstance` with an empty
|
|
||||||
`method:` field. The iter-22 `MissingClass` pre-pass plausibly rejects
|
|
||||||
this earlier, but the fallback masks the dependency.
|
|
||||||
|
|
||||||
**Outcome:** fix in `24.tidy` — replace with explicit `Internal` or
|
|
||||||
`expect(...)` to surface registry/index drift on regression.
|
|
||||||
|
|
||||||
### [medium-2] `normalize_type_for_lookup` duplicated across two sites
|
|
||||||
|
|
||||||
**Path:** `crates/ailang-check/src/mono.rs:687-696` + `:1287-1294`.
|
|
||||||
|
|
||||||
The two `normalize_type_for_lookup` call sites use a literal copy of
|
|
||||||
the body comment rather than extracting a helper. The two sites must
|
|
||||||
agree byte-identically (per iter-24.3 journal: "Must agree byte-
|
|
||||||
identically with that site or Phase 3 rewrite cursor produces a mono-
|
|
||||||
symbol name that differs from the Phase 2 synthesis name"). A
|
|
||||||
`normalize_free_fn_type_args(env, module_name, metas, subst)` helper
|
|
||||||
would make the lockstep enforceable by construction.
|
|
||||||
|
|
||||||
**Outcome:** fix in `24.tidy` — extract helper.
|
|
||||||
|
|
||||||
### [medium-3] Negative-test coverage single-shape (deferred)
|
|
||||||
|
|
||||||
**Paths:** `examples/show_no_instance.ail.json` and the broader
|
|
||||||
NoInstance Show test surface.
|
|
||||||
|
|
||||||
The negative E2E covers `f : Int -> Int` (function type without Show
|
|
||||||
instance). Other NoInstance shapes — user type without instance,
|
|
||||||
partial-application residual carrying both a missing constraint AND a
|
|
||||||
NoInstance, polymorphic call with two unsatisfied constraints — are
|
|
||||||
not pinned. The Show-aware NoInstance arm fires for any
|
|
||||||
non-prelude-Show type by construction, so the single fixture is
|
|
||||||
*structurally representative*, but coverage breadth is thin.
|
|
||||||
|
|
||||||
**Outcome:** roadmap backlog P3 (not in `24.tidy`). Single-fixture
|
|
||||||
coverage is acceptable for the architecture-shipping milestone;
|
|
||||||
broader coverage waits for the corpus-migration milestone (the queued
|
|
||||||
P2 retirement of `io/print_int|bool|float`) when more downstream
|
|
||||||
fixtures naturally exercise these shapes.
|
|
||||||
|
|
||||||
### [low-1] bench/architect_sweeps.sh noise on pre-milestone-24 DESIGN.md lines
|
|
||||||
|
|
||||||
**Path:** `bench/architect_sweeps.sh` matches on `docs/DESIGN.md:50`,
|
|
||||||
`:449`, `:614`.
|
|
||||||
|
|
||||||
All three matched lines predate milestone 24 (text not touched in the
|
|
||||||
milestone-24 diff). Legitimate references or empirical-addendum
|
|
||||||
headings, not new history anchors. Sweep noise from prior eras.
|
|
||||||
|
|
||||||
**Outcome:** carry-on (not a milestone-24 drift item).
|
|
||||||
|
|
||||||
## Bench
|
|
||||||
|
|
||||||
- `bench/check.py`: exit 1 with metric identities migrating between
|
|
||||||
consecutive runs:
|
|
||||||
- Run 1: 2 regressed (`latency.implicit_at_rc.max_us +209%`,
|
|
||||||
`bench_list_sum.bump_s +11.9%`), 1 improved
|
|
||||||
(`bench_list_sum.gc_over_bump`).
|
|
||||||
- Run 2: 2 regressed (`latency.explicit_at_rc.max_us +66%`,
|
|
||||||
`bench_list_sum.bump_s +11.9%` again), 1 improved (different
|
|
||||||
metric).
|
|
||||||
- Pattern: the `latency.*_at_rc.max_us` metric cluster shifts
|
|
||||||
identity between runs; `bench_list_sum.bump_s` is the 5th
|
|
||||||
consecutive sighting since `audit-mq.tidy`. Same envelope the
|
|
||||||
iter-24.3 journal documented as the 8th consecutive observation;
|
|
||||||
this audit makes it 9.
|
|
||||||
- `bench/compile_check.py`: exit 1 with 3-5 regressed metrics across
|
|
||||||
runs; metric identity migrates between `build_O0_ms.*` cluster (run
|
|
||||||
1) and `check_ms.*` cluster (run 2). All deltas are +5% to +34%
|
|
||||||
across runs; tolerance is 20-25% so multiple `check_ms.*` deltas
|
|
||||||
cross threshold sporadically. Same migration pattern.
|
|
||||||
- `bench/cross_lang.py`: exit 0; 25/25 stable.
|
|
||||||
|
|
||||||
**Conservative call: baseline pristine for the 9th consecutive audit.**
|
|
||||||
The metric-identity-migration is itself attribution evidence that the
|
|
||||||
signal is variance, not regression. If signal were present, the metric
|
|
||||||
would not migrate between runs at this rate. Ratifying via
|
|
||||||
`--update-baseline` would pin a snapshot that does not characterise
|
|
||||||
the actual distribution (since the noise migrates, any single
|
|
||||||
snapshot is wrong). The right ratification path is the queued P3
|
|
||||||
"Latency methodology rework — switch from per-run timing to a
|
|
||||||
histogram-based approach"; until that ships, conservative-call is the
|
|
||||||
correct response.
|
|
||||||
|
|
||||||
No `--update-baseline` invocation this audit.
|
|
||||||
|
|
||||||
## Resolution
|
|
||||||
|
|
||||||
- `24.tidy` iter to land 5 drift fixes: `[high-1]` (DESIGN.md
|
|
||||||
documentation of the three iter-24.3 strengthenings), `[high-2]`
|
|
||||||
(codegen import_map-fallback pin), `[high-3]` (FreeFnCall
|
|
||||||
constraint-residual push generalisation OR documented invariant),
|
|
||||||
`[medium-1]` (replace `unwrap_or_default` with explicit error),
|
|
||||||
`[medium-2]` (extract `normalize_free_fn_type_args` helper). Routes
|
|
||||||
through `planner` → `implement`.
|
|
||||||
- `[medium-3]` (negative-test coverage single-shape): roadmap backlog
|
|
||||||
P3.
|
|
||||||
- `[low-1]` (bench sweep noise): carry-on, not actionable.
|
|
||||||
- Bench: extend lineage to 9th consecutive observation. Pristine
|
|
||||||
baseline. Document for future histogram-methodology landing.
|
|
||||||
|
|
||||||
## Files touched
|
|
||||||
|
|
||||||
- `docs/journals/2026-05-13-audit-24.md` (new, this file)
|
|
||||||
- `docs/journals/INDEX.md` (one new line)
|
|
||||||
|
|
||||||
No production code or test edits in this audit; all recommendations
|
|
||||||
land in `24.tidy`.
|
|
||||||
@@ -1,145 +0,0 @@
|
|||||||
# audit-form-a — Milestone close (Form-A as the default authoring surface)
|
|
||||||
|
|
||||||
**Date:** 2026-05-13
|
|
||||||
**Audited range:** aabcadc (iter form-a.0) .. 9fdc4ca (iter form-a.1 T6-12) HEAD
|
|
||||||
**Spec:** `docs/specs/2026-05-13-form-a-default-authoring.md` (amended at 9fcda8b)
|
|
||||||
**Status:** drift_found (4 items, all documentary-only); bench clean (exit 0 across all three scripts)
|
|
||||||
**Recommendation:** carry-on — no form-a.tidy queued at architect's explicit recommendation; drift items deferred to documentary cleanup at next opportunity.
|
|
||||||
|
|
||||||
## What holds (architect-verified)
|
|
||||||
|
|
||||||
- DESIGN.md §"Roundtrip Invariant" restated correctly: four numbered
|
|
||||||
properties (parse-determinism + idempotency-under-print + CLI-
|
|
||||||
pipeline-idempotency + carve-out-anchor) replace the prior
|
|
||||||
Direction-1/Direction-2 framing; five surviving enforcement tests
|
|
||||||
named verbatim (parse_is_deterministic_over_every_ail_fixture,
|
|
||||||
parse_then_print_then_parse_is_idempotent_on_every_ail_fixture,
|
|
||||||
cli_parse_then_render_then_parse_is_idempotent,
|
|
||||||
every_ast_variant_is_observed_in_the_fixture_corpus,
|
|
||||||
examples_ail_json_inventory_matches_carve_outs). §"Float literals"
|
|
||||||
+ §"Why anchored at top level" preserved.
|
|
||||||
- §A4 doctrine rewording landed verbatim per spec at CLAUDE.md:5-6
|
|
||||||
+ DESIGN.md:465-466 (canonical form = JSON-AST; authoring projection
|
|
||||||
= `.ail`; "Form (A) is one projection; the JSON-AST stays canonical"
|
|
||||||
preserved as the second sentence).
|
|
||||||
- Carve-out inventory matches the eight-file spec post-amendment:
|
|
||||||
`ls examples/*.ail.json | wc -l` returns 8, alphabetical order
|
|
||||||
`broken_unbound + prelude + 3× test_22b2_* + 3× test_ct1_*`.
|
|
||||||
`crates/ailang-core/tests/carve_out_inventory.rs::EXPECTED`
|
|
||||||
hardcodes the same eight entries.
|
|
||||||
- 156 non-carve-out `.ail.json` deleted; 157 `.ail` files present
|
|
||||||
(98 fresh-rendered in iter form-a.1 T2 + 58 pre-iter + the
|
|
||||||
prelude pilot from form-a.0). No regressions in
|
|
||||||
`cargo test --workspace` (557 green + 3 ignored).
|
|
||||||
- WhatsNew milestone-close entry editorial-clean per /boss
|
|
||||||
convention: English, no crates / iter-codes / agent names, leads
|
|
||||||
with the change, factual tone, telegram-pragmatic length.
|
|
||||||
- Roadmap milestone struck `[x]` with closing attribution
|
|
||||||
`(Closed 2026-05-13 by iter form-a.1.)`; follow-up milestone
|
|
||||||
`[Prelude embed: Form-A as compile-time source]` present with
|
|
||||||
motivation + problem + three resolution options.
|
|
||||||
- Per-iter journals (`2026-05-13-iter-form-a.0.md`,
|
|
||||||
`2026-05-13-iter-form-a.1.md`) both present; `INDEX.md` carries
|
|
||||||
both lines in chronological order.
|
|
||||||
|
|
||||||
## Drift items (deferred per architect recommendation)
|
|
||||||
|
|
||||||
| Severity | Path | Issue |
|
|
||||||
|----------|------|-------|
|
|
||||||
| medium | `docs/specs/2026-05-13-form-a-default-authoring.md` (§C1, §C2, §"Data flow") | Three sites still say "seven carve-outs" post-amendment; acceptance #1/#2 correctly say eight. Spec self-inconsistent against its own amendment commit 9fcda8b. |
|
|
||||||
| medium | `docs/plans/2026-05-13-iter-form-a.1.md` (two sites) | Same "seven carve-outs" wording surviving the amendment; harmless now but contradicts the eight-file reality the plan executed. |
|
|
||||||
| low | `crates/ailang-core/src/hash.rs:57` | Empty `mod tests {}` placeholder left after iter form-a.1 T5 relocation. Documentary-only today; drift if it stays past one further milestone. |
|
|
||||||
| low | `crates/ailang-surface/tests/round_trip.rs:16` | Module docstring still says "Direction 2 of the Roundtrip Invariant", but DESIGN.md T10 retired the Direction-1/Direction-2 framing. Stale cross-reference to a renamed concept. |
|
|
||||||
|
|
||||||
All four items are documentary-only: the spec / plan orphans
|
|
||||||
contradict their own post-amendment sections but do not affect the
|
|
||||||
shipped implementation (which is eight-carve-out-correct); the
|
|
||||||
hash.rs placeholder and the round_trip.rs docstring are cleanup
|
|
||||||
hygiene that does not change behaviour or invariants.
|
|
||||||
|
|
||||||
Architect's explicit recommendation: "carry on as planned. Queue a
|
|
||||||
documentary `form-a.tidy` only if a subsequent reader hits the
|
|
||||||
seven-vs-eight orphans; the implementation is correct and the spec
|
|
||||||
orphans are post-hoc retro-clean-up, not active drift." The audit
|
|
||||||
follows this recommendation.
|
|
||||||
|
|
||||||
## Bench-regression report
|
|
||||||
|
|
||||||
All three bench scripts exit 0; baseline left pristine.
|
|
||||||
|
|
||||||
### `bench/check.py` (exit 0)
|
|
||||||
|
|
||||||
63 metrics; 2 regressed-by-magnitude, 2 improved-by-magnitude, 59
|
|
||||||
stable. Regressions both on `max_us` tail-latency metrics:
|
|
||||||
|
|
||||||
- `latency.explicit_at_rc.max_us` +124.99% (tol 25%) — paired with
|
|
||||||
`latency.explicit_at_rc.p99_us` *improvement* −27.02%.
|
|
||||||
- `latency.implicit_at_rc.max_us` +126.13% (tol 30%).
|
|
||||||
|
|
||||||
This is the 12th consecutive audit observation of the metric-
|
|
||||||
identity-migrating noise envelope on tail-latency metrics, dating
|
|
||||||
back to audit-cma (2026-05-12). The pattern across the lineage:
|
|
||||||
different metric identifiers fire each run, magnitudes oscillate,
|
|
||||||
median + p99 stay stable; the firing-metric set has migrated
|
|
||||||
through approximately a dozen distinct identifiers across the
|
|
||||||
twelve audits. The convention since the audit-cma originating
|
|
||||||
observation is to hold the baseline pristine — the metric-migration-
|
|
||||||
between-runs is itself attribution evidence that the cluster is
|
|
||||||
noise-class, not signal-class. The queued P3 latency-methodology-
|
|
||||||
histogram rework is the right ratification path; --update-baseline
|
|
||||||
is not.
|
|
||||||
|
|
||||||
The form-a milestone is intentionally test-infra + corpus migration,
|
|
||||||
not language semantics. Zero language-semantic surface was touched.
|
|
||||||
The tail-latency regressions are not caused by the migration; they
|
|
||||||
are the noise envelope continuing.
|
|
||||||
|
|
||||||
### `bench/compile_check.py` (exit 0)
|
|
||||||
|
|
||||||
24 metrics; 2 regressed-by-magnitude, 0 improved, 22 stable.
|
|
||||||
|
|
||||||
- `check_ms.hello` +32.44% (tol 25%) — 1.2ms → 1.5ms.
|
|
||||||
- `check_ms.borrow_own_demo` +26.18% (tol 25%) — 1.3ms → 1.7ms.
|
|
||||||
|
|
||||||
Both on the smallest fixtures in the corpus where `check_ms` is
|
|
||||||
sub-2-millisecond. iter form-a.1 T6 journal already flagged this:
|
|
||||||
"First compile_check run reported exit-1 due to sub-millisecond
|
|
||||||
`check_ms` noise on `hello` and `borrow_own_demo`; second run
|
|
||||||
clean." Now both still fire at the current audit re-run. This is
|
|
||||||
sub-millisecond timing-jitter, not a migration-induced regression.
|
|
||||||
The migration touched only fixture file extensions and test-loader
|
|
||||||
call sites; the `ail check` binary path was not modified.
|
|
||||||
|
|
||||||
### `bench/cross_lang.py` (exit 0)
|
|
||||||
|
|
||||||
25 metrics; 0 regressed, 0 improved-beyond-tolerance, 25 stable.
|
|
||||||
Cross-language ratios (AILang RC / AILang bump / C reference) all
|
|
||||||
hold within tolerance. The most interesting movement is
|
|
||||||
`bench_tree_walk.rc_over_c` from 2.7394 to 2.5871 (−5.56%) — a
|
|
||||||
modest improvement still inside the noise envelope.
|
|
||||||
|
|
||||||
## Recommendation summary
|
|
||||||
|
|
||||||
| Item | Recommendation |
|
|
||||||
|------|----------------|
|
|
||||||
| 4 architect drift items | carry-on (per architect's explicit recommendation; documentary-only) |
|
|
||||||
| `bench/check.py` 2 regressions | noise-envelope continuation (12th audit); baseline pristine |
|
|
||||||
| `bench/compile_check.py` 2 regressions | sub-millisecond timing-jitter; baseline pristine |
|
|
||||||
| `bench/cross_lang.py` clean | no action |
|
|
||||||
|
|
||||||
No `form-a.tidy` iter is queued. The four documentary drift items
|
|
||||||
are recorded here for future-reader visibility; they will be
|
|
||||||
addressed inline at the next iter that touches the affected files,
|
|
||||||
or in a dedicated cleanup pass if a subsequent reader hits them.
|
|
||||||
|
|
||||||
The form-a-default-authoring milestone is fully closed.
|
|
||||||
|
|
||||||
## Next-step pointer
|
|
||||||
|
|
||||||
Per `/boss` and the milestone-close pipeline: the next milestone
|
|
||||||
candidate at the top of the roadmap is `[Retire io/print_int/_bool/_float]`
|
|
||||||
(P2, post-milestone-24 mechanical follow-up). The `[Prelude embed:
|
|
||||||
Form-A as compile-time source]` milestone is the second P2 entry,
|
|
||||||
queued behind the io/print retire. Starting either is a `/boss`
|
|
||||||
bounce-back per the new-milestone rule — the Boss surfaces the
|
|
||||||
candidate and the user picks the session shape.
|
|
||||||
@@ -1,195 +0,0 @@
|
|||||||
# audit-mq — milestone close (module-qualified-class-names)
|
|
||||||
|
|
||||||
**Date:** 2026-05-13
|
|
||||||
**Milestone:** module-qualified-class-names
|
|
||||||
**Iters covered:** mq.1 (`0eb3323`) → mq.2 (`2e6a4ca`) → mq.3 (`99d3968`)
|
|
||||||
**Range from previous close:** `0dcdaab..99d3968` (audit-ct-tidy → mq.3)
|
|
||||||
**Status:** drift_found — `mq.tidy` queued
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
The module-qualified-class-names milestone landed in three iters:
|
|
||||||
mq.1 lifted the canonical-form rule to the three class-ref schema
|
|
||||||
fields and qualified all workspace-internal class-name keys; mq.2
|
|
||||||
installed the type-driven dispatch mechanism without exercising it
|
|
||||||
end-to-end; mq.3 retired `WorkspaceLoadError::MethodNameCollision`,
|
|
||||||
re-keyed `class_methods` to a `(class, method)` tuple, plumbed
|
|
||||||
`Env.active_declared_constraints`, added the
|
|
||||||
`class-method-shadowed-by-fn` warning, and shipped three positive
|
|
||||||
E2E fixtures exercising trajectories C (ambiguous), E (explicit
|
|
||||||
qualifier), and the class-fn shadow case.
|
|
||||||
|
|
||||||
Architect drift review surfaces seven items: two [high] (rigid-var
|
|
||||||
refinement type-unification leg missing; same-module bare-class
|
|
||||||
qualifier shape unreachable), three [medium] (warning over-fires on
|
|
||||||
unrelated locals shadowing prelude method names; DESIGN.md Data
|
|
||||||
Model schema fragments don't reflect the canonical-form rule for
|
|
||||||
class-refs; `synth(...)` has grown to 10 mut-ref parameters), and
|
|
||||||
two [low] backlog items (trajectories B + D covered by unit tests
|
|
||||||
only — no E2E fixture; `collect_mono_targets` rebuilds env without
|
|
||||||
`active_declared_constraints`, currently latent).
|
|
||||||
|
|
||||||
Bench: `check.py` exit 1 across four consecutive runs with metric
|
|
||||||
identity shifting between runs (3 → 1 → 1 → 0 regressions across
|
|
||||||
re-runs, different metrics each time; one run showed only
|
|
||||||
improvements). Pattern is consistent with 5th-consecutive audit
|
|
||||||
noise-class observation. `compile_check.py` exit 0 (24/24 stable);
|
|
||||||
`cross_lang.py` exit 0 (25/25 stable). Baseline left pristine per
|
|
||||||
the 5th-consecutive conservative-call convention; see Bench notes
|
|
||||||
below.
|
|
||||||
|
|
||||||
Resolution path: `mq.tidy` iteration covers the four actionable
|
|
||||||
drift items (two [high] + two [medium]). The 5th [medium]
|
|
||||||
(`synth(...)` signature growth) is acknowledged as accepted
|
|
||||||
post-mq.3 cost and not fixed — refactoring `synth(...)` would
|
|
||||||
ripple through 15+ recursive callsites and 5 external callers,
|
|
||||||
disproportionate to the readability gain. The two [low] items
|
|
||||||
join the roadmap backlog.
|
|
||||||
|
|
||||||
## Architect drift items
|
|
||||||
|
|
||||||
### [high-1] Rigid-var refinement misses type-unification leg
|
|
||||||
|
|
||||||
**Path:** `crates/ailang-check/src/lib.rs:2163-2168`
|
|
||||||
|
|
||||||
The `refine_multi_candidate_residual` rigid-var branch filters
|
|
||||||
declared constraints on `dc.class == c` only. Spec §"Constraint-
|
|
||||||
discharge refinement" (line 130-138) requires the filter to also
|
|
||||||
unify the declared constraint's `type_` with the residual's
|
|
||||||
`type_`. Two same-class declared constraints on different typevars
|
|
||||||
(`forall a b. Show a, Show b => (a, b) -> String` style) cannot
|
|
||||||
currently be discriminated; the filter returns the first match
|
|
||||||
regardless of which typevar the residual actually came from. No
|
|
||||||
unit test pins the type-unification leg.
|
|
||||||
|
|
||||||
**Outcome:** fix in `mq.tidy`.
|
|
||||||
|
|
||||||
### [high-2] Same-module bare-class qualifier shape unreachable
|
|
||||||
|
|
||||||
**Path:** `crates/ailang-check/src/lib.rs:2570-2575`
|
|
||||||
|
|
||||||
`qualifier_is_class_shape = q.contains('.')` makes same-module bare-
|
|
||||||
class qualifiers (`Show.show`) unreachable; the gate only admits
|
|
||||||
cross-module class qualifiers (`<module>.<Class>.<method>`). This
|
|
||||||
contradicts the canonical-form rule's symmetry — bare class refs
|
|
||||||
are legal at every other class-ref schema field (per mq.1's rule).
|
|
||||||
Trajectory E E2E coverage is cross-module-only as a consequence.
|
|
||||||
|
|
||||||
**Outcome:** fix in `mq.tidy`.
|
|
||||||
|
|
||||||
### [medium-1] `class-method-shadowed-by-fn` over-fires
|
|
||||||
|
|
||||||
**Path:** `crates/ailang-check/src/lib.rs:2479-2502`
|
|
||||||
|
|
||||||
The warning fires whenever any class declares the method, not
|
|
||||||
when a class candidate for the actual arg type exists per spec
|
|
||||||
§"Class-fn collisions" (line 161-162). Will spam on unrelated
|
|
||||||
locals shadowing prelude method names (e.g. `let show = …` inside
|
|
||||||
an arbitrary body where `show` has no Show-context).
|
|
||||||
|
|
||||||
**Outcome:** fix in `mq.tidy`.
|
|
||||||
|
|
||||||
### [medium-2] DESIGN.md Data Model schema fragments don't reflect canonical-form rule for class-refs
|
|
||||||
|
|
||||||
**Path:** `docs/DESIGN.md:2139, 2152, 2273`
|
|
||||||
|
|
||||||
Data Model schema fragments for `InstanceDef`, `Constraint`,
|
|
||||||
`SuperclassRef` describe class-ref `<id>` without flagging the
|
|
||||||
canonical-form rule. Only the prose at lines 1156-1162 carries the
|
|
||||||
rule. A schema-section reader without the prose context sees
|
|
||||||
bare-only.
|
|
||||||
|
|
||||||
**Outcome:** fix in `mq.tidy` (doc-only).
|
|
||||||
|
|
||||||
### [medium-3] `synth(...)` signature growth (accepted)
|
|
||||||
|
|
||||||
**Path:** `crates/ailang-check/src/lib.rs:2415`
|
|
||||||
|
|
||||||
`synth(...)` now carries 10 mut-ref parameters (added `warnings`
|
|
||||||
in mq.3). Two re-entries (`mono.rs:614`, `lift.rs:700`) discard
|
|
||||||
the warnings channel by convention.
|
|
||||||
|
|
||||||
**Outcome:** acknowledged debt, NOT fixed in `mq.tidy`. Rationale:
|
|
||||||
refactoring `synth(...)` (e.g. into a `SynthCtx` struct carrying
|
|
||||||
the mut-refs) would ripple through 15+ recursive callsites and 5
|
|
||||||
external callers, with the only payoff a marginal readability
|
|
||||||
gain. The mut-ref pattern is consistent with the rest of the
|
|
||||||
crate (`check_in_workspace`'s `Vec<CheckError>` accumulator), so
|
|
||||||
this is project-style rather than drift.
|
|
||||||
|
|
||||||
### [low-1] Trajectories B + D no E2E
|
|
||||||
|
|
||||||
**Paths:** spec §"Data flow" trajectories B (type-driven narrow-
|
|
||||||
to-one) + D (rigid-var narrow-to-one) are covered by unit tests
|
|
||||||
only — `tests/method_dispatch_pin.rs` 5-step rule tests. No
|
|
||||||
on-disk fixture exercises either path end-to-end.
|
|
||||||
|
|
||||||
**Outcome:** roadmap backlog (P3). Single-class-per-method
|
|
||||||
workspaces don't exercise these branches; the real coverage gap
|
|
||||||
opens when downstream code starts producing the multi-class
|
|
||||||
shapes the unit tests already pin.
|
|
||||||
|
|
||||||
### [low-2] `collect_mono_targets` rebuilds env without `active_declared_constraints`
|
|
||||||
|
|
||||||
**Path:** `crates/ailang-check/src/mono.rs:574-625`
|
|
||||||
|
|
||||||
Structural divergence from `check_fn`'s env setup. Currently
|
|
||||||
latent: mono's residuals are concrete-type so the rigid-var
|
|
||||||
filter never runs. Env-shape invariant unpinned.
|
|
||||||
|
|
||||||
**Outcome:** roadmap backlog (P3). Test that pins the invariant
|
|
||||||
(`env.active_declared_constraints` populated consistently
|
|
||||||
wherever `Env` is built) belongs with the [low-1] E2E work for
|
|
||||||
trajectory D.
|
|
||||||
|
|
||||||
## Bench
|
|
||||||
|
|
||||||
- `bench/check.py`: exit 1 across four consecutive re-runs with
|
|
||||||
metric identity shifting between runs:
|
|
||||||
- Run 1: 1 regression (`latency.implicit_at_rc.p99_over_median`),
|
|
||||||
5 improvements (across `bench_list_sum.gc_over_bump` +
|
|
||||||
`bench_closure_chain.gc_over_bump` + 3
|
|
||||||
`latency.explicit_at_rc.*` metrics).
|
|
||||||
- Run 2: 3 regressions (`bench_list_sum.bump_s` 3rd-consecutive
|
|
||||||
sighting + 2 `latency.implicit_at_rc.*` max-tail), 3
|
|
||||||
improvements (same `latency.explicit_at_rc` cluster).
|
|
||||||
- Run 3: 1 regression (different metric again), 5 improvements.
|
|
||||||
- Run 4: 0 regressions, 2 improvements
|
|
||||||
(`bench_list_sum.gc_over_bump` +
|
|
||||||
`bench_closure_chain.gc_over_bump`).
|
|
||||||
- Pattern: metric identity is not stable; the recurring
|
|
||||||
`bench_list_sum.bump_s` 3rd-consecutive sighting per mq.3
|
|
||||||
journal lineage shows up in 1 of 4 runs only. Audit-ct-tidy
|
|
||||||
(2026-05-12), audit-eob (2026-05-12), audit-ms (2026-05-12),
|
|
||||||
audit-cma (2026-05-12) all left baseline pristine on the same
|
|
||||||
`latency.explicit_at_rc` improvement cluster reappearing
|
|
||||||
across milestones — this is the 5th consecutive audit
|
|
||||||
observation of the same noise envelope.
|
|
||||||
- Conservative call: baseline pristine for 5th consecutive
|
|
||||||
audit. The latency cluster has narrowed-then-widened-then-
|
|
||||||
narrowed across audits, which is itself attribution evidence
|
|
||||||
that this is variance, not signal. If signal were present
|
|
||||||
the metric would not migrate between runs at this rate.
|
|
||||||
- `bench/compile_check.py`: exit 0; 24/24 stable.
|
|
||||||
- `bench/cross_lang.py`: exit 0; 25/25 stable.
|
|
||||||
|
|
||||||
No `--update-baseline` invocation this audit.
|
|
||||||
|
|
||||||
## Resolution
|
|
||||||
|
|
||||||
- `mq.tidy` iter to land four drift fixes: `[high-1]`, `[high-2]`,
|
|
||||||
`[medium-1]`, `[medium-2]`. Routes through `planner` →
|
|
||||||
`implement`.
|
|
||||||
- `[medium-3]` (`synth(...)` signature growth): acknowledged
|
|
||||||
debt, no action.
|
|
||||||
- `[low-1]` (Trajectory B + D E2E) + `[low-2]` (mono env shape):
|
|
||||||
roadmap backlog P3. Same scope — single follow-up iter when the
|
|
||||||
multi-class workspace shape lands.
|
|
||||||
|
|
||||||
## Files touched
|
|
||||||
|
|
||||||
- `docs/journals/2026-05-13-audit-mq.md` (new, this file)
|
|
||||||
- `docs/journals/INDEX.md` (one new line)
|
|
||||||
|
|
||||||
No production code or test edits in this audit; all
|
|
||||||
recommendations land in `mq.tidy`.
|
|
||||||
@@ -1,189 +0,0 @@
|
|||||||
# iter 24.2 — Show class + 4 primitive instances + 22b TShow/tshow migration
|
|
||||||
|
|
||||||
**Date:** 2026-05-13
|
|
||||||
**Started from:** 64cea0e
|
|
||||||
**Status:** DONE
|
|
||||||
**Tasks completed:** 8 of 8
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
Ships `class Show a where show : (a borrow) -> Str` in
|
|
||||||
`examples/prelude.ail.json` alongside primitive instances `Show Int`,
|
|
||||||
`Show Bool`, `Show Str`, `Show Float`. Each instance body is a
|
|
||||||
single-application lambda invoking the corresponding runtime primitive
|
|
||||||
(`int_to_str`, `bool_to_str`, `str_clone`, `float_to_str`) — first
|
|
||||||
prelude instance bodies to call runtime primitives directly
|
|
||||||
(prior Eq/Ord bodies used operator + codegen intercept). The 22b
|
|
||||||
typeclass test corpus migrates `class Show` / `show` →
|
|
||||||
`class TShow` / `tshow` preemptively (Tasks 1–2 before Task 3 lands
|
|
||||||
`prelude.Show`) so the workspace stays green throughout. Three new
|
|
||||||
tests pin the post-mono shape: mono-synthesis existence
|
|
||||||
(`show_mono_synthesis.rs`), dispatch singleton (`show_dispatch_pin.rs`
|
|
||||||
× 2 cases), and body-hash stability (`mono_hash_stability.rs::primitive_show_mono_symbol_hashes_stay_bit_identical`).
|
|
||||||
DESIGN.md §"Prelude (built-in) classes" gains a milestone-24 paragraph
|
|
||||||
naming Show + instances + the Float caveat. `print`-rewire stays
|
|
||||||
deferred to iter 24.3.
|
|
||||||
|
|
||||||
## Per-task notes
|
|
||||||
|
|
||||||
- iter 24.2.1: Renamed `"Show"`/`"show"` → `"TShow"`/`"tshow"` across 21
|
|
||||||
`test_22b*_*.ail.json` fixture files; cross-module qualifier forms
|
|
||||||
(`"<modname>.Show"` → `"<modname>.TShow"`) handled by regex in 3
|
|
||||||
fixtures (`test_22b1_dup_a`, `test_22b1_dup_b`,
|
|
||||||
`test_22b1_orphan_third`). False-positive scan empty; fixture
|
|
||||||
parse round-trip green.
|
|
||||||
- iter 24.2.2: Renamed literals in `crates/ail/tests/typeclass_22b{2,3}.rs`
|
|
||||||
(`"Show"` → `"TShow"`, `"show"` → `"tshow"`, `show__T` → `tshow__T`),
|
|
||||||
including comment-prose forms (`` `Show` `` → `` `TShow` ``,
|
|
||||||
`class Show` → `class TShow`, etc.) and one assertion-message string
|
|
||||||
("exactly one show__Int across the workspace" → "tshow__Int...").
|
|
||||||
Plan undercount surfaced: the rename touches more than the 2 named
|
|
||||||
test files — see Concerns below.
|
|
||||||
- iter 24.2.3: Inserted 5 new top-level defs into `examples/prelude.ail.json`
|
|
||||||
immediately before `fn ne` (line 223 pre-edit). The `instance Show Str`
|
|
||||||
body invokes `str_clone` as `Term::Var` — first prelude instance body
|
|
||||||
to do so. Round-trip green; `primitive_eq_ord_mono_symbol_hashes_stay_bit_identical`
|
|
||||||
stays green (Eq/Ord hashes unaffected by Show defs).
|
|
||||||
- iter 24.2.4: New fixture `examples/show_mono_pin_smoke.ail.json`
|
|
||||||
exercises `show 42`, `show true`, `show "x"`, `show 1.5` in a
|
|
||||||
let-chain ending in `io/print_str s1`. New test
|
|
||||||
`crates/ail/tests/show_mono_synthesis.rs::primitive_show_mono_symbols_synthesise`
|
|
||||||
asserts `show__Int`/`show__Bool`/`show__Str`/`show__Float` materialise
|
|
||||||
as `Def::Fn` entries in the `prelude` post-mono module. `str_clone`
|
|
||||||
reachability in instance-lambda position verified implicitly:
|
|
||||||
`show__Str` materialises, which means the lambda body's `Term::Var
|
|
||||||
"str_clone"` resolved against the checker's builtin table during
|
|
||||||
typecheck.
|
|
||||||
- iter 24.2.5: New pin test `crates/ailang-check/tests/show_dispatch_pin.rs`
|
|
||||||
asserts `env.method_to_candidate_classes["show"] == {"prelude.Show"}`
|
|
||||||
(singleton) and `env.method_to_candidate_classes["tshow"] == {"*.TShow"}`
|
|
||||||
(singleton). API spelled `build_check_env(&ws)` not `Env::from_workspace`
|
|
||||||
(plan's placeholder name). 2/2 cases pass first-shot.
|
|
||||||
- iter 24.2.6: Extended `crates/ail/tests/mono_hash_stability.rs` with
|
|
||||||
a second test function `primitive_show_mono_symbol_hashes_stay_bit_identical`
|
|
||||||
loading `show_mono_pin_smoke.ail.json` (kept separate from the
|
|
||||||
Eq/Ord fixture to avoid coupling). Hashes captured first-run:
|
|
||||||
`show__Int 891eaebe64b3180d`, `show__Bool 1bdb621494e50607`,
|
|
||||||
`show__Str 3f1d57c90ddce081`, `show__Float 5ab0bdd40b9f8b8f`.
|
|
||||||
- iter 24.2.7: DESIGN.md §"Prelude (built-in) classes" amended:
|
|
||||||
milestone-23 paragraph drops "Show" from the deferred list and notes
|
|
||||||
Show ships in milestone 24; new milestone-24 paragraph names class
|
|
||||||
signature, instance set (including Float), body shape (single-app
|
|
||||||
lambda invoking runtime primitive), and the iter-24.3 carry-forward
|
|
||||||
for `print`-rewire.
|
|
||||||
- iter 24.2.8: Workspace tests 552 passed / 0 failed
|
|
||||||
(was 548 pre-iter, +4 new: 1 `show_mono_synthesis` + 2 `show_dispatch_pin`
|
|
||||||
+ 1 hash-stability). `bench/compile_check.py` exit 0 (24/24 stable).
|
|
||||||
`bench/cross_lang.py` exit 0 (25/25 stable). `bench/check.py` exit 1
|
|
||||||
with 2 noise-class metrics (`bench_list_sum.bump_s` +11.71% — 4th
|
|
||||||
consecutive sighting per `audit-mq` / `mq.tidy` lineage;
|
|
||||||
`latency.implicit_at_rc.max_us` +145.55% — 7th consecutive sighting of
|
|
||||||
the documented noise envelope, metric-identity-migrating across runs).
|
|
||||||
Baseline pristine per the conservative-call convention.
|
|
||||||
|
|
||||||
## Concerns
|
|
||||||
|
|
||||||
- **Plan undercount on 22b migration scope.** The plan's "2 Rust test
|
|
||||||
files" carrier (typeclass_22b{2,3}.rs) missed 4 additional consumer
|
|
||||||
sites that hardcode the renamed literals: (a)
|
|
||||||
`crates/ailang-core/src/hash.rs` ct4 canonical-form hash pin (2
|
|
||||||
entries); (b) `crates/ailang-core/src/workspace.rs` 3 in-crate iter22b1
|
|
||||||
tests; (c) `crates/ailang-prose/tests/snapshot.rs` (1 snapshot file at
|
|
||||||
`examples/test_22b2_instance_present.prose.txt`); (d) one
|
|
||||||
`mono_symbol_compound_type_uses_hash_suffix` assertion in typeclass_22b3.rs
|
|
||||||
that called `mono_symbol("tshow", ...)` (post my batch rename) but
|
|
||||||
still asserted `name.starts_with("show__")`. All fixed inline per the
|
|
||||||
precedent of mq.1 / mq.3 "Plan defects fixed inline" — fixture parse
|
|
||||||
was the only "green" pre-iter signal the plan's Step 3/4 anchored on,
|
|
||||||
which masked the broader downstream consumer surface. Suggested
|
|
||||||
Boss-side reaction: future migration plans run an upfront
|
|
||||||
`grep -rln '"<rename target>"' crates/` to enumerate the full
|
|
||||||
consumer set before committing to a per-file enumeration.
|
|
||||||
- **`str_clone` reachability is implicit.** Pre-flight note 3 flagged
|
|
||||||
this as needing first-round-trip verification; verification is
|
|
||||||
implicit in `primitive_show_mono_symbols_synthesise` passing
|
|
||||||
(mono of `show 42 / true / "x" / 1.5` requires the four `show__T`
|
|
||||||
bodies to typecheck, including the `str_clone` call in `show__Str`).
|
|
||||||
No explicit isolated unit test for `str_clone`-in-lambda was added
|
|
||||||
because the integration pin is strictly stronger; consider an
|
|
||||||
isolated unit pin only if a future regression makes the integration
|
|
||||||
test slow to bisect.
|
|
||||||
- **Hash-stability pin uses a separate fixture, not the existing
|
|
||||||
`mono_hash_pin_smoke.ail.json`.** Plan Task 6 offered both options;
|
|
||||||
the separate-fixture path was chosen because `show_mono_pin_smoke.ail.json`
|
|
||||||
already existed (minted in Task 4) and reusing it kept the Eq/Ord
|
|
||||||
pin structurally isolated. Both fixtures live side-by-side; the
|
|
||||||
second test function in `mono_hash_stability.rs` mirrors the first
|
|
||||||
byte-for-byte modulo the fixture path and pin table.
|
|
||||||
- **`bench/check.py` exit 1 is the documented recurring noise envelope.**
|
|
||||||
`latency.implicit_at_rc.*` max-tail metric has been migrating
|
|
||||||
between runs since `audit-cma` (2026-05-12) — 7th consecutive
|
|
||||||
observation; `bench_list_sum.bump_s` is the 4th since `audit-mq`.
|
|
||||||
Plan Task 8 Step 4 explicitly anticipates "1-3 regressed metrics
|
|
||||||
ratify-without-attribution per conservative-call convention"; this
|
|
||||||
iter follows that. The baseline stays pristine.
|
|
||||||
|
|
||||||
## Known debt
|
|
||||||
|
|
||||||
- **iter 24.3 still pending.** Carries `print : forall a. Show a => a -> () !IO`
|
|
||||||
rewire to route through `show` + `io/print_str`. Spec at
|
|
||||||
`docs/specs/2026-05-13-24-show-print.md`. 24.2 is structurally
|
|
||||||
complete; 24.3 is operationally distinct.
|
|
||||||
- **mq1 cross-module fixtures unmigrated by design.**
|
|
||||||
`examples/mq1_xmod_constraint_class.ail.json` and `..._dep.ail.json`
|
|
||||||
reference `mq1_xmod_constraint_class_dep.Show` (cross-module, fully
|
|
||||||
qualified) and ship zero `show` call sites; no dispatch ambiguity
|
|
||||||
arises once `prelude.Show` lands. They stay as-is per the plan's
|
|
||||||
pre-flight ratification.
|
|
||||||
- **mq3 fixtures deliberately retain two-Show shapes.**
|
|
||||||
`examples/mq3_two_show_*` fixtures test the multi-class dispatch
|
|
||||||
scenarios (ambiguity + qualifier resolution) and stay unchanged in
|
|
||||||
24.2 by design.
|
|
||||||
|
|
||||||
## Files touched
|
|
||||||
|
|
||||||
**Created (3):**
|
|
||||||
- `crates/ail/tests/show_mono_synthesis.rs`
|
|
||||||
- `crates/ailang-check/tests/show_dispatch_pin.rs`
|
|
||||||
- `examples/show_mono_pin_smoke.ail.json`
|
|
||||||
|
|
||||||
**Modified (29):**
|
|
||||||
|
|
||||||
Prelude + DESIGN:
|
|
||||||
- `examples/prelude.ail.json` (+5 defs)
|
|
||||||
- `docs/DESIGN.md` (§"Prelude (built-in) classes" amendment)
|
|
||||||
|
|
||||||
22b fixture migration (21 files):
|
|
||||||
- `examples/test_22b1_dup_a.ail.json`
|
|
||||||
- `examples/test_22b1_dup_b.ail.json`
|
|
||||||
- `examples/test_22b1_dup_classmod.ail.json`
|
|
||||||
- `examples/test_22b1_dup_same_module.ail.json`
|
|
||||||
- `examples/test_22b1_orphan_class.ail.json`
|
|
||||||
- `examples/test_22b1_orphan_third.ail.json`
|
|
||||||
- `examples/test_22b1_orphan_third_classmod.ail.json`
|
|
||||||
- `examples/test_22b2_class_method_lookup.ail.json`
|
|
||||||
- `examples/test_22b2_constraint_declared.ail.json`
|
|
||||||
- `examples/test_22b2_instance_present.ail.json`
|
|
||||||
- `examples/test_22b2_missing_constraint.ail.json`
|
|
||||||
- `examples/test_22b2_no_instance.ail.json`
|
|
||||||
- `examples/test_22b2_two_fns_missing_constraint.ail.json`
|
|
||||||
- `examples/test_22b2_xmod_classmod.ail.json`
|
|
||||||
- `examples/test_22b2_xmod_classmod_noinst.ail.json`
|
|
||||||
- `examples/test_22b2_xmod_instance_present.ail.json`
|
|
||||||
- `examples/test_22b2_xmod_missing_constraint.ail.json`
|
|
||||||
- `examples/test_22b2_xmod_no_instance.ail.json`
|
|
||||||
- `examples/test_22b3_dup_call_sites.ail.json`
|
|
||||||
- `examples/test_22b3_no_call_sites.ail.json`
|
|
||||||
- `examples/test_22b3_shadow_class_method.ail.json`
|
|
||||||
|
|
||||||
22b consumer-test migration (5 source files + 1 snapshot):
|
|
||||||
- `crates/ail/tests/typeclass_22b2.rs`
|
|
||||||
- `crates/ail/tests/typeclass_22b3.rs`
|
|
||||||
- `crates/ailang-core/src/hash.rs` (ct4 hash pin)
|
|
||||||
- `crates/ailang-core/src/workspace.rs` (3 iter22b1 tests)
|
|
||||||
- `crates/ail/tests/mono_hash_stability.rs` (new show test fn)
|
|
||||||
- `examples/test_22b2_instance_present.prose.txt` (snapshot accept)
|
|
||||||
|
|
||||||
## Stats
|
|
||||||
|
|
||||||
`bench/orchestrator-stats/2026-05-13-iter-24.2.json`
|
|
||||||
@@ -1,238 +0,0 @@
|
|||||||
# iter 24.3 — fn print polymorphic free fn + E2E + DESIGN.md sync; milestone 24 close
|
|
||||||
|
|
||||||
**Date:** 2026-05-13
|
|
||||||
**Started from:** c04c07f
|
|
||||||
**Status:** DONE
|
|
||||||
**Tasks completed:** 8 of 8
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
Ships `fn print : forall a. Show a => (a borrow) -> () !IO` in
|
|
||||||
`examples/prelude.ail.json` with explicit-let body
|
|
||||||
`\x -> let s = show x in do io/print_str s`. The let-binder is
|
|
||||||
structurally pinned by `crates/ail/tests/print_mono_body_shape.rs`
|
|
||||||
(post-mono `print__Int.body` is `Term::Let → Term::App(show__Int) →
|
|
||||||
Term::Do(io/print_str)`). Three new E2E fixtures + tests verify the
|
|
||||||
full path: `show_print_smoke` (4 primitives) and `show_user_adt`
|
|
||||||
(`data IntBox` + `instance prelude.Show IntBox` + `print (MkIntBox 7)`)
|
|
||||||
both compile, run, and produce expected stdout; `show_no_instance`
|
|
||||||
fires a Show-aware `NoInstance` diagnostic with the canonical
|
|
||||||
DESIGN.md cross-reference. DESIGN.md §"Prelude (built-in) classes"
|
|
||||||
flips `print` from "ships in 24.3" to "shipped in iter 24.3" with the
|
|
||||||
explicit-let body + pin reference. DESIGN.md §"Float semantics" gains
|
|
||||||
a Show-Float NaN-spelling cross-reference paragraph. Roadmap P1
|
|
||||||
"Post-22 Prelude — Show + print rewire" entry checked off; new P2
|
|
||||||
entry "Retire `io/print_int|bool|float` effect-ops + migrate example
|
|
||||||
corpus to `print`" inserted at top of P2. **Three plan-defects-fixed-
|
|
||||||
inline** surfaced when running the user-ADT E2E: (a) `MonoTarget::
|
|
||||||
FreeFn::type_args` were carrying bare type-cons references that broke
|
|
||||||
the post-mono walk's registry lookup when the synthesised body lived
|
|
||||||
in a different module from the type — fixed by normalising via
|
|
||||||
`workspace_registry.normalize_type_for_lookup` at two
|
|
||||||
`mono.rs` collection sites; (b) codegen's cross-module fn resolution
|
|
||||||
went through `import_map`, but post-mono synthesised bodies may
|
|
||||||
reference modules their source template didn't import — fixed by
|
|
||||||
falling back to direct `module_user_fns` lookup at three codegen
|
|
||||||
sites (`resolve_top_level_fn`, `lower_app` cross-module arm,
|
|
||||||
`synth_with_extras` Var arm); (c) the synth FreeFnCall arm didn't push
|
|
||||||
residuals for declared constraints, so `Show (Int -> Int)` never
|
|
||||||
reached the typecheck-time discharge logic — fixed by walking
|
|
||||||
`Type::Forall.constraints` at the FreeFnCall site and pushing one
|
|
||||||
residual per constraint with the fresh-metavar substitution. Full
|
|
||||||
`cargo test --workspace` 556 passed / 0 failed (was 552 post-24.2,
|
|
||||||
+4 new = 1 print_mono_body_shape + 2 show_print_e2e + 1
|
|
||||||
show_no_instance_e2e). `bench/cross_lang.py` clean; `bench/compile_check.py`
|
|
||||||
+ `bench/check.py` exit 1 with documented noise-class metrics per the
|
|
||||||
audit-cma lineage envelope (baseline pristine, 8th consecutive
|
|
||||||
audit-grade observation). Milestone 24 closes structurally; only the
|
|
||||||
audit pipeline remains.
|
|
||||||
|
|
||||||
## Per-task notes
|
|
||||||
|
|
||||||
- iter 24.3.1: Appended `fn print` to `examples/prelude.ail.json`
|
|
||||||
after `fn ge` (last def pre-edit). 20 defs total (was 19). Body
|
|
||||||
is the literal `Term::Let { name: "s", value: App(show, [x]), body:
|
|
||||||
Do(io/print_str, [s]) }` with the constraint `Show a` bare per
|
|
||||||
mq.1 same-module canonical form. 552 tests stay green.
|
|
||||||
- iter 24.3.2: `crates/ail/tests/print_mono_body_shape.rs` asserts
|
|
||||||
post-mono `print__Int.body` shape. Initial test asserted outer
|
|
||||||
`Term::Lam` wrapper — corrected after the panic surfaced that
|
|
||||||
`FnDef.body: Term` is the *inner* function body for top-level fns
|
|
||||||
(params carried separately on `FnDef.params`). The test now asserts
|
|
||||||
directly `Term::Let → App(show__Int, [x]) → Do(io/print_str, [s])`,
|
|
||||||
faithful to the plan's intent (preserve the explicit-let-binder
|
|
||||||
discipline) while matching the actual AST shape.
|
|
||||||
- iter 24.3.3: `examples/show_print_smoke.ail.json` written with
|
|
||||||
nested `Term::Seq` of four `App(print, [lit])` calls (Int 42, Bool
|
|
||||||
true, Str "hello", Float bits=40091eb851eb851f for 3.14). `ail
|
|
||||||
build` clean; stdout: `42\ntrue\nhello\n3.14` — io/print_str
|
|
||||||
appends a newline via `@puts`.
|
|
||||||
- iter 24.3.4: `examples/show_user_adt.ail.json` written + 2-test
|
|
||||||
`crates/ail/tests/show_print_e2e.rs`. Surfaced three substantive
|
|
||||||
plan-defects-fixed-inline (Concerns below); after fix, both E2E
|
|
||||||
tests pass (`print (MkIntBox 7)` → stdout "7").
|
|
||||||
- iter 24.3.5: Extended NoInstance Float-aware arm in
|
|
||||||
`crates/ailang-check/src/lib.rs:770-779` with a parallel
|
|
||||||
`class == "prelude.Show"` branch. The arm fires for ANY type
|
|
||||||
lacking a Show instance (function types, user types without
|
|
||||||
instance) — cross-references DESIGN.md §"Prelude (built-in)
|
|
||||||
classes" verbatim. Negative fixture `examples/show_no_instance.ail.json`
|
|
||||||
(let-bound `f : Int -> Int` + `print f`) and pin test in
|
|
||||||
`crates/ail/tests/show_no_instance_e2e.rs` (asserts code,
|
|
||||||
Show-substring, Prelude-(built-in)-classes substring). After
|
|
||||||
the constraint-residual-push fix in synth (Concerns below), the
|
|
||||||
diagnostic fires at typecheck.
|
|
||||||
- iter 24.3.6: DESIGN.md §"Prelude (built-in) classes" milestone-24
|
|
||||||
paragraph amended: `print` flipped to past tense ("shipped in iter
|
|
||||||
24.3"), body shape + pin file reference added, retirement
|
|
||||||
follow-up named. DESIGN.md §"Float semantics" gained a new
|
|
||||||
paragraph in the "Unspecified" block cross-referencing `show 1.5`
|
|
||||||
/ `show nan` / `show inf` to `instance Show Float` via
|
|
||||||
`float_to_str`. ~10 lines added net (2697 → 2707).
|
|
||||||
- iter 24.3.7: Roadmap P1 "Post-22 Prelude — Show + print rewire"
|
|
||||||
entry flipped to `[x]` with a closing-summary block naming all
|
|
||||||
three shipped iters (24.1 f38bad8, 24.2 iter-24.2, 24.3 this
|
|
||||||
iter). New P2 entry "Retire `io/print_int|bool|float` effect-ops
|
|
||||||
+ migrate example corpus to `print`" inserted at top of P2,
|
|
||||||
before the existing operator-routing-through-Eq/Ord entry.
|
|
||||||
- iter 24.3.8: Full workspace 556 passed / 0 failed. `prelude_free_fns`
|
|
||||||
5/5 green; `mono_hash_stability` 2/2 green (Eq/Ord hashes
|
|
||||||
unchanged, Show hashes from 24.2 unchanged). `bench/cross_lang.py`
|
|
||||||
exit 0 (25/25 stable). `bench/compile_check.py` exit 1 with 1
|
|
||||||
noise-class regression on a `check_ms.*` metric (regressed metric
|
|
||||||
identity migrates between runs — pattern consistent with the
|
|
||||||
documented noise envelope per audit-cma lineage, 8th consecutive
|
|
||||||
observation). `bench/check.py` exit 1 with 2 improvements beyond
|
|
||||||
tolerance on `latency.explicit_at_rc.*` — same envelope, same
|
|
||||||
conservative call. Baseline pristine.
|
|
||||||
|
|
||||||
## Concerns
|
|
||||||
|
|
||||||
- **Three plan-defects-fixed-inline surfaced during Task 4 (user-ADT
|
|
||||||
E2E).** All three are necessary repairs to make the spec's stated
|
|
||||||
user-ADT trajectory (§"Data flow / print x at user type IntBox"
|
|
||||||
lines 297-310) work end-to-end. The plan did not anticipate them
|
|
||||||
because the test architecture envisioned (Tasks 3+4 sharing one
|
|
||||||
test file) didn't actually exercise the cross-module mono synthesis
|
|
||||||
before this iter; primitive-only smoke worked through the existing
|
|
||||||
pipeline.
|
|
||||||
- (a) **Bare type-cons in `MonoTarget::FreeFn::type_args`.** At
|
|
||||||
mono target-collection (`collect_mono_targets` + `collect_residuals_ordered`
|
|
||||||
free-fn arms), `subst.apply(meta)` returns the unification result
|
|
||||||
in whatever qualification the caller-module's view used. For a
|
|
||||||
user-defined type referenced from `main` (in `show_user_adt`),
|
|
||||||
`IntBox` came out bare. The downstream `synthesise_mono_fn_for_free_fn`
|
|
||||||
then substituted bare `IntBox` everywhere in the synthesised body,
|
|
||||||
and Phase 3 rewrite of the synthesised body (run in PRELUDE
|
|
||||||
caller-module context, where `IntBox` is not a known TypeDef) saw
|
|
||||||
`Type::Con{IntBox}` that didn't normalise to the registry-keyed
|
|
||||||
`show_user_adt.IntBox`. Lookup missed → no `show__IntBox` target
|
|
||||||
scheduled → bare `Var "show"` persisted post-mono. Fix:
|
|
||||||
`mono.rs` lines 685-696 + 1267-1278 normalise via
|
|
||||||
`env.workspace_registry.normalize_type_for_lookup(module_name, &resolved)`
|
|
||||||
before pushing the type into `MonoTarget::FreeFn::type_args`. Same
|
|
||||||
invariant applies symmetric to the existing
|
|
||||||
`r_ty_norm = normalize_type_for_lookup(...)` calls already in
|
|
||||||
`collect_mono_targets`'s class-method arm.
|
|
||||||
- (b) **Codegen cross-module references via import_map.** Post
|
|
||||||
fix-(a), the synthesised body now correctly references
|
|
||||||
`show_user_adt.show__cb5eb497` from inside `prelude.print__cb5eb497`.
|
|
||||||
But `prelude` does not have `show_user_adt` in its `import_map`
|
|
||||||
(prelude is the base; user modules import IT, not the other way
|
|
||||||
around). Three codegen sites raised UnknownVar:
|
|
||||||
`resolve_top_level_fn:2200`, `lower_app` cross-module arm:1981,
|
|
||||||
`synth_with_extras` Var arm:2777. All three now fall back to a
|
|
||||||
direct `module_user_fns.contains_key(prefix)` (or
|
|
||||||
`module_def_ail_types.contains_key(prefix)`) lookup when the
|
|
||||||
prefix isn't in the current module's import_map. This is the
|
|
||||||
post-mono synthesised-body invariant: cross-module references in
|
|
||||||
synthesised bodies may bypass the source template's imports
|
|
||||||
(both ends were independently typechecked under their own module
|
|
||||||
contexts before mono ran; the cross-module reference is created
|
|
||||||
by mono, not by the source). Doc comments cross-reference all
|
|
||||||
three sites.
|
|
||||||
- (c) **Poly-free-fn constraint residuals not pushed at synth.**
|
|
||||||
Pre-iter-24.3, the FreeFnCall arm at `lib.rs:2820` had
|
|
||||||
`constraints: _` — declared constraints of the poly free fn were
|
|
||||||
ignored. For all milestone-23 fixtures (`ne`/`lt`/`le`/`gt`/`ge`
|
|
||||||
at primitive types), this was latent because every call site had
|
|
||||||
a registry entry; the mono pass silently scheduled the
|
|
||||||
corresponding `eq__T`/`compare__T` targets, and codegen succeeded.
|
|
||||||
But for negative cases — `print f` where `f : Int -> Int` has no
|
|
||||||
Show instance — the constraint never reached the typecheck-time
|
|
||||||
`NoInstance` discharge (lib.rs:1880-1905). The build would emit
|
|
||||||
a confusing post-mono `unknown variable: show` from codegen
|
|
||||||
rather than the right typecheck-phase diagnostic. Fix: at
|
|
||||||
`lib.rs:2820+`, walk `Type::Forall.constraints` at FreeFnCall
|
|
||||||
sites, substitute the rigid vars with their fresh metavars, and
|
|
||||||
push one `ResidualConstraint` per declared constraint. The
|
|
||||||
discharge loop then fires the correct NoInstance with full
|
|
||||||
diagnostic context (including the Show-aware addendum from
|
|
||||||
Task 5). Pre-existing milestone-23 tests stay green because
|
|
||||||
primitive-type Show/Eq/Ord registry entries discharge silently.
|
|
||||||
- **bench/compile_check.py + check.py exit 1.** Both report
|
|
||||||
noise-class metrics (1 regression / 2 improvements respectively).
|
|
||||||
Metric identity migrates between consecutive runs — pattern
|
|
||||||
consistent with the documented `latency.implicit_at_rc.*` /
|
|
||||||
`check_ms.*` noise envelope per audit-cma + audit-ms + audit-eob +
|
|
||||||
audit-ct-tidy + audit-mq + iter-24.2 lineage. 8th consecutive
|
|
||||||
observation. Baseline pristine per conservative-call convention.
|
|
||||||
- **Plan deviation on Task 2 (IR-shape pin).** The plan's test stub
|
|
||||||
asserted `Term::Lam { body: Term::Let { ... } }` for `print__Int`.
|
|
||||||
The actual `FnDef.body: Term` shape for top-level fns is the
|
|
||||||
*inner* function body (params separate); no outer `Term::Lam`. The
|
|
||||||
test was corrected to assert `Term::Let { ... }` directly. The
|
|
||||||
load-bearing property (explicit let-binder for heap-Str RC
|
|
||||||
discipline) is identical between the plan's intent and the
|
|
||||||
shipped assertion; only the wrapper assumption changed. Doc
|
|
||||||
comment in the test file explains the FnDef.body shape contract.
|
|
||||||
|
|
||||||
## Known debt
|
|
||||||
|
|
||||||
- **Milestone 24 closes structurally with this iter.** The standard
|
|
||||||
`audit` pipeline (architect drift review + bench regression
|
|
||||||
attribution) runs next. The three plan-defects-fixed-inline above
|
|
||||||
may merit architect commentary on whether the fix sites belong as
|
|
||||||
permanent invariants or are themselves transitional. Specifically:
|
|
||||||
fix (a) implies that `MonoTarget::FreeFn::type_args` carries
|
|
||||||
CANONICAL types post-collection — this is a strengthening of the
|
|
||||||
pre-iter invariant which was "subst.apply result"; future planner
|
|
||||||
work that touches the mono pass should keep that strengthening
|
|
||||||
visible.
|
|
||||||
- **P2 entry "Retire io/print_int|bool|float effect-ops" is queued.**
|
|
||||||
The new entry's `~86 fixtures affected` count is from a rough
|
|
||||||
grep against `do io/print_<T>`; precise count + migration plan is
|
|
||||||
the planner's job when the milestone opens.
|
|
||||||
- **Negative-fixture coverage is single-shape.** `show_no_instance.ail.json`
|
|
||||||
uses `f : Int -> Int`. Other NoInstance shapes (user type without
|
|
||||||
instance, `print x` where `x : SomeUnsupportedClass`) are not
|
|
||||||
separately tested — the NoInstance arm fires for any non-prelude-Show
|
|
||||||
type by construction, so the single fixture is structurally
|
|
||||||
representative.
|
|
||||||
|
|
||||||
## Files touched
|
|
||||||
|
|
||||||
**Created (6):**
|
|
||||||
- `crates/ail/tests/print_mono_body_shape.rs`
|
|
||||||
- `crates/ail/tests/show_print_e2e.rs`
|
|
||||||
- `crates/ail/tests/show_no_instance_e2e.rs`
|
|
||||||
- `examples/show_print_smoke.ail.json`
|
|
||||||
- `examples/show_user_adt.ail.json`
|
|
||||||
- `examples/show_no_instance.ail.json`
|
|
||||||
|
|
||||||
**Modified (6):**
|
|
||||||
- `examples/prelude.ail.json` (+1 def: `fn print`)
|
|
||||||
- `crates/ailang-check/src/lib.rs` (NoInstance Show-aware branch +
|
|
||||||
FreeFnCall constraint-residual push)
|
|
||||||
- `crates/ailang-check/src/mono.rs` (FreeFn type_args canonical-form
|
|
||||||
normalisation, 2 sites)
|
|
||||||
- `crates/ailang-codegen/src/lib.rs` (cross-module reference fallback
|
|
||||||
for post-mono synthesised bodies, 3 sites: `resolve_top_level_fn`,
|
|
||||||
`lower_app`'s cross-module call arm, `synth_with_extras`'s Var arm)
|
|
||||||
- `docs/DESIGN.md` (§"Prelude (built-in) classes" milestone-24
|
|
||||||
paragraph + §"Float semantics" Show-Float cross-reference)
|
|
||||||
- `docs/roadmap.md` (P1 Show+print → [x]; new P2 entry at top)
|
|
||||||
|
|
||||||
## Stats
|
|
||||||
|
|
||||||
`bench/orchestrator-stats/2026-05-13-iter-24.3.json`
|
|
||||||
@@ -1,157 +0,0 @@
|
|||||||
# iter 24.tidy — close 5 actionable drift items from audit-24
|
|
||||||
|
|
||||||
**Date:** 2026-05-13
|
|
||||||
**Started from:** 0e27533
|
|
||||||
**Status:** DONE
|
|
||||||
**Tasks completed:** 6 of 6
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
Closes the 5 actionable drift items audit-24 surfaced (3× [high] +
|
|
||||||
2× [medium]): T1 adds a new DESIGN.md subsection
|
|
||||||
`### Cross-module references in synthesised bodies` under
|
|
||||||
`## Decision 11`'s `### Resolution and monomorphisation`, anchoring
|
|
||||||
the three iter-24.3 strengthenings as load-bearing lockstep
|
|
||||||
invariants (canonical-form type_args / import_map-fallback / FreeFnCall
|
|
||||||
constraint-residual push). T2 pins the codegen import_map-fallback
|
|
||||||
path with an integration test asserting the synthesised
|
|
||||||
`prelude.print__IntBox` body contains a `show_user_adt.show__IntBox`
|
|
||||||
Var AND the prelude source module does not import `show_user_adt`.
|
|
||||||
T3 pins the bare-name poly-fn dot-qualified-branch invariant by
|
|
||||||
asserting `show_no_instance.ail.json` produces exactly one
|
|
||||||
`no-instance` diagnostic and zero `unknown-variable` /
|
|
||||||
`internal` diagnostics. T4 tightens
|
|
||||||
`crates/ailang-check/src/lib.rs:2852` from `unwrap_or_default()` to
|
|
||||||
`.expect(...)` with a registry-coherence panic message; the strict
|
|
||||||
tightening surfaced no existing violations (558 tests pass post-edit).
|
|
||||||
T5 extracts the duplicated `subst.apply + is_fully_concrete +
|
|
||||||
normalize_type_for_lookup` body into a single `apply_subst_and_normalize`
|
|
||||||
helper returning `Option<Type>`, replacing the two byte-identical
|
|
||||||
sites at `mono.rs::collect_mono_targets` (685-714) and
|
|
||||||
`::collect_residuals_ordered` (1284-1303); the byte-identity
|
|
||||||
invariant is now enforced by construction, `mono_hash_stability`
|
|
||||||
green on both primitive Show + Eq/Ord hashes. T6 verifies: 558
|
|
||||||
tests pass (was 556 + 2 new pins, exactly the expected count);
|
|
||||||
`bench/cross_lang.py` exit 0 (25/25 stable); `bench/compile_check.py`
|
|
||||||
exit 0 with 4 `check_ms.*` noise-class regressions in the documented
|
|
||||||
audit-24 lineage; `bench/check.py` exit 0 with 3 `latency.implicit_at_rc.*`
|
|
||||||
+ 4 `latency.explicit_at_rc.*` noise-class observations in the same
|
|
||||||
lineage (10th consecutive observation; baseline pristine).
|
|
||||||
|
|
||||||
## Per-task notes
|
|
||||||
|
|
||||||
- iter 24.tidy.1 (T1): DESIGN.md +68 lines, new `### Cross-module
|
|
||||||
references in synthesised bodies` subsection inserted between
|
|
||||||
`### Resolution and monomorphisation` (ends line 1693) and the
|
|
||||||
re-anchored `### Defaults and superclasses`. Subsection enumerates
|
|
||||||
three lockstep invariants with cross-references to source files
|
|
||||||
(`mono.rs::collect_mono_targets` / `::collect_residuals_ordered`,
|
|
||||||
`codegen/lib.rs::resolve_top_level_fn` / `::lower_app` /
|
|
||||||
`::synth_with_extras`, `check/lib.rs` synth FreeFnCall arm) and
|
|
||||||
to the protective test pins. Closing paragraph names the lockstep
|
|
||||||
partnership and the regression cost of loosening any one.
|
|
||||||
|
|
||||||
- iter 24.tidy.2 (T2): New `crates/ail/tests/codegen_import_map_fallback_pin.rs`.
|
|
||||||
Loads `examples/show_user_adt.ail.json`, runs `check_workspace` +
|
|
||||||
`monomorphise_workspace`, finds the synthesised `Def::Fn` whose
|
|
||||||
name starts with `print__` in the `prelude` post-mono module,
|
|
||||||
recursively walks its body looking for a `Term::Var { name }`
|
|
||||||
whose name starts with `show_user_adt.` and contains `show__`,
|
|
||||||
and confirms the prelude source module's `Vec<Import>` does NOT
|
|
||||||
include `show_user_adt`. Plan draft adapted at three points: (a)
|
|
||||||
`Term::App` field name is `callee` not `fn` (the latter is the
|
|
||||||
serde rename; Rust field is `callee`); (b) `imports.iter()` gives
|
|
||||||
`&Import` not `&String` so the check is on `imp.module`; (c)
|
|
||||||
recursive walker extended with `Seq` / `Clone` / `ReuseAs` /
|
|
||||||
`LetRec` / `If` / `Match` / `Ctor` arms for exhaustiveness — all
|
|
||||||
data-shape adaptations the plan explicitly authorised. Test passes
|
|
||||||
first-shot (1 passed).
|
|
||||||
|
|
||||||
- iter 24.tidy.3 (T3): New `crates/ail/tests/polyfn_dot_qualified_branch_pin.rs`.
|
|
||||||
Loads `examples/show_no_instance.ail.json`, asserts exactly one
|
|
||||||
`no-instance` diagnostic AND zero `unknown-variable` / `internal`
|
|
||||||
diagnostics. The plan's draft `d.code` field accesses match the
|
|
||||||
actual `Diagnostic` struct (verified against
|
|
||||||
`crates/ailang-check/src/diagnostic.rs:131`). Test passes first-shot.
|
|
||||||
|
|
||||||
- iter 24.tidy.4 (T4): One-line tightening at
|
|
||||||
`crates/ailang-check/src/lib.rs:2858` —
|
|
||||||
`.unwrap_or_default()` → `.expect("class_methods registry coherence ...")`
|
|
||||||
with the verbatim plan message. The `.expect(...)` covers the
|
|
||||||
registry-coherence invariant: a declared constraint's class is
|
|
||||||
expected to be in `env.class_methods` because the pre-pass
|
|
||||||
`MissingClass` diagnostic should have rejected it earlier; reaching
|
|
||||||
this point with `None` means workspace-registry / class-index drift.
|
|
||||||
No existing test violates the new invariant — 558 tests pass.
|
|
||||||
|
|
||||||
- iter 24.tidy.5 (T5): New `apply_subst_and_normalize` helper at
|
|
||||||
`crates/ailang-check/src/mono.rs:555-583` (immediately preceding
|
|
||||||
`collect_mono_targets`). Signature
|
|
||||||
`fn apply_subst_and_normalize(env: &crate::Env, module_name: &str, m: &Type, subst: &crate::Subst) -> Option<Type>`.
|
|
||||||
Returns `Some(normalised)` if `subst.apply(m)` is fully concrete
|
|
||||||
(via `crate::is_fully_concrete`); else `None`. Two call sites
|
|
||||||
retrofit: site 1 at `collect_mono_targets` (now ~ lines 707-727)
|
|
||||||
keeps its rigid-var / Unit-default branching at the call site
|
|
||||||
(helper-None → check `contains_rigid_var(&subst.apply(m))`; if
|
|
||||||
rigid set `has_rigid = true` and break; else push `Type::unit()`);
|
|
||||||
site 2 at `collect_residuals_ordered` (now ~ lines 1284-1290) uses
|
|
||||||
`apply_subst_and_normalize(&env, ...).unwrap_or_else(Type::unit)`.
|
|
||||||
Site 1 needed `&env` (local owned `Env`) where the plan draft had
|
|
||||||
bare `env` — one-character edit. `mono_hash_stability` both tests
|
|
||||||
pass (primitive Show + Eq/Ord mono-symbol hashes bit-identical),
|
|
||||||
full workspace 558 green. Byte-identity invariant now enforced by
|
|
||||||
construction at the helper boundary; each call site explicitly
|
|
||||||
documents its post-helper-None policy.
|
|
||||||
|
|
||||||
- iter 24.tidy.6 (T6): Integration verification. Full workspace
|
|
||||||
`cargo test --workspace --no-fail-fast` = 558 passed, 0 failed
|
|
||||||
(matches plan's prediction: 556 baseline + 2 new pins). No
|
|
||||||
FAILED entries among milestone-24 specific test groups
|
|
||||||
(show_/print_/prelude_free_fns/mono_hash_stability/typeclass_22b/mq3).
|
|
||||||
Bench: `cross_lang.py` exit 0, 25/25 stable; `compile_check.py`
|
|
||||||
exit 0 with 4 `check_ms.*` ms-level noise-class regressions
|
|
||||||
(`hello`, `borrow_own_demo`, `bench_tree_walk`, `bench_hof_pipeline`);
|
|
||||||
`check.py` exit 0 with 3 noise-class regressions
|
|
||||||
(`latency.implicit_at_rc.{p99_9_us, max_us}` first-sightings in
|
|
||||||
this iter) + 4 noise-class improvements
|
|
||||||
(`latency.explicit_at_rc.{p99_us, p99_over_median}` improvements
|
|
||||||
continuing the documented audit-cma → audit-24 lineage). 10th
|
|
||||||
consecutive observation of metric-identity-migrating noise;
|
|
||||||
baseline pristine, conservative-call convention holds per the
|
|
||||||
documented lineage.
|
|
||||||
|
|
||||||
## Concerns
|
|
||||||
|
|
||||||
- T5 pre-extraction reading vs plan: the plan said site 1's unbound-
|
|
||||||
meta path "likely a `continue` without push" — actual pre-extraction
|
|
||||||
code did `type_args.push(Type::unit())` for unbound metas (iter 23.4
|
|
||||||
behaviour, comment in place). I preserved the actual behaviour, not
|
|
||||||
the plan's guess. Lockstep with site 2 (which has the same Unit-default
|
|
||||||
policy) is improved post-extraction. Observation, not correctness.
|
|
||||||
|
|
||||||
- T2 plan draft had three data-shape mismatches against actual Rust
|
|
||||||
schema (`Term::App.callee` vs plan's `fn`, `Vec<Import>` vs
|
|
||||||
`Vec<String>`, exhaustive Term variants). The plan explicitly
|
|
||||||
flagged these as "implementer adapts to the actual field name"
|
|
||||||
cases; not plan defects but recon imprecisions.
|
|
||||||
|
|
||||||
## Known debt
|
|
||||||
|
|
||||||
- audit-24 [medium-3] negative-test coverage breadth → deferred to
|
|
||||||
P3 downstream-corpus-migration milestone (carried over from
|
|
||||||
audit-24, not closed here).
|
|
||||||
- audit-24 [low-1] bench/architect_sweeps.sh noise → carry-on,
|
|
||||||
pre-milestone-24 lines not new drift.
|
|
||||||
|
|
||||||
## Files touched
|
|
||||||
|
|
||||||
- `docs/DESIGN.md` — new subsection (T1), +68 lines.
|
|
||||||
- `crates/ail/tests/codegen_import_map_fallback_pin.rs` — new pin (T2).
|
|
||||||
- `crates/ail/tests/polyfn_dot_qualified_branch_pin.rs` — new pin (T3).
|
|
||||||
- `crates/ailang-check/src/lib.rs` — one-line tightening at :2858 (T4).
|
|
||||||
- `crates/ailang-check/src/mono.rs` — new helper + two call-site
|
|
||||||
replacements (T5).
|
|
||||||
|
|
||||||
## Stats
|
|
||||||
|
|
||||||
`bench/orchestrator-stats/2026-05-13-iter-24.tidy.json`
|
|
||||||
@@ -1,94 +0,0 @@
|
|||||||
# iter bugfix-instance-body-unbound-var — instance method bodies now walk through check_fn
|
|
||||||
|
|
||||||
**Date:** 2026-05-13
|
|
||||||
**Started from:** 72f3f6541b3e41f2d4a7706dcad5bd784ec07d81
|
|
||||||
**Status:** DONE
|
|
||||||
**Tasks completed:** 1 of 1
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
Fixes the bug surfaced by the 2026-05-13 form-a fieldtest: an unbound
|
|
||||||
identifier inside an `instance` method body slipped past `ail check`
|
|
||||||
and surfaced only at `ail build` as the degraded internal-error
|
|
||||||
diagnostic `monomorphise_workspace: unknown identifier: ...`. Root
|
|
||||||
cause: `check_def`'s `Def::Class(_) | Def::Instance(_)` arm at
|
|
||||||
`crates/ailang-check/src/lib.rs:1574-1595` early-returned `Ok(())`
|
|
||||||
without ever invoking the body walker — the comment claimed "instance-
|
|
||||||
body typechecking (with class-method substitution) land[s] in 22b.2",
|
|
||||||
but that wiring was never implemented. Workspace-load coherence checks
|
|
||||||
(`build_registry`'s Orphan/Duplicate/MissingMethod) only inspect
|
|
||||||
instance schema, not method-body identifier graphs.
|
|
||||||
|
|
||||||
Fix is the minimum-scope change at that call site: split the combined
|
|
||||||
arm so `Def::Class(_)` continues to be schema-only, while
|
|
||||||
`Def::Instance(inst)` calls a new helper `check_instance` that, for
|
|
||||||
each `InstanceMethod`, lifts the body `Term::Lam` into a synthetic
|
|
||||||
`FnDef` (class-method-substituted via `substitute_rigids` /
|
|
||||||
`substitute_rigids_in_term` against the class's `param` name as
|
|
||||||
declared in `env.class_methods[(qualified_class, method)]`) and
|
|
||||||
hands it to the existing `check_fn`. The reuse of `check_fn` is what
|
|
||||||
gets the body walked through the same identifier-resolution path as
|
|
||||||
fn bodies, so an unbound name fires `[unbound-var]` at `ail check`
|
|
||||||
with exit 1 and the standard structured diagnostic.
|
|
||||||
|
|
||||||
Per the carrier's "minimal fix, no surrounding cleanup" constraint:
|
|
||||||
no widening to `Def::Class` (still schema-only); no changes to the
|
|
||||||
`monomorphise_workspace` diagnostic wording (the defence-in-depth
|
|
||||||
internal-error path stays as a backstop); no changes to mono.rs,
|
|
||||||
codegen, or any other crate.
|
|
||||||
|
|
||||||
## Per-task notes
|
|
||||||
|
|
||||||
- iter bugfix-instance-body-unbound-var.1: split the
|
|
||||||
`Def::Class(_) | Def::Instance(_)` arm into `Def::Class(_) =>
|
|
||||||
Ok(())` and `Def::Instance(inst) => check_instance(inst, env,
|
|
||||||
out_warnings)`. New free fn `check_instance` reads
|
|
||||||
`env.class_methods[(qualify_class_ref_in_check(&inst.class,
|
|
||||||
&env.current_module), im.name)]` to recover the class's `param`
|
|
||||||
name, builds a `{class_param: inst.type_}` substitution, applies
|
|
||||||
it to the Lam's `param_tys` / `ret_ty` and to the Lam's body
|
|
||||||
Term (via the existing `substitute_rigids` /
|
|
||||||
`substitute_rigids_in_term` helpers), then constructs a synthetic
|
|
||||||
`FnDef` (`name = "<class>::<method>"`, `ty =
|
|
||||||
Type::fn_implicit(subst_param_tys, subst_ret_ty,
|
|
||||||
lam.effects)`, params from Lam, body = substituted Lam body)
|
|
||||||
and calls `check_fn` on it.
|
|
||||||
|
|
||||||
First-attempt sketch wrapped the Lam's `param_tys` / `ret_ty`
|
|
||||||
unsubstituted on the assumption that the on-disk canonical form
|
|
||||||
was post-substitution — the `codegen_import_map_fallback_pin`
|
|
||||||
regression (and a sweep of the prelude `(typed x a) (typed y a)`
|
|
||||||
shape) immediately disproved that. The substitution step was
|
|
||||||
added at the second iteration of the implementer phase, before
|
|
||||||
reaching the spec / quality phases.
|
|
||||||
|
|
||||||
Non-Lam method bodies are silently skipped — current schema
|
|
||||||
doesn't admit them and the pre-fix behaviour was also silent.
|
|
||||||
|
|
||||||
## Concerns
|
|
||||||
|
|
||||||
- None.
|
|
||||||
|
|
||||||
## Known debt
|
|
||||||
|
|
||||||
- `check_instance` does **not** verify that the substituted method
|
|
||||||
signature matches the corresponding `ClassMethodEntry.method_ty`
|
|
||||||
(with `class_param ↦ inst.type_`). The body-walk catches unbound
|
|
||||||
vars and effect-mismatches against the Lam's own declared types,
|
|
||||||
but a malformed instance whose Lam declares
|
|
||||||
`(typed x (con Int)) (typed y (con Int)) -> Bool` for an
|
|
||||||
`instance Eq Bool` body would currently typecheck cleanly. The
|
|
||||||
workspace-load `MissingMethod` / coherence checks only inspect
|
|
||||||
the (class, type) pair, not the method's declared type. Out of
|
|
||||||
scope per the carrier's "minimal fix" constraint; queued as a
|
|
||||||
follow-up roadmap candidate ("instance-method declared-type
|
|
||||||
vs class-method-type cross-check").
|
|
||||||
|
|
||||||
## Files touched
|
|
||||||
|
|
||||||
- `crates/ailang-check/src/lib.rs` — `check_def` arm split,
|
|
||||||
new `check_instance` helper.
|
|
||||||
|
|
||||||
## Stats
|
|
||||||
|
|
||||||
bench/orchestrator-stats/2026-05-13-iter-bugfix-instance-body-unbound-var.json
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
# iter drift-test-narrowing — design_schema_drift.rs scans §"Data model" only
|
|
||||||
|
|
||||||
**Date:** 2026-05-13
|
|
||||||
**Started from:** rustdoc-sweep working-tree state
|
|
||||||
**Status:** DONE
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
`crates/ailang-core/tests/design_schema_drift.rs` is the
|
|
||||||
drift-detection layer between AST enum definitions in
|
|
||||||
`crates/ailang-core/src/ast.rs` and the canonical schema
|
|
||||||
documentation in `docs/DESIGN.md` §"Data model". Each AST variant
|
|
||||||
must have its JSON-schema anchor (e.g. `"t": "lit"`, `"k": "fn"`)
|
|
||||||
documented; the tests assert presence of each anchor.
|
|
||||||
|
|
||||||
Pre-iter, the assertion was `DESIGN_MD.contains(anchor)` —
|
|
||||||
substring search over the whole document. That was loose: an
|
|
||||||
anchor present only in §"Decision 11" (or any other discussion
|
|
||||||
section) was treated as documented even though §"Data model" —
|
|
||||||
the canonical schema reference section — was missing it. The
|
|
||||||
audit-form-a-precursor `[high]` drift report flagged this as the
|
|
||||||
silent-pass failure mode.
|
|
||||||
|
|
||||||
This iter narrows the scan. New helper `data_model_section()`
|
|
||||||
extracts the slice of `DESIGN_MD` from the `## Data model` header
|
|
||||||
to the next top-level `## ` header. The 7 existing anchor tests
|
|
||||||
now call `data_model_section().contains(anchor)` instead of
|
|
||||||
`DESIGN_MD.contains(anchor)`. All 38 anchors verified present in
|
|
||||||
§"Data model" pre-edit (no regressions). A new test
|
|
||||||
`data_model_section_is_bounded` pins the extractor itself
|
|
||||||
(starts-with `## Data model`, does not bleed past `\n## Pipeline`,
|
|
||||||
length > 1 KB) so a future refactor cannot silently regress the
|
|
||||||
extractor to whole-document scanning.
|
|
||||||
|
|
||||||
## Files touched
|
|
||||||
|
|
||||||
- `crates/ailang-core/tests/design_schema_drift.rs` (M)
|
|
||||||
|
|
||||||
## Verification
|
|
||||||
|
|
||||||
- `cargo test -p ailang-core --test design_schema_drift` →
|
|
||||||
8 passed (was 7; +1 for the bounded-section pin), 0 failed.
|
|
||||||
- `cargo test --workspace` → 563 passed (was 562; +1 from the
|
|
||||||
new pin), 0 failed.
|
|
||||||
|
|
||||||
## Concerns
|
|
||||||
|
|
||||||
- (none)
|
|
||||||
|
|
||||||
## Known debt
|
|
||||||
|
|
||||||
- (none)
|
|
||||||
@@ -1,71 +0,0 @@
|
|||||||
# Iter form-a.0 — Prelude Pilot
|
|
||||||
|
|
||||||
**Date:** 2026-05-13
|
|
||||||
**Parent spec:** `docs/specs/2026-05-13-form-a-default-authoring.md`
|
|
||||||
**Parent plan:** `docs/plans/2026-05-13-iter-form-a.0.md`
|
|
||||||
**Started from:** 4c2a3c5d0811aafb1bce5d6de8ad9a938fd98681
|
|
||||||
|
|
||||||
## Outcome
|
|
||||||
|
|
||||||
`examples/prelude.ail` rendered from `examples/prelude.ail.json` via
|
|
||||||
`ail render` (116 lines / 6386 bytes — exact recon match). The two
|
|
||||||
auto-discovering roundtrip tests in
|
|
||||||
`crates/ailang-surface/tests/round_trip.rs` both pass on the new
|
|
||||||
fixture without test-code changes:
|
|
||||||
|
|
||||||
- `every_ail_fixture_matches_its_json_counterpart` — green; the
|
|
||||||
`prelude.ail` parses to canonical bytes byte-equal to
|
|
||||||
`prelude.ail.json`.
|
|
||||||
- `parse_then_print_then_parse_is_idempotent_on_every_ail_fixture`
|
|
||||||
— green; the Form-A printer is idempotent over prelude.
|
|
||||||
|
|
||||||
`prelude.ail.json` is retained on disk per spec §A2; iter 1 deletes
|
|
||||||
it alongside the bulk test-infrastructure refactor. This is the
|
|
||||||
only iteration in the milestone where a fixture is dual-form.
|
|
||||||
|
|
||||||
## Why this iter is the pilot
|
|
||||||
|
|
||||||
Spec §A2 names prelude as the pilot because it exercises the widest
|
|
||||||
cross-section of language features in a single file: typeclasses
|
|
||||||
with superclasses (`Ord extends Eq`), polymorphic free functions
|
|
||||||
with constraints (`forall<a> where Show a fn print`), IO-effect
|
|
||||||
signatures, primitive ctor patterns, and the loader's auto-injection
|
|
||||||
path. A green roundtrip on prelude is strong evidence the bulk
|
|
||||||
migration in iter 1+ will not surface render or parse bugs.
|
|
||||||
|
|
||||||
## Verifications run
|
|
||||||
|
|
||||||
- `cargo run -q --bin ail -- render examples/prelude.ail.json > examples/prelude.ail` — exit 0
|
|
||||||
- `wc -lc examples/prelude.ail` — `116 6386` (exact recon prediction)
|
|
||||||
- `head -n 5 examples/prelude.ail` — first line `(module prelude`, second ` (data Ordering` (exact recon prediction)
|
|
||||||
- `cargo test -p ailang-surface --test round_trip every_ail_fixture_matches_its_json_counterpart` — PASS (1/1)
|
|
||||||
- `cargo test -p ailang-surface --test round_trip parse_then_print_then_parse_is_idempotent_on_every_ail_fixture` — PASS (1/1)
|
|
||||||
- `cargo test --workspace` — PASS, **558 tests green**, identical to pre-iter baseline (audit-24 / iter-24.tidy lineage).
|
|
||||||
|
|
||||||
## Scope NOT touched
|
|
||||||
|
|
||||||
Per spec §"Iteration scope": no test-infrastructure changes, no
|
|
||||||
CLAUDE.md / DESIGN.md edits, no deletion of `prelude.ail.json`,
|
|
||||||
no migration script yet. Those land in iter 1.
|
|
||||||
|
|
||||||
## INDEX append — Boss-side
|
|
||||||
|
|
||||||
Plan Task 2 Step 2 asks for an INDEX.md append. Per the
|
|
||||||
`ailang-implement-orchestrator` Iron Law, `docs/journals/INDEX.md`
|
|
||||||
is Boss-only. The orchestrator does not touch it. The Boss appends
|
|
||||||
the line at commit time. Suggested INDEX line (from the plan
|
|
||||||
verbatim, byte/line counts confirmed by Task 1 Step 2):
|
|
||||||
|
|
||||||
```
|
|
||||||
- 2026-05-13 — iter form-a.0: prelude pilot for Form-A-as-default-authoring milestone — examples/prelude.ail rendered (116 lines / 6386 bytes) via `ail render`; two auto-discovering roundtrip tests (every_ail_fixture_matches_its_json_counterpart + parse_then_print_then_parse_is_idempotent_on_every_ail_fixture) green on the new fixture without test-code changes; prelude.ail.json retained per spec §A2 (singular dual-form iter, deletion lands iter 1 with bulk test-infra refactor); cargo test --workspace green → 2026-05-13-iter-form-a.0.md
|
|
||||||
```
|
|
||||||
|
|
||||||
## Files touched
|
|
||||||
|
|
||||||
- `examples/prelude.ail` (new, ~6386 bytes / 116 lines)
|
|
||||||
- `docs/journals/2026-05-13-iter-form-a.0.md` (this file)
|
|
||||||
- `bench/orchestrator-stats/2026-05-13-iter-form-a.0.json` (stats)
|
|
||||||
|
|
||||||
## Stats
|
|
||||||
|
|
||||||
`bench/orchestrator-stats/2026-05-13-iter-form-a.0.json`
|
|
||||||
@@ -1,431 +0,0 @@
|
|||||||
# iter form-a.1 — Big-bang migration (Tasks 1-5: additive phase + relocation)
|
|
||||||
|
|
||||||
**Date:** 2026-05-13
|
|
||||||
**Started from:** 1a065b37 (post-iter 24.tidy)
|
|
||||||
**Status:** DONE (Tasks 1-5 only; Tasks 6-12 deferred to next dispatch)
|
|
||||||
**Tasks completed:** 5 of 12
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
First half of the form-a-default-authoring milestone-close iter: T1
|
|
||||||
adds three new tests (parse-determinism + CLI-pipeline-idempotency +
|
|
||||||
carve-out-inventory-but-`#[ignore]`'d), T2 bulk-renders the 99
|
|
||||||
missing `examples/<stem>.ail` files via `ail render` (corpus 58 →
|
|
||||||
157), T3 + T4 migrate ~26 test files from `ailang_core::load_workspace`
|
|
||||||
to `ailang_surface::load_workspace` (Group A) and flip subprocess
|
|
||||||
fixture-suffix strings from `.ail.json` to `.ail` (Group B), T5
|
|
||||||
relocates `#[cfg(test)] mod tests` blocks from production source
|
|
||||||
(`ailang-core/src/hash.rs`, `ailang-core/src/workspace.rs`
|
|
||||||
non-carve-out subset, `ailang-codegen/src/lib.rs:3717-3799`) to
|
|
||||||
integration test crates that carry `ailang-surface` as a
|
|
||||||
dev-dependency, eliminating the production-source dependency on
|
|
||||||
`.ail.json` fixture reads. The remaining Tasks 6-12 (bench-driver
|
|
||||||
suffix flips, e2e diff-test rewrite, the actual 156-file
|
|
||||||
`.ail.json` deletion, retiring obsolete roundtrip tests +
|
|
||||||
schema_coverage corpus flip, DESIGN.md §Roundtrip Invariant
|
|
||||||
restatement, CLAUDE.md + DESIGN.md doctrine edits, WhatsNew +
|
|
||||||
roadmap close) run in the Boss's next dispatch on this iter ID.
|
|
||||||
`cargo test --workspace` is green at every per-task boundary (560
|
|
||||||
passed + 4 ignored at iter-half close; 558 + 3 new − 1 transit
|
|
||||||
`#[ignore]` on the inventory test). One Task-2 plan defect surfaced
|
|
||||||
inline and was repaired by pre-emptive forward-pull of Task-3
|
|
||||||
migrations.
|
|
||||||
|
|
||||||
## Per-task notes
|
|
||||||
|
|
||||||
- **iter form-a.1.1** — Added `parse_is_deterministic_over_every_ail_fixture`
|
|
||||||
(round_trip.rs) and `cli_parse_then_render_then_parse_is_idempotent`
|
|
||||||
(roundtrip_cli.rs); created `crates/ailang-core/tests/carve_out_inventory.rs`
|
|
||||||
with the 8-file hardcoded list and `#[ignore]`'d until Task 8.
|
|
||||||
The plan's `cli_parse_then_render_then_parse_is_idempotent` body
|
|
||||||
used an `ail()` helper that doesn't exist in the repo (assert_cmd
|
|
||||||
is not a dev-dep); adapted to use `Command::new(ail_bin())`
|
|
||||||
matching the file's existing pattern. Plan acknowledged this
|
|
||||||
possibility — "verify before writing the body; if `list_ail_fixtures`
|
|
||||||
is missing in roundtrip_cli.rs, add a parallel helper at lines 47+."
|
|
||||||
- **iter form-a.1.2** — Rendered the 99 missing `.ail` files. The
|
|
||||||
bulk render exposed a recon defect: `load_workspace` (since
|
|
||||||
ext-cli.1) prefers `.ail` siblings for *imports* even when the
|
|
||||||
entry is `.ail.json`. The seven Group-A test files whose entries
|
|
||||||
were `.ail.json` but whose imports gained newly-rendered `.ail`
|
|
||||||
siblings broke at Task 2's green-gate. Repair pulled forward five
|
|
||||||
Task-3 migrations (mono_xmod_ctor_pattern, 3 sites in
|
|
||||||
typeclass_22b2, 1 site in typeclass_22b3, env_construction_pin,
|
|
||||||
4 sites in ailang-check/tests/workspace.rs, ctt2_registry_rekey)
|
|
||||||
plus `#[ignore]`'d the 4 fixture-loading tests in
|
|
||||||
`ailang-core/src/workspace.rs` `mod tests` with explicit
|
|
||||||
`form-a.1 transit: Task 5 relocates...` markers. Also added
|
|
||||||
`ailang-surface = { path = "../ailang-surface" }` to
|
|
||||||
`crates/ailang-core/Cargo.toml` `[dev-dependencies]` (required
|
|
||||||
for the ctt2_registry_rekey integration test to use
|
|
||||||
`ailang_surface::load_workspace`).
|
|
||||||
- **iter form-a.1.3** — Finished Group A migration across the
|
|
||||||
remaining 12 Group-A files (10 + the 2 already migrated in T2
|
|
||||||
scope-pull). Two files (`mono_recursive_fn.rs`,
|
|
||||||
`mono_xmod_qualified_ref.rs`) were Group-B-pattern (subprocess
|
|
||||||
CLI, suffix-only flip) but listed in plan's Group A; treated
|
|
||||||
per their actual shape.
|
|
||||||
- **iter form-a.1.4** — Group-B migration: bulk regex flip of
|
|
||||||
`build_and_run("X.ail.json")` → `build_and_run("X.ail")` (3
|
|
||||||
patterns) in e2e.rs across 59 invocations; plus targeted
|
|
||||||
subprocess `examples/<fixture>.ail.json` → `.ail` flips at 11
|
|
||||||
more e2e.rs sites (6× `ws_main`, 2× `sum`, 1× `escape_local_demo`,
|
|
||||||
1× `list_map_poly`, 1× `std_either` left as-is per its raw-bytes
|
|
||||||
roundtrip semantics). Four additional sites in e2e.rs preserved
|
|
||||||
intentionally for raw-JSON inspection (`borrow_own_demo`,
|
|
||||||
`reuse_as_demo`, the `sum.ail.json` diff-test pending Task 7
|
|
||||||
rewrite, and the `ail_run_accepts_ail_source...` dual-form smoke).
|
|
||||||
Four plan-Group-B files (`mono_hash_stability`, `prelude_free_fns`,
|
|
||||||
`print_mono_body_shape`, `show_mono_synthesis`) were
|
|
||||||
Group-A-pattern (consumers of `load_workspace`); migrated both
|
|
||||||
imports AND paths.
|
|
||||||
- **iter form-a.1.5** — Relocated 10 fixture-loading tests from
|
|
||||||
`ailang-core/src/workspace.rs` `mod tests` to new
|
|
||||||
`crates/ailang-core/tests/workspace_pin.rs` (integration test
|
|
||||||
crate); relocated all 8 tests from `ailang-core/src/hash.rs`
|
|
||||||
`mod tests` plus 2 in-memory unit tests to
|
|
||||||
`crates/ailang-core/tests/hash_pin.rs`; relocated 3 tests from
|
|
||||||
`ailang-codegen/src/lib.rs:3717-3799` to
|
|
||||||
`crates/ailang-codegen/tests/eq_primitives_pin.rs`; migrated
|
|
||||||
`ailang-prose/tests/snapshot.rs` (helper + 8 fixtures) to `.ail`
|
|
||||||
+ `ailang_surface::load_module`. Carve-out tests stay in-place
|
|
||||||
(3× `class_param_in_applied_position` / `superclass_with_wrong_param`
|
|
||||||
/ `constraint_with_unbound_var` for 22b2; 3× `ct1_fixture_*`
|
|
||||||
for ct.1). Tempdir-based loader tests
|
|
||||||
(`user_module_named_prelude_is_rejected`, `detects_import_cycle`,
|
|
||||||
`module_not_found_yields_structured_error`) stay in-place too —
|
|
||||||
they don't consume the examples corpus, so they don't need
|
|
||||||
migration for the Form-A goal. Added `ailang-surface` as a
|
|
||||||
`[dev-dependencies]` entry to `ailang-codegen/Cargo.toml` and
|
|
||||||
`ailang-prose/Cargo.toml`. Hash.rs has `mod tests {}` placeholder
|
|
||||||
kept; workspace.rs `mod tests` shrunk from 73 to 57 tests +
|
|
||||||
6 carve-outs unchanged.
|
|
||||||
|
|
||||||
## Concerns
|
|
||||||
|
|
||||||
- **e2e.rs raw-JSON-inspect tests** (4 sites: `borrow_own_demo_modes_are_metadata_only`,
|
|
||||||
`reuse_as_demo_under_rc_uses_inplace_rewrite`, `diff_detects_changed_def`,
|
|
||||||
`render_parse_round_trip_canonical`, plus the dual-form smoke
|
|
||||||
`ail_run_accepts_ail_source_with_same_stdout_as_ail_json`) currently
|
|
||||||
read raw `.ail.json` bytes for substring asserts or byte-roundtrip
|
|
||||||
comparison. They pass today (the `.ail.json` files still exist
|
|
||||||
pre-T8) but will break at T8 when `.ail.json` siblings are deleted.
|
|
||||||
Plan Task 7 only addresses the `diff_detects_changed_def` test (the
|
|
||||||
one that mutates JSON). The other three are out of T1-5 scope and
|
|
||||||
inherit to Boss for T6-12 dispatch resolution (either parse-derived
|
|
||||||
JSON via `ail parse` per T7 pattern, or `#[ignore]` with explicit
|
|
||||||
marker).
|
|
||||||
- **`ailang-core/src/workspace.rs` `mod tests`**: 3 tempdir-based
|
|
||||||
loader-mechanism tests (lines :1743, :1759, :1776 pre-relocation;
|
|
||||||
current line numbers shifted by the deletions) remain in-place
|
|
||||||
per implementer-judgement (they don't consume `examples/`, so
|
|
||||||
the Form-A goal is trivially satisfied). Plan suggested
|
|
||||||
relocating them for doctrinal cleanup; scope reduction is
|
|
||||||
deliberate. They continue to test what they always tested
|
|
||||||
(loader cycle/missing-module/user-prelude-conflict behaviour)
|
|
||||||
and pass.
|
|
||||||
- **Plan recon defect on Group-A vs. Group-B classification**:
|
|
||||||
`mono_hash_stability.rs`, `prelude_free_fns.rs`,
|
|
||||||
`print_mono_body_shape.rs`, `show_mono_synthesis.rs` were listed
|
|
||||||
in plan's Group B but actually use `ailang_core::workspace::load_workspace`
|
|
||||||
directly (Group-A pattern). Migrated correctly by switching both
|
|
||||||
the import and the path. Recorded so a future recon-tightening
|
|
||||||
pass on the planner skill can review whether path-grep-only
|
|
||||||
recon is sufficient for Group A/B classification.
|
|
||||||
- **`mono_recursive_fn.rs` and `mono_xmod_qualified_ref.rs`** were
|
|
||||||
symmetric — listed in plan's Group A but actually CLI subprocess
|
|
||||||
callers (Group-B pattern). Suffix-only flip applied as if Group B.
|
|
||||||
|
|
||||||
## Known debt
|
|
||||||
|
|
||||||
- 4 raw-JSON-inspect tests in e2e.rs need T7-style rewrites or
|
|
||||||
`#[ignore]` markers before Task 8's `.ail.json` deletion lands
|
|
||||||
(see Concerns).
|
|
||||||
- 3 tempdir tests in `ailang-core/src/workspace.rs` `mod tests`
|
|
||||||
could be relocated as doctrinal cleanup but functionally don't
|
|
||||||
need to be (see Concerns).
|
|
||||||
- `mod tests {}` placeholder in `ailang-core/src/hash.rs` could
|
|
||||||
be deleted entirely; left as a structural breadcrumb pointing at
|
|
||||||
the integration test crate.
|
|
||||||
|
|
||||||
## Files touched
|
|
||||||
|
|
||||||
Working tree: 37 modified + 14 new test files + 99 new `.ail`
|
|
||||||
fixtures = 150 paths. Detail:
|
|
||||||
|
|
||||||
### Modified production / test source (37)
|
|
||||||
|
|
||||||
- `crates/ail/tests/`: codegen_import_map_fallback_pin.rs,
|
|
||||||
ct1_check_cli.rs, e2e.rs (extensive build_and_run + 11
|
|
||||||
subprocess sites), eq_float_noinstance.rs, eq_ord_e2e.rs,
|
|
||||||
floats_e2e.rs, ir_snapshot.rs, mono_hash_stability.rs,
|
|
||||||
mono_recursive_fn.rs, mono_unification.rs, mono_xmod_ctor_pattern.rs,
|
|
||||||
mono_xmod_qualified_ref.rs, mq3_multi_class_e2e.rs,
|
|
||||||
polyfn_dot_qualified_branch_pin.rs, prelude_free_fns.rs,
|
|
||||||
print_mono_body_shape.rs, roundtrip_cli.rs, show_mono_synthesis.rs,
|
|
||||||
show_no_instance_e2e.rs, show_print_e2e.rs, typeclass_22b2.rs,
|
|
||||||
typeclass_22b3.rs, typeclass_22c.rs (23 files)
|
|
||||||
- `crates/ailang-check/tests/`: env_construction_pin.rs,
|
|
||||||
method_collision_pin.rs, show_dispatch_pin.rs, workspace.rs (4)
|
|
||||||
- `crates/ailang-codegen/`: Cargo.toml + src/lib.rs (2)
|
|
||||||
- `crates/ailang-core/`: Cargo.toml + src/hash.rs + src/workspace.rs
|
|
||||||
+ tests/ctt2_registry_rekey.rs (4)
|
|
||||||
- `crates/ailang-prose/`: Cargo.toml + tests/snapshot.rs (2)
|
|
||||||
- `crates/ailang-surface/tests/round_trip.rs` (1)
|
|
||||||
- `Cargo.lock` (1)
|
|
||||||
|
|
||||||
### New (14 test files + 99 rendered .ail fixtures)
|
|
||||||
|
|
||||||
- `crates/ailang-codegen/tests/eq_primitives_pin.rs` (relocated T5)
|
|
||||||
- `crates/ailang-core/tests/hash_pin.rs` (relocated T5)
|
|
||||||
- `crates/ailang-core/tests/workspace_pin.rs` (relocated T5)
|
|
||||||
- `crates/ailang-core/tests/carve_out_inventory.rs` (new T1)
|
|
||||||
- 99× `examples/<stem>.ail` rendered via `ail render` (T2)
|
|
||||||
|
|
||||||
## Stats
|
|
||||||
|
|
||||||
bench/orchestrator-stats/2026-05-13-iter-form-a.1.json
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
# iter form-a.1 — Big-bang migration (Tasks 6-12: bench drivers, e2e rewrites, deletion, doctrine, milestone close)
|
|
||||||
|
|
||||||
**Date:** 2026-05-13
|
|
||||||
**Started from:** 9332d1e (post-T1-5 commit 77b28ad + plan-amendment 9332d1e)
|
|
||||||
**Status:** DONE (Tasks 6-12; iter form-a.1 fully closed)
|
|
||||||
**Tasks completed:** 7 of 7 (this dispatch); 12 of 12 (iter total)
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
Second half of the form-a-default-authoring milestone-close iter: T6
|
|
||||||
flips the bench-driver fixture suffix from `.ail.json` to `.ail` (4
|
|
||||||
Python scripts + run.sh). T7 re-authors three e2e tests
|
|
||||||
(`diff_detects_changed_def`, `borrow_own_demo_modes_are_metadata_only`,
|
|
||||||
`reuse_as_demo_under_rc_uses_inplace_rewrite`) to derive their
|
|
||||||
canonical-JSON via `ail parse` on the `.ail` source rather than
|
|
||||||
raw-reading the soon-to-be-deleted `.ail.json` file, retires the
|
|
||||||
redundant `render_parse_round_trip_canonical` (subsumed by T1's
|
|
||||||
`cli_parse_then_render_then_parse_is_idempotent`), and re-authors
|
|
||||||
`ail_run_accepts_ail_source_with_same_stdout_as_ail_json` to derive
|
|
||||||
a tempdir-based `hello.ail.json` from `hello.ail` via `ail parse`
|
|
||||||
rather than reading a deleted `.ail.json` sibling. T8 deletes the
|
|
||||||
156 non-carve-out `.ail.json` files (count drops 164 → 8), removes
|
|
||||||
the `#[ignore]` on the carve-out inventory test, and pulls forward
|
|
||||||
20 stale-`.ail.json`-reference repairs that the T1-5 dispatch missed
|
|
||||||
(12 `let example = "X.ail.json"` Group-B sites + 5
|
|
||||||
`ailang_core::load_workspace` Group-A sites in e2e.rs + 3 `ail_run`
|
|
||||||
Group-B sites in typeclass_22b3.rs); to keep T8's green-gate, T9
|
|
||||||
Step 5 (schema_coverage corpus flip from `.ail.json` to `.ail`) is
|
|
||||||
also pulled forward into T8. T9 retires the 3 obsolete roundtrip
|
|
||||||
tests (`print_then_parse_round_trips_every_fixture`,
|
|
||||||
`every_ail_fixture_matches_its_json_counterpart`,
|
|
||||||
`cli_render_then_parse_preserves_canonical_bytes_on_every_fixture`)
|
|
||||||
plus the dead helpers `list_json_fixtures` (×2),
|
|
||||||
`round_trip_one`, and `strip_trailing_newlines`. T10 restates the
|
|
||||||
DESIGN.md §Roundtrip Invariant with parse-determinism + idempotency
|
|
||||||
+ CLI-pipeline-idempotency + carve-out-anchor framing, replacing
|
|
||||||
the old Direction-1/Direction-2 framing and the five enforcement
|
|
||||||
bullets with the five surviving test names. T11 replaces the
|
|
||||||
CLAUDE.md and DESIGN.md doctrine sentences per spec §A4. T12
|
|
||||||
appends the WhatsNew milestone-close entry, strikes the roadmap
|
|
||||||
entry (`- [x]` + `(Closed 2026-05-13 by iter form-a.1.)`), and
|
|
||||||
verifies final inventory (8 `.ail.json` carve-outs + 157 `.ail`).
|
|
||||||
Final test count: 557 green + 3 ignored (3 pre-existing doctests).
|
|
||||||
The `cargo test --workspace` baseline holds green at every per-task
|
|
||||||
boundary; both `python3 bench/compile_check.py` and
|
|
||||||
`python3 bench/cross_lang.py` exit 0. The milestone
|
|
||||||
`[Form-A as the default authoring surface]` is fully closed.
|
|
||||||
|
|
||||||
## Per-task notes
|
|
||||||
|
|
||||||
- **iter form-a.1.6** — Bench-driver suffix flips in
|
|
||||||
`bench/compile_check.py:43,101`, `bench/cross_lang.py:48,80`,
|
|
||||||
`bench/mono_dispatch.py:10,112`, `bench/run.sh:68,182,183`.
|
|
||||||
Mechanical edit; comment text and path string updated at each
|
|
||||||
site. `python3 bench/compile_check.py` exit 0; `python3
|
|
||||||
bench/cross_lang.py` exit 0. (First compile_check run reported
|
|
||||||
exit-1 due to sub-millisecond `check_ms` noise on `hello` and
|
|
||||||
`borrow_own_demo`; second run clean. The noise is unrelated to
|
|
||||||
the migration — the bench measures `ail check` runtime, and the
|
|
||||||
Python edits did not touch the binary path.)
|
|
||||||
- **iter form-a.1.7** — Re-authored 3 raw-JSON-inspect tests +
|
|
||||||
retired 1 + re-authored the dual-form smoke. Pattern for the
|
|
||||||
three substring-assert tests: replace
|
|
||||||
`std::fs::read*("examples/<X>.ail.json")` with a `Command::new(ail_bin()).args(["parse", "examples/<X>.ail", "-o", tmpfile])`
|
|
||||||
+ `read_to_string(tmpfile)`. `reuse_as_demo_under_rc_uses_inplace_rewrite`
|
|
||||||
additionally switches its `ailang_core::load_workspace(&json_path)`
|
|
||||||
call to `ailang_surface::load_workspace(&ail_path)` (the
|
|
||||||
IR-lowering path no longer needs the JSON file). The dual-form
|
|
||||||
smoke (`ail_run_accepts_ail_source_with_same_stdout_as_ail_json`)
|
|
||||||
uses a per-process tempdir with `hello.ail.json` as the file
|
|
||||||
basename — necessary because the loader name-checks the module
|
|
||||||
name against the file stem, and the first attempt at a
|
|
||||||
tempfile-with-PID-suffix path tripped that check. Net test
|
|
||||||
count: −1 (retirement of `render_parse_round_trip_canonical`).
|
|
||||||
559 green at task close.
|
|
||||||
- **iter form-a.1.8** — CRITICAL task. Before the deletion command,
|
|
||||||
20 stale-`.ail.json`-reference repairs were pulled forward
|
|
||||||
(T1-5 dispatch missed these despite the plan's Group-A/B scope):
|
|
||||||
(a) 12 `let example = "X.ail.json"` patterns in `e2e.rs` flipped
|
|
||||||
to `"X.ail"` via sed; (b) 5 `ailang_core::load_workspace(&json_path)`
|
|
||||||
sites in `e2e.rs` switched to `ailang_surface::load_workspace`
|
|
||||||
(the `let example = "X.ail"` from (a) feeds into `workspace.join("examples").join(example)`,
|
|
||||||
which is `.ail` post-flip — needs the extension-dispatching loader);
|
|
||||||
(c) 3 `ail_run("X.ail.json")` sites in `typeclass_22b3.rs` flipped
|
|
||||||
via sed. Cargo-test green pre-deletion at 559. The plan's deletion
|
|
||||||
bash loop ran clean (exit 0); `ls examples/*.ail.json | wc -l` →
|
|
||||||
8, `ls examples/*.ail | wc -l` → 157. The `#[ignore]` attribute
|
|
||||||
on `examples_ail_json_inventory_matches_carve_outs` was removed;
|
|
||||||
the test passes (8 files match `EXPECTED`). Post-deletion
|
|
||||||
cargo-test surfaced 1 failure
|
|
||||||
(`every_ast_variant_is_observed_in_the_fixture_corpus` in
|
|
||||||
`schema_coverage.rs`) because its corpus walker still looked at
|
|
||||||
`.ail.json` (now down to 8 carve-outs, insufficient to cover all
|
|
||||||
AST variants). The plan-T8 vs. T9 ordering implicitly assumed T9
|
|
||||||
Step 5 (schema_coverage corpus flip) would land before T8 — but
|
|
||||||
T9 is plan-ordered after T8. To satisfy T8's green-gate, T9
|
|
||||||
Step 5 was pulled forward inline (helper rename
|
|
||||||
`list_json_fixtures` → `list_ail_fixtures`, filter `.ail.json`
|
|
||||||
→ `.ail`, loader `ailang_core::load_module` →
|
|
||||||
`ailang_surface::load_module`). Post-fix: 560 green + 3 ignored
|
|
||||||
(was 559 pre-T8 with 4 ignored including carve_out_inventory;
|
|
||||||
un-ignore + correct schema_coverage flip = +1 net = 560).
|
|
||||||
- **iter form-a.1.9** — Retired 3 obsolete roundtrip tests +
|
|
||||||
cleanups. `round_trip.rs`: retired
|
|
||||||
`print_then_parse_round_trips_every_fixture` (lines 57-101 in
|
|
||||||
pre-iter numbering) and `every_ail_fixture_matches_its_json_counterpart`
|
|
||||||
(lines 120-204); retired the `list_json_fixtures` helper and
|
|
||||||
the `round_trip_one` helper (only consumers were the two retired
|
|
||||||
tests); updated the file's doc-comment to describe the two
|
|
||||||
surviving tests (`parse_is_deterministic_*` and
|
|
||||||
`parse_then_print_then_parse_*`); removed the unused `Path`
|
|
||||||
import (only `PathBuf` remains). `roundtrip_cli.rs`: retired
|
|
||||||
`cli_render_then_parse_preserves_canonical_bytes_on_every_fixture`
|
|
||||||
(lines 116-146 pre-iter) and the `list_json_fixtures` +
|
|
||||||
`roundtrip_one` + `strip_trailing_newlines` helpers; updated the
|
|
||||||
doc-comment. T9 Step 5 (schema_coverage corpus flip) was already
|
|
||||||
done in T8; nothing to do here. 557 green + 3 ignored at task
|
|
||||||
close (560 pre-T9 − 3 retired = 557).
|
|
||||||
- **iter form-a.1.10** — DESIGN.md §Roundtrip Invariant restatement.
|
|
||||||
Lines 2029-2048 (Direction 1/Direction 2 framing) replaced with
|
|
||||||
the new 4-property framing (parse-determinism +
|
|
||||||
idempotency-under-print + CLI-pipeline-idempotency +
|
|
||||||
carve-out-anchor) verbatim from the plan. Lines 2061-2094
|
|
||||||
(Enforcement bullets, five tests) replaced with the five
|
|
||||||
surviving tests verbatim. Float-literals subsection (lines
|
|
||||||
2050-2059 pre-edit, 2065-2074 post-edit) preserved. Why-anchored-at-top-level
|
|
||||||
subsection (lines 2096-2108 pre-edit, 2108-2120 post-edit)
|
|
||||||
preserved. The numbered prose (4 properties) and the bulleted
|
|
||||||
enforcement (5 tests) are internally consistent; the carve-out
|
|
||||||
inventory is the 5th enforcement bullet, covering property 4
|
|
||||||
("carve-out anchor") as a meta-property.
|
|
||||||
- **iter form-a.1.11** — Doctrine edits. CLAUDE.md:5-6 sentence
|
|
||||||
replaced verbatim with spec §A4 wording. DESIGN.md:465-466 first
|
|
||||||
sentence replaced verbatim with spec §A4 wording; second
|
|
||||||
sentence ("Form (A) is one projection; the JSON-AST stays
|
|
||||||
canonical.") preserved as the trailing sentence of the new
|
|
||||||
replacement block. Doc-only; tests unaffected.
|
|
||||||
- **iter form-a.1.12** — Milestone close. WhatsNew.md entry
|
|
||||||
appended ("Form A is now the authoring surface", per-boss
|
|
||||||
editorial rules: no crate names, no iter codes, lead with the
|
|
||||||
change, factual). docs/roadmap.md entry at line 97 changed from
|
|
||||||
`- [ ]` to `- [x]` with `(Closed 2026-05-13 by iter form-a.1.)`
|
|
||||||
prepended. Final cargo-test: 557 green + 3 ignored. Final bench:
|
|
||||||
both compile_check and cross_lang exit 0. Final inventory:
|
|
||||||
exactly 8 `.ail.json` (carve-outs alphabetically: broken_unbound,
|
|
||||||
prelude, test_22b2_invalid_superclass_param,
|
|
||||||
test_22b2_kind_mismatch, test_22b2_unbound_constraint_var,
|
|
||||||
test_ct1_bad_qualifier, test_ct1_bare_xmod_rejected,
|
|
||||||
test_ct1_qualified_class_rejected) + 157 `.ail`.
|
|
||||||
|
|
||||||
## Concerns
|
|
||||||
|
|
||||||
- **Forward-pull of T9 Step 5 into T8.** The plan ordered T8
|
|
||||||
(deletion) before T9 (test retirement + schema_coverage flip),
|
|
||||||
but T8's green-gate required schema_coverage to be flipped
|
|
||||||
*first* — otherwise the schema_coverage corpus walker shrinks
|
|
||||||
from 164 `.ail.json` to 8 carve-outs and the AST-variant
|
|
||||||
coverage assertion fails. Pulled T9 Step 5 inline into T8;
|
|
||||||
T9 then had no schema_coverage work left to do. Recorded so a
|
|
||||||
future planner-skill recon pass can detect this ordering
|
|
||||||
defect class (a Task K green-gate depending on an edit
|
|
||||||
scripted for Task K+N).
|
|
||||||
- **T1-5 Group-A/B classification gaps.** The T1-5 dispatch's
|
|
||||||
Concerns flagged 4 raw-JSON-inspect e2e.rs tests but missed
|
|
||||||
20 additional sites of the same kind in two patterns: 12
|
|
||||||
`let example = "X.ail.json"` Group-B-pattern sites in e2e.rs
|
|
||||||
(feeds `build_and_run_with_alloc`) + 5
|
|
||||||
`ailang_core::load_workspace` Group-A-pattern sites in e2e.rs
|
|
||||||
+ 3 `ail_run` Group-B sites in typeclass_22b3.rs. All repaired
|
|
||||||
inline in T8 before the deletion command ran. The pattern that
|
|
||||||
T1-5 missed is "binding pulled out of the call expression" —
|
|
||||||
the `let example = "X.ail.json"` introduces a local that the
|
|
||||||
Group-B sed for `build_and_run("X.ail.json")` doesn't match.
|
|
||||||
Worth noting for a future planner-recon tightening pass.
|
|
||||||
- **bench/compile_check.py first-run flakiness.** The first run
|
|
||||||
after T6 reported exit-1 because two `check_ms` metrics on
|
|
||||||
sub-millisecond timings (`hello`, `borrow_own_demo`) drifted
|
|
||||||
past the 25% tolerance; the second run was exit-0. The drift is
|
|
||||||
measurement noise (system load + ms-scale latency), not a
|
|
||||||
migration-induced regression. Final T12 bench run was exit-0
|
|
||||||
on both scripts.
|
|
||||||
|
|
||||||
## Known debt
|
|
||||||
|
|
||||||
- **None known.** The iter closes the milestone cleanly. Two
|
|
||||||
follow-up roadmap items already exist:
|
|
||||||
- `[milestone] Prelude embed: Form-A as compile-time source`
|
|
||||||
(queued; retires carve-out (b) `prelude.ail.json`).
|
|
||||||
- The Show/print fieldtest example-corpus migration (separately
|
|
||||||
queued; orthogonal to this milestone).
|
|
||||||
- Cosmetic: the `mod tests {}` placeholder in
|
|
||||||
`ailang-core/src/hash.rs` from T5 is still in-tree; could be
|
|
||||||
deleted entirely in a doctrinal cleanup pass. Functionally
|
|
||||||
harmless.
|
|
||||||
|
|
||||||
## Files touched (Tasks 6-12)
|
|
||||||
|
|
||||||
### Modified bench (4)
|
|
||||||
|
|
||||||
- `bench/compile_check.py`
|
|
||||||
- `bench/cross_lang.py`
|
|
||||||
- `bench/mono_dispatch.py`
|
|
||||||
- `bench/run.sh`
|
|
||||||
|
|
||||||
### Modified test source (5)
|
|
||||||
|
|
||||||
- `crates/ail/tests/e2e.rs` — T7 re-authoring of 5 tests; T8
|
|
||||||
forward-pull of 12 Group-B + 5 Group-A sites
|
|
||||||
- `crates/ail/tests/typeclass_22b3.rs` — T8 forward-pull of 3
|
|
||||||
Group-B sites
|
|
||||||
- `crates/ail/tests/roundtrip_cli.rs` — T9 retirement +
|
|
||||||
helpers cleanup + doc-comment update
|
|
||||||
- `crates/ailang-surface/tests/round_trip.rs` — T9 retirement +
|
|
||||||
helpers cleanup + doc-comment update + import cleanup
|
|
||||||
- `crates/ailang-core/tests/schema_coverage.rs` — T8 corpus flip
|
|
||||||
to `.ail` (pulled forward from T9 Step 5)
|
|
||||||
|
|
||||||
### Modified test discipline (1)
|
|
||||||
|
|
||||||
- `crates/ailang-core/tests/carve_out_inventory.rs` — T8 un-ignored
|
|
||||||
|
|
||||||
### Modified doc (4)
|
|
||||||
|
|
||||||
- `CLAUDE.md` — T11 doctrine sentence (lines 5-6)
|
|
||||||
- `docs/DESIGN.md` — T10 §Roundtrip Invariant restatement; T11
|
|
||||||
§"What this Decision deliberately does not do" first sentence
|
|
||||||
- `docs/WhatsNew.md` — T12 milestone-close entry
|
|
||||||
- `docs/roadmap.md` — T12 strike of the Form-A milestone entry
|
|
||||||
|
|
||||||
### Deleted (T8) — 156 files
|
|
||||||
|
|
||||||
`examples/*.ail.json` — every non-carve-out fixture (the eight
|
|
||||||
carve-outs `broken_unbound`, `prelude`,
|
|
||||||
`test_22b2_invalid_superclass_param`, `test_22b2_kind_mismatch`,
|
|
||||||
`test_22b2_unbound_constraint_var`, `test_ct1_bad_qualifier`,
|
|
||||||
`test_ct1_bare_xmod_rejected`, `test_ct1_qualified_class_rejected`
|
|
||||||
preserved).
|
|
||||||
|
|
||||||
## Stats
|
|
||||||
|
|
||||||
bench/orchestrator-stats/2026-05-13-iter-form-a.1.json
|
|
||||||
@@ -1,103 +0,0 @@
|
|||||||
# iter form-a.tidy — close 3 of 4 documentary drift items from audit-form-a + fix 5 contradictory "seven carve-outs" sites
|
|
||||||
|
|
||||||
**Date:** 2026-05-13
|
|
||||||
**Started from:** 5e94204c218c410d44fbb8e658b677a7294744b0
|
|
||||||
**Status:** DONE
|
|
||||||
**Tasks completed:** 6 of 6
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
Pure documentary tidy iteration. Three new sections landed in
|
|
||||||
`crates/ailang-core/specs/form_a.md` (Class declarations, Instance
|
|
||||||
declarations, Constraints on polymorphic fns), each anchored to a
|
|
||||||
real corpus fixture (`test_22c_user_class_e2e.ail`,
|
|
||||||
`mq3_class_eq_vs_fn_eq_classmod.ail`, `show_user_adt.ail`,
|
|
||||||
`cmp_max_smoke.ail`) and each fixture was `ail check`-verified to
|
|
||||||
produce the expected symbol counts before close. Six sites in
|
|
||||||
`docs/specs/2026-05-13-form-a-default-authoring.md` had "seven
|
|
||||||
carve-outs" tightened to "eight carve-outs" to match the §C4(b)
|
|
||||||
amendment. The empty `#[cfg(test)] mod tests {}` placeholder plus
|
|
||||||
its 6-line relocation comment was deleted from
|
|
||||||
`crates/ailang-core/src/hash.rs` (tests live in
|
|
||||||
`crates/ailang-core/tests/hash_pin.rs` since form-a.1 T5). The
|
|
||||||
`round_trip.rs` module-level and inner docstrings were rewritten
|
|
||||||
to use the post-T10 four-property framing (parse-determinism /
|
|
||||||
idempotency-under-print / CLI-pipeline-idempotency /
|
|
||||||
carve-out-anchor) instead of the retired Direction-1/Direction-2
|
|
||||||
language, with cross-references to the sibling enforcement points
|
|
||||||
in `crates/ail/tests/roundtrip_cli.rs` and
|
|
||||||
`crates/ailang-core/tests/carve_out_inventory.rs`.
|
|
||||||
|
|
||||||
No production-code behaviour changed. Workspace test count holds
|
|
||||||
at 559 passed at every per-task gate.
|
|
||||||
|
|
||||||
## Per-task notes
|
|
||||||
|
|
||||||
- iter form-a.tidy.1: `form_a.md` Class declarations section landed; §Definitions
|
|
||||||
intro updated `Three kinds` → `Five kinds`; example anchored to
|
|
||||||
`examples/test_22c_user_class_e2e.ail` (`ok (24 symbols across 2 modules)`).
|
|
||||||
- iter form-a.tidy.2: `form_a.md` Instance declarations section landed; two
|
|
||||||
examples (same-module abbreviated from `mq3_class_eq_vs_fn_eq_classmod.ail`,
|
|
||||||
cross-module from `show_user_adt.ail`); `bare-cross-module-class-ref`
|
|
||||||
diagnostic anchor named inline.
|
|
||||||
- iter form-a.tidy.3: `form_a.md` Constraints on polymorphic fns: extended
|
|
||||||
the four-shapes EBNF `(forall ...)` line with optional `(constraints ...)`
|
|
||||||
clause, added explanatory paragraph (with `no-instance` diagnostic anchor),
|
|
||||||
added fifth example to the Examples block anchored to `cmp_max_smoke.ail`.
|
|
||||||
- iter form-a.tidy.4: `docs/specs/2026-05-13-form-a-default-authoring.md`
|
|
||||||
six "seven" → "eight" edits across the preamble + §C1 + §C2 + §C3 + §"Data flow"
|
|
||||||
(two sites). Post-edit grep returned 4 surviving "seven" mentions at lines
|
|
||||||
233, 238, 463, 469 — all correctly §C4(a)-scoped or arithmetic/future-state
|
|
||||||
(see Concerns below for one carrier-vs-plan transcription drift).
|
|
||||||
- iter form-a.tidy.5: `hash.rs` empty `mod tests {}` placeholder + 6-line
|
|
||||||
relocation comment block deleted (9 lines including the preceding blank).
|
|
||||||
`hash_pin.rs` integration tests still 10/10 passing.
|
|
||||||
- iter form-a.tidy.6: `round_trip.rs` module-level `//!` and inner `///`
|
|
||||||
rewritten to four-property framing with sibling-crate breadcrumbs.
|
|
||||||
`grep -n 'Direction [12]'` now zero hits.
|
|
||||||
|
|
||||||
## Concerns
|
|
||||||
|
|
||||||
- T4 carrier-vs-plan transcription drift on the Step 6 sanity-grep expected
|
|
||||||
output. The plan's Step 6 expected-output block listed four lines (233,
|
|
||||||
238, 463, 469) but the carrier compressed this to "exactly the three
|
|
||||||
surviving lines named in T4 Step 6 (lines 238, 463, 469)". The actual
|
|
||||||
post-edit grep returns four lines matching the plan listing; line 233
|
|
||||||
is §C4(a)-scope ("(seven files; the canonical") and is correctly retained.
|
|
||||||
No edit was missed. The carrier's three-line restatement was wrong;
|
|
||||||
the plan's four-line listing was right. Recording so a future auditor
|
|
||||||
re-reading the carrier doesn't second-guess the result.
|
|
||||||
|
|
||||||
## Known debt
|
|
||||||
|
|
||||||
- (none)
|
|
||||||
|
|
||||||
## Files touched
|
|
||||||
|
|
||||||
- `crates/ailang-core/specs/form_a.md` (+105/-)
|
|
||||||
- `crates/ailang-core/src/hash.rs` (-9)
|
|
||||||
- `crates/ailang-surface/tests/round_trip.rs` (+19/-12)
|
|
||||||
- `docs/specs/2026-05-13-form-a-default-authoring.md` (six 1-line edits)
|
|
||||||
|
|
||||||
## Phase 3 (E2E coverage)
|
|
||||||
|
|
||||||
Deliberately skipped per carrier: pure-documentary iter, no
|
|
||||||
behaviour change to cover. Verification gate was the per-task
|
|
||||||
`cargo test --workspace` smoke (559 green at every task close)
|
|
||||||
plus the per-task `ail check` runs on the four corpus fixtures
|
|
||||||
cited in the new form_a.md sections, all producing the expected
|
|
||||||
symbol counts (`test_22c_user_class_e2e.ail` ok 24/2,
|
|
||||||
`mq3_class_eq_vs_fn_eq_classmod.ail` ok 22/2,
|
|
||||||
`show_user_adt.ail` ok 23/2, `cmp_max_smoke.ail` ok 22/2).
|
|
||||||
|
|
||||||
## Decision recorded — Drift item B2 (plan-file "seven carve-outs" orphans)
|
|
||||||
|
|
||||||
Per plan §"Decision recorded" and the recon report: `docs/plans/2026-05-13-iter-form-a.1.md`
|
|
||||||
contains zero defective `seven` mentions; its four hits are all internally
|
|
||||||
scoped to §C4(a) (which IS seven), arithmetic, or future-state. The
|
|
||||||
audit-form-a journal's "two sites" claim against the plan file did not
|
|
||||||
match its contents at HEAD. No plan-file edit included in this iter.
|
|
||||||
|
|
||||||
## Stats
|
|
||||||
|
|
||||||
bench/orchestrator-stats/2026-05-13-iter-form-a.tidy.json
|
|
||||||
@@ -1,176 +0,0 @@
|
|||||||
# iter mq.1 — Canonical-form extension for class-ref fields + workspace internal qualification
|
|
||||||
|
|
||||||
**Date:** 2026-05-13
|
|
||||||
**Started from:** 1a5f8289b7fbad3ae5bdd6badde27c80eca6dad3
|
|
||||||
**Status:** DONE
|
|
||||||
**Tasks completed:** 7 of 7
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
mq.1 lands the canonical-form rule for the three class-reference
|
|
||||||
schema fields (`InstanceDef.class`, `Constraint.class`,
|
|
||||||
`SuperclassRef.class`) — bare for same-module, `<module>.<Class>`
|
|
||||||
for cross-module — symmetric to ct.1's `Type::Con.name` rule.
|
|
||||||
`ClassDef.name` stays bare (defining site, like `TypeDef.name`).
|
|
||||||
ct.1's `validate_canonical_type_names` validator gains a sibling
|
|
||||||
walk for class refs via the new `check_class_ref` helper, with
|
|
||||||
two new sibling diagnostics `BareCrossModuleClassRef` and
|
|
||||||
`BadCrossModuleClassRef`. Workspace-internal class-name keys
|
|
||||||
(`class_def_module`, `class_by_name`, registry `entries.0`,
|
|
||||||
`ClassMethodEntry.class_name`, `class_superclasses`, mono's
|
|
||||||
`class_index`, `Origin::Class.class_name`) all carry the qualified
|
|
||||||
form post-mq.1; the on-disk schema value is the canonical form (bare
|
|
||||||
or qualified) that `qualify_class_ref` lifts to the workspace key
|
|
||||||
at every lookup boundary. `MethodNameCollision` and the dispatch
|
|
||||||
path are unchanged in this iter (iter 2 + 3 land them).
|
|
||||||
|
|
||||||
## Per-task notes
|
|
||||||
|
|
||||||
- iter mq.1.1: two new diagnostic variants
|
|
||||||
`BareCrossModuleClassRef` / `BadCrossModuleClassRef` sibling to the
|
|
||||||
type-ref variants; two Diagnostic-shape Display arms in `ail/main.rs`
|
|
||||||
mirroring the existing arm. 2 RED unit tests, both GREEN.
|
|
||||||
- iter mq.1.2: doc-comments on the four class-reference fields
|
|
||||||
in `ast.rs` (`ClassDef.name` stays bare with cross-reference;
|
|
||||||
`SuperclassRef.class` / `InstanceDef.class` / `Constraint.class`
|
|
||||||
document canonical form). Zero semantic effect.
|
|
||||||
- iter mq.1.3: `check_class_ref` helper plus three per-def walks
|
|
||||||
inside `validate_canonical_type_names`; `check_class_name_fields`
|
|
||||||
narrowed to `ClassDef.name` only; four in-test pin tests inverted
|
|
||||||
(qualified is now accepted); on-disk `test_ct1_qualified_class_rejected`
|
|
||||||
fixture-test repurposed (now expects `OrphanInstance` —
|
|
||||||
the natural post-mq.1 rejection mode for the same shape); positive
|
|
||||||
on-disk fixture pair `mq1_xmod_constraint_class{,_dep}` shipped
|
|
||||||
plus the corresponding pin test. 4 new bare-xmod tests + 1 positive
|
|
||||||
qualified test, all GREEN.
|
|
||||||
- iter mq.1.4: `qualify_class_ref` helper at module scope; `build_registry`
|
|
||||||
Pass-1 re-keyed (qualified); Pass-2 lookups use `qualify_class_ref` to
|
|
||||||
lift `inst.class` to the registry-key shape; superclass walk threads
|
|
||||||
`current_class_module` to qualify bare `SuperclassRef.class`;
|
|
||||||
`Origin::Class.class_name` qualified (one construction site, not two as
|
|
||||||
the plan claimed); type-leg of coherence check now also handles
|
|
||||||
qualified head names symmetrically (split-and-lookup).
|
|
||||||
- iter mq.1.5: `ClassMethodEntry.class_name` insert qualifies in
|
|
||||||
`build_module_globals`; `class_superclasses` key + value both
|
|
||||||
qualified; mono's `build_class_index` keyed qualified (plan said
|
|
||||||
"no code change needed in mono.rs consumers" — that statement was
|
|
||||||
wrong, the build-side needed the matching qualification).
|
|
||||||
- iter mq.1.6: test assertions updated for the new qualified shape —
|
|
||||||
the two `MethodNameCollision` pin tests, `typeclass_22b2.rs:42`
|
|
||||||
(`class_method_class("show")` now `Some("...Show")`),
|
|
||||||
`typeclass_22b3.rs:151` (`MonoTarget.class` qualified). The other
|
|
||||||
lines plan flagged (217/232/295/301/341/353) are local-construction
|
|
||||||
tests that never round-trip through the workspace — left as-is.
|
|
||||||
- iter mq.1.7: full `cargo test --workspace` 520 passed / 0 failed;
|
|
||||||
`examples/prelude.ail.json` zero diff (confirms spec assumption 17:
|
|
||||||
intra-prelude refs stay bare); `bench/compile_check.py` exit 0,
|
|
||||||
`bench/cross_lang.py` exit 0; `bench/check.py` exit 1 due to 4
|
|
||||||
improvements-beyond-tolerance (audit-ratifiable per convention,
|
|
||||||
not a regression). Roundtrip on the new fixture: `ail check
|
|
||||||
examples/mq1_xmod_constraint_class.ail.json` → "ok (16 symbols
|
|
||||||
across 3 modules)", exit 0.
|
|
||||||
|
|
||||||
## Concerns
|
|
||||||
|
|
||||||
- **Boss-note recon claim was empirically wrong.** The boss-note
|
|
||||||
said "every existing `examples/test_22b*.ail.json` class-ref field
|
|
||||||
is intra-module under the canonical-form rule, so existing fixtures
|
|
||||||
carry zero diff". Five test_22b* fixtures actually carried bare
|
|
||||||
cross-module class refs and required migration: `test_22b1_orphan_third.ail.json`
|
|
||||||
(bare `Show` from imported classmod), `test_22b1_dup_a.ail.json` +
|
|
||||||
`test_22b1_dup_b.ail.json` (the duplicate-instance pair structurally
|
|
||||||
relied on the pre-mq.1 bare cross-module shape), and
|
|
||||||
`test_22b2_unbound_constraint_var.ail.json` (constraint references
|
|
||||||
a class that doesn't exist anywhere → fired BareCrossModuleClassRef
|
|
||||||
before reaching UnboundConstraintTypeVar). Six non-test_22b
|
|
||||||
fixtures also needed migration (`eq_ord_polymorphic`, `eq_ord_user_adt`,
|
|
||||||
`cmp_max_smoke`, `ctt2_collision_lib`, `ctt2_collision_main`). All
|
|
||||||
migrations are minimal and aligned with the canonical-form rule;
|
|
||||||
no proactive "fixture cleanup" added.
|
|
||||||
- **Duplicate-instance test restructured by structural necessity.**
|
|
||||||
Pre-mq.1 the test relied on two coherent instances on the same
|
|
||||||
`(class, type)` declared from different modules; post-mq.1, the
|
|
||||||
orphan-freedom invariant prevents this shape (any second instance
|
|
||||||
in a module that owns neither the class nor the type fires
|
|
||||||
`OrphanInstance` first). The new `test_22b1_dup_same_module.ail.json`
|
|
||||||
fixture lands both instances in the same module — the only post-mq.1
|
|
||||||
way to land two on the same canonical key. The old dup_a/dup_b/entry
|
|
||||||
fixtures stay as supporting modules (referenced by hash.rs's
|
|
||||||
canonical-form hash pin) but no longer participate in the duplicate
|
|
||||||
test. This is arguably a strengthening of the architecture: duplicate
|
|
||||||
instances are now structurally impossible across coherent modules.
|
|
||||||
- **Plan's "no code change needed" claims in Task 4 Step 8 and Task 5
|
|
||||||
Step 5 were wrong.** Task 4 Step 8 said `Origin::Class.class_name`
|
|
||||||
qualified at "both sites" but there's only one construction site
|
|
||||||
(the plan's recon was off-by-one). Task 5 Step 5 said mono's
|
|
||||||
`class_index.get(class)` lookups would just work post-rekey, but
|
|
||||||
`build_class_index` itself was bare-keyed. Both fixed inline.
|
|
||||||
- **Several adjacent fixes were necessary that the plan didn't anticipate:**
|
|
||||||
(a) `build_module_globals` rejected `inst.class` containing `.` —
|
|
||||||
added a `Def::Instance` carve-out so qualified class refs survive.
|
|
||||||
(b) `check_fn`'s declared-constraint matching compared bare
|
|
||||||
`c.class` to qualified residual `r.class` → added an inline lift via
|
|
||||||
`qualify_class_ref_in_check`. (c) `NoInstance` Float-aware message
|
|
||||||
arm matched on bare `"Eq" / "Ord"` — updated to `"prelude.Eq" /
|
|
||||||
"prelude.Ord"`. (d) Coherence check's type-leg lookup couldn't
|
|
||||||
resolve qualified head names — extended with a split-and-lookup
|
|
||||||
fallback. None of these widened the contract; each is a one-line
|
|
||||||
bridge keeping the post-mq.1 qualified shape consistent across the
|
|
||||||
pipeline.
|
|
||||||
- **Tasks 3-6 ran as one coherent fix.** The plan structured them as
|
|
||||||
sequential tasks, but Task 3 alone left several existing tests red
|
|
||||||
(registry still bare-keyed, residual still bare). Walking the plan
|
|
||||||
literally and expecting "green after Task 3" per the plan's Step 9
|
|
||||||
was impossible; the four tasks form one consistent rekey.
|
|
||||||
|
|
||||||
## Known debt
|
|
||||||
|
|
||||||
- The `test_22b1_dup_a` / `test_22b1_dup_b` / `test_22b1_dup_entry`
|
|
||||||
fixtures are now orphan from the duplicate-instance test (which
|
|
||||||
uses `test_22b1_dup_same_module` instead). They're still
|
|
||||||
referenced by `hash.rs:293` for the canonical-form hash pin. A
|
|
||||||
cleanup pass could either retire them entirely (and replace the
|
|
||||||
hash pin with the new fixture) or keep them as historical
|
|
||||||
reference. Deferred — not load-bearing.
|
|
||||||
- `qualify_class_ref_in_check` in `ailang-check/src/lib.rs` is a
|
|
||||||
one-line duplicate of `workspace::qualify_class_ref`. The
|
|
||||||
alternative is exposing `qualify_class_ref` as `pub(crate)` or
|
|
||||||
re-exporting; the duplication was the minimum-edit path. A
|
|
||||||
consolidation tidy could land in iter 2.
|
|
||||||
|
|
||||||
## Files touched
|
|
||||||
|
|
||||||
Code:
|
|
||||||
- crates/ail/src/main.rs
|
|
||||||
- crates/ail/tests/ct1_check_cli.rs
|
|
||||||
- crates/ail/tests/typeclass_22b2.rs
|
|
||||||
- crates/ail/tests/typeclass_22b3.rs
|
|
||||||
- crates/ailang-check/src/lib.rs
|
|
||||||
- crates/ailang-check/src/mono.rs
|
|
||||||
- crates/ailang-check/tests/env_construction_pin.rs
|
|
||||||
- crates/ailang-core/src/ast.rs
|
|
||||||
- crates/ailang-core/src/hash.rs
|
|
||||||
- crates/ailang-core/src/workspace.rs
|
|
||||||
- crates/ailang-core/tests/ctt2_registry_rekey.rs
|
|
||||||
|
|
||||||
Fixtures (existing, migrated):
|
|
||||||
- examples/cmp_max_smoke.ail.json
|
|
||||||
- examples/ctt2_collision_lib.ail.json
|
|
||||||
- examples/ctt2_collision_main.ail.json
|
|
||||||
- examples/eq_ord_polymorphic.ail.json
|
|
||||||
- examples/eq_ord_user_adt.ail.json
|
|
||||||
- examples/test_22b1_dup_a.ail.json
|
|
||||||
- examples/test_22b1_dup_b.ail.json
|
|
||||||
- examples/test_22b1_dup_entry.ail.json
|
|
||||||
- examples/test_22b1_orphan_third.ail.json
|
|
||||||
- examples/test_22b2_unbound_constraint_var.ail.json
|
|
||||||
|
|
||||||
Fixtures (new):
|
|
||||||
- examples/mq1_xmod_constraint_class.ail.json
|
|
||||||
- examples/mq1_xmod_constraint_class_dep.ail.json
|
|
||||||
- examples/test_22b1_dup_classmod.ail.json
|
|
||||||
- examples/test_22b1_dup_same_module.ail.json
|
|
||||||
|
|
||||||
## Stats
|
|
||||||
|
|
||||||
bench/orchestrator-stats/2026-05-13-iter-mq.1.json
|
|
||||||
@@ -1,169 +0,0 @@
|
|||||||
# iter mq.2 — Type-driven dispatch mechanism (installed, not yet exercised)
|
|
||||||
|
|
||||||
**Date:** 2026-05-13
|
|
||||||
**Started from:** e9e45c77affa635652505cc937b77b9349d18c8e
|
|
||||||
**Status:** DONE
|
|
||||||
**Tasks completed:** 9 of 9
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
mq.2 installs the type-driven dispatch infrastructure that mq.3 will
|
|
||||||
exercise end-to-end once `MethodNameCollision` retires. Three new
|
|
||||||
`CheckError` variants (`AmbiguousMethodResolution`, `UnknownClass`,
|
|
||||||
plus an additive `candidate_classes` field on `NoInstance`); a new
|
|
||||||
workspace-flat `method_to_candidate_classes` index on `Env` (inverse
|
|
||||||
of `class_methods`); an extended `ResidualConstraint` carrying an
|
|
||||||
optional `candidates: Option<BTreeSet<String>>` for the multi-
|
|
||||||
candidate dispatch path; a pure `resolve_method_dispatch` helper
|
|
||||||
implementing the spec's 5-step rule (qualifier → singleton →
|
|
||||||
type-driven filter → constraint-driven filter → `Multi` for discharge-
|
|
||||||
time refinement); a `refine_multi_candidate_residual` helper for
|
|
||||||
discharge / mono refinement; and a `resolve_residual_class_for_mono`
|
|
||||||
helper that wires the refinement into mono's residual-to-target
|
|
||||||
mapping. The synth `Term::Var` arm class-method branch is rewritten
|
|
||||||
to consult the new index via `parse_method_qualifier`, with a gate
|
|
||||||
that prevents capturing the existing cross-module-fn path
|
|
||||||
(`<module>.<fn>`) by requiring class-qualifiers to carry an inner
|
|
||||||
dot (per mq.1's canonical-form rule, class qualifiers are always
|
|
||||||
`<module>.<Class>`).
|
|
||||||
|
|
||||||
With `MethodNameCollision` still active, the new multi-candidate
|
|
||||||
branch is exercised exclusively by 15 unit tests: 6 in
|
|
||||||
`tests/method_dispatch_pin.rs` (the 5-step rule's six cases), 6 in
|
|
||||||
`lib.rs` `mod tests` (the new `CheckError` variants + the
|
|
||||||
`ResidualConstraint`/`Env` field shapes + the discharge-side
|
|
||||||
refinement), and 3 in `mono.rs` `mod tests` (the mono-side helper).
|
|
||||||
Real workspaces continue producing single-class residuals
|
|
||||||
(`candidates: None`), and every pre-mq.2 fixture typechecks
|
|
||||||
unchanged.
|
|
||||||
|
|
||||||
## Per-task notes
|
|
||||||
|
|
||||||
- iter mq.2.1: Two new `CheckError` variants
|
|
||||||
`AmbiguousMethodResolution` and `UnknownClass`, added as siblings
|
|
||||||
after `NoInstance`. `code()` + `ctx()` table entries; module-doc
|
|
||||||
list in `diagnostic.rs` extended. 2 RED-then-GREEN smoke tests.
|
|
||||||
- iter mq.2.2: `NoInstance` extended with an additive
|
|
||||||
`candidate_classes: Vec<String>` field. `ctx()` emits the field
|
|
||||||
only when non-empty (preserves pre-mq.2 JSON shape). 2 tests:
|
|
||||||
with + without populated candidates.
|
|
||||||
- iter mq.2.3: `ResidualConstraint` extended with `candidates:
|
|
||||||
Option<BTreeSet<String>>`. Struct bumped to `pub` (was
|
|
||||||
`pub(crate)`) for unit-test access from the dispatch-pin / Task 7
|
|
||||||
+ 8 tests. Single existing construction site updated with
|
|
||||||
`candidates: None`. 1 smoke test.
|
|
||||||
- iter mq.2.4: `Env.method_to_candidate_classes:
|
|
||||||
BTreeMap<String, BTreeSet<String>>` built workspace-flat in
|
|
||||||
`build_check_env` alongside the existing `class_methods` insert
|
|
||||||
(coalesced into one loop). 1 in-test fixture round-trip.
|
|
||||||
- iter mq.2.5: `MethodDispatchOutcome` enum + pure
|
|
||||||
`resolve_method_dispatch` helper. 6 unit tests cover unique
|
|
||||||
candidate, qualifier-match, qualifier-no-match (UnknownClass),
|
|
||||||
type-driven narrow-to-one, constraint-driven narrow-to-one
|
|
||||||
(rigid-var fallback), and true ambiguity. Helper uses
|
|
||||||
`ailang_core::pretty::type_to_string` for the at_type rendering
|
|
||||||
rather than the plan's invented `format_type_for_display` (one
|
|
||||||
fewer duplicate; the canonical pretty-printer is already in use
|
|
||||||
across other diagnostic surfaces).
|
|
||||||
- iter mq.2.6: Synth `Term::Var` arm class-method branch rewritten
|
|
||||||
to consult `method_to_candidate_classes` via
|
|
||||||
`parse_method_qualifier`. Branch gate: qualifier must be either
|
|
||||||
absent (bare form `"show"`) or contain an inner dot
|
|
||||||
(`<module>.<Class>` per mq.1's canonical-form rule). 1-dot names
|
|
||||||
like `std_list.length` fall through to the existing qualified-fn
|
|
||||||
path. Plan's `active_declared_constraints` plumbing intentionally
|
|
||||||
skipped at synth time — synth's Var arm does not carry the body's
|
|
||||||
declared constraints; the constraint-driven filter runs at
|
|
||||||
discharge time (Task 7) where `expanded` is in scope. With
|
|
||||||
`MethodNameCollision` still active, the candidate set is always
|
|
||||||
singleton at this branch, so the `&[]` synth-time filter is
|
|
||||||
load-bearing only post-mq.3 and only for the rigid-var fallback;
|
|
||||||
on the post-mq.3 multi-candidate fully-concrete path discharge
|
|
||||||
will refine the residual on `r_ty` against the workspace registry.
|
|
||||||
- iter mq.2.7: `RefineOutcome` enum + `refine_multi_candidate_residual`
|
|
||||||
helper. Wired into `check_fn`'s residual loop BEFORE the existing
|
|
||||||
single-class discharge — multi-candidate residuals refine first,
|
|
||||||
`Resolved` falls through to `continue`, error variants return.
|
|
||||||
Used `expanded` (already-superclass-expanded declared constraints)
|
|
||||||
for the rigid-var path, sounder than `declared_constraints` direct.
|
|
||||||
3 unit tests for the discharge path.
|
|
||||||
- iter mq.2.8: `resolve_residual_class_for_mono` helper in mono.rs
|
|
||||||
+ 3 unit tests in a new `mod tests` block. Mono's residual-to-
|
|
||||||
target mapping (`collect_residuals_ordered`) now calls the helper
|
|
||||||
before constructing the registry key; resolved-class overrides
|
|
||||||
the residual's tentative `r.class` in `MonoTarget::ClassMethod`.
|
|
||||||
Single-class residuals flow through unchanged.
|
|
||||||
- iter mq.2.9: full `cargo test --workspace` 539 passed / 0 failed
|
|
||||||
(was 520 at start of mq.2; +19 = 15 new mq.2 tests + 4 pre-
|
|
||||||
existing mq.1 follow-on additions in cross-language test files
|
|
||||||
already shipped). All pre-mq.2 fixtures (prelude, mq1_xmod,
|
|
||||||
eq_ord_polymorphic) typecheck unchanged. Bench results:
|
|
||||||
`compile_check.py` 0 regressed / 0 improved / 24 stable;
|
|
||||||
`cross_lang.py` 0 regressed / 0 improved / 25 stable;
|
|
||||||
`check.py` 1 regressed / 2-4 improved / 58-60 stable (latency
|
|
||||||
noise — different metric regressed between two runs, runtime
|
|
||||||
benches cannot be touched by a typecheck-side iter). Prelude
|
|
||||||
roundtrip clean.
|
|
||||||
|
|
||||||
## Concerns
|
|
||||||
|
|
||||||
- **Helper-cost: `parse_method_qualifier` does an `rfind('.')` on
|
|
||||||
every `Term::Var` lookup that reaches the class-method branch.**
|
|
||||||
Today this is bounded by the per-Var-arm cost (already O(name-
|
|
||||||
length) due to the dot-counting and unification ladder), so it's
|
|
||||||
noise. If profiling ever shows the Var arm hot, the right fix is
|
|
||||||
to cache `(method_name, qualifier_opt)` on the Var node rather
|
|
||||||
than recompute — but that's a Term struct change and out of
|
|
||||||
scope for mq.2.
|
|
||||||
|
|
||||||
- **`pub` bump on `ResidualConstraint`.** The unit-test crate
|
|
||||||
(`tests/method_dispatch_pin.rs`) needs to construct
|
|
||||||
`ResidualConstraint` for Task 5; Task 7's in-lib tests also
|
|
||||||
reference it. Bumping from `pub(crate)` to `pub` is the minimum
|
|
||||||
edit; the struct semantically belongs at the workspace boundary
|
|
||||||
anyway (the spec's published shape for what the dispatcher
|
|
||||||
produces). No risk: no external crate depends on `ailang-check`
|
|
||||||
outside this workspace.
|
|
||||||
|
|
||||||
- **`MissingConstraint.class` field for multi-candidate rigid-var
|
|
||||||
case is best-effort.** When `refine_multi_candidate_residual`
|
|
||||||
emits `MissingConstraint`, the surfaced `class` is the first
|
|
||||||
candidate (BTreeSet order). The diagnostic ctx still names the
|
|
||||||
method, the type, and (via the diagnostic Display) the full
|
|
||||||
candidate set is reachable; the `class` is just a hint. Real
|
|
||||||
workspaces never reach this branch pre-mq.3.
|
|
||||||
|
|
||||||
## Known debt
|
|
||||||
|
|
||||||
- **mq.3 retires `MethodNameCollision` and exercises the
|
|
||||||
multi-candidate path end-to-end.** The unit tests in this iter
|
|
||||||
are the only coverage of the new branches until then. mq.3 will
|
|
||||||
add cross-class-collision fixtures (e.g. two `Show` classes in
|
|
||||||
separate modules) and the existing tests should continue to pass
|
|
||||||
unchanged.
|
|
||||||
|
|
||||||
- **`class_methods` keyed by method name only.** Pre-mq.3 invariant
|
|
||||||
guarantees `env.class_methods.get(method_name)` is unambiguous;
|
|
||||||
post-mq.3 this lookup needs to be reworked (likely keyed by
|
|
||||||
`(class, method)` or by `method` returning a list). The synth
|
|
||||||
branch's `expect("invariant")` documents the dependency. Not
|
|
||||||
load-bearing in mq.2.
|
|
||||||
|
|
||||||
- **Synth-time `declared_constraints: &[]` in the Var arm rewrite.**
|
|
||||||
This is correct for pre-mq.3 (singleton candidate set never
|
|
||||||
needs filtering) but means the post-mq.3 constraint-driven
|
|
||||||
filter cannot fire at synth time. The branch will need
|
|
||||||
`env.active_declared_constraints` plumbed through (Env field
|
|
||||||
+ `check_fn` initialization) before mq.3's first multi-candidate
|
|
||||||
workspace with declared constraints. Cost: ~10-line edit.
|
|
||||||
|
|
||||||
## Files touched
|
|
||||||
|
|
||||||
- crates/ailang-check/src/diagnostic.rs
|
|
||||||
- crates/ailang-check/src/lib.rs
|
|
||||||
- crates/ailang-check/src/mono.rs
|
|
||||||
- crates/ailang-check/tests/method_dispatch_pin.rs (new)
|
|
||||||
|
|
||||||
## Stats
|
|
||||||
|
|
||||||
bench/orchestrator-stats/2026-05-13-iter-mq.2.json
|
|
||||||
@@ -1,284 +0,0 @@
|
|||||||
# iter mq.3 — Retire MethodNameCollision + multi-class E2E + DESIGN.md sync
|
|
||||||
|
|
||||||
**Date:** 2026-05-13
|
|
||||||
**Started from:** 90075715d99c9e8c59ea79267c2615f5756520f2
|
|
||||||
**Status:** DONE
|
|
||||||
**Tasks completed:** 9 of 9
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
mq.3 closes the module-qualified-class-names milestone by retiring
|
|
||||||
the `WorkspaceLoadError::MethodNameCollision` workaround that mq.1 +
|
|
||||||
mq.2 made redundant. The variant + pre-pass + Origin enum + Display
|
|
||||||
arm + two pin tests are deleted; the two on-disk fixtures that fired
|
|
||||||
the collision now load cleanly and the equivalent observations
|
|
||||||
relocate to `env.method_to_candidate_classes` (multi-entry set) plus
|
|
||||||
a per-call-site warning. Three positive E2E fixtures (Trajectory C
|
|
||||||
ambiguous, Trajectory E explicit-qualifier, class-fn shadow) exercise
|
|
||||||
the multi-candidate dispatch path end-to-end via `ail check --json`.
|
|
||||||
|
|
||||||
Two out-of-band corrections that mq.2 surfaced as known debt land
|
|
||||||
here: (1) `Env.active_declared_constraints: Vec<Constraint>` is
|
|
||||||
plumbed pre-synth in `check_fn` so the post-superclass-expansion
|
|
||||||
constraint set is reachable at synth time by
|
|
||||||
`resolve_method_dispatch`'s constraint-driven filter; (2)
|
|
||||||
`ModuleGlobals.class_methods` + `Env.class_methods` re-keyed from
|
|
||||||
`BTreeMap<MethodName, ClassMethodEntry>` to
|
|
||||||
`BTreeMap<(QualifiedClass, MethodName), ClassMethodEntry>` so the
|
|
||||||
post-retirement world has O(1) `(class, method)` lookup, with
|
|
||||||
mono's two presence-check sites switched to consult the natively
|
|
||||||
method-keyed `method_to_candidate_classes`. A third
|
|
||||||
correction — `synth(...)` signature extended with a `warnings: &mut
|
|
||||||
Vec<Diagnostic>` out-parameter — installs the channel for the new
|
|
||||||
structured warning `class-method-shadowed-by-fn` that fires when a
|
|
||||||
free fn shadows a class method of the same name (per spec
|
|
||||||
§"Class-fn collisions": precedence + warning, the spec's default).
|
|
||||||
|
|
||||||
DESIGN.md sync removes the `MethodNameCollision` bullet from
|
|
||||||
"Workspace-load (registry-build) diagnostics" with a forward-pointing
|
|
||||||
note, adds `AmbiguousMethodResolution` / `UnknownClass` /
|
|
||||||
`class-method-shadowed-by-fn` to the "Typecheck diagnostics" block,
|
|
||||||
rewords the `AmbiguousInstance` paragraph (per-class coherence stays
|
|
||||||
via `DuplicateInstance`; cross-class method ambiguity is the new
|
|
||||||
diagnostic at the call site), rewrites the class-names paragraph in
|
|
||||||
the canonical-form section to point at the mq.1 rule + the new
|
|
||||||
dispatch model, and adds a new `### Method dispatch` subsection
|
|
||||||
anchoring the 5-step rule with `method_to_candidate_classes` as the
|
|
||||||
load-bearing data structure. Roadmap P2 entry → `[x]` with a summary
|
|
||||||
of the three iters; milestone-24 (`Post-22 Prelude — Show + print
|
|
||||||
rewire`) `depends on:` line struck and the entry annotated
|
|
||||||
"ready for re-brainstorm".
|
|
||||||
|
|
||||||
545 tests green (was 539 at start of mq.3; +6 net = 3 new mq.3.x lib
|
|
||||||
tests + 2 relocated method_collision_pin + 3 new mq3_multi_class_e2e
|
|
||||||
- 2 deleted workspace.rs pin tests). `bench/compile_check.py` and
|
|
||||||
`bench/cross_lang.py` exit 0; `bench/check.py` exit 1 with 2
|
|
||||||
regressions both noise-class (bench_list_sum.bump_s persistence —
|
|
||||||
3rd consecutive sighting since audit-ct-tidy — and a max_us tail
|
|
||||||
metric, plus 4 improvements). Prelude zero-diff.
|
|
||||||
|
|
||||||
## Per-task notes
|
|
||||||
|
|
||||||
- **mq.3.1** — `Env.active_declared_constraints: Vec<Constraint>`
|
|
||||||
field plumbed; populated in `check_fn` pre-synth alongside the
|
|
||||||
rigid-vars install (consolidated with the existing post-synth
|
|
||||||
`expanded` computation — single source of truth, no clone-and-
|
|
||||||
duplicate as the plan literally suggested). Synth Var-arm
|
|
||||||
`resolve_method_dispatch` call site swapped `&[]` for
|
|
||||||
`&env.active_declared_constraints`. 1 new test mq3_env_active_declared_constraints_field_exists.
|
|
||||||
|
|
||||||
- **mq.3.2** — `ModuleGlobals.class_methods` re-keyed
|
|
||||||
`IndexMap<String, ClassMethodEntry>` → `IndexMap<(String, String),
|
|
||||||
ClassMethodEntry>`; sibling `Env.class_methods` similarly re-keyed
|
|
||||||
to `BTreeMap<(String, String), ClassMethodEntry>`. Accessor methods
|
|
||||||
on `ModuleGlobals` (`has_class_method`, `class_method_class`,
|
|
||||||
`class_method`) all take `(class, name)`; new
|
|
||||||
`class_method_candidates(name) -> Vec<(&class, &entry)>` for the
|
|
||||||
enumerate-all-classes-declaring-this-method use case (returning
|
|
||||||
`Vec` not `impl Iterator` because the borrow-checker rejected the
|
|
||||||
`'a`/`'_` capture without explicit `use<'a, '_>` syntax not yet
|
|
||||||
idiomatic in this crate). `build_module_globals` insert site +
|
|
||||||
`build_check_env` merge loop both threaded through the tuple key.
|
|
||||||
Synth Var-arm `class_methods.get(&(residual_class, method))` uses
|
|
||||||
the dispatcher-resolved tentative class. Mono's two presence-check
|
|
||||||
sites (`rewrite_mono_calls`, `interleave_slots`) switched to
|
|
||||||
consult `method_to_candidate_classes` natively — method-keyed,
|
|
||||||
preserves the existing presence-check signature shape; the four
|
|
||||||
call sites at module-walk + interleave_slots entry pass
|
|
||||||
`&env.method_to_candidate_classes` instead of `&env.class_methods`.
|
|
||||||
Test assertion in `crates/ail/tests/typeclass_22b2.rs:42` updated
|
|
||||||
to pass the qualified class explicitly. 1 new test
|
|
||||||
mq3_env_class_methods_tuple_keyed.
|
|
||||||
|
|
||||||
- **mq.3.3** — `synth(...)` signature extended with `warnings:
|
|
||||||
&mut Vec<Diagnostic>` (15+ recursive synth callsites threaded).
|
|
||||||
Five external synth callers (check_fn, check_const, 3 in
|
|
||||||
builtins.rs, 1 in lift.rs, 2 in mono.rs, 1 in `check`) all
|
|
||||||
updated; check_fn / check_const pass through to caller via new
|
|
||||||
`out_warnings: &mut Vec<Diagnostic>` parameter on the caller
|
|
||||||
chain (`check_def`, `check_in_workspace`); `check_workspace`
|
|
||||||
extends per-module synth_warnings into `module_diags`. Plan
|
|
||||||
suggested `check_fn` return `Result<(CheckedFn, Vec<Diagnostic>)>`
|
|
||||||
but `check_fn`'s actual signature is `Result<()>` and the
|
|
||||||
mut-ref-accumulator pattern matches the existing `Vec<CheckError>`
|
|
||||||
accumulator at `check_in_workspace`'s caller. New diagnostic
|
|
||||||
`class-method-shadowed-by-fn` (warning, kebab-case code,
|
|
||||||
structured `ctx` carrying `name` + `method` + `fn_owner_module` +
|
|
||||||
`candidate_classes`) emitted by a closure at all three
|
|
||||||
fn-precedence branches (locals, same-module fn, implicit-import
|
|
||||||
fn). Implicit-import-fn branch reordered ABOVE the class-method
|
|
||||||
branch per spec §"Class-fn collisions" so fn-wins precedence is
|
|
||||||
structural, not just convention. Diagnostic doc updated in
|
|
||||||
`diagnostic.rs`. One existing test
|
|
||||||
`crates/ail/tests/typeclass_22b3.rs:rewrite_walker_skips_locally_shadowed_class_method`
|
|
||||||
was a known trip-wire — the fixture intentionally shadows a class
|
|
||||||
method with a local binding, which now correctly fires the new
|
|
||||||
warning; filtered out of the test's `assert!(diags.is_empty())`
|
|
||||||
with a comment naming why. 1 new test
|
|
||||||
mq3_class_method_shadowed_by_fn_warning_fires.
|
|
||||||
|
|
||||||
- **mq.3.4** — `WorkspaceLoadError::MethodNameCollision` variant +
|
|
||||||
the Origin enum + the per-def collision loop in `build_registry`
|
|
||||||
(workspace.rs 596-681 pre-edit) + the Display arm in
|
|
||||||
`main.rs:1201` deleted. The two in-workspace.rs pin tests
|
|
||||||
(`class_class_method_name_collision_fires` +
|
|
||||||
`class_fn_method_name_collision_fires`) deleted; relocation lands
|
|
||||||
in mq.3.5. The on-disk fixtures
|
|
||||||
`test_22b2_method_name_collision_class_{class,fn}.ail.json` stay
|
|
||||||
on-disk (now exercised as positive-load by the relocated tests).
|
|
||||||
|
|
||||||
- **mq.3.5** — `crates/ailang-check/tests/method_collision_pin.rs`
|
|
||||||
created with two repurposed pin tests: the class-class fixture
|
|
||||||
loads cleanly and `env.method_to_candidate_classes["foo"]` has
|
|
||||||
exactly two qualified-class entries (`...A` + `...B`); the
|
|
||||||
class-fn fixture loads cleanly (the warning is a call-site
|
|
||||||
concern, not a load-time concern; covered by mq.3.6 E2E). The
|
|
||||||
relocation from `ailang-core` to `ailang-check` is required
|
|
||||||
because `Env.method_to_candidate_classes` is built by
|
|
||||||
`build_check_env`, which lives in `ailang-check` (not visible
|
|
||||||
from `ailang-core`'s `mod tests`).
|
|
||||||
|
|
||||||
- **mq.3.6** — Three new on-disk fixtures plus three integration
|
|
||||||
tests in `crates/ail/tests/mq3_multi_class_e2e.rs`. Fixture (a)
|
|
||||||
`mq3_two_show_ambiguous` exercises the bare-method ambiguous
|
|
||||||
case (two `Show` classes, both with `Show Int`, bare `show 42`
|
|
||||||
→ `AmbiguousMethodResolution`). Fixture (b)
|
|
||||||
`mq3_two_show_qualified` exercises explicit-qualifier resolution
|
|
||||||
(same workspace, `mq3_two_show_ambiguous_a.Show.show 42` →
|
|
||||||
clean). Fixture (c) `mq3_class_eq_vs_fn_eq` exercises class-fn
|
|
||||||
shadow (`class MyEq { myeq }` + `fn myeq` + bare `myeq 1 2` →
|
|
||||||
fn wins, warning fires). The plan's draft fixtures used a bare
|
|
||||||
`Term::App` for instance method bodies; the existing-convention
|
|
||||||
shape (per `test_22b3_mono_synthetic.ail.json`) is `Term::Lam`
|
|
||||||
with `paramTypes`/`retType` — implementer-phase repair adjusted
|
|
||||||
inline. Naming `MyEq`/`myeq` (rather than `Eq`/`eq`) avoids an
|
|
||||||
extra prelude-vs-fn shadow that would have confused the warning
|
|
||||||
assertion. All three tests GREEN first-shot; spot-checked via
|
|
||||||
`ail check --json` for completeness.
|
|
||||||
|
|
||||||
- **mq.3.7** — DESIGN.md sync: class-names paragraph (1156-1160)
|
|
||||||
rewritten to point at mq.1 canonical-form + mq.3 dispatch model;
|
|
||||||
`MethodNameCollision` bullet struck from "Workspace-load
|
|
||||||
(registry-build) diagnostics" with forward-pointing note; three
|
|
||||||
new typecheck diagnostics + `NoInstance.candidate_classes`
|
|
||||||
addendum added to "Typecheck diagnostics"; `AmbiguousInstance`
|
|
||||||
paragraph reworded to clarify the registry-level vs call-site
|
|
||||||
distinction; new `### Method dispatch` subsection anchors the
|
|
||||||
5-step rule with `method_to_candidate_classes` as the
|
|
||||||
load-bearing data structure, the class-fn precedence rule, and
|
|
||||||
the post-mq.3 tuple-keyed `class_methods` shape.
|
|
||||||
|
|
||||||
- **mq.3.8** — Roadmap P2 milestone "Module-qualified class names
|
|
||||||
+ type-driven method dispatch" → `[x]` with a one-paragraph
|
|
||||||
summary of the three iters (mq.1 canonical-form, mq.2 mechanism,
|
|
||||||
mq.3 retirement) and the milestone's user-facing outcome (two
|
|
||||||
libraries can each declare `class Eq` with their own `eq`).
|
|
||||||
Milestone-24 (`Post-22 Prelude — Show + print rewire`) `depends
|
|
||||||
on:` line struck and the entry annotated "ready for
|
|
||||||
re-brainstorm" so the next time `/boss` walks the roadmap it
|
|
||||||
picks up the un-blocked spec re-derivation against the
|
|
||||||
post-retirement architecture.
|
|
||||||
|
|
||||||
- **mq.3.9** — Integration verification. `cargo test --workspace`
|
|
||||||
545 passed / 0 failed. `bench/compile_check.py` 0 regressed / 0
|
|
||||||
improved / 24 stable. `bench/cross_lang.py` 0 regressed / 0
|
|
||||||
improved / 25 stable. `bench/check.py` 2 regressed / 4 improved
|
|
||||||
/ 57 stable: regression #1 `throughput.bench_list_sum.bump_s`
|
|
||||||
(3rd consecutive sighting since audit-ct-tidy 2026-05-12, audit-
|
|
||||||
ratified-class noise per the journal lineage); regression #2
|
|
||||||
`latency.implicit_at_rc.max_us` (same noisy max-tail metric
|
|
||||||
that mq.2 journal flagged as runtime-uncoupled-to-typecheck-
|
|
||||||
iter, audit-ratified-class). Prelude zero-diff. All three new
|
|
||||||
E2E fixtures observable via `ail check` with the expected
|
|
||||||
diagnostic / no-diagnostic / warning outcomes.
|
|
||||||
|
|
||||||
## Concerns
|
|
||||||
|
|
||||||
- **Plan's `check_fn` signature suggestion was off.** Plan Step 4
|
|
||||||
proposed `check_fn(...) -> Result<(CheckedFn, Vec<Diagnostic>),
|
|
||||||
CheckError>`; the actual signature is `Result<()>` (no
|
|
||||||
CheckedFn). The mut-ref-accumulator pattern adopted instead
|
|
||||||
matches the existing `check_in_workspace` shape (Vec<CheckError>
|
|
||||||
accumulator). Same outcome, different mechanism.
|
|
||||||
|
|
||||||
- **`class_method_candidates` returns `Vec` not `impl Iterator`.**
|
|
||||||
The plan's accessor signature `impl Iterator<Item = (&'a String,
|
|
||||||
&'a ClassMethodEntry)>` requires the `use<'a, '_>` opaque-type
|
|
||||||
capture syntax — not yet idiomatic in this crate (the rest of
|
|
||||||
the codebase uses `Vec`-returning accessors at module-graph
|
|
||||||
boundaries). The semantic contract is the same; performance is
|
|
||||||
one allocation per call, bounded by the per-method candidate
|
|
||||||
count (currently ≤ 2 across the corpus).
|
|
||||||
|
|
||||||
- **One existing E2E test trips the new warning.**
|
|
||||||
`crates/ail/tests/typeclass_22b3.rs:rewrite_walker_skips_locally_shadowed_class_method`
|
|
||||||
uses a fixture (`test_22b3_shadow_class_method`) that
|
|
||||||
intentionally shadows the class method `show` with a local
|
|
||||||
`let show = "shadow"`. Pre-mq.3 this fixture typechecked clean;
|
|
||||||
post-mq.3 it correctly fires the `class-method-shadowed-by-fn`
|
|
||||||
warning. The test's `assert!(diags.is_empty())` was relaxed to
|
|
||||||
filter out the new warning with a comment naming why. This is
|
|
||||||
the warning behaving correctly, not a test workaround.
|
|
||||||
|
|
||||||
- **Plan's instance-method body shape was wrong.** Plan Step 1 of
|
|
||||||
Task 6 drafted `body: { "t": "app", ... }` for instance
|
|
||||||
methods; the existing-convention shape (per `test_22b3_mono_synthetic`)
|
|
||||||
is `Term::Lam` with `paramTypes`/`retType` keys. Implementer-phase
|
|
||||||
repair adjusted all three fixtures inline. The plan's caveat
|
|
||||||
("implementer adjusts to match the exact shape used by
|
|
||||||
`examples/prelude.ail.json`'s Eq/Ord instances") covered this
|
|
||||||
case; just exercising the caveat verbatim.
|
|
||||||
|
|
||||||
## Known debt
|
|
||||||
|
|
||||||
- **`bench_list_sum.bump_s` 3rd-consecutive regression.** Noise-class
|
|
||||||
per the audit-ct-tidy 2026-05-12 journal entry. No attribution
|
|
||||||
cause across three audits. The pattern is a candidate for
|
|
||||||
ratify-without-attribution at the milestone-close audit (audit-mq)
|
|
||||||
but this iter follows the conservative pristine-baseline
|
|
||||||
convention.
|
|
||||||
|
|
||||||
- **`latency.implicit_at_rc.max_us` max-tail noise.** Continues from
|
|
||||||
mq.2's bench profile. Runtime metric, structurally uncoupled to
|
|
||||||
this typecheck-side iter. Audit-ratifiable.
|
|
||||||
|
|
||||||
- **`bench/check.py` exit 1.** Same noise-class status as the two
|
|
||||||
regressions above. Conservative interpretation: leave the
|
|
||||||
baseline pristine; audit-mq decides whether to ratify.
|
|
||||||
|
|
||||||
- **`class_method_candidates` allocation per call.** Bounded by
|
|
||||||
per-method candidate count (currently ≤ 2). A future tidy that
|
|
||||||
introduces `use<'a, '_>` syntax across the crate could revisit
|
|
||||||
the iterator-return shape.
|
|
||||||
|
|
||||||
## Files touched
|
|
||||||
|
|
||||||
Code:
|
|
||||||
- crates/ail/src/main.rs
|
|
||||||
- crates/ail/tests/typeclass_22b2.rs
|
|
||||||
- crates/ail/tests/typeclass_22b3.rs
|
|
||||||
- crates/ailang-check/src/builtins.rs
|
|
||||||
- crates/ailang-check/src/diagnostic.rs
|
|
||||||
- crates/ailang-check/src/lib.rs
|
|
||||||
- crates/ailang-check/src/lift.rs
|
|
||||||
- crates/ailang-check/src/mono.rs
|
|
||||||
- crates/ailang-core/src/workspace.rs
|
|
||||||
|
|
||||||
New tests + fixtures:
|
|
||||||
- crates/ail/tests/mq3_multi_class_e2e.rs
|
|
||||||
- crates/ailang-check/tests/method_collision_pin.rs
|
|
||||||
- examples/mq3_two_show_ambiguous_a.ail.json
|
|
||||||
- examples/mq3_two_show_ambiguous_b.ail.json
|
|
||||||
- examples/mq3_two_show_ambiguous.ail.json
|
|
||||||
- examples/mq3_two_show_qualified.ail.json
|
|
||||||
- examples/mq3_class_eq_vs_fn_eq_classmod.ail.json
|
|
||||||
- examples/mq3_class_eq_vs_fn_eq_fnmod.ail.json
|
|
||||||
- examples/mq3_class_eq_vs_fn_eq.ail.json
|
|
||||||
|
|
||||||
Docs:
|
|
||||||
- docs/DESIGN.md
|
|
||||||
- docs/roadmap.md
|
|
||||||
|
|
||||||
## Stats
|
|
||||||
|
|
||||||
bench/orchestrator-stats/2026-05-13-iter-mq.3.json
|
|
||||||
@@ -1,219 +0,0 @@
|
|||||||
# iter mq.tidy — Close 4 actionable drift items from audit-mq
|
|
||||||
|
|
||||||
**Date:** 2026-05-13
|
|
||||||
**Started from:** 64d3feeb972f4ddfe9bd48484585c69a457e7e1e
|
|
||||||
**Status:** DONE
|
|
||||||
**Tasks completed:** 5 of 5
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
mq.tidy closes the four actionable drift items audit-mq surfaced
|
|
||||||
(commit `f382931`), moving the module-qualified-class-names milestone
|
|
||||||
from "structurally complete" to "drift-clean ready for next milestone".
|
|
||||||
T1 extends the discharge-time `refine_multi_candidate_residual`
|
|
||||||
rigid-var filter from class-only to class + type-unification via the
|
|
||||||
existing `constraint_type_matches` helper, so two same-class declared
|
|
||||||
constraints on different typevars are correctly discriminated by the
|
|
||||||
residual's typevar identity (high-1). T2 extracts the inline
|
|
||||||
`qualifier_is_class_shape` predicate as a free `pub(crate)` helper and
|
|
||||||
broadens it to accept PascalCase single-segment qualifiers
|
|
||||||
(`"Show.show"`) alongside module-qualified ones (`"prelude.Show.show"`)
|
|
||||||
— same-module bare-class call sites are now reachable, symmetric to
|
|
||||||
the canonical-form rule at the schema level (high-2). T3 introduces
|
|
||||||
the `any_candidate_class_has_instance` predicate and gates the
|
|
||||||
`class-method-shadowed-by-fn` warning closure on it, so class methods
|
|
||||||
with zero registry instances anywhere no longer fire the warning;
|
|
||||||
conservative approximation of the full spec rule per Boss Q2 (the arg
|
|
||||||
type required for the strict version is unavailable at the Var-arm)
|
|
||||||
(medium-1). T4 annotates the four Data Model schema fragments
|
|
||||||
(`SuperclassRef`, `InstanceDef.class`, `Type::Con.name`,
|
|
||||||
`Constraint.class`) with the canonical-form cross-reference, so a
|
|
||||||
schema-section reader without the prose context no longer sees
|
|
||||||
bare-only (medium-2).
|
|
||||||
|
|
||||||
T5 verification: 548 tests green (was 545 pre-iter + 3 new
|
|
||||||
mq_tidy_* unit tests). `bench/compile_check.py` exit 0 (24/24 stable
|
|
||||||
after a one-run noise blip absorbed under tolerance on re-run).
|
|
||||||
`bench/cross_lang.py` exit 0 (25/25 stable). `bench/check.py` exit 0
|
|
||||||
both runs; metric-identity-migrating noise on the
|
|
||||||
`latency.implicit_at_rc.*` max-tail cluster — 5th-consecutive audit
|
|
||||||
observation per audit-mq's lineage, baseline pristine. The
|
|
||||||
`mq3_class_method_shadowed_by_fn_warning_fires` in-crate test was a
|
|
||||||
trip-wire: pre-tidy it used `Registry::default()` (no instances) and
|
|
||||||
the warning fired anyway because the old gate didn't check instances;
|
|
||||||
post-tidy it correctly suppresses, so the fixture was updated to ship
|
|
||||||
a `clsmod.Show Int` registry instance — fixture-repair as a coherence
|
|
||||||
consequence of the deliberately tightened spec rule. mq3 E2E (3 tests)
|
|
||||||
+ typeclass_22b3 (18 tests) all stay green; the E2E fixtures already
|
|
||||||
ship instances for the shadowed class.
|
|
||||||
|
|
||||||
## Per-task notes
|
|
||||||
|
|
||||||
- **mq.tidy.1** — Rigid-var refinement type-unification leg.
|
|
||||||
`refine_multi_candidate_residual` filter at lib.rs:2163-2180 extends
|
|
||||||
from `dc.class == c` to `dc.class == c && constraint_type_matches(&dc.type_, &residual.type_)`.
|
|
||||||
New unit test `mq_tidy_rigid_var_filter_uses_type_unification` pins
|
|
||||||
the same-class-different-typevar discrimination
|
|
||||||
(`prelude.Show a` + `userlib.Show b` declared, residual on Var `a`
|
|
||||||
→ `Resolved("prelude.Show")`, `userlib.Show` drops because its
|
|
||||||
declared type `Var b` doesn't unify with the residual's `Var a`).
|
|
||||||
Architecture decision per plan: only the discharge-time call site
|
|
||||||
needs the type-unification leg; the synth-time `resolve_method_dispatch`
|
|
||||||
is invoked with `concrete_arg_type: None` and constructs the
|
|
||||||
residual's metavar AFTER the dispatch call, so the rigid-var leg
|
|
||||||
there has no residual type to unify against (fresh metavar would
|
|
||||||
trivially unify with any declared-constraint type, degenerating to
|
|
||||||
class-only filter). The class-only filter at synth time is therefore
|
|
||||||
semantically correct, not drift.
|
|
||||||
|
|
||||||
- **mq.tidy.2** — Bare-class qualifier shape accepted. Inline
|
|
||||||
`qualifier_is_class_shape` predicate at the synth Var-arm dispatch
|
|
||||||
entry (originally lib.rs:2570-2575) extracted as a free
|
|
||||||
`pub(crate) fn qualifier_is_class_shape(&Option<String>) -> bool`
|
|
||||||
helper adjacent to `parse_method_qualifier`. Predicate body:
|
|
||||||
`None` → true (bare method); `Some(q)` with inner dot → true
|
|
||||||
(`<module>.<Class>`); `Some(q)` single-segment → first char
|
|
||||||
uppercase (PascalCase). Bare-fn qualifiers (`"std_list"`, lowercase)
|
|
||||||
correctly reject and fall through to the cross-module-fn arm.
|
|
||||||
Call site at the original location reduced to a one-liner. New
|
|
||||||
unit test `mq_tidy_bare_class_qualifier_shape_accepted` covers all
|
|
||||||
four shapes.
|
|
||||||
|
|
||||||
- **mq.tidy.3** — `class-method-shadowed-by-fn` warning tightened.
|
|
||||||
New `pub(crate) fn any_candidate_class_has_instance(...)` helper
|
|
||||||
using BTreeMap range-scan (O(log n) per candidate, O(|candidates|
|
|
||||||
* log n) total) checks the workspace registry for at least one
|
|
||||||
instance under any candidate class. Warning closure at
|
|
||||||
lib.rs:2479-2502 (now ~2498-2540 post-T2-insertion-shift) builds
|
|
||||||
a `registry_unit_view: BTreeMap<(String, String), ()>` per
|
|
||||||
closure invocation and calls the helper before emitting. New
|
|
||||||
unit test `mq_tidy_shadow_warning_suppressed_when_no_instance`
|
|
||||||
pins both directions (empty registry → suppress; one instance
|
|
||||||
→ fire). The existing `mq3_class_method_shadowed_by_fn_warning_fires`
|
|
||||||
in-crate test was a trip-wire — its fixture used
|
|
||||||
`Registry::default()` (no instances) and pre-tidy the warning
|
|
||||||
fired anyway because the old gate didn't check instances. Post-tidy
|
|
||||||
the spec rule correctly suppresses; fixture repaired by injecting
|
|
||||||
a `clsmod.Show Int` registry entry directly into `ws.registry.entries`
|
|
||||||
so the test continues to assert positive-fire behaviour under the
|
|
||||||
post-tidy rule. The three mq3 E2E fixtures (`mq3_class_eq_vs_fn_eq`
|
|
||||||
+ `mq3_two_show_*`) already ship instances on the shadowed class,
|
|
||||||
so they stayed green without modification.
|
|
||||||
|
|
||||||
- **mq.tidy.4** — DESIGN.md schema fragments. Four trailing-comment
|
|
||||||
annotations: `SuperclassRef` (`"superclass"` field at line 2139),
|
|
||||||
`InstanceDef.class` (line 2152), `Type::Con.name` (line 2253), and
|
|
||||||
`Constraint.class` (line 2273). Class-ref annotations cross-reference
|
|
||||||
§"Class names" / mq.1; the Type::Con annotation cross-references
|
|
||||||
§"Type::Con name scoping" / ct.1 (the actual section title — the
|
|
||||||
plan's "§Type names" / "Canonical type names" search did not match
|
|
||||||
exactly; used the canonical anchor title verified via grep). Doc
|
|
||||||
count of "canonical form" went from 1 → 5 (+4 as planned). All
|
|
||||||
annotations are inline trailing-comment appends; no line-count
|
|
||||||
change in DESIGN.md (2678 → 2678).
|
|
||||||
|
|
||||||
- **mq.tidy.5** — Integration verification.
|
|
||||||
`cargo test --workspace --no-fail-fast` → 548 passed, 0 failed
|
|
||||||
(was 545 pre-iter + 3 new mq_tidy_* unit tests). `bench/compile_check.py`
|
|
||||||
exit 0; first run showed 1 regression
|
|
||||||
(`build_O0_ms.bench_list_sum_explicit` +5.43% within 20.0% tolerance,
|
|
||||||
classified `ok`; the summary's "1 regressed" count is a
|
|
||||||
false-positive on the first run, second run shows 0 regressed,
|
|
||||||
24/24 stable). `bench/cross_lang.py` exit 0 both runs, 25/25 stable.
|
|
||||||
`bench/check.py` exit 0 both runs; run 1 had 3 regressed / 3
|
|
||||||
improved (all `latency.implicit_at_rc.*` max-tail metrics — same
|
|
||||||
noise class the audit-mq journal documented across 4 consecutive
|
|
||||||
runs); run 2 had 0 regressed / 1 improved / 62 stable — exactly
|
|
||||||
the metric-identity-migration pattern the audit named as variance,
|
|
||||||
not signal. Baseline pristine per the conservative-call convention
|
|
||||||
(6th-consecutive observation now). Plan Step 5 (prelude roundtrip
|
|
||||||
via `cargo run --bin ail -- check examples/prelude.ail.json`)
|
|
||||||
returned exit 1 with "module name 'prelude' is reserved" — but
|
|
||||||
this is identical to pre-edit behaviour (verified via `git stash`),
|
|
||||||
not a regression from this iter. The plan's expectation was
|
|
||||||
inaccurate; the actual sanity gate (DESIGN.md doc edits do not
|
|
||||||
break prelude consistency) is satisfied by the workspace test
|
|
||||||
suite passing 548/548.
|
|
||||||
|
|
||||||
## Concerns
|
|
||||||
|
|
||||||
- **Plan Task 3 Step 6 anticipation was off for one in-crate test.**
|
|
||||||
The plan said: "Expected: PASS — the existing positive fixtures
|
|
||||||
(`mq3_class_eq_vs_fn_eq`, `typeclass_22b3::rewrite_walker_skips_locally_shadowed_class_method`)
|
|
||||||
all ship registry instances for the shadowed class, so the
|
|
||||||
tightened predicate continues to admit the warning emission." This
|
|
||||||
was correct for those two fixtures (both E2E), but the in-crate
|
|
||||||
unit test `mq3_class_method_shadowed_by_fn_warning_fires` builds
|
|
||||||
its workspace with `Registry::default()` (zero instances) and
|
|
||||||
pre-tidy relied on the looser rule. Post-tidy it correctly
|
|
||||||
suppresses; the fixture was repaired inline by injecting a
|
|
||||||
`clsmod.Show Int` registry entry. This is exactly analogous to the
|
|
||||||
`typeclass_22b3::rewrite_walker_skips_locally_shadowed_class_method`
|
|
||||||
trip-wire mq.3 documented — same pattern, different test.
|
|
||||||
Fixture-repair as a coherence consequence of a deliberately
|
|
||||||
tightened spec rule; the test's intent ("warning fires when a
|
|
||||||
free fn shadows a class method") is unchanged.
|
|
||||||
|
|
||||||
- **Plan Task 5 Step 5 prelude check expectation was off.** The plan
|
|
||||||
said `cargo run --bin ail -- check examples/prelude.ail.json`
|
|
||||||
expected `ok` exit 0; the actual behaviour is exit 1 with the
|
|
||||||
"module name 'prelude' is reserved (auto-injected by the loader)"
|
|
||||||
error. This is identical to pre-edit behaviour (verified via
|
|
||||||
`git stash`); the prelude file is the loader's auto-injected
|
|
||||||
module, not a workspace entry, so `ail check` against it has
|
|
||||||
always failed by design. The right sanity gate for "DESIGN.md
|
|
||||||
doc edits do not break prelude consistency" is the workspace
|
|
||||||
test pass (548/548), which exercises every workspace test that
|
|
||||||
uses the prelude. The plan's prescribed command can be removed
|
|
||||||
from future tidy plans.
|
|
||||||
|
|
||||||
- **T2 docstring update on `parse_method_qualifier` was not
|
|
||||||
explicitly in the plan.** The plan asked for adding the new
|
|
||||||
`qualifier_is_class_shape` helper but did not enumerate the
|
|
||||||
paragraph update on `parse_method_qualifier`'s docstring. The
|
|
||||||
old docstring said "Callers gate on the qualifier shape: a class
|
|
||||||
qualifier is always qualified per mq.1's canonical-form rule" —
|
|
||||||
which the broadened gate directly contradicts (bare PascalCase
|
|
||||||
qualifiers now go to class-dispatch). Updating the existing
|
|
||||||
helper's documentation to reference the new helper and remove
|
|
||||||
the now-incorrect claim is strictly required to keep the helper's
|
|
||||||
documentation coherent with the requested behaviour change; it
|
|
||||||
is the docstring analog of "code edits strictly required to make
|
|
||||||
the requested changes compile." Defensible as a coherence
|
|
||||||
consequence, not unrequested scope creep.
|
|
||||||
|
|
||||||
## Known debt
|
|
||||||
|
|
||||||
- **bench/check.py max-tail noise envelope.** 6th-consecutive
|
|
||||||
observation of the `latency.implicit_at_rc.*` max-tail metric
|
|
||||||
identity migrating between runs. The conservative-call convention
|
|
||||||
has held the baseline pristine across 5 prior audits; this iter
|
|
||||||
adds run 6 to the lineage. A future audit may consider ratification
|
|
||||||
via `--update-baseline` if the pattern persists for another 2-3
|
|
||||||
audits; until then, the lineage is the documentation.
|
|
||||||
|
|
||||||
- **mq.tidy did not deliver the [low-1] (Trajectory B + D E2E)
|
|
||||||
or [low-2] (mono env shape) roadmap-backlog items.** Same scope
|
|
||||||
as audit-mq's deferral; these wait for the multi-class workspace
|
|
||||||
shape to land downstream.
|
|
||||||
|
|
||||||
- **mq.tidy did not refactor `synth(...)`'s 10-mut-ref signature.**
|
|
||||||
audit-mq's [medium-3] item was explicitly acknowledged-debt, not
|
|
||||||
in tidy scope. Same disposition; future iter may revisit if a
|
|
||||||
natural refactor opportunity arises.
|
|
||||||
|
|
||||||
## Files touched
|
|
||||||
|
|
||||||
Code:
|
|
||||||
- crates/ailang-check/src/lib.rs
|
|
||||||
|
|
||||||
Docs:
|
|
||||||
- docs/DESIGN.md
|
|
||||||
- docs/journals/2026-05-13-iter-mq.tidy.md (this file, new)
|
|
||||||
|
|
||||||
No new test files; the three new mq_tidy_* unit tests are inline in
|
|
||||||
`crates/ailang-check/src/lib.rs`'s `mod tests` block.
|
|
||||||
|
|
||||||
## Stats
|
|
||||||
|
|
||||||
bench/orchestrator-stats/2026-05-13-iter-mq.tidy.json
|
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
# iter rustdoc-sweep — clear all 23 cargo-doc warnings
|
|
||||||
|
|
||||||
**Date:** 2026-05-13
|
|
||||||
**Started from:** 48b1f77 (post-WhatsNew str-concat done-state)
|
|
||||||
**Status:** DONE
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
Hygiene sweep against `cargo doc --workspace --no-deps`. Before: 23
|
|
||||||
warnings (17 in `ailang-check` + 4 in `ailang-core` + 2 in
|
|
||||||
`ailang-surface`). After: zero. All edits are documentation-only —
|
|
||||||
no production code, no test code changed. Tests stay 562 green.
|
|
||||||
|
|
||||||
Two warning classes, fixed with two different shapes:
|
|
||||||
|
|
||||||
- **Public doc links to private items** (15 warnings). Pattern:
|
|
||||||
a `pub` item's `///` doc references `[`fn_name`]` where `fn_name`
|
|
||||||
is `pub(crate)` or private. Fix: replace ``[`fn_name`]`` with
|
|
||||||
the plain backtick-code-span ``` `fn_name` ```. The identifier
|
|
||||||
stays readable in rendered docs but rustdoc no longer tries to
|
|
||||||
resolve the link.
|
|
||||||
- **Unresolved links** (8 warnings). Sub-classes:
|
|
||||||
- ``[`Registry`]`` in `mono.rs` (×3) — `Registry` lives in
|
|
||||||
`ailang_core::workspace` and the path was bare. Fixed by
|
|
||||||
fully-qualifying as `[`ailang_core::workspace::Registry`]` /
|
|
||||||
`[`ailang_core::workspace::Registry::entries`]`.
|
|
||||||
- ``[`Env::class_methods`]`` — `Env` is in this crate but
|
|
||||||
bare; the doc string was demoted to plain backticks
|
|
||||||
(`` `crate::Env::class_methods` ``) because `Env` itself has
|
|
||||||
no `pub` re-export at the crate root that rustdoc could
|
|
||||||
follow.
|
|
||||||
- ``[`crate::parse`]`` ambiguous in `loader.rs` (×2) — rustdoc
|
|
||||||
saw both a function and a module with that path. Disambiguated
|
|
||||||
to the function form via ``[`crate::parse()`]`` per
|
|
||||||
rustdoc's own suggestion.
|
|
||||||
- `metas[i]` in `lib.rs` — rustdoc parsed `[i]` as a link.
|
|
||||||
Escaped as `metas\[i\]` (same fix for the sibling `vars\[i\]`).
|
|
||||||
|
|
||||||
## Files touched
|
|
||||||
|
|
||||||
- `crates/ailang-core/src/desugar.rs` (1 line)
|
|
||||||
- `crates/ailang-core/src/workspace.rs` (3 lines)
|
|
||||||
- `crates/ailang-surface/src/loader.rs` (2 lines)
|
|
||||||
- `crates/ailang-check/src/uniqueness.rs` (2 lines)
|
|
||||||
- `crates/ailang-check/src/diagnostic.rs` (1 line)
|
|
||||||
- `crates/ailang-check/src/mono.rs` (5 lines)
|
|
||||||
- `crates/ailang-check/src/lib.rs` (8 lines)
|
|
||||||
|
|
||||||
## Verification
|
|
||||||
|
|
||||||
- `cargo doc --workspace --no-deps 2>&1 | grep "^warning:" | wc -l`
|
|
||||||
→ `0`.
|
|
||||||
- `cargo test --workspace` → 562 passed, 0 failed (baseline holds).
|
|
||||||
|
|
||||||
## Concerns
|
|
||||||
|
|
||||||
- (none)
|
|
||||||
|
|
||||||
## Known debt
|
|
||||||
|
|
||||||
- (none)
|
|
||||||
@@ -1,149 +0,0 @@
|
|||||||
# iter str-concat — heap-Str concatenation primitive in four-site lockstep
|
|
||||||
|
|
||||||
**Date:** 2026-05-13
|
|
||||||
**Started from:** 679572a92d594d94b915d09fd60e7ef7f7bb83bd
|
|
||||||
**Status:** DONE
|
|
||||||
**Tasks completed:** 7 of 7
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
`str_concat : (borrow Str, borrow Str) -> Str` shipped as a heap-Str
|
|
||||||
primitive in the four-site-lockstep pattern established by `str_clone`
|
|
||||||
(iter 24.1). Closes fieldtest-form-a friction finding #4. The
|
|
||||||
LLM-natural Show-body shape
|
|
||||||
`(app str_concat "label=" (app int_to_str x))` now parses, checks,
|
|
||||||
builds, and runs end-to-end via the new
|
|
||||||
`examples/show_user_adt_with_label.ail` fixture (`Item 42\n`
|
|
||||||
through `print . MkItem 42`).
|
|
||||||
|
|
||||||
Net delta: +3 tests (559 baseline → 562 green): the E2E pin
|
|
||||||
(`str_concat_e2e_show_user_adt_with_label`), the builtin-signature
|
|
||||||
unit test (`install_str_concat_signature` asserting the arity-2
|
|
||||||
Borrow-Borrow / Own-return shape directly on `env.globals`), and the
|
|
||||||
codegen IR-pin (`str_concat_emits_call_to_ailang_str_concat`
|
|
||||||
asserting both the extern declaration AND the call instruction in
|
|
||||||
emitted IR). Five `crates/ail/tests/snapshots/*.ll` files were
|
|
||||||
regenerated to absorb the new unconditional
|
|
||||||
`declare ptr @ailang_str_concat(ptr, ptr)` line in the IR header
|
|
||||||
preamble — same upkeep pattern as hs.4 (which regenerated the same
|
|
||||||
5 snapshots for the unconditional `@ailang_int_to_str` /
|
|
||||||
`@ailang_float_to_str` declares).
|
|
||||||
|
|
||||||
Task 5 repaired the bug-fixture / pin-test collision: the
|
|
||||||
`bugfix-instance-body-unbound-var` iter (commit 77f584a) used
|
|
||||||
`str_concat` as the literal unbound name because that was the
|
|
||||||
LLM-natural shape the fieldtester reached for; renamed to
|
|
||||||
`format_label` (LLM-author-realistic helper name) to preserve the
|
|
||||||
pin's intent (instance-method-body walked through unbound-var check)
|
|
||||||
while substituting a name that will never become a builtin.
|
|
||||||
|
|
||||||
## Per-task notes
|
|
||||||
|
|
||||||
- iter str-concat.1: RED fixture `examples/show_user_adt_with_label.ail`
|
|
||||||
(single-ctor `Item Int` ADT + `Show Item` body producing labelled
|
|
||||||
output) + E2E pin `crates/ail/tests/str_concat_e2e.rs` (check +
|
|
||||||
build + run asserting `Item 42\n` stdout). Confirmed RED with
|
|
||||||
`[unbound-var] prelude.Show: unknown identifier: 'str_concat'`.
|
|
||||||
- iter str-concat.2: `runtime/str.c` C helper `ailang_str_concat`
|
|
||||||
appended after `ailang_str_clone`; reads `len` from both source
|
|
||||||
payloads, `str_alloc(la+lb)`, memcpys both bytes, NUL-terminates.
|
|
||||||
Clean `clang -O2 -c` compile.
|
|
||||||
- iter str-concat.3: `crates/ailang-check/src/builtins.rs` —
|
|
||||||
`env.globals.insert("str_concat", ...)` install entry after
|
|
||||||
str_clone block (arity-2, both Borrow, Own return); list() row
|
|
||||||
`("str_concat", "(Str, Str) -> Str")` after str_clone row;
|
|
||||||
`install_str_concat_signature` test reaches into `env.globals`
|
|
||||||
directly to match the arity-2 / param_modes / ret_mode discipline
|
|
||||||
(deeper than the sibling `synth_in_builtins_env`-based tests
|
|
||||||
because str_concat is the first builtin with arity-2 Borrow params).
|
|
||||||
- iter str-concat.4: `crates/ailang-codegen/src/lib.rs` four-site lockstep:
|
|
||||||
extern declaration after `@ailang_str_clone`; lower_app arm after
|
|
||||||
str_clone arm (arity-2 check + two `lower_term` + `fresh_ssa` +
|
|
||||||
body push); `is_builtin_callable` list extended with `"str_concat"`;
|
|
||||||
IR-pin test mirrors the sibling `str_clone_emits_call_to_ailang_str_clone`
|
|
||||||
pattern (Module/FnDef/Term::Do scaffolding via `super::*` +
|
|
||||||
`SCHEMA`, not the plan's `ailang_surface::parse` structural sketch —
|
|
||||||
plan explicitly authorised this in Step 4 note). IR-pin test
|
|
||||||
asserts both the extern declare AND the call site. RED→GREEN flip
|
|
||||||
on the E2E pin happens at this task. Five IR snapshots regenerated
|
|
||||||
via `UPDATE_SNAPSHOTS=1` to absorb the new declare line (sole
|
|
||||||
diff verified at line 17 across all snapshots).
|
|
||||||
- iter str-concat.5: bug-fixture / pin-test collision repaired —
|
|
||||||
`examples/bug_unbound_in_instance_method.ail` line 14 rename
|
|
||||||
`str_concat` → `format_label`; pin test
|
|
||||||
`crates/ail/tests/unbound_in_instance_method_pin.rs` `replace_all`
|
|
||||||
rename (one test name changed to
|
|
||||||
`check_fires_unbound_var_for_format_label_in_instance_method_body`).
|
|
||||||
Both pin tests pass; new fixture `ail check` reports
|
|
||||||
`ok (23 symbols across 2 modules)` — matches the plan's empirical
|
|
||||||
guess exactly.
|
|
||||||
- iter str-concat.6: `docs/DESIGN.md` new §"Heap-Str primitives"
|
|
||||||
subsection between the milestone-24 paragraph and the
|
|
||||||
`Primitive output goes through ...` paragraph; lists all five
|
|
||||||
shipping primitives with their type, iter origin, and prelude
|
|
||||||
role, plus the user-visible vs prelude-internal distinction.
|
|
||||||
- iter str-concat.7: `docs/roadmap.md` struck `[x] [feature] str_concat`
|
|
||||||
entry at end of P2 cluster (right before `## P3 — Ideas`). All
|
|
||||||
four cited fixtures (`show_user_adt_with_label`, `show_user_adt`,
|
|
||||||
`test_22c_user_class_e2e`, `cmp_max_smoke`) `ail check` clean.
|
|
||||||
`ail builtins | grep str_concat` returns `str_concat (Str, Str) -> Str`.
|
|
||||||
|
|
||||||
## Concerns
|
|
||||||
|
|
||||||
- Task 3 (Step 5) test-count prediction was off: plan expected
|
|
||||||
`passed: 560 failed: 1` after registering the builtin but actual
|
|
||||||
was `passed: 558 failed: 3`. Reason: the two
|
|
||||||
`unbound_in_instance_method_pin` tests turn RED as soon as
|
|
||||||
`str_concat` is registered in the checker, NOT only at codegen.
|
|
||||||
The pins assert on `ail check`'s `[unbound-var]` diagnostic, which
|
|
||||||
depends only on the checker registry. Implementation is correct;
|
|
||||||
Task 5 fully repairs. No follow-up needed beyond noting the
|
|
||||||
prediction-vs-actual gap.
|
|
||||||
- Task 4 (Step 7) test-count prediction was off: plan expected
|
|
||||||
`passed: 562 failed: 1` after codegen lands; actual after
|
|
||||||
IR-snapshot regen was `passed: 560 failed: 2`. The difference is
|
|
||||||
fully accounted for by the 2 still-RED pin tests (repaired in
|
|
||||||
T5) and by the IR-snapshot regen being out-of-band of the
|
|
||||||
per-task counters in the plan. Final state at T5 Step 5 is
|
|
||||||
exactly `passed: 562 failed: 0`, matching the plan's iter target.
|
|
||||||
- IR snapshot regeneration was not explicitly scripted in the plan
|
|
||||||
for Task 4, but it follows the hs.4 precedent (which regenerated
|
|
||||||
the same 5 snapshots for the same reason: a new unconditional
|
|
||||||
declare line in the IR header). The sole diff in each snapshot
|
|
||||||
is the new `declare ptr @ailang_str_concat(ptr, ptr)` line on
|
|
||||||
IR line 17, immediately before the existing
|
|
||||||
`declare i64 @llvm.fptosi.sat.i64.f64(double)` declaration.
|
|
||||||
|
|
||||||
## Known debt
|
|
||||||
|
|
||||||
- (none)
|
|
||||||
|
|
||||||
## Files touched
|
|
||||||
|
|
||||||
Code:
|
|
||||||
- `runtime/str.c` (M) — append `ailang_str_concat` helper
|
|
||||||
- `crates/ailang-check/src/builtins.rs` (M) — install entry, list()
|
|
||||||
row, signature unit test
|
|
||||||
- `crates/ailang-codegen/src/lib.rs` (M) — extern declare,
|
|
||||||
lower_app arm, is_builtin_callable list, IR-pin unit test
|
|
||||||
- `crates/ail/tests/str_concat_e2e.rs` (new) — E2E pin
|
|
||||||
- `crates/ail/tests/unbound_in_instance_method_pin.rs` (M) — rename
|
|
||||||
`str_concat` → `format_label` (4 sites + 1 test name)
|
|
||||||
- `examples/show_user_adt_with_label.ail` (new) — Show body fixture
|
|
||||||
- `examples/bug_unbound_in_instance_method.ail` (M) — rename
|
|
||||||
`str_concat` → `format_label` (1 site)
|
|
||||||
|
|
||||||
IR snapshots regenerated (5; sole diff = new declare line):
|
|
||||||
- `crates/ail/tests/snapshots/hello.ll`
|
|
||||||
- `crates/ail/tests/snapshots/list.ll`
|
|
||||||
- `crates/ail/tests/snapshots/max3.ll`
|
|
||||||
- `crates/ail/tests/snapshots/sum.ll`
|
|
||||||
- `crates/ail/tests/snapshots/ws_main.ll`
|
|
||||||
|
|
||||||
Docs:
|
|
||||||
- `docs/DESIGN.md` (M) — new §"Heap-Str primitives" subsection
|
|
||||||
- `docs/roadmap.md` (M) — struck `[x] [feature] str_concat` line
|
|
||||||
|
|
||||||
## Stats
|
|
||||||
|
|
||||||
bench/orchestrator-stats/2026-05-13-iter-str-concat.json
|
|
||||||
@@ -1,119 +0,0 @@
|
|||||||
# audit-pd — milestone close (prelude-decouple)
|
|
||||||
|
|
||||||
**Date:** 2026-05-14
|
|
||||||
**Scope:** commits e6298f5..9a8d385 (spec → pd.1 plan → pd.1 → housekeeping → pd.2 plan → pd.2 → pd.3 plan → pd.3)
|
|
||||||
**Status:** DONE — drift fixed inline as `audit-pd-tidy`; bench ratified pristine
|
|
||||||
|
|
||||||
## Architect drift review
|
|
||||||
|
|
||||||
`ailang-architect` returned **drift_found** with one Important + one
|
|
||||||
Minor + one Nit:
|
|
||||||
|
|
||||||
- `[Important]` `docs/DESIGN.md` lines 2095-2099 + 2140-2143 — §"Roundtrip
|
|
||||||
Invariant" point 4 ("Carve-out anchor") still asserted "Eight
|
|
||||||
`.ail.json`-only fixtures (seven subject-matter rejection tests +
|
|
||||||
one compile-time-embed for the prelude)" and §Enforcement still
|
|
||||||
said "exactly the eight named carve-out files exist", but
|
|
||||||
post-pd.3 the count is seven and the compile-time-embed clause
|
|
||||||
is empty. The form-a spec was retired in place during pd.3 (§C4 (b)
|
|
||||||
RETIRED status marker); DESIGN.md was missed.
|
|
||||||
- `[Minor]` `crates/ail/src/main.rs` lines 2438-2448 — `local_types.get("prelude")`
|
|
||||||
fallback in `rewrite_type` (the migrate-canonical-types subcommand's
|
|
||||||
helper) was dead-by-construction after pd.3 retired the synthetic
|
|
||||||
prelude load in the same subcommand. A literal `"prelude"` string
|
|
||||||
in CLI scope that survived a milestone whose explicit purpose was
|
|
||||||
to delete exactly that pattern in core; the same drift category
|
|
||||||
one crate over.
|
|
||||||
- `[Nit]` `crates/ailang-core/src/lib.rs` — three pre-existing cargo
|
|
||||||
doc warnings on dangling `[load_workspace]` references carried
|
|
||||||
from pd.1 (already recorded as known debt in pd.1 + pd.2 journals).
|
|
||||||
Pre-existing baseline; not in this audit's scope. The roadmap's
|
|
||||||
P2 "Rustdoc warning sweep" todo covers it.
|
|
||||||
|
|
||||||
Both Important and Minor were fixed inline as `audit-pd-tidy` per
|
|
||||||
CLAUDE.md "trivial mechanical edit" carve-out (≤30 LOC, no
|
|
||||||
judgement). The Nit was deferred to its existing roadmap todo.
|
|
||||||
|
|
||||||
### Inline fixes applied
|
|
||||||
|
|
||||||
- `docs/DESIGN.md:2095-2099` rewritten: "Seven `.ail.json`-only
|
|
||||||
fixtures (subject-matter rejection tests) ... The prelude's
|
|
||||||
compile-time-embed carve-out, originally listed here as the eighth
|
|
||||||
fixture, was retired 2026-05-14 by milestone prelude-decouple;
|
|
||||||
the prelude is now embedded as `examples/prelude.ail` in
|
|
||||||
`ailang-surface` and parsed at compile time via
|
|
||||||
`ailang_surface::parse_prelude`."
|
|
||||||
- `docs/DESIGN.md:2141` rewritten: "exactly the seven named
|
|
||||||
carve-out files exist".
|
|
||||||
- `crates/ail/src/main.rs:2427-2448` — the `or_else` arm scanning
|
|
||||||
`local_types["prelude"]` was deleted (5 LOC body); replaced with
|
|
||||||
a multi-line comment explaining why (with a forward pointer for
|
|
||||||
any future migrate variant that needs prelude awareness:
|
|
||||||
repopulate `local_types["prelude"]` explicitly via
|
|
||||||
`parse_prelude`). The owner-search now scans `import_names` only.
|
|
||||||
No behavioural change against the current corpus (the `or_else`
|
|
||||||
arm could never fire post-pd.3).
|
|
||||||
|
|
||||||
`cargo test -p ailang-core --test carve_out_inventory` PASS post-fix.
|
|
||||||
`cargo build -p ail` clean.
|
|
||||||
|
|
||||||
## Bench-regression check
|
|
||||||
|
|
||||||
| Script | Exit | Result |
|
|
||||||
|--------|------|--------|
|
|
||||||
| `bench/check.py` | 1 | 63 metrics; 1 regressed (`latency.implicit_at_rc.p99_us` +14.42%, p99_9 +16.17%, max +27.61%, p99_over_median +9.79% — same `implicit_at_rc` cluster), 4 improved (`latency.explicit_at_rc.*` cluster), 58 stable. **Established 14-audit envelope-noise.** |
|
|
||||||
| `bench/compile_check.py` | 1 | 24 metrics; 11 regressed, 0 improved, 13 stable. The build-time-O0 noise cluster (audit-form-a, clippy-sweep). All within the 20% tolerance, ratifying as noise. |
|
|
||||||
| `bench/cross_lang.py` | 0 | 25 metrics; 0 regressed, 0 improved, 25 stable. Clean. |
|
|
||||||
|
|
||||||
`prelude-decouple` is a workspace-loader refactor + spec text +
|
|
||||||
file-system + test code only. Zero codegen / runtime / typecheck
|
|
||||||
path edited. The bench numbers cannot be causally affected by this
|
|
||||||
milestone; the `implicit_at_rc.p99_*` oscillation and the build-time
|
|
||||||
noise are the same long-running envelope every audit since audit-cma
|
|
||||||
has flagged. Baseline left pristine (15th consecutive audit decision
|
|
||||||
to do so on this envelope; the right resolution is a methodology
|
|
||||||
fix — already queued as P3 "Latency methodology rework" — not
|
|
||||||
per-audit baseline-juggling).
|
|
||||||
|
|
||||||
## Resolution
|
|
||||||
|
|
||||||
- `[Important]` DESIGN.md drift — FIXED inline (audit-pd-tidy).
|
|
||||||
- `[Minor]` `main.rs` dead prelude fallback — FIXED inline (audit-pd-tidy).
|
|
||||||
- `[Nit]` pre-existing cargo doc warnings — DEFERRED to P2 "Rustdoc
|
|
||||||
warning sweep" (existing roadmap todo).
|
|
||||||
- Bench check.py + compile_check.py exit 1 — RATIFIED as noise
|
|
||||||
envelope, baseline pristine, 15th consecutive observation.
|
|
||||||
|
|
||||||
## Files touched
|
|
||||||
|
|
||||||
- `docs/DESIGN.md` (two paragraphs in §"Roundtrip Invariant")
|
|
||||||
- `crates/ail/src/main.rs` (5 LOC body deletion + 8 LOC replacement
|
|
||||||
comment in `rewrite_type`)
|
|
||||||
- `docs/journals/2026-05-14-audit-pd.md` (this file)
|
|
||||||
|
|
||||||
No bench baseline files modified.
|
|
||||||
|
|
||||||
## Milestone status
|
|
||||||
|
|
||||||
`prelude-decouple` milestone: **CLOSED**. The roadmap entry at P0
|
|
||||||
moves to `[x]` and is removed from the active queue (the milestone
|
|
||||||
trace stays in the journal index for chronological context). The
|
|
||||||
post-milestone state of the working tree:
|
|
||||||
|
|
||||||
- `examples/prelude.ail.json` deleted; the prelude exists on disk
|
|
||||||
only as `examples/prelude.ail`.
|
|
||||||
- `crates/ailang-core/src/` contains zero literal-`"prelude"` strings.
|
|
||||||
- `crates/ail/src/main.rs` contains zero literal-`"prelude"` strings
|
|
||||||
in production code (the migrate-subcommand fallback is gone post-
|
|
||||||
audit-tidy).
|
|
||||||
- The two-phase workspace-loading composition `(load_modules_with →
|
|
||||||
caller-side prelude inject → build_workspace)` is the canonical
|
|
||||||
shape, exposed as public API in `ailang-core` and composed by
|
|
||||||
`ailang-surface::load_workspace`.
|
|
||||||
- `ailang_surface::parse_prelude` + `PRELUDE_AIL` are the canonical
|
|
||||||
embed surface, re-exported from `ailang-surface/src/lib.rs`.
|
|
||||||
- The CLAUDE.md "authors write `.ail`; the build derives the
|
|
||||||
JSON-AST in-process via `ailang_surface::parse`" doctrine holds
|
|
||||||
without exception; the prelude was the last carve-out and is now
|
|
||||||
inside the doctrine.
|
|
||||||
- 573 tests green workspace-wide.
|
|
||||||
@@ -1,95 +0,0 @@
|
|||||||
# iter bugfix-mono-cursor-print-with-class-method-arg — `(app print (app eq …))` no longer rewrites the inner class-method to a stale `Show T` symbol
|
|
||||||
|
|
||||||
**Date:** 2026-05-14
|
|
||||||
**Started from:** 05e4c04e3ba74f3382216ff3ea285e3fb1e3a45d
|
|
||||||
**Status:** DONE
|
|
||||||
**Tasks completed:** 1 of 1
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
The iter 24.3 `synth` Var-arm pushes BOTH a class `ResidualConstraint`
|
|
||||||
for each declared constraint AND a `FreeFnCall` observation at the
|
|
||||||
same poly-free-fn `Var` site (`crates/ailang-check/src/lib.rs` ≈ 2908–
|
|
||||||
2966). The two mono walkers in `crates/ailang-check/src/mono.rs`,
|
|
||||||
however, were each consuming exactly one slot per `Var`: at a
|
|
||||||
poly-free-fn `Var` they popped one `free_fn_slot` and zero
|
|
||||||
`class_slot`s. Every constraint on a poly-free-fn-with-constraints
|
|
||||||
left a leftover class slot, so the next class-method `Var` in the
|
|
||||||
same body consumed the wrong slot — the residual that belonged to
|
|
||||||
`print`'s `Show T` constraint got cursor-aligned with the inner `eq`
|
|
||||||
`Var`, and codegen reported `call prelude.show__Bool arg type
|
|
||||||
mismatch: expected i1, got i64`.
|
|
||||||
|
|
||||||
The fix advances both walkers' cursors by `1 + N` at a poly-free-fn
|
|
||||||
`Var` whose source `Type::Forall` carries `N` class constraints. A
|
|
||||||
new sibling helper `poly_free_fn_constraint_counts_for_module`
|
|
||||||
mirrors `poly_free_fn_names_for_module`'s three-source spelling
|
|
||||||
contract (same-module / implicit-import bare / dot-qualified
|
|
||||||
`alias.name`) and produces a `BTreeMap<String, usize>` from synth-
|
|
||||||
visible name to declared-constraint count. The map is threaded into
|
|
||||||
the rewrite-phase loop alongside `poly_free_fns` and passed to both
|
|
||||||
`rewrite_mono_calls` and `interleave_slots`.
|
|
||||||
|
|
||||||
In `interleave_slots`, a poly-free-fn `Var` now pushes the `FreeFn`
|
|
||||||
slot first (this is what the rewrite walker reads for the rename),
|
|
||||||
then `N` filler entries drawn in order from `class_slots` (consumed
|
|
||||||
only to keep `class_cur` aligned). In `rewrite_mono_calls`, the
|
|
||||||
`Var`-arm captures `n_filler` from the count map BEFORE the rename
|
|
||||||
mutates `name` (lookup keys are the synth-visible spelling), reads
|
|
||||||
the slot at `*cursor` as before, then advances by `1 + n_filler`.
|
|
||||||
Class-method `Var`s stay at advance-by-one (synth pushes exactly one
|
|
||||||
residual and no `FreeFnCall`).
|
|
||||||
|
|
||||||
## Per-task notes
|
|
||||||
|
|
||||||
- task 1: write/verify the cursor-alignment fix.
|
|
||||||
- Reproduced RED at `print_with_class_method_arg_does_not_misalign_mono_cursor`
|
|
||||||
with the exact failure mode stated in the carrier:
|
|
||||||
`call prelude.show__Bool arg type mismatch: expected i1, got i64`.
|
|
||||||
- Added `poly_free_fn_constraint_counts_for_module` helper
|
|
||||||
immediately after `poly_free_fn_names_for_module`; identical
|
|
||||||
name-source contract, returning constraint counts instead of a
|
|
||||||
presence set.
|
|
||||||
- Threaded `poly_free_fn_ccounts: &BTreeMap<String, usize>` into
|
|
||||||
`collect_residuals_ordered`, `interleave_slots`, and
|
|
||||||
`rewrite_mono_calls` (signatures + every recursive call site).
|
|
||||||
- `interleave_slots::Term::Var` poly-free-fn branch: after pushing
|
|
||||||
the `FreeFn` slot, drain `N` entries from `class_slots` and push
|
|
||||||
them as fillers in `out`, advancing `class_cur` by `N`.
|
|
||||||
- `rewrite_mono_calls::Term::Var`: compute `n_filler` from the
|
|
||||||
count map before the rename mutation, then `*cursor += 1 +
|
|
||||||
n_filler`. Class-method `Var`s are guarded with `if
|
|
||||||
is_poly_free_fn { … } else { 0 }`.
|
|
||||||
- RED `print_with_class_method_arg_does_not_misalign_mono_cursor`
|
|
||||||
flips to GREEN with stdout `false` as predicted by the fixture
|
|
||||||
docstring.
|
|
||||||
- Full `cargo test --workspace` passes 564 / 564 (= 563 baseline
|
|
||||||
from `clippy-sweep` + the 1 new mini-mode RED). Zero
|
|
||||||
regressions across any of the existing show / mono / typeclass
|
|
||||||
test suites.
|
|
||||||
|
|
||||||
## Concerns
|
|
||||||
|
|
||||||
(none)
|
|
||||||
|
|
||||||
## Known debt
|
|
||||||
|
|
||||||
(none — the constraint set is a single new sibling helper plus a
|
|
||||||
new threaded argument; the two-walker symmetry remains the invariant
|
|
||||||
the existing comments already document.)
|
|
||||||
|
|
||||||
## Files touched
|
|
||||||
|
|
||||||
- `crates/ailang-check/src/mono.rs` — the fix (helper + two walker
|
|
||||||
signatures + Var-arm cursor advancement + recursive-call
|
|
||||||
threading).
|
|
||||||
- `crates/ail/tests/show_print_e2e.rs` — RED pin
|
|
||||||
`print_with_class_method_arg_does_not_misalign_mono_cursor`
|
|
||||||
(committed by `/debug` upstream of this iter; flips RED → GREEN
|
|
||||||
here).
|
|
||||||
- `examples/print_eq_arg_repro.ail` — RED fixture (committed by
|
|
||||||
`/debug` upstream; consumed by the pin).
|
|
||||||
|
|
||||||
## Stats
|
|
||||||
|
|
||||||
bench/orchestrator-stats/2026-05-14-iter-bugfix-mono-cursor-print-with-class-method-arg.json
|
|
||||||
@@ -1,85 +0,0 @@
|
|||||||
# iter bugfix-print-leak-show-ret-mode — print() leaks heap-Str under --alloc=rc; Show class method's ret_mode stripped twice
|
|
||||||
|
|
||||||
**Date:** 2026-05-14
|
|
||||||
**Started from:** 301cbc33a0b2fed18f465c6c17041211ff7da35e
|
|
||||||
**Status:** DONE
|
|
||||||
**Tasks completed:** 1 of 1
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
Two-layer bug: (i) the `Show` class method declaration in
|
|
||||||
`examples/prelude.ail.json` omitted the `ret_mode` key, so serde
|
|
||||||
defaulted it to `ParamMode::Implicit`; (ii) even with `ret_mode: own`
|
|
||||||
declared, `substitute_rigids` in `crates/ailang-check/src/lib.rs`
|
|
||||||
was hard-resetting `param_modes` to `vec![]` and `ret_mode` to
|
|
||||||
`Implicit` in its `Type::Fn` arm — silently stripping the mode
|
|
||||||
metadata during class-method mono synthesis (`show__Int`, `eq__T`,
|
|
||||||
`compare__T`, user-instance `eq__<UserType>`, etc.). Codegen's
|
|
||||||
`is_rc_heap_allocated` App-arm requires the callee's `ret_mode == Own`
|
|
||||||
to mark the let-binder trackable, so print's `let s = show x` binder
|
|
||||||
was never `ailang_rc_dec`'d at let-scope-close. The fix declares
|
|
||||||
`ret_mode: "own"` on the Show class method (parallel to the
|
|
||||||
`int_to_str`/`bool_to_str`/`float_to_str`/`str_clone`/`str_concat`
|
|
||||||
builtins which already carry `ret_mode: Own`) and repairs
|
|
||||||
`substitute_rigids`'s `Type::Fn` arm to clone `param_modes` and
|
|
||||||
preserve `ret_mode` through rigid substitution. The Form-A sibling
|
|
||||||
`examples/prelude.ail` is regenerated via `ail render` to stay in
|
|
||||||
lock-step per spec §C4 (b). Ten pinned mono-body hashes
|
|
||||||
(`eq__Int/Bool/Str`, `compare__Int/Bool/Str`, `show__Int/Bool/Str/Float`,
|
|
||||||
plus `eq__IntBox`) are re-pinned to the new GREEN baselines because
|
|
||||||
the mono bodies now carry the previously-stripped `param_modes` /
|
|
||||||
`ret_mode` metadata; bodies are semantically identical.
|
|
||||||
|
|
||||||
## Per-task notes
|
|
||||||
|
|
||||||
- bugfix.1: applied the fix.
|
|
||||||
- `examples/prelude.ail.json`: Show class method now has
|
|
||||||
`"ret_mode": "own"`.
|
|
||||||
- `examples/prelude.ail`: regenerated via
|
|
||||||
`cargo run -p ail -- render examples/prelude.ail.json
|
|
||||||
> examples/prelude.ail`; round-trips parse-isomorphic.
|
|
||||||
- `crates/ailang-check/src/lib.rs` `substitute_rigids` Type::Fn
|
|
||||||
arm: pattern-match all five fields explicitly; clone
|
|
||||||
`param_modes` and pass `*ret_mode` through (`ParamMode: Copy`).
|
|
||||||
Comment cites the bugfix iter id and the cause.
|
|
||||||
- `crates/ail/tests/mono_hash_stability.rs`: re-pinned 10 hashes
|
|
||||||
across the Eq/Ord and Show test fns; both bodies now report
|
|
||||||
`captured: {h}` on assertion failure (the Eq/Ord arm was
|
|
||||||
missing the captured-suffix; small in-place tidy folded into
|
|
||||||
the re-pin since the assertion-message string was being
|
|
||||||
rewritten anyway).
|
|
||||||
- `crates/ail/tests/eq_ord_e2e.rs`: re-pinned `eq__IntBox`
|
|
||||||
body hash to `3c4cf040cb4e8bb2`.
|
|
||||||
|
|
||||||
## Concerns
|
|
||||||
|
|
||||||
The debugger's cause analysis (carrier) pointed only at the JSON
|
|
||||||
omission. The JSON-only fix leaves the test RED — `substitute_rigids`
|
|
||||||
was silently stripping modes too. Suggest: at the next /audit pass,
|
|
||||||
verify there are no *other* call sites that build a `Type::Fn` with
|
|
||||||
explicit `..` field-spread and end up dropping modes. The pattern
|
|
||||||
`Type::Fn { ..., .. }` is the smell.
|
|
||||||
|
|
||||||
## Known debt
|
|
||||||
|
|
||||||
None — the fix is local. The wider question of "should `Type::Fn`'s
|
|
||||||
mode fields be field-init-by-name everywhere to prevent this class
|
|
||||||
of bug at the compiler level" is out of scope for this minimal fix.
|
|
||||||
|
|
||||||
## Files touched
|
|
||||||
|
|
||||||
- `examples/prelude.ail.json` — Show class: added `ret_mode: own`.
|
|
||||||
- `examples/prelude.ail` — regenerated Form-A sibling.
|
|
||||||
- `crates/ailang-check/src/lib.rs` — `substitute_rigids` Type::Fn
|
|
||||||
arm: preserve `param_modes` + `ret_mode`.
|
|
||||||
- `crates/ail/tests/mono_hash_stability.rs` — re-pinned 10 hashes
|
|
||||||
(6 Eq/Ord + 4 Show); added cause comments.
|
|
||||||
- `crates/ail/tests/eq_ord_e2e.rs` — re-pinned `eq__IntBox` hash.
|
|
||||||
- `crates/ail/tests/print_no_leak_pin.rs` (from /debug) — RED-pin
|
|
||||||
for the leak; now GREEN.
|
|
||||||
- `examples/print_int_no_leak_pin.ail` (from /debug) — fixture for
|
|
||||||
the RED-pin; now GREEN.
|
|
||||||
|
|
||||||
## Stats
|
|
||||||
|
|
||||||
bench/orchestrator-stats/2026-05-14-iter-bugfix-print-leak-show-ret-mode.json
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
# iter cli-diag-human — non-JSON CLI surfaces `WorkspaceLoadError` with bracketed `[code]`
|
|
||||||
|
|
||||||
**Date:** 2026-05-14
|
|
||||||
**Started from:** 6755060 (post-rpe.1.tidy)
|
|
||||||
**Status:** DONE
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
Closes the P2 todo "CLI human-mode diagnostic surface for
|
|
||||||
`WorkspaceLoadError`" surfaced by the ct.1.8 tester (2026-05-11).
|
|
||||||
Pre-iter, the non-JSON path of every CLI subcommand that called
|
|
||||||
`ailang_surface::load_workspace(&path)?` propagated the error via
|
|
||||||
anyhow's Display — producing `Error: <thiserror-formatted-message>`
|
|
||||||
on stderr — while the JSON path of `ail check --json` correctly
|
|
||||||
routed through `workspace_error_to_diagnostic` and emitted the
|
|
||||||
bracketed `[code]` prefix. The asymmetry made the human-mode
|
|
||||||
output harder to triage and less consistent across the toolchain.
|
|
||||||
|
|
||||||
Fix: new private helper `load_workspace_human(path) -> Result<Workspace>`
|
|
||||||
in `crates/ail/src/main.rs` that wraps `ailang_surface::load_workspace`,
|
|
||||||
threads any `WorkspaceLoadError` through the existing
|
|
||||||
`workspace_error_to_diagnostic`, formats the resulting `Diagnostic`
|
|
||||||
into a single stderr line of shape
|
|
||||||
`error: [<code>] <def>: <message>`, and exits non-zero. Pure I/O
|
|
||||||
errors (where `workspace_error_to_diagnostic` returns `None` per
|
|
||||||
the loader's own contract) still propagate as anyhow errors so the
|
|
||||||
caller's `?`-flow stays well-behaved.
|
|
||||||
|
|
||||||
Nine call sites in `main.rs` swapped from
|
|
||||||
`ailang_surface::load_workspace(<expr>)?` to
|
|
||||||
`load_workspace_human(<expr>)?`: the `ail check` (non-JSON arm),
|
|
||||||
`build`, `run`, `emit-ir`, `prose`, `describe`, `deps`, `diff`,
|
|
||||||
`manifest` paths. The JSON arm of `ail check` (line 596) kept its
|
|
||||||
explicit `match` shape — it builds a structured diagnostics array
|
|
||||||
on stdout, which is a different output contract.
|
|
||||||
|
|
||||||
The `BareCrossModuleTypeRef` arm of `workspace_error_to_diagnostic`
|
|
||||||
gained the existing migration-command hint
|
|
||||||
("Run `ail migrate-canonical-types` to fix legacy fixtures.") so
|
|
||||||
that the ct.1.8 actionable-hint test
|
|
||||||
(`check_human_mode_emits_actionable_message_to_stderr`) stays
|
|
||||||
green. The hint was previously carried by `WorkspaceLoadError`'s
|
|
||||||
thiserror Display impl; making `workspace_error_to_diagnostic` the
|
|
||||||
single source for both JSON and human paths required moving it
|
|
||||||
into the Diagnostic message body.
|
|
||||||
|
|
||||||
## Files touched
|
|
||||||
|
|
||||||
- `crates/ail/src/main.rs` — new `load_workspace_human` helper
|
|
||||||
(~25 lines, including doc comment); 9 call-site swaps; one
|
|
||||||
Diagnostic message gained the migration-hint suffix.
|
|
||||||
- `crates/ail/tests/cli_diag_human_workspace_load_error.rs` —
|
|
||||||
new RED-pin test (tempdir-based fixture asserting
|
|
||||||
`[module-not-found]` in stderr on a missing-import workspace).
|
|
||||||
- `docs/roadmap.md` — entry struck through.
|
|
||||||
|
|
||||||
## Verification
|
|
||||||
|
|
||||||
- RED test `check_non_json_emits_bracketed_module_not_found`
|
|
||||||
flipped from RED to GREEN.
|
|
||||||
- `cargo test --workspace` → 565 / 0 / 3 (was 564 pre-iter; +1
|
|
||||||
from this iter's RED pin).
|
|
||||||
- `cargo clippy --workspace --all-targets` → 0 warnings.
|
|
||||||
- `cargo doc --workspace --no-deps` → 0 warnings.
|
|
||||||
|
|
||||||
## Concerns
|
|
||||||
|
|
||||||
- (none)
|
|
||||||
|
|
||||||
## Known debt
|
|
||||||
|
|
||||||
- (none)
|
|
||||||
@@ -1,134 +0,0 @@
|
|||||||
# iter clippy-sweep — clear all 61 `cargo clippy` warnings
|
|
||||||
|
|
||||||
**Date:** 2026-05-14
|
|
||||||
**Started from:** 04258c5 (post-WhatsNew of rustdoc-sweep + drift-test-narrowing)
|
|
||||||
**Status:** DONE
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
Hygiene sweep against `cargo clippy --workspace --all-targets`. Before:
|
|
||||||
61 warnings across all eight crates. After: zero. Tests stay 563 green;
|
|
||||||
`cargo doc --workspace --no-deps` stays at zero warnings (the
|
|
||||||
rustdoc-sweep baseline); all three bench scripts (`bench/check.py`,
|
|
||||||
`bench/compile_check.py`, `bench/cross_lang.py`) all exit 0 against
|
|
||||||
the existing baselines — a free Stabilitätsbeweis that the sweep
|
|
||||||
touched no semantics.
|
|
||||||
|
|
||||||
Twelve warning classes, fixed with three different shapes:
|
|
||||||
|
|
||||||
- **Pure documentation fixes** (~32 warnings). `doc_lazy_continuation`
|
|
||||||
on multi-line `///` and `//!` comments where clippy parsed the
|
|
||||||
second line as a list continuation; resolved by either adding a
|
|
||||||
blank line, escaping the marker, or rephrasing the offending line.
|
|
||||||
Affected: `lex.rs:235-243` (3-line bullet list), `lib.rs:2360-2368`
|
|
||||||
in `ailang-check` (3-line bullet list), `lib.rs:3736` in
|
|
||||||
`ailang-check` (a `> 1` numeric comparison parsed as a quote
|
|
||||||
marker; rephrased to "any cardinality above one"), `drop.rs:570-585`
|
|
||||||
(numbered list + paragraph mix), `e2e.rs:915-920` (a `+ three
|
|
||||||
primitive instances` line where `+` was parsed as bullet marker),
|
|
||||||
`e2e.rs:1607-1614`, `typeclass_22b3.rs:706-708`, `eq_ord_e2e.rs:13`.
|
|
||||||
Plus four `empty_line_after_doc_comments` hits in
|
|
||||||
`workspace.rs:1675-2506` — these were `///` doc-comments left
|
|
||||||
orphaned by form-a.1 Task 5 test relocation; converted to plain
|
|
||||||
`//` comments (eight blocks total) since they no longer document
|
|
||||||
any function.
|
|
||||||
|
|
||||||
- **Idiomatic refactors** (8 warnings). `err_expect` (5 hits in
|
|
||||||
`parse.rs`): `.err().expect("…")` → `.expect_err("…")`.
|
|
||||||
`useless_conversion` (3 hits): `.extend(x.into_iter())` → `.extend(x)`
|
|
||||||
in `desugar.rs:275` + `lift.rs:201`; `vec!["x".into()]` →
|
|
||||||
`vec!["x"]` in `lib.rs:5522` (where the call-site `fn_def`
|
|
||||||
parameter is `Vec<&str>` so the conversion was identity).
|
|
||||||
`redundant_closure` (2 hits): `|f| collect_pattern_binders(f)` →
|
|
||||||
`collect_pattern_binders` in `linearity.rs:717`; same pattern in
|
|
||||||
`loader.rs:108`. `trim_split_whitespace`: `.trim().split_whitespace()`
|
|
||||||
→ `.split_whitespace()` in `prose/src/lib.rs:2103` (since
|
|
||||||
`split_whitespace` already ignores leading/trailing whitespace).
|
|
||||||
`single_char_add_str`: `out.push_str("x")` → `out.push('x')` in
|
|
||||||
`prose/src/lib.rs:383`. `collapsible_match` (2 hits): nested
|
|
||||||
`if let Some(_) = …` collapsed to `if let Some(Some(_)) = …` in
|
|
||||||
`mono.rs:1028` + `drop.rs:535-536`.
|
|
||||||
|
|
||||||
- **`derivable_impls`** (2 hits). `impl Default for ParamMode {
|
|
||||||
fn default() -> Self { ParamMode::Implicit } }` in `ast.rs:731`
|
|
||||||
→ `#[derive(Default)]` + `#[default] Implicit`. Same shape for
|
|
||||||
`AllocStrategy` in `codegen/src/lib.rs:164`. The derive form is
|
|
||||||
strictly more explicit (the `#[default]` attribute marks which
|
|
||||||
variant in-source rather than the impl body referencing it by
|
|
||||||
name) so the change is also a small clarity gain.
|
|
||||||
|
|
||||||
- **`blocks_in_conditions`** (1 hit). The synth `Term::Var` arm in
|
|
||||||
`check/src/lib.rs` had a 13-line block inside an `else if`
|
|
||||||
condition (the mq.2 dispatch entry: `else if { let (m, q) =
|
|
||||||
parse_method_qualifier(name); qualifier_is_class_shape(&q) &&
|
|
||||||
env.method_to_candidate_classes.contains_key(m) }`). Extracted
|
|
||||||
to a new helper `fn is_class_method_dispatch(name, env) -> bool`
|
|
||||||
next to `qualifier_is_class_shape`. The helper inherits the
|
|
||||||
block's full doc-comment so the why-each-branch reasoning is
|
|
||||||
preserved at the call site.
|
|
||||||
|
|
||||||
- **`only_used_in_recursion`** (1 hit). `walk_pattern(p, f)` in
|
|
||||||
`workspace.rs:1399` carries an `f: &mut F` parameter that
|
|
||||||
Pattern walking never invokes (ct.1 left this seam unused: no
|
|
||||||
Pattern variant carries a cross-module reference). Rather than
|
|
||||||
delete `f` (which would break the symmetry with the five other
|
|
||||||
`walk_*` framework functions in this file that all share the
|
|
||||||
same `<F>(_, f: &mut F) -> Result<…>` signature), added
|
|
||||||
`#[allow(clippy::only_used_in_recursion)]` with an inline
|
|
||||||
comment naming the framework-uniformity rationale.
|
|
||||||
|
|
||||||
- **`if_same_then_else`** (2 hits). Both in `qualify_local_types`
|
|
||||||
shapes (`check/src/lib.rs:3457` + `codegen/src/subst.rs:165`):
|
|
||||||
the if-chain has two branches returning `name.clone()` for
|
|
||||||
semantically distinct reasons (`name.contains('.')` = already
|
|
||||||
qualified; `is_primitive_name(name)` = primitive needs no
|
|
||||||
qualification). Combining the branches with `||` would obscure
|
|
||||||
the reasoning. Added `#[allow(clippy::if_same_then_else)]` with
|
|
||||||
an inline comment naming why each branch disqualifies the name.
|
|
||||||
|
|
||||||
- **`too_many_arguments`** (1 hit). `Emitter::new` takes 8 of the
|
|
||||||
workspace-flat tables the emitter needs at construction time.
|
|
||||||
Bundling them into a context struct would just rename the
|
|
||||||
boilerplate without reducing it. Added
|
|
||||||
`#[allow(clippy::too_many_arguments)]` with the rationale
|
|
||||||
inline.
|
|
||||||
|
|
||||||
## Files touched
|
|
||||||
|
|
||||||
- `crates/ailang-core/src/ast.rs` (1 derive flip)
|
|
||||||
- `crates/ailang-core/src/desugar.rs` (1 .extend)
|
|
||||||
- `crates/ailang-core/src/workspace.rs` (1 walk_pattern allow + 8 `///`→`//` blocks)
|
|
||||||
- `crates/ailang-surface/src/lex.rs` (1 doc-list blank line)
|
|
||||||
- `crates/ailang-surface/src/parse.rs` (5 `.expect_err`)
|
|
||||||
- `crates/ailang-surface/tests/loader.rs` (1 redundant closure)
|
|
||||||
- `crates/ailang-prose/src/lib.rs` (push_str + trim removal)
|
|
||||||
- `crates/ailang-check/src/lib.rs` (helper extraction + 1 allow + 1 doc rephrase + 1 doc-list blank line + 1 vec literal)
|
|
||||||
- `crates/ailang-check/src/lift.rs` (1 .extend)
|
|
||||||
- `crates/ailang-check/src/linearity.rs` (1 redundant closure)
|
|
||||||
- `crates/ailang-codegen/src/lib.rs` (1 derive flip + 1 too_many_args allow)
|
|
||||||
- `crates/ailang-codegen/src/subst.rs` (1 if_same_then_else allow)
|
|
||||||
- `crates/ailang-codegen/src/drop.rs` (1 doc-list blank line + 1 collapsible_match)
|
|
||||||
- `crates/ailang-check/src/mono.rs` (1 collapsible_match)
|
|
||||||
- `crates/ail/tests/e2e.rs` (2 doc rephrasings)
|
|
||||||
- `crates/ail/tests/typeclass_22b3.rs` (1 doc rephrasing)
|
|
||||||
- `crates/ail/tests/eq_ord_e2e.rs` (1 doc rephrasing)
|
|
||||||
|
|
||||||
## Verification
|
|
||||||
|
|
||||||
- `cargo clippy --workspace --all-targets 2>&1 | grep -c '^warning:'`
|
|
||||||
→ `0` (was 61).
|
|
||||||
- `cargo test --workspace` → 563 passed + 3 ignored, 0 failed
|
|
||||||
(baseline holds).
|
|
||||||
- `cargo doc --workspace --no-deps 2>&1 | grep -c '^warning:'` →
|
|
||||||
`0` (rustdoc-sweep baseline holds — no regression).
|
|
||||||
- `bench/check.py` → exit 0.
|
|
||||||
- `bench/compile_check.py` → exit 0.
|
|
||||||
- `bench/cross_lang.py` → exit 0.
|
|
||||||
|
|
||||||
## Concerns
|
|
||||||
|
|
||||||
- (none)
|
|
||||||
|
|
||||||
## Known debt
|
|
||||||
|
|
||||||
- (none)
|
|
||||||
@@ -1,120 +0,0 @@
|
|||||||
# iter pd.1 — core API split (load_modules_with + build_workspace) — surface unchanged via shim
|
|
||||||
|
|
||||||
**Date:** 2026-05-14
|
|
||||||
**Started from:** 61167a4ef0ade5dd65dadfbdde1a44b65c6693ca
|
|
||||||
**Status:** DONE
|
|
||||||
**Tasks completed:** 6 of 6
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
pd.1 carved out two new public fns in `crates/ailang-core/src/workspace.rs`
|
|
||||||
(`load_modules_with` for DFS-only loading, `build_workspace` for validation +
|
|
||||||
registry construction with a caller-supplied `implicit_imports: &[&str]`),
|
|
||||||
threaded the new parameter through `validate_canonical_type_names` /
|
|
||||||
`check_class_ref` / `check_type_con_name`, and replaced the four
|
|
||||||
hardcoded literal-`"prelude"` fallback blocks in the diagnostic helpers
|
|
||||||
with a parameterised iter-loop over `implicit_imports`. The surviving
|
|
||||||
`load_workspace_with` collapses from a ~70-line inline DFS+inject+validate
|
|
||||||
block to a 3-line composition (`load_modules_with` → caller-side prelude
|
|
||||||
inject → `build_workspace`). The legacy JSON-only wrapper `pub fn
|
|
||||||
load_workspace` retired with zero production callers (only doc-comment
|
|
||||||
references and 9 in-mod test callers; the latter migrated to
|
|
||||||
`load_workspace_with(&entry, load_one)`). Surface (`ailang-surface`) and
|
|
||||||
all downstream crates compile + test unchanged because the shim
|
|
||||||
preserves today's `load_workspace_with` semantics; the regression-pin
|
|
||||||
test asserts the prelude module hash matches `load_prelude()` post-refit.
|
|
||||||
Production-code literal-`"prelude"` count in workspace.rs dropped from 8
|
|
||||||
to exactly the 4 expected sites (all in the shim, none in the
|
|
||||||
diagnostic helpers). pd.2 will move the prelude-embed + the inject step
|
|
||||||
into `ailang-surface` and retire the shim; pd.3 will delete
|
|
||||||
`examples/prelude.ail.json`.
|
|
||||||
|
|
||||||
## Per-task notes
|
|
||||||
|
|
||||||
- iter pd.1.1: added `pub fn load_modules_with` above `load_workspace_with`
|
|
||||||
(extracted DFS pre-amble verbatim from the old inline body); RED test
|
|
||||||
`load_modules_with_returns_modules_without_prelude` proves the new fn
|
|
||||||
returns modules without auto-injecting prelude. First-shot GREEN.
|
|
||||||
- iter pd.1.2: added `pub fn build_workspace` next to `load_modules_with`
|
|
||||||
(validation + registry construction; takes pre-assembled modules-map
|
|
||||||
and `implicit_imports: &[&str]`). RED test
|
|
||||||
`build_workspace_accepts_assembled_modules_and_runs_validation` ends
|
|
||||||
the task RED-on-purpose per the plan (the inner call to
|
|
||||||
`validate_canonical_type_names` doesn't have the parameter yet);
|
|
||||||
Task 3 lands the green.
|
|
||||||
- iter pd.1.3: added `implicit_imports: &[&str]` parameter to
|
|
||||||
`validate_canonical_type_names`, `check_class_ref`,
|
|
||||||
`check_type_con_name`; threaded it through 6 internal call sites in
|
|
||||||
`validate_canonical_type_names` (2 in the type-walk + 4 in the
|
|
||||||
class-ref match-arms); rewrote the two literal-`"prelude"` fallback
|
|
||||||
blocks in the diagnostic helpers as iter-loops over `implicit_imports`.
|
|
||||||
RED test `implicit_imports_arg_drives_prelude_fallback_in_diagnostics`
|
|
||||||
exercises both branches (with-prelude includes `prelude.Eq` candidate;
|
|
||||||
without-prelude excludes it). Migrated 18 in-mod test callers + 1
|
|
||||||
production callsite (the still-inline shim body) to
|
|
||||||
`&["prelude"]`. Task 2's test now passes. New helpers
|
|
||||||
`module_with_bare_classref` and `module_with_class_def` use the
|
|
||||||
existing `serde_json::from_value(json!(...))` pattern (the rest of
|
|
||||||
mod tests' convention) rather than the plan's struct-literal style
|
|
||||||
(which would have failed against actual `ClassDef.superclass:
|
|
||||||
Option<...>` field shape).
|
|
||||||
- iter pd.1.4: replaced `load_workspace_with`'s ~70-line body with the
|
|
||||||
3-line shim composition; doc-comment rewritten to describe the shim
|
|
||||||
role + retain the ext-cli.1 cycle-avoidance rationale on the
|
|
||||||
loader-injection point. Pin test
|
|
||||||
`load_workspace_with_shim_preserves_today_semantics` asserts
|
|
||||||
prelude-module hash equality with `load_prelude()` post-refit.
|
|
||||||
- iter pd.1.5: deleted `pub fn load_workspace`; removed it from
|
|
||||||
`lib.rs` re-export; rewrote the workspace.rs module rustdoc example
|
|
||||||
+ 3 lib.rs `//!` references to point at `load_workspace_with`;
|
|
||||||
hoisted the orphaned "Algorithm: DFS over imports" doc-comment block
|
|
||||||
onto `load_modules_with` (where it now belongs). Migrated 9 in-mod
|
|
||||||
test callers from `load_workspace(&entry)` to
|
|
||||||
`load_workspace_with(&entry, load_one)`. Required `#[cfg(test)]`
|
|
||||||
gating of `fn load_one` and the `load_module` import (now consumed
|
|
||||||
only by the test mod) to silence dead-code warnings.
|
|
||||||
- iter pd.1.6: verification — `cargo test --workspace` all green
|
|
||||||
(every crate's tests pass); `bench/check.py` + `bench/compile_check.py`
|
|
||||||
+ `bench/cross_lang.py` all exit 0; production literal-`"prelude"`
|
|
||||||
count in workspace.rs is exactly 4 (the four expected shim sites at
|
|
||||||
lines 553/555/558/560 — all in `load_workspace_with` body — none in
|
|
||||||
the diagnostic helpers).
|
|
||||||
|
|
||||||
## Concerns
|
|
||||||
|
|
||||||
- pd.1.2 ends RED-on-purpose per the plan. This is intentional and
|
|
||||||
documented in the plan, but if a future iter ever bisects through
|
|
||||||
this plan the workspace WILL NOT compile at the Task 2 boundary —
|
|
||||||
Task 3 must land in the same Boss commit. The Boss-side commit
|
|
||||||
shape for pd.1 is therefore "one cohesive iter commit" not "per-task
|
|
||||||
commits".
|
|
||||||
- pd.1.5 needed an unanticipated `#[cfg(test)]` gating step on the
|
|
||||||
`load_one` fn + the `load_module` import. The plan didn't anticipate
|
|
||||||
this because it framed the `load_workspace` retirement as "doc + lib
|
|
||||||
cleanup only", but `load_one` was the only consumer of those imports
|
|
||||||
in production. Recording so a future planner adds an "if removing a
|
|
||||||
caller, check whether its callees become test-only" check.
|
|
||||||
|
|
||||||
## Known debt
|
|
||||||
|
|
||||||
- pd.2 will move prelude-embed (`PRELUDE_JSON` const + `load_prelude` fn)
|
|
||||||
into `ailang-surface` and rewire surface's `load_workspace` to call
|
|
||||||
`load_modules_with` + `build_workspace` directly, retiring the shim.
|
|
||||||
Out of scope for pd.1 by spec.
|
|
||||||
- pd.3 will delete `examples/prelude.ail.json`. Out of scope for pd.1.
|
|
||||||
- The latency.implicit_at_rc improvement cluster appearing in
|
|
||||||
`bench/check.py` (4 metrics regressed, 2 improved) reappears for
|
|
||||||
the 4th audit in a row; cause not localised. pd.1 is a workspace.rs
|
|
||||||
refactor with zero codegen / runtime touch, so cannot be the source.
|
|
||||||
Baseline left pristine (consistent with audit-eob and earlier
|
|
||||||
decisions).
|
|
||||||
|
|
||||||
## Files touched
|
|
||||||
|
|
||||||
- `crates/ailang-core/src/workspace.rs` (extraction + threading +
|
|
||||||
shim refit + dead-fn delete + test-mod migration)
|
|
||||||
- `crates/ailang-core/src/lib.rs` (re-export drop + 3 rustdoc updates)
|
|
||||||
|
|
||||||
## Stats
|
|
||||||
|
|
||||||
`bench/orchestrator-stats/2026-05-14-iter-pd.1.json`
|
|
||||||
@@ -1,176 +0,0 @@
|
|||||||
# iter pd.2 — surface owns prelude embed; pd.1 shim retired
|
|
||||||
|
|
||||||
**Date:** 2026-05-14
|
|
||||||
**Started from:** 116157a2b84a98060c1b6c04eb4ec071fe40eb93
|
|
||||||
**Status:** DONE
|
|
||||||
**Tasks completed:** 6 of 6
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
pd.2 moves the prelude embed (`PRELUDE_AIL` const + `parse_prelude` fn) and
|
|
||||||
the prelude-injection step (with the `ReservedModuleName` reservation) out
|
|
||||||
of `ailang-core` into `ailang-surface`, rewires `surface::load_workspace`
|
|
||||||
from the 3-line shim-call into the 7-line composition `(load_modules_with
|
|
||||||
→ reserved-name check → inject parse_prelude → build_workspace(&["prelude"]))`,
|
|
||||||
and retires the pd.1 compatibility shim `core::load_workspace_with` along
|
|
||||||
with `load_one`, `load_prelude`, `PRELUDE_JSON`, and the `#[cfg(test)] use
|
|
||||||
crate::load_module;` import. Cross-form-identity preflight (hash-equality
|
|
||||||
between `parse(prelude.ail)` and `serde_json::from_str(prelude.ail.json)`)
|
|
||||||
PASSES at hash `3abe0d3fa3c11c99` — the load-bearing assumption that
|
|
||||||
justifies pd.3's deletion of `prelude.ail.json` is verified. Production
|
|
||||||
literal-`"prelude"` count in `core/src/` is now zero. Test count: 573
|
|
||||||
green (from the pd.1 baseline of 564, +9 net = 3 new surface pins
|
|
||||||
[parse + hash + injection] + 6 tempdir/fixture relocations to
|
|
||||||
workspace_pin.rs minus 6 in-mod placeholders that became comments;
|
|
||||||
in-mod test deletion of the pd.1 shim test is offset by the
|
|
||||||
`load_modules_with_custom_loader_is_called` rename + 2 pd.1 tests that
|
|
||||||
now use an inline test-private `load_one` helper). `bench/cross_lang.py`
|
|
||||||
+ `bench/compile_check.py` exit 0; `bench/check.py` exit 1 with the
|
|
||||||
12-audit noise envelope (the `bench_list_sum.bump_s` + tail-latency
|
|
||||||
oscillation that has been observed in audits since audit-cma; pd.2 is
|
|
||||||
a workspace-loader refactor with zero codegen / runtime touch). pd.3
|
|
||||||
will delete `examples/prelude.ail.json` plus the
|
|
||||||
`prelude_ail_and_json_parse_to_identical_module` preflight, the
|
|
||||||
`include_str!` carve-out in `crates/ail/src/main.rs:472-484`, the
|
|
||||||
`carve_out_inventory.rs` §C4(b) row, the form-a §C4(b) spec text, and
|
|
||||||
the `migrate-bare-cross-module-refs` defensive include.
|
|
||||||
|
|
||||||
## Per-task notes
|
|
||||||
|
|
||||||
- iter pd.2.1: `pub const PRELUDE_AIL: &str = include_str!("../../../examples/prelude.ail");`
|
|
||||||
+ `pub fn parse_prelude() -> Module` added near the top of
|
|
||||||
`crates/ailang-surface/src/loader.rs`; both re-exported from
|
|
||||||
`ailang-surface/src/lib.rs`. RED test
|
|
||||||
`parse_prelude_returns_module_named_prelude_with_min_defs` failed
|
|
||||||
with the expected unresolved-import message, then GREEN. First-shot
|
|
||||||
clean.
|
|
||||||
- iter pd.2.2: `crates/ailang-surface/tests/prelude_module_hash_pin.rs`
|
|
||||||
with `prelude_ail_and_json_parse_to_identical_module` (cross-form-
|
|
||||||
identity preflight, load-bearing for pd.3) + `prelude_parse_yields_canonical_hash`
|
|
||||||
(literal-hex anchor). Preflight PASSED first-run (the round-trip
|
|
||||||
invariant from form-a.0 + form-a.1 was already exercising
|
|
||||||
parse-then-print idempotency on every `.ail` fixture, so
|
|
||||||
parse(prelude.ail) ≡ deserialize(prelude.ail.json) was not an
|
|
||||||
accident). Hash `3abe0d3fa3c11c99` captured at pd.2 close.
|
|
||||||
- iter pd.2.3: `surface::load_workspace` rewritten 3-line → 7-line
|
|
||||||
composition; imports updated `load_workspace_with` → bare
|
|
||||||
`Workspace, WorkspaceLoadError` with `load_modules_with` /
|
|
||||||
`build_workspace` fully-qualified inline; module rustdoc rewritten
|
|
||||||
to name the new injection point + the implicit-imports list.
|
|
||||||
Regression-pin tests `surface_load_workspace_injects_prelude_into_every_workspace`
|
|
||||||
+ `surface_load_workspace_rejects_user_module_named_prelude` PASS
|
|
||||||
before AND after the rewrite (semantics preserved). DONE_WITH_CONCERNS:
|
|
||||||
the plan's example used `examples/hello.ail.json` which doesn't
|
|
||||||
exist post-form-a.1 (deleted in T8); switched to `examples/hello.ail`.
|
|
||||||
- iter pd.2.4: 10 in-mod tests RELOCATED to
|
|
||||||
`crates/ailang-core/tests/workspace_pin.rs` rather than aliased per
|
|
||||||
the plan's literal text — the dev-dep cycle (`ailang-core` ↔
|
|
||||||
`ailang-surface` via `[dev-dependencies]`) makes
|
|
||||||
`surface::load_workspace` calls from `ailang-core`'s lib-test crate
|
|
||||||
structurally impossible (two distinct compilations of `ailang-core`
|
|
||||||
appear in the test build, types don't match). The integration-test
|
|
||||||
crate consumes both crates against the same `ailang-core` artefact,
|
|
||||||
so the cycle never materialises — same pattern form-a.1 T5 used for
|
|
||||||
similar relocations. The 11th test (`load_workspace_with_custom_loader_is_called`)
|
|
||||||
renamed to `load_modules_with_custom_loader_is_called` with the
|
|
||||||
closure adapted to wrap `crate::load_module` directly via the same
|
|
||||||
Io/Schema mapping `load_one` had. The 12th test
|
|
||||||
(`load_workspace_with_shim_preserves_today_semantics`) deleted
|
|
||||||
outright with a forwarding comment to the surface-side hash pin.
|
|
||||||
- iter pd.2.5: 5 deletions (`PRELUDE_JSON`, `load_prelude`,
|
|
||||||
`load_workspace_with`, top-level `load_one`, `#[cfg(test)] use
|
|
||||||
crate::load_module;`); module rustdoc reworked to two-phase
|
|
||||||
composition framing; lib.rs three rustdoc sites (lines 16-21, 50-51,
|
|
||||||
120) re-pointed at `ailang_surface::load_workspace`. ONE plan-vs-actual
|
|
||||||
deviation: the plan didn't enumerate the 2 pd.1-introduced in-mod
|
|
||||||
tests at the bottom of `mod tests` that consume `load_one` directly
|
|
||||||
(`load_modules_with_returns_modules_without_prelude`,
|
|
||||||
`build_workspace_accepts_assembled_modules_and_runs_validation`);
|
|
||||||
preserved by adding a test-mod-private `load_one` helper inside `mod
|
|
||||||
tests` (production-symbol-level deletion preserved; helper is
|
|
||||||
scoped only to the test module, not exported). The
|
|
||||||
`build_workspace...` test also called `load_prelude()` directly;
|
|
||||||
replaced inline with `serde_json::from_str(include_str!(...))` of
|
|
||||||
`prelude.ail.json` (the same one-line code `load_prelude` was). Zero
|
|
||||||
literal-`"prelude"` strings in production `core/src/` confirmed by
|
|
||||||
grep. cargo doc warnings: 3 (unchanged from pre-pd.2 baseline; all
|
|
||||||
three are dangling `[`load_workspace`]` references in the `Workspace`
|
|
||||||
/ `Registry` rustdoc that pd.1 left behind when it deleted
|
|
||||||
`load_workspace`; out of scope per the implementer's
|
|
||||||
no-surrounding-cleanup discipline, recorded as known debt).
|
|
||||||
- iter pd.2.6: bench verification — `cross_lang.py` exit 0 (25/25
|
|
||||||
stable), `compile_check.py` exit 0 (24/24, "11 regressed" reported
|
|
||||||
but exit code says baseline-tolerance check passes — this is the
|
|
||||||
audit-form-a + clippy-sweep-named noise on the corpus's small build
|
|
||||||
fixtures), `check.py` exit 1 with 1 regression on `bench_list_sum.bump_s`
|
|
||||||
+13.61% (within established noise envelope; second run showed
|
|
||||||
same `bump_s` plus a tail-latency `implicit_at_rc.p99_us` blip).
|
|
||||||
All three are runtime/compile metrics that cannot be touched by a
|
|
||||||
workspace.rs/loader.rs/lib.rs refactor (no codegen / no runtime /
|
|
||||||
no typecheck path edited).
|
|
||||||
|
|
||||||
## Concerns
|
|
||||||
|
|
||||||
- pd.2.3: plan's regression-pin used `examples/hello.ail.json` which
|
|
||||||
doesn't exist post-form-a.1 T8. Switched to `examples/hello.ail`.
|
|
||||||
Recommend a planner check: when a plan references a `.ail.json`
|
|
||||||
fixture, verify the fixture exists post-form-a.1 (the carve-out
|
|
||||||
inventory is the right reference).
|
|
||||||
- pd.2.4: plan's "alias to surface_load_workspace and stay in-mod" was
|
|
||||||
structurally invalid because of the `ailang-core` ↔ `ailang-surface`
|
|
||||||
dev-dep cycle inside the lib-test target. Relocated to
|
|
||||||
`tests/workspace_pin.rs` instead — same pattern form-a.1 T5 used.
|
|
||||||
Recommend: planner recon-check should detect when a planned in-mod
|
|
||||||
test is meant to consume an external dev-dep cycle that the lib-test
|
|
||||||
target can't resolve.
|
|
||||||
- pd.2.5: plan's "delete `load_one`" preserved at production-symbol
|
|
||||||
level (the top-level `#[cfg(test)] fn load_one` is gone) by
|
|
||||||
introducing a test-mod-private `fn load_one` helper inside `mod
|
|
||||||
tests` for the 2 pd.1 in-mod tests that still consume it. The
|
|
||||||
symbol's name is preserved for clarity; its scope is now narrower
|
|
||||||
than pre-pd.2.
|
|
||||||
- pd.2.6: `check.py` exit 1 — established noise envelope, 13th
|
|
||||||
consecutive observation pattern (audit-cma onwards). pd.2 is a
|
|
||||||
workspace-loader refactor with zero codegen / runtime / typecheck
|
|
||||||
touch, so cannot be the source. Baseline left pristine consistent
|
|
||||||
with all prior audit decisions on this envelope.
|
|
||||||
|
|
||||||
## Known debt
|
|
||||||
|
|
||||||
- 3 cargo doc warnings on `[`load_workspace`]` references in the
|
|
||||||
`Workspace` (line 44, 49) + `Registry` (line 64) rustdoc — pd.1
|
|
||||||
deleted `load_workspace` but didn't sweep these references. Pre-
|
|
||||||
pd.2 baseline already carried them; pd.2 didn't add to the count;
|
|
||||||
not within pd.2 scope (no-surrounding-cleanup discipline).
|
|
||||||
- pd.3 work explicitly out of scope: deleting `examples/prelude.ail.json`,
|
|
||||||
the `prelude_ail_and_json_parse_to_identical_module` preflight test
|
|
||||||
(which depends on the JSON file's existence), the `include_str!` of
|
|
||||||
`prelude.ail.json` in `crates/ail/src/main.rs:472-484`, the
|
|
||||||
`carve_out_inventory.rs` §C4(b) row, the form-a §C4(b) spec text
|
|
||||||
edits, and the `migrate-bare-cross-module-refs` defensive include.
|
|
||||||
|
|
||||||
## Files touched
|
|
||||||
|
|
||||||
Modified:
|
|
||||||
- `crates/ailang-core/src/lib.rs` (3 rustdoc sites updated)
|
|
||||||
- `crates/ailang-core/src/workspace.rs` (5 deletions + module rustdoc
|
|
||||||
reframe + 2 pd.1 in-mod test repairs + 6 in-mod test placeholders +
|
|
||||||
test-mod-private `load_one` helper restored at narrow scope)
|
|
||||||
- `crates/ailang-core/tests/workspace_pin.rs` (10 relocated tests
|
|
||||||
appended + helper fn `write_simple_module_json` + preamble extension)
|
|
||||||
- `crates/ailang-surface/src/lib.rs` (re-export of `parse_prelude` +
|
|
||||||
`PRELUDE_AIL`)
|
|
||||||
- `crates/ailang-surface/src/loader.rs` (`PRELUDE_AIL` const +
|
|
||||||
`parse_prelude` fn added; `load_workspace` body 3-line → 7-line
|
|
||||||
composition rewrite; module rustdoc reframed; imports updated)
|
|
||||||
|
|
||||||
New:
|
|
||||||
- `crates/ailang-surface/tests/prelude_parse_pin.rs` (1 test)
|
|
||||||
- `crates/ailang-surface/tests/prelude_module_hash_pin.rs` (2 tests:
|
|
||||||
cross-form-identity preflight + literal-hex anchor `3abe0d3fa3c11c99`)
|
|
||||||
- `crates/ailang-surface/tests/prelude_injection_pin.rs` (2 tests:
|
|
||||||
inject + reservation pin)
|
|
||||||
|
|
||||||
## Stats
|
|
||||||
|
|
||||||
`bench/orchestrator-stats/2026-05-14-iter-pd.2.json`
|
|
||||||
@@ -1,209 +0,0 @@
|
|||||||
# iter pd.3 — prelude.ail.json deleted; prelude-decouple milestone closed
|
|
||||||
|
|
||||||
**Date:** 2026-05-14
|
|
||||||
**Started from:** c0dd2bc334d490e5e90ad10501c33e48076ff2bd
|
|
||||||
**Status:** DONE
|
|
||||||
**Tasks completed:** 8 of 8
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
pd.3 closes the **prelude-decouple milestone** (pd.1 + pd.2 + pd.3) by
|
|
||||||
deleting `examples/prelude.ail.json` from the working tree, sweeping the
|
|
||||||
three remaining `include_str!` consumers (one in-mod test in
|
|
||||||
`workspace.rs`, the cross-form-identity preflight in
|
|
||||||
`prelude_module_hash_pin.rs`, the defensive include in `ail/src/main.rs`'s
|
|
||||||
`migrate-canonical-types` subcommand) and the lockstep skip-branch the
|
|
||||||
defensive include necessitated, dropping the §C4 (b) entry from
|
|
||||||
`carve_out_inventory.rs` (8 → 7 named carve-outs), retiring §C4 (b) of
|
|
||||||
`docs/specs/2026-05-13-form-a-default-authoring.md` in place with a
|
|
||||||
"Status: RETIRED 2026-05-14" marker that preserves the original framing
|
|
||||||
as historical context, and adding `prelude_decouple_carve_out_pin.rs`
|
|
||||||
as the milestone's permanent guard against regression. At milestone
|
|
||||||
close: the prelude exists on disk only as `examples/prelude.ail`; core
|
|
||||||
embeds zero prelude bytes; the `migrate-canonical-types` subcommand no
|
|
||||||
longer auto-injects a synthetic prelude entry; the doctrine "authors
|
|
||||||
write `.ail`; the build derives the JSON-AST in-process via
|
|
||||||
`ailang_surface::parse`" (CLAUDE.md) holds without exception. Test count:
|
|
||||||
573 green workspace-wide (preserved from pd.2; +1 carve-out pin -1
|
|
||||||
cross-form-identity preflight +1 relocated `build_workspace_accepts...`
|
|
||||||
test -1 in-mod twin = net 0). `bench/cross_lang.py` + `bench/compile_check.py`
|
|
||||||
exit 0; `bench/check.py` exit 1 with the same envelope-noise pattern
|
|
||||||
observed since audit-cma (14th consecutive observation; pd.3 is a
|
|
||||||
file-system + spec-text + test deletion + dead-code cleanup with zero
|
|
||||||
codegen / runtime / typecheck touch, so cannot be the source).
|
|
||||||
|
|
||||||
## Per-task notes
|
|
||||||
|
|
||||||
- iter pd.3.1: `build_workspace_accepts_assembled_modules_and_runs_validation`
|
|
||||||
RELOCATED from `crates/ailang-core/src/workspace.rs::tests` (in-mod) to
|
|
||||||
`crates/ailang-core/tests/workspace_pin.rs` (integration crate) instead
|
|
||||||
of the literal call-site swap the plan prescribed. The plan specified
|
|
||||||
"use the fully-qualified `ailang_surface::parse_prelude()` at the call
|
|
||||||
site" claiming it was the workaround pd.2 used; in fact pd.2 worked
|
|
||||||
around this same dev-dep cycle by inlining
|
|
||||||
`serde_json::from_str(include_str!("prelude.ail.json"))` precisely
|
|
||||||
BECAUSE `parse_prelude()` doesn't typecheck from in-mod (the lib-test
|
|
||||||
target sees two distinct `ailang-core` compilations and the `Module`
|
|
||||||
types fail to unify). pd.3 cannot use the JSON workaround because the
|
|
||||||
JSON file is being deleted. The structural fix is the relocation
|
|
||||||
pattern pd.2.4 already used for 10 sibling tests — same crate edge,
|
|
||||||
same outcome. A 6-line stub-comment in `workspace.rs::tests` documents
|
|
||||||
the dev-dep cycle so future maintainers don't try to put the test
|
|
||||||
back. New test `load_one_adapter` helper at the top of `workspace_pin.rs`
|
|
||||||
mirrors the in-mod `load_one` exactly (Io/Schema mapping). DONE_WITH_CONCERNS:
|
|
||||||
plan defect (treated under pd.2.4 lesson recommendation but not
|
|
||||||
caught at planning time).
|
|
||||||
- iter pd.3.2: 5 deletions in `prelude_module_hash_pin.rs` (cross-form-
|
|
||||||
identity preflight test fn, `PRELUDE_JSON` const, `PRELUDE_AIL` import,
|
|
||||||
`const _: &str = PRELUDE_AIL;` shape-pin, lines 1-15 of original
|
|
||||||
module doc-comment); module doc-comment replaced verbatim with the
|
|
||||||
surviving-only version (preserves the historical record of why the
|
|
||||||
preflight existed, what it discharged, when it was retired). Hash
|
|
||||||
`3abe0d3fa3c11c99` still asserted by the surviving
|
|
||||||
`prelude_parse_yields_canonical_hash`. Zero orphan-import warnings.
|
|
||||||
First-shot clean.
|
|
||||||
- iter pd.3.3: defensive `include_str!` block (12 lines) deleted from
|
|
||||||
`Cmd::MigrateCanonicalTypes` arm; lockstep skip-branch (4 lines)
|
|
||||||
deleted from the rewrite loop's Pass 2. The plan referenced the
|
|
||||||
subcommand by its variant tag `MigrateBareCrossModuleRefs` but the
|
|
||||||
user-facing subcommand name is `migrate-canonical-types` — spot-test
|
|
||||||
ran under the correct name and the help text printed. DONE_WITH_CONCERNS:
|
|
||||||
plan referenced obsolete variant name; doc-comment update on
|
|
||||||
`MigrateCanonicalTypes` (one paragraph) was needed beyond strict task
|
|
||||||
text because the prelude-fallback capability that the doc-comment
|
|
||||||
asserted ("Prelude is consulted as an implicit last-resort fallback
|
|
||||||
when no listed import owns the name") was silently removed by Step 2
|
|
||||||
— leaving the doc-comment in would have been an active mis-statement
|
|
||||||
in `--help` output. Update names pd.3 as the retirement point.
|
|
||||||
- iter pd.3.4: 3 edits to `carve_out_inventory.rs` (header sentence
|
|
||||||
"eight" → "seven"; comment block re-published per plan; `"prelude.ail.json"`
|
|
||||||
entry + its `// §C4 (b) — compile-time-embed` comment dropped from
|
|
||||||
EXPECTED). Test RED-on-purpose post-edit (file still on disk);
|
|
||||||
flips GREEN at Task 7.
|
|
||||||
- iter pd.3.5: Status marker inserted verbatim above §C4 (b) heading
|
|
||||||
in `docs/specs/2026-05-13-form-a-default-authoring.md`. Original §C4
|
|
||||||
(b) text preserved as historical context (per the spec's convention
|
|
||||||
for retired sections). Reads coherently top-to-bottom; the marker
|
|
||||||
reads as a forward-pointer to the resolution and the original text
|
|
||||||
reads as historical motivation.
|
|
||||||
- iter pd.3.6: `crates/ailang-surface/tests/prelude_decouple_carve_out_pin.rs`
|
|
||||||
created verbatim per the plan. RED-on-purpose at this task (file still
|
|
||||||
exists); flips GREEN at Task 7. Failure message names the four things
|
|
||||||
a future maintainer must do to legitimately reintroduce the file —
|
|
||||||
pre-empts the "it's just one file" rationalisation.
|
|
||||||
- iter pd.3.7: pre-deletion verification clean (zero `include_str!`
|
|
||||||
consumers; the two non-`include_str!` `.rs` references are inert —
|
|
||||||
`print_no_leak_pin.rs:110` is a string-literal in an assertion
|
|
||||||
message, `prelude_injection_pin.rs:28` constructs a tempdir-relative
|
|
||||||
path that does not read the in-tree file). `rm examples/prelude.ail.json`.
|
|
||||||
Both pin tests flip RED → GREEN at this step. Workspace test count
|
|
||||||
unchanged at 573 green.
|
|
||||||
- iter pd.3.8: `bench/compile_check.py` exit 0; `bench/cross_lang.py`
|
|
||||||
exit 0; `bench/check.py` exit 1 with the established noise envelope
|
|
||||||
(the `bench_list_sum.bump_s` and `implicit_at_rc` tail-latency
|
|
||||||
oscillation observed since audit-cma — 14th consecutive observation).
|
|
||||||
Acceptance criterion sweep: zero `include_str!` of `prelude.ail.json`
|
|
||||||
workspace-wide; zero literal-`"prelude"` in `crates/ailang-core/src/`
|
|
||||||
production scope (the matches in `workspace.rs` are all inside
|
|
||||||
`#[cfg(test)] mod tests`); CLAUDE.md "Authors write `.ail`" sentence
|
|
||||||
is consistent with the post-milestone state without modification (the
|
|
||||||
prelude exception it implicitly tolerated pre-milestone is gone);
|
|
||||||
`docs/DESIGN.md` carries no production-doctrine assertions about core
|
|
||||||
embedding the prelude or referencing `prelude.ail.json`.
|
|
||||||
|
|
||||||
## Concerns
|
|
||||||
|
|
||||||
- pd.3.1: plan defect — the task text instructed a call-site swap
|
|
||||||
("In each affected test fn body, replace the pattern ... with
|
|
||||||
`ailang_surface::parse_prelude()`") that doesn't typecheck due to
|
|
||||||
the dev-dep cycle. The plan also misattributed pd.2's actual
|
|
||||||
workaround (`serde_json::from_str(include_str!(...))`) as
|
|
||||||
successful use of `parse_prelude()` from in-mod. Recommend planner
|
|
||||||
recon-check: when a plan proposes a call-site change involving
|
|
||||||
`ailang_surface::*` from in-mod tests, verify the test target can
|
|
||||||
resolve the dev-dep edge (the lib-test target generally cannot;
|
|
||||||
the integration-test crate generally can — same lesson pd.2.4
|
|
||||||
recorded but didn't propagate to the planner skill).
|
|
||||||
- pd.3.3: plan referenced subcommand by its internal variant tag
|
|
||||||
(`MigrateBareCrossModuleRefs`) instead of its user-facing name
|
|
||||||
(`migrate-canonical-types`). Trivial repair (test under correct
|
|
||||||
name) but recommend planner check: verify subcommand-arm references
|
|
||||||
in spot-test commands by enumeration-after-rename.
|
|
||||||
- pd.3.3 (deeper): the plan's "Architecture" preamble called the
|
|
||||||
defensive include "a documented no-op" because the inserted entry
|
|
||||||
was unconditionally skipped by the lockstep guard at lines 500-505.
|
|
||||||
This is correct about WRITING the rewritten module file back, but
|
|
||||||
not about CONSULTING `local_types["prelude"]` for fallback rewrites
|
|
||||||
(which the inserted entry populated, lines 487-497, before the Pass
|
|
||||||
2 skip). Removing both blocks silently retired the prelude-fallback
|
|
||||||
capability of the rewrite logic. The plan called the change net-zero
|
|
||||||
behaviour; it isn't. The doc-comment update I made on
|
|
||||||
`MigrateCanonicalTypes` makes the new behaviour visible. The
|
|
||||||
rewrite-helper's prelude-fallback branch at lines ~2436-2446 in
|
|
||||||
`main.rs` is now dead-by-construction (the `local_types` map will
|
|
||||||
never have a `"prelude"` key, since no prelude module is loaded by
|
|
||||||
this subcommand) — left in place per the implementer's
|
|
||||||
no-surrounding-cleanup discipline; tagged below as known debt.
|
|
||||||
|
|
||||||
## Known debt
|
|
||||||
|
|
||||||
- `crates/ail/src/main.rs:~2436-2446`: the `local_types.get("prelude")`
|
|
||||||
fallback in `rewrite_def`'s `rewrite_type_con_owner` (or analogue)
|
|
||||||
is dead-by-construction post-pd.3 since the `local_types` map for
|
|
||||||
the `migrate-canonical-types` subcommand no longer contains a
|
|
||||||
`"prelude"` key. Out of pd.3's literal scope (no surrounding cleanup);
|
|
||||||
candidate for a follow-up tidy iter or for the post-pd.3 audit's
|
|
||||||
drift sweep.
|
|
||||||
- 3 cargo doc warnings on dangling `[`load_workspace`]` references in
|
|
||||||
`Workspace` and `Registry` rustdoc — pre-existing baseline carried
|
|
||||||
forward from pd.2; out of pd.3 scope. (Identical entry was in the
|
|
||||||
pd.2 known-debt section.)
|
|
||||||
- The `migrate-canonical-types` subcommand's substantive behaviour
|
|
||||||
(rewriting bare cross-module refs across a directory of `.ail.json`
|
|
||||||
files) hasn't been exercised since the canonical-type-names
|
|
||||||
milestone. No in-tree fixtures known to need rewriting today; spot-
|
|
||||||
tested via `--help` only. If a future migration is requested, the
|
|
||||||
prelude-fallback removal + the rewrite-helper dead-branch should
|
|
||||||
be reviewed together.
|
|
||||||
|
|
||||||
## Files touched
|
|
||||||
|
|
||||||
Modified:
|
|
||||||
- `crates/ail/src/main.rs` (delete defensive include 472-484, delete
|
|
||||||
skip-branch 500-505, update `MigrateCanonicalTypes` doc-comment to
|
|
||||||
reflect retired prelude-fallback)
|
|
||||||
- `crates/ailang-core/src/workspace.rs` (delete in-mod
|
|
||||||
`build_workspace_accepts_assembled_modules_and_runs_validation` test;
|
|
||||||
replace with 6-line stub comment documenting the dev-dep cycle as
|
|
||||||
invariant)
|
|
||||||
- `crates/ailang-core/tests/carve_out_inventory.rs` (header sentence
|
|
||||||
+ comment block + EXPECTED list — 8 → 7)
|
|
||||||
- `crates/ailang-core/tests/workspace_pin.rs` (add `load_one_adapter`
|
|
||||||
helper at top; append the relocated test at end)
|
|
||||||
- `crates/ailang-surface/tests/prelude_module_hash_pin.rs` (delete
|
|
||||||
cross-form-identity preflight + supporting `PRELUDE_JSON` /
|
|
||||||
`PRELUDE_AIL` references; trim doc-comment to surviving-only
|
|
||||||
framing)
|
|
||||||
- `docs/specs/2026-05-13-form-a-default-authoring.md` (insert "Status:
|
|
||||||
RETIRED" marker above §C4 (b) heading)
|
|
||||||
|
|
||||||
New:
|
|
||||||
- `crates/ailang-surface/tests/prelude_decouple_carve_out_pin.rs`
|
|
||||||
(single test asserting `examples/prelude.ail.json` does not exist)
|
|
||||||
|
|
||||||
Deleted:
|
|
||||||
- `examples/prelude.ail.json` (the milestone's primary artefact
|
|
||||||
retirement)
|
|
||||||
|
|
||||||
## Stats
|
|
||||||
|
|
||||||
`bench/orchestrator-stats/2026-05-14-iter-pd.3.json`
|
|
||||||
|
|
||||||
## Note on pre-existing dirty INDEX.md
|
|
||||||
|
|
||||||
When this iter dispatched, `docs/journals/INDEX.md` was already
|
|
||||||
modified in the working tree with one Boss-side line for pd.2 (the
|
|
||||||
prior iter); that edit was not committed before pd.3 dispatch. This
|
|
||||||
agent did NOT touch INDEX.md (it is Boss-only per skill discipline).
|
|
||||||
The Boss should commit the pd.2 INDEX line either with the pd.3
|
|
||||||
commit or as a separate prior commit — both options are clean.
|
|
||||||
@@ -1,175 +0,0 @@
|
|||||||
# iter rpe.1 — Retire per-type print effect-ops
|
|
||||||
|
|
||||||
**Date:** 2026-05-14
|
|
||||||
**Started from:** 8b455bee4ca2b09bcadc56d95e0d79dcef75d284
|
|
||||||
**Status:** DONE
|
|
||||||
**Tasks completed:** 10 of 10
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
The retire-per-type-print-effect-ops milestone shipped in one iter,
|
|
||||||
covering: a RED test pinning the deletion gate (`crates/ailang-check/
|
|
||||||
tests/no_per_type_print_ops.rs`); 92 `examples/*.ail` fixture
|
|
||||||
migrations from `(do io/print_<T> x)` → `(app print x)` plus 6
|
|
||||||
`.prose.txt` snapshot regenerations; four-site lockstep compiler
|
|
||||||
deletion (`crates/ailang-check/src/builtins.rs` × 5 regions,
|
|
||||||
`crates/ailang-codegen/src/lib.rs::lower_app` × 3 arms + 1 test,
|
|
||||||
`crates/ailang-codegen/src/synth.rs::builtin_effect_op_ret` × 1
|
|
||||||
match arm), with the dead `intern_string` helper removed as a
|
|
||||||
clean follow-up; six test-body migrations (`ailang-check` × 2,
|
|
||||||
`ailang-core` tests × 2, `ailang-surface/src/lex.rs`, `ailang-prose`);
|
|
||||||
the Cat B test-harness patch in `crates/ail/tests/e2e.rs` (6
|
|
||||||
IR-shape tests gained a `monomorphise_workspace` call so they
|
|
||||||
follow the same desugar → lift → mono → lower pipeline as `ail
|
|
||||||
build`); 6 doc-comment touch-ups; 7-site DESIGN.md sweep; 3 E2E
|
|
||||||
test-comment polish; bench-run-and-no-ratification (all 3 scripts
|
|
||||||
exit 0 against existing baselines, drift envelope within
|
|
||||||
tolerance); plus IR-snapshot refresh (4 `.ll` files: `sum`,
|
|
||||||
`list`, `max3`, `ws_main`) and 1 canonical-hash pin update
|
|
||||||
(`ordering_match::main`) as plan-unanticipated downstream
|
|
||||||
consequences of the corpus migration. Tests 564/564 green
|
|
||||||
post-iter (was 563 at start; +1 from the new RED test).
|
|
||||||
|
|
||||||
## Per-task notes
|
|
||||||
|
|
||||||
- iter rpe.1.1: RED test `no_per_type_print_ops.rs` — asserts
|
|
||||||
`io/print_int|bool|float` absent from `env.effect_ops`,
|
|
||||||
`io/print_str` present.
|
|
||||||
- iter rpe.1.2: 92 `.ail` corpus migrated (89 single-line +
|
|
||||||
3 multi-line shapes); 5 ancillary comment touch-ups in
|
|
||||||
`borrow_own_demo`, `eq_demo`, `int_to_print_int_borrow`,
|
|
||||||
`floats_1_newton_sqrt`, `floats_3_safe_division` to satisfy
|
|
||||||
the §H acceptance criterion `grep -rl ... examples/ == 0`.
|
|
||||||
- iter rpe.1.3: 6 `.prose.txt` snapshots regenerated via
|
|
||||||
`cargo run -p ail -- prose`.
|
|
||||||
- iter rpe.1.4: builtins.rs deletes (3 effect_ops.insert blocks
|
|
||||||
+ 3 `list()` rows + 1 unit test + 2 doc-comment swaps);
|
|
||||||
codegen lib.rs deletes (3 `lower_app` arms + 1 test);
|
|
||||||
synth.rs `builtin_effect_op_ret` match-arm narrowed.
|
|
||||||
`intern_string` removed as orphan helper. `Emitter.strings`
|
|
||||||
field left in place (still consumed by emit-time loop;
|
|
||||||
cycles over empty map; non-functional). RED test flips
|
|
||||||
GREEN.
|
|
||||||
- iter rpe.1.5: 6 test-body sites swapped to `io/print_str` +
|
|
||||||
`Str` args; Cat B harness patch at all 6 e2e.rs sites
|
|
||||||
inserts `ailang_check::monomorphise_workspace(&lifted_ws)`
|
|
||||||
between the existing lift loop and the lower call (the plan
|
|
||||||
said "replace the desugar+lift loop with mono", but mono's
|
|
||||||
precondition is "already lifted" — interpreting per spirit
|
|
||||||
and matching the `ail build` pipeline). E2E 85/85 green
|
|
||||||
after this step.
|
|
||||||
- iter rpe.1.6: doc-comment swaps in 6 sites (`lex.rs`,
|
|
||||||
`parse.rs`, `main.rs` × 2, `runtime/str.c`, `form_a.md`).
|
|
||||||
- iter rpe.1.7: DESIGN.md 7 sites swept; the iter's
|
|
||||||
past-tense paragraph at line 1990-1992 anchors the
|
|
||||||
milestone close.
|
|
||||||
- iter rpe.1.8: E2E test-comment polish at `eq_ord_e2e.rs` × 2,
|
|
||||||
`floats_e2e.rs`. Plus three plan-unanticipated sites:
|
|
||||||
`int_arg_to_effect_op_does_not_rc_track` test assertions
|
|
||||||
shifted from `0/0/0` to `1/1/0` (post-iter `print n` for
|
|
||||||
Int now goes via Show Int → int_to_str → io/print_str,
|
|
||||||
exactly one heap-Str slab cycle per spec §E); 4 IR
|
|
||||||
snapshots refreshed via `UPDATE_SNAPSHOTS=1` because the
|
|
||||||
format-string globals (`@.str_<module>_fmt_int_0` /
|
|
||||||
`%lld\n`) no longer appear in IR; 1 canonical-hash pin
|
|
||||||
update for `ordering_match::main` (body migration changes
|
|
||||||
the def's canonical-form hash).
|
|
||||||
- iter rpe.1.9: bench run — all 3 scripts exit 0 against
|
|
||||||
existing baselines. No ratification needed; spec §C4 (a)
|
|
||||||
anticipated possible latency drift but actual fell within
|
|
||||||
tolerance. Largest single deltas: `latency.explicit_at_rc.
|
|
||||||
max_us` +154.84% (a tail metric in the noise-class
|
|
||||||
envelope observed across the last ~12 audits); `throughput.
|
|
||||||
bench_list_sum.bump_s` +11.85% (REGRESSION, single bench,
|
|
||||||
3rd-iter-recurring; tracked as background noise per
|
|
||||||
audit-24 lineage). Baseline pristine for the 13th
|
|
||||||
consecutive audit-equivalent.
|
|
||||||
- iter rpe.1.10: roadmap entry struck `[x]`; journal written;
|
|
||||||
stats written.
|
|
||||||
|
|
||||||
## Concerns
|
|
||||||
|
|
||||||
- Cat B harness patch deviates from plan-literal: plan said
|
|
||||||
"replace the desugar+lift loop with monomorphise_workspace";
|
|
||||||
I inserted mono after the existing lift loop. Mono's
|
|
||||||
documented precondition is "already lifted", so the literal
|
|
||||||
reading would have broken mono's contract. Spirit-reading
|
|
||||||
was unambiguous: match `ail build`'s pipeline.
|
|
||||||
- Plan Task 8 under-scoped: it named 3 E2E-comment sites,
|
|
||||||
but the corpus migration also forced (a) one assertion
|
|
||||||
update on `int_arg_to_effect_op_does_not_rc_track` (allocs
|
|
||||||
0→1, frees 0→1, fully documented in spec §E), (b) IR
|
|
||||||
snapshot refresh × 4, (c) one canonical-hash pin update.
|
|
||||||
All three are mechanical downstream consequences of Task 2.
|
|
||||||
- `Emitter.strings` field in `crates/ailang-codegen/src/lib.rs`
|
|
||||||
is now functionally dead (no caller after `intern_string`
|
|
||||||
removal) but the field + the per-module collection loop +
|
|
||||||
the emit-time globals loop are all still in place. They
|
|
||||||
cycle over an empty map and are harmless. Cleaning them
|
|
||||||
up cascades through `emit_workspace_with_alloc` and
|
|
||||||
parallel `str_literals` infrastructure; out of scope for
|
|
||||||
this iter.
|
|
||||||
|
|
||||||
## Known debt
|
|
||||||
|
|
||||||
- The `int_arg_to_effect_op_does_not_rc_track` test name now
|
|
||||||
describes a pre-iter property; the body pins the post-iter
|
|
||||||
shape (one Show-Int slab cycle). Renaming to e.g.
|
|
||||||
`print_int_one_slab_cycle` was floated and explicitly
|
|
||||||
declined (plan Task 8 step 2 said "lean toward keep-name-
|
|
||||||
update-doc"). If a future iter wants the rename, the test
|
|
||||||
body is already a clean reference.
|
|
||||||
- The `intern_string` helper is gone but `Emitter.strings`
|
|
||||||
field remains — see Concerns above.
|
|
||||||
|
|
||||||
## Files touched
|
|
||||||
|
|
||||||
121 files in the working tree (1 new + 120 modified).
|
|
||||||
|
|
||||||
Code (compiler):
|
|
||||||
- `crates/ailang-check/src/builtins.rs` (3 effect_ops + 3 list() rows + 1 test + 2 doc-comments deleted/swapped)
|
|
||||||
- `crates/ailang-check/src/lib.rs` (2 test-body sites swapped)
|
|
||||||
- `crates/ailang-codegen/src/lib.rs` (3 lower_app arms + 1 test + `intern_string` helper removed)
|
|
||||||
- `crates/ailang-codegen/src/synth.rs` (builtin_effect_op_ret match-arm narrowed)
|
|
||||||
- `crates/ailang-surface/src/lex.rs` (1 test-body + 1 module doc swap)
|
|
||||||
- `crates/ailang-surface/src/parse.rs` (1 diagnostic example swap)
|
|
||||||
- `crates/ailang-prose/src/lib.rs` (1 round-trip test-body swap)
|
|
||||||
- `crates/ail/src/main.rs` (2 doc-comments swapped)
|
|
||||||
- `runtime/str.c` (1 comment re-anchored on %g)
|
|
||||||
|
|
||||||
Tests:
|
|
||||||
- `crates/ailang-check/tests/no_per_type_print_ops.rs` (NEW)
|
|
||||||
- `crates/ailang-core/tests/spec_drift.rs` (1 swap)
|
|
||||||
- `crates/ailang-core/tests/design_schema_drift.rs` (1 swap)
|
|
||||||
- `crates/ailang-core/tests/hash_pin.rs` (1 hash pin updated)
|
|
||||||
- `crates/ail/tests/e2e.rs` (6 Cat B harness patches + 1 assertion update + 1 doc-comment)
|
|
||||||
- `crates/ail/tests/eq_ord_e2e.rs` (2 doc-comments)
|
|
||||||
- `crates/ail/tests/floats_e2e.rs` (1 doc-comment)
|
|
||||||
- `crates/ail/tests/snapshots/{sum,list,max3,ws_main}.ll` (regenerated; format-string globals removed)
|
|
||||||
|
|
||||||
Docs:
|
|
||||||
- `docs/DESIGN.md` (7 sites)
|
|
||||||
- `docs/roadmap.md` (P2 entry struck [x])
|
|
||||||
- `crates/ailang-core/specs/form_a.md` (1 example swap)
|
|
||||||
|
|
||||||
Examples: 92 `.ail` migrations + 6 `.prose.txt` regenerations + 5 ancillary comment touch-ups.
|
|
||||||
|
|
||||||
## Verification
|
|
||||||
|
|
||||||
- `cargo test --workspace`: 564 passed, 0 failed (was 563 at
|
|
||||||
start; +1 = new RED test, no test counts shifted otherwise).
|
|
||||||
- `cargo build --workspace`: clean.
|
|
||||||
- `python3 bench/check.py` exit 0 (drift within tolerance,
|
|
||||||
no ratification).
|
|
||||||
- `python3 bench/compile_check.py` exit 0.
|
|
||||||
- `python3 bench/cross_lang.py` exit 0 (25/25 stable).
|
|
||||||
- `grep -rl "io/print_int\|io/print_bool\|io/print_float"
|
|
||||||
examples/` returns 0 results (acceptance criterion met).
|
|
||||||
- `grep -n "io/print_int\|io/print_bool\|io/print_float"
|
|
||||||
crates/ailang-check/src/builtins.rs` returns 0 lines.
|
|
||||||
- `crates/ailang-codegen/src/lib.rs::lower_app` has no arm
|
|
||||||
for any of the three retired op names.
|
|
||||||
|
|
||||||
## Stats
|
|
||||||
|
|
||||||
`bench/orchestrator-stats/2026-05-14-iter-rpe.1.json`
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
# iter rpe.1.tidy — Close audit-rpe.1 [medium] drift
|
|
||||||
|
|
||||||
**Date:** 2026-05-14
|
|
||||||
**Started from:** 973f50b (post-rpe.1 + audit-rpe.1)
|
|
||||||
**Status:** DONE
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
Closes the single `[medium]` drift item surfaced by audit-rpe.1:
|
|
||||||
`crates/ailang-codegen/src/subst.rs:188,217` — `qualify_local_types_codegen`
|
|
||||||
and `apply_subst_to_type` both carried the exact
|
|
||||||
`Type::Fn { params, ret, effects, .. }` field-spread that drops
|
|
||||||
`param_modes` and `ret_mode` to `vec![]` / `ParamMode::Implicit`. This
|
|
||||||
is the same shape the print-leak bugfix at commit feb9413 corrected
|
|
||||||
in `crates/ailang-check/src/lib.rs::substitute_rigids`, and the
|
|
||||||
feb9413 commit message explicitly flagged any other instance as a
|
|
||||||
latent bug in the same class.
|
|
||||||
|
|
||||||
Fix: preserve both fields through the rebuild — `param_modes.clone()`
|
|
||||||
(it's a `Vec`) and `*ret_mode` (Copy). Symmetric to the feb9413 fix.
|
|
||||||
|
|
||||||
The audit's `[nit]` finding (dead `Emitter.strings` field in
|
|
||||||
`crates/ailang-codegen/src/lib.rs`, an orphan after `intern_string`
|
|
||||||
was removed in rpe.1 Task 4) is left as a follow-up tidy candidate —
|
|
||||||
it cycles over an empty map harmlessly and isn't an audit gate.
|
|
||||||
|
|
||||||
## Files touched
|
|
||||||
|
|
||||||
- `crates/ailang-codegen/src/subst.rs:188` —
|
|
||||||
`qualify_local_types_codegen` Type::Fn arm now binds
|
|
||||||
`param_modes` and `ret_mode` and threads them through.
|
|
||||||
- `crates/ailang-codegen/src/subst.rs:217` —
|
|
||||||
`apply_subst_to_type` Type::Fn arm same shape.
|
|
||||||
|
|
||||||
## Verification
|
|
||||||
|
|
||||||
- `cargo test --workspace` → 564 / 0 / 3 (baseline holds).
|
|
||||||
- `cargo clippy --workspace --all-targets` → 0 warnings.
|
|
||||||
- `cargo doc --workspace --no-deps` → 0 warnings.
|
|
||||||
|
|
||||||
## Concerns
|
|
||||||
|
|
||||||
- (none)
|
|
||||||
|
|
||||||
## Known debt
|
|
||||||
|
|
||||||
- `Emitter.strings` field in `crates/ailang-codegen/src/lib.rs`
|
|
||||||
remains dead. Cycles over empty map; no behavioural impact.
|
|
||||||
Roll into a future opportunistic cleanup.
|
|
||||||
- Three other field-spread `Type::Fn` rebuild sites
|
|
||||||
(`crates/ailang-check/src/lib.rs:93` and `:3492`,
|
|
||||||
`crates/ailang-check/src/lift.rs:875`) ALSO drop `param_modes`
|
|
||||||
/ `ret_mode` to `vec![]` / `Implicit`. They were not flagged
|
|
||||||
by audit-rpe.1 because no currently-failing test pins them.
|
|
||||||
Leaving conservatively in place — a flag for a future
|
|
||||||
fieldtest or audit cycle. The pattern of field-spread-and-
|
|
||||||
drop is the bug class; whether each site is correctness-
|
|
||||||
reachable is what needs verification before changing.
|
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
# bugfix mut-diag-double-code — doubled `[code]` prefix in mut-local human diagnostics
|
|
||||||
|
|
||||||
**Date:** 2026-05-15
|
|
||||||
**Status:** DONE
|
|
||||||
**Trigger:** fieldtest finding F2 (`docs/specs/2026-05-15-fieldtest-mut-local.md`).
|
|
||||||
**Started from:** 1faee67
|
|
||||||
|
|
||||||
## Symptom
|
|
||||||
|
|
||||||
Non-JSON `ail check` stderr rendered the kebab diagnostic code
|
|
||||||
twice for the four mut-local `CheckError` variants, e.g.
|
|
||||||
|
|
||||||
```
|
|
||||||
error: [mut-var-captured-by-lambda] main: [mut-var-captured-by-lambda] mut-var `x` cannot be captured...
|
|
||||||
```
|
|
||||||
|
|
||||||
## Cause
|
|
||||||
|
|
||||||
`MutAssignOutOfScope`, `AssignTypeMismatch`,
|
|
||||||
`UnsupportedMutVarType`, `MutVarCapturedByLambda` were authored in
|
|
||||||
iters mut.2 / mut.4-tidy with their `#[error("[<code>] ...")]`
|
|
||||||
`thiserror` Display bodies embedding the bracketed code. The
|
|
||||||
non-JSON `Cmd::Check` formatter in `crates/ail/src/main.rs`
|
|
||||||
independently prepends `[{d.code}]` from `CheckError::code()`.
|
|
||||||
Embedded + prepended = doubled. The non-mut `CheckError` variants
|
|
||||||
never embedded the bracket, so only these four doubled — they were
|
|
||||||
the inconsistent ones, brought in line here.
|
|
||||||
|
|
||||||
## Fix
|
|
||||||
|
|
||||||
RED-first via the `debug` skill (`ailang-debugger` wrote the
|
|
||||||
failing test; the GREEN side was a trivial four-string mechanical
|
|
||||||
edit applied inline by the Boss per the CLAUDE.md
|
|
||||||
trivial-mechanical-edit carve-out — no review-and-commit
|
|
||||||
discipline shed).
|
|
||||||
|
|
||||||
- RED test: `crates/ail/tests/ct1_check_cli.rs` —
|
|
||||||
`check_human_mode_renders_mut_diagnostic_code_exactly_once`.
|
|
||||||
Pins the *observable* property: the code appears exactly once in
|
|
||||||
the rendered human diagnostic, exercising the same formatter path
|
|
||||||
the CLI uses (not a brittle assertion on the raw `#[error]` body).
|
|
||||||
- GREEN: dropped the leading `[<code>] ` literal from the four
|
|
||||||
`#[error(...)]` Display strings in `crates/ailang-check/src/lib.rs`.
|
|
||||||
|
|
||||||
The mut-typecheck pin tests (`mut_typecheck_pin.rs`) and the
|
|
||||||
in-source unit assertions match on the struct variants / `code()`,
|
|
||||||
not the Display string, so the prefix removal did not touch them.
|
|
||||||
|
|
||||||
## Tests
|
|
||||||
|
|
||||||
598 → 599 green (the new RED-now-GREEN human-formatter test).
|
|
||||||
Full workspace clean, zero failures.
|
|
||||||
|
|
||||||
## Not in scope (stays queued)
|
|
||||||
|
|
||||||
F1/F4 (accumulator-over-iteration: the milestone motivation is
|
|
||||||
unmet without a loop construct) and F3 (zero-arg `(app f)` rejected
|
|
||||||
at parse) from the same fieldtest are NOT addressed here — they are
|
|
||||||
design/roadmap items, not this bug.
|
|
||||||
@@ -1,251 +0,0 @@
|
|||||||
# iter it.1 — loop/recur additive (parse/print/prose/check/codegen)
|
|
||||||
|
|
||||||
**Date:** 2026-05-15
|
|
||||||
**Started from:** 7381a4233b0ea34b23871c59e3a3e802441751ef
|
|
||||||
**Status:** DONE
|
|
||||||
**Tasks completed:** 8 of 8
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
First of three iterations in the iteration-discipline milestone.
|
|
||||||
Adds `Term::Loop` / `Term::Recur` / `LoopBinder` as first-class
|
|
||||||
additive AST nodes end-to-end — parse, print, prose projection,
|
|
||||||
canonical-JSON serde + round-trip, schema/spec-drift lockstep,
|
|
||||||
typecheck (binder typing, recur arity/type unification via a
|
|
||||||
`loop_stack: &mut Vec<Vec<Type>>` threaded exactly as mut.2 threaded
|
|
||||||
`mut_scope_stack`, recur tail-position via a new private
|
|
||||||
`verify_loop_body` helper with `verify_tail_positions`'s public
|
|
||||||
signature unchanged), and codegen (loop-header block with one phi
|
|
||||||
per binder, `recur` as a back-edge `br` with a NEW parallel
|
|
||||||
`block_terminated = true` setter). The change is **strictly
|
|
||||||
additive**: zero deletions touch the existing `tail-app`/`tail-do`
|
|
||||||
paths, `verify_tail_positions`' tail-app role, or any of the seven
|
|
||||||
existing codegen `block_terminated` sites — the 32 within-iter
|
|
||||||
deletion-lines are exclusively the Task-1 `Internal(...)` stubs and
|
|
||||||
the Task-1 `verify_tail_positions` structural-passthrough being
|
|
||||||
replaced by their real Task-5/Task-7 implementations (DD-3
|
|
||||||
stub-then-fill). This additive discipline is what makes the
|
|
||||||
destructive it.3 safe later. Full `cargo test --workspace` green
|
|
||||||
(63 ok result groups, 0 failed); `loop_counter.ail` builds and
|
|
||||||
prints `55`; the four `Recur*` diagnostics fire on their negatives;
|
|
||||||
`tail-app` fixtures (`bench_compute_intsum`, `list_map_poly`) run
|
|
||||||
byte-identically; zero IR/prose snapshot drift.
|
|
||||||
|
|
||||||
## Per-task notes
|
|
||||||
|
|
||||||
- iter it.1.1: AST variants + every walker arm + serde/schema
|
|
||||||
lockstep. `Term::Loop { binders: Vec<LoopBinder>, body: Box<Term> }`,
|
|
||||||
`Term::Recur { #[serde(default)] args: Vec<Term> }`, and
|
|
||||||
`LoopBinder { name, #[serde(rename="type")] ty, init: Box<Term> }`
|
|
||||||
(DD-2 derives = `Debug, Clone, Serialize, Deserialize`, no
|
|
||||||
PartialEq; note `init` is `Box<Term>` per the plan's literal code
|
|
||||||
block, whereas `MutVar.init` is bare `Term` — a small divergence
|
|
||||||
from "mirrors MutVar exactly", recorded under Concerns). ~40
|
|
||||||
walker arms across desugar (incl. 3 in-test `any_*` helpers not
|
|
||||||
enumerated by the plan), workspace, all of ailang-check
|
|
||||||
(lib/lift/linearity/mono/pre_desugar_validation/reuse_shape/
|
|
||||||
uniqueness), codegen (escape×3/lambda/lib), prose, surface
|
|
||||||
parse/print, ail main + the test-side
|
|
||||||
`codegen_import_map_fallback_pin.rs` walker the plan flagged
|
|
||||||
(mut.1 missed its analogue; did not miss it here). Typecheck +
|
|
||||||
codegen *dispatch* stubbed `Internal(...)` (tuple variant —
|
|
||||||
the plan pseudo-code's `Internal { msg }` struct form does not
|
|
||||||
exist; copied mut.1's verbatim tuple shape). `synth_with_extras`
|
|
||||||
got real arms (loop → body's type, recur → unit) per plan.
|
|
||||||
schema_coverage left RED on "variant never observed" until
|
|
||||||
Task 3 (plan-expected); every other test green.
|
|
||||||
|
|
||||||
- iter it.1.2: Form-A parse. `parse_loop` / `parse_recur` modelled
|
|
||||||
on `parse_mut` / `parse_assign`; the binder list is an explicitly
|
|
||||||
parenthesised group `(loop ((var ...)*) BODY+)` (form_a.md
|
|
||||||
production added in Task 1.10 lockstep). Plan pseudo-code named
|
|
||||||
non-existent helpers (`parse_term_str`, `parse_body_seq`,
|
|
||||||
`parse_loop_binders`); substituted the real harness (module-level
|
|
||||||
`parse_term`) and inlined the per-`(var …)` decode exactly as
|
|
||||||
`parse_mut` does — the plan explicitly forbade introducing a new
|
|
||||||
body-folding helper. EBNF prologue + unknown-head error list
|
|
||||||
extended. RED→GREEN confirmed.
|
|
||||||
|
|
||||||
- iter it.1.3: Form-A print + positive fixtures. Print arms landed
|
|
||||||
early in Task 1 as a build-unblock necessity (documented mut.1
|
|
||||||
precedent — print arms are exhaustive-match neighbours of the
|
|
||||||
synth/lower_term dispatchers). `loop_smoke.ail` /
|
|
||||||
`loop_nested_in_lambda.ail` written in real Form-A matching
|
|
||||||
`mut_counter.ail`'s verbatim surface spellings (`(con Int)`,
|
|
||||||
`(app == …)`, `(app + …)`, `(lam (params (typed …)) (ret …)
|
|
||||||
(body …))`) — the plan's fixture pseudo-code (`(fn count_to :
|
|
||||||
Int -> Int …)`) is not valid Form-A and the plan's prose
|
|
||||||
explicitly directed matching mut_counter.ail verbatim.
|
|
||||||
parse→print→parse byte-idempotent; schema_coverage now GREEN
|
|
||||||
(Task 1.11 deferred RED closed).
|
|
||||||
|
|
||||||
- iter it.1.4: Prose projection lockstep. Step 4.1's literal
|
|
||||||
instruction ("render to prose and back, asserting AST equality")
|
|
||||||
is **not expressible** — AILang has no Form-B→AST parser by
|
|
||||||
design (CLAUDE.md / `crates/ail/src/main.rs:207` "no parser
|
|
||||||
exists for the prose surface"). Recorded as a plan defect (see
|
|
||||||
Concerns). The substantive work (render/free-var/subst arms,
|
|
||||||
landed in Task 1) is correct and verified by the full prose
|
|
||||||
suite; added `loop_and_recur_project_to_prose`, a render-assertion
|
|
||||||
pin using the same `render_term` harness the nearest existing
|
|
||||||
prose tests (`not_with_tail_flag_keeps_prefix_form`) use — the
|
|
||||||
feasible equivalent of the plan's intent. This is the documented
|
|
||||||
mut.1-class plan-pseudo-vs-real-API substitution, not a silent
|
|
||||||
swap. Status DONE_WITH_CONCERNS.
|
|
||||||
|
|
||||||
- iter it.1.5: Real typecheck (DD-1). Four `CheckError` variants
|
|
||||||
(`RecurOutsideLoop` / `RecurArityMismatch` / `RecurTypeMismatch`
|
|
||||||
/ `RecurNotInTailPosition`) — **no `span` field** because no
|
|
||||||
`Span` type is in scope in this crate and no existing CheckError
|
|
||||||
variant carries one (the plan pseudo-code's `span: Option<Span>`
|
|
||||||
references a non-existent-here type; mirrored the real
|
|
||||||
`MutAssignOutOfScope` field-shape exactly as the plan's prose
|
|
||||||
instructed). `loop_stack: &mut Vec<Vec<Type>>` threaded through
|
|
||||||
`synth`'s signature + every call site (signature + 22 single-line
|
|
||||||
recursive + 3 multi-line recursive + 2 production entry points +
|
|
||||||
lift.rs + 2 mono.rs + builtins.rs test helpers + 8 in-source
|
|
||||||
`#[cfg(test)]` calls), exactly the mut.2 pattern. Real Loop arm
|
|
||||||
binds binder names into `self`-… `locals` (save/restore like
|
|
||||||
`Term::Let` — the correct precedent for *immutable* bindings,
|
|
||||||
not the mut-var mechanism) and pushes the binder type-vector onto
|
|
||||||
`loop_stack`. **`Term::Recur` synth returns a fresh metavar
|
|
||||||
(`Subst::fresh(counter)`), not the plan's flagged-risky
|
|
||||||
`Type::unit()`** — see Concerns; this resolves the plan's own
|
|
||||||
self-review "Open risk" correctly (a fresh metavar is the
|
|
||||||
codebase-idiomatic bottom that unifies anywhere, which is what
|
|
||||||
makes `(if c then (recur …))` typecheck — `Type::unit()` would
|
|
||||||
have broken `loop_smoke`'s if-branch unification). `verify_loop_body`
|
|
||||||
added as a new private helper; `verify_tail_positions`' public
|
|
||||||
signature unchanged (it.3 reworks it). The diagnostic-doc list in
|
|
||||||
`diagnostic.rs` was NOT extended — the mut codes were never added
|
|
||||||
there (false plan premise; followed the actual mut.2 precedent).
|
|
||||||
RED→GREEN: loop_smoke clean, four negatives each return their
|
|
||||||
exact code. `loop_stack` threading caused zero regressions.
|
|
||||||
|
|
||||||
- iter it.1.6: Carve-out inventory. EXPECTED extended with the four
|
|
||||||
`test_recur_*.ail.json` negatives, 13→17, in an it.1 group
|
|
||||||
(the const is milestone-grouped not alphabetical; the test uses
|
|
||||||
a BTreeSet so order is functionally irrelevant). GREEN.
|
|
||||||
|
|
||||||
- iter it.1.7: Real codegen (loop-header + phi + recur back-edge +
|
|
||||||
lambda boundary). `loop_frames: Vec<LoopFrame>` field added (mut.3
|
|
||||||
analogue to `mut_var_allocas` — init/clear/lambda-save-reset-
|
|
||||||
restore symmetric). Loop lowers to `loop.header.<id>` with one
|
|
||||||
`phi` per binder; binder phi SSA pushed into `self.locals` so
|
|
||||||
`Term::Var` resolves to it via the existing lookup. Each phi's
|
|
||||||
back-edge incoming list is emitted as a unique `/*RECUR_EDGES…*/`
|
|
||||||
LLVM-comment placeholder, then `replacen(…, 1)`-patched once the
|
|
||||||
body and all its recur sites are lowered (the plan's
|
|
||||||
`patch_loop_phis` mechanism; the placeholder is a comment so an
|
|
||||||
unpatched one would be inert IR, not a syntax error). `Term::Recur`
|
|
||||||
records `(pred_block, [arg_ssa])`, emits the back-edge `br`, sets
|
|
||||||
`block_terminated = true` — a NEW parallel setter; the seven
|
|
||||||
existing tail-driven setters are untouched (verified: zero
|
|
||||||
deletions touch them). Reused `start_block`/`fresh_ssa`/`fresh_id`/
|
|
||||||
`self.current_block` instead of adding the plan-pseudo's sprawl of
|
|
||||||
single-use helper methods (plan explicitly: "REUSE … grep before
|
|
||||||
adding"). `loop_counter.ail` builds + prints `55`.
|
|
||||||
|
|
||||||
- iter it.1.8: tail-app coexistence + IR snapshots + acceptance.
|
|
||||||
`bench_compute_intsum.ail` → `3500003500000`,
|
|
||||||
`list_map_poly.ail` → `2\n3\n…` (byte-identical to pre-it.1; no
|
|
||||||
existing codegen path modified). Zero IR/prose snapshot drift (no
|
|
||||||
blanket regen). All it.1 acceptance bullets verified — the spec's
|
|
||||||
"five Recur diagnostics" is four in it.1 (the fifth,
|
|
||||||
`NonStructuralRecursion`, is it.2; resolved per plan Step 8.3).
|
|
||||||
|
|
||||||
- Phase 3 (E2E): added `loop_in_lambda_e2e.ail` + e2e
|
|
||||||
`loop_in_lambda_runs_and_prints_49` — a `loop` inside a `lam`
|
|
||||||
body invoked via a returned closure, proving the lambda-boundary
|
|
||||||
`loop_frames` save/restore is codegen-sound (the no-`main`
|
|
||||||
`loop_nested_in_lambda.ail` parse/print fixture cannot reach
|
|
||||||
codegen via the CLI; this runnable fixture closes that gap).
|
|
||||||
|
|
||||||
## Concerns
|
|
||||||
|
|
||||||
- `LoopBinder.init` is `Box<Term>` whereas the analogous
|
|
||||||
`MutVar.init` is bare `Term`. Per the plan's literal Step 1.1
|
|
||||||
code block (which specified `init: Box<Term>`), not implementer
|
|
||||||
drift. DD-2 says "mirrors MutVar exactly" — the divergence is in
|
|
||||||
the plan's own pseudo-code vs. its own DD-2 prose. Functional
|
|
||||||
impact: none (consistent `(*b.init).clone()` / `Box::new(...)` /
|
|
||||||
`&mut b.init` handling everywhere). A future iter that wants
|
|
||||||
strict parity could unbox; no it.1 test depends on the boxing.
|
|
||||||
|
|
||||||
- `Term::Recur` synth result: chose `Subst::fresh(counter)` (a
|
|
||||||
fresh metavar) over the plan's named fallback `Type::unit()`.
|
|
||||||
The plan's self-review flagged this exact point as an "Open
|
|
||||||
risk" with `Type::unit()` as the fallback "because recur is
|
|
||||||
always in tail position so its value is never consumed". But
|
|
||||||
`recur` frequently appears as an `if` branch (`loop_smoke`:
|
|
||||||
`(if (== i n) i (recur …))`), where the branch types MUST
|
|
||||||
unify — `unify(Int, unit)` fails, so `Type::unit()` would have
|
|
||||||
broken `loop_smoke`. A fresh metavar unifies with any type
|
|
||||||
(codegen-idiomatic bottom; the same mechanism `Subst::fresh`
|
|
||||||
serves everywhere). This is the correct resolution of the plan's
|
|
||||||
own flagged risk, not a deviation from its intent.
|
|
||||||
|
|
||||||
- Step 4.1 plan defect: it asks for a prose round-trip asserting
|
|
||||||
AST equality; AILang has no Form-B parser by design. Substituted
|
|
||||||
a feasible render-assertion pin (mut.1-class plan-pseudo-vs-real
|
|
||||||
substitution). The plan's *substance* (prose lockstep for the new
|
|
||||||
variants) is fully met. Boss may want to tighten the planner
|
|
||||||
template so it does not script prose round-trips.
|
|
||||||
|
|
||||||
- Step 5.2 plan premise defect: "Add the four code strings to the
|
|
||||||
diagnostic-doc list … same list the mut codes were added to" —
|
|
||||||
the mut codes were never added to `diagnostic.rs`'s doc list.
|
|
||||||
Followed the actual mut.2 precedent (not extended); the
|
|
||||||
authoritative registry is `CheckError::code()`, which has all
|
|
||||||
four.
|
|
||||||
|
|
||||||
- The plan's recon line numbers (e.g. `desugar.rs:349/559/…`,
|
|
||||||
`lib.rs:2590/2613/3593/3614`, `parse.rs:1212`) had drifted
|
|
||||||
vs. the live tree; drove every site off `cargo build`'s
|
|
||||||
E0004 enumeration as Step 1.2 instructs. Site *count* also
|
|
||||||
differed (6 desugar E0004 sites vs. plan's 9; resolved via the
|
|
||||||
build, which is authoritative). No behavioural impact —
|
|
||||||
same class as mut.1's documented recon misindexing.
|
|
||||||
|
|
||||||
## Known debt
|
|
||||||
|
|
||||||
- Prose-side `(loop …)` / `(recur …)` rendering is a
|
|
||||||
minimal-correctness shape (`loop { var n = init; …; body }` /
|
|
||||||
`recur(a, b)`), explicitly mirroring the deliberately-placeholder
|
|
||||||
mut-block prose shape. The prose surface for loops is not yet
|
|
||||||
designed; a follow-on prose iter refines it once the LLM-author
|
|
||||||
signal arrives. Not drift — recorded for visibility.
|
|
||||||
|
|
||||||
- `loop_nested_in_lambda.ail` has no `main`, so it is a
|
|
||||||
parse/print/typecheck fixture only (CLI codegen needs a `main`).
|
|
||||||
Codegen-of-loop-in-lambda is instead proven by the runnable
|
|
||||||
Phase-3 `loop_in_lambda_e2e.ail`. No gap remains; recorded so a
|
|
||||||
future reader does not mistake the no-main fixture for dead
|
|
||||||
coverage.
|
|
||||||
|
|
||||||
## Files touched
|
|
||||||
|
|
||||||
AST/core: `crates/ailang-core/src/ast.rs`,
|
|
||||||
`crates/ailang-core/src/desugar.rs`,
|
|
||||||
`crates/ailang-core/src/workspace.rs`,
|
|
||||||
`crates/ailang-core/specs/form_a.md`.
|
|
||||||
Drift/coverage tests: `crates/ailang-core/tests/{design_schema_drift,
|
|
||||||
schema_coverage,spec_drift,carve_out_inventory}.rs`.
|
|
||||||
Check: `crates/ailang-check/src/{lib,lift,linearity,mono,
|
|
||||||
pre_desugar_validation,reuse_shape,uniqueness,builtins}.rs`,
|
|
||||||
`crates/ailang-check/tests/loop_recur_pin.rs` (new).
|
|
||||||
Codegen: `crates/ailang-codegen/src/{lib,escape,lambda}.rs`.
|
|
||||||
Surface/prose: `crates/ailang-surface/src/{parse,print}.rs`,
|
|
||||||
`crates/ailang-prose/src/lib.rs`.
|
|
||||||
CLI: `crates/ail/src/main.rs`,
|
|
||||||
`crates/ail/tests/{e2e,codegen_import_map_fallback_pin}.rs`.
|
|
||||||
Spec: `docs/DESIGN.md`.
|
|
||||||
Fixtures (new): `examples/loop_smoke.ail`,
|
|
||||||
`examples/loop_nested_in_lambda.ail`, `examples/loop_counter.ail`,
|
|
||||||
`examples/loop_in_lambda_e2e.ail`,
|
|
||||||
`examples/test_recur_{outside_loop,arity_mismatch,type_mismatch,
|
|
||||||
not_in_tail_position}.ail.json`.
|
|
||||||
|
|
||||||
## Stats
|
|
||||||
|
|
||||||
bench/orchestrator-stats/2026-05-15-iter-it.1.json
|
|
||||||
@@ -1,241 +0,0 @@
|
|||||||
# iter it.2 — structural-guardedness checker + first real Diverge effect
|
|
||||||
|
|
||||||
**Date:** 2026-05-15
|
|
||||||
**Started from:** bc9f5120034f2552ad7e78454bb198472404d4b1
|
|
||||||
**Status:** DONE
|
|
||||||
**Tasks completed:** 6 of 6
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
Second of three iterations in the iteration-discipline milestone.
|
|
||||||
Adds the structural-recursion guardedness checker
|
|
||||||
(`CheckError::NonStructuralRecursion`) and the first real
|
|
||||||
implementation of the Decision-3 `Diverge` effect, both strictly
|
|
||||||
additive — nothing `tail`-related is removed (that is it.3). A new
|
|
||||||
whole-body pass `verify_structural_recursion` runs as a sibling of
|
|
||||||
`verify_tail_positions` in `check_fn`'s post-synth region (DD-1); it
|
|
||||||
implements the `smaller`-set algorithm with implicit candidate
|
|
||||||
inference and unconstrained accumulator positions (DD-2: the
|
|
||||||
foldl-shape accumulator walk classifies as structural), self- and
|
|
||||||
mutual-recursion identification with ADT-family connected components
|
|
||||||
via an inline union-find (DD-3), and the it.2-only `tail==false`
|
|
||||||
grandfather. A new `term_contains_loop` (stopping at `Term::Lam`
|
|
||||||
boundaries, DD-4) injects `"Diverge"` into the raised effect set so
|
|
||||||
the existing `UndeclaredEffect` reconciliation enforces it with no
|
|
||||||
new diagnostic variant; the lam-arrow and `Term::LetRec` cases are
|
|
||||||
wired at their respective sub-effect reconcile sites. DESIGN.md
|
|
||||||
Decision 3 + the §Data-model hook are synced to present tense. All
|
|
||||||
20 tail-app corpus fixtures stay grandfathered-clean;
|
|
||||||
`cargo test --workspace` green at 622 passed / 0 failed; the it.1
|
|
||||||
loop fixtures gained `!Diverge` (in scope per the plan).
|
|
||||||
|
|
||||||
Two spec/plan boundary defects surfaced and were resolved inside
|
|
||||||
the task's own stated invariants (corpus stays clean, structural
|
|
||||||
check not weakened) — see Concerns. Both are recorded so the it.3
|
|
||||||
planner and a future spec-tightening pass have the signal.
|
|
||||||
|
|
||||||
## Per-task notes
|
|
||||||
|
|
||||||
- iter it.2.1: `CheckError::NonStructuralRecursion { callee, arg }`
|
|
||||||
added beside the it.1 `Recur*` variants (bracket-`[code]`-free
|
|
||||||
Display per F2), `code()` → `"non-structural-recursion"`, `ctx()`
|
|
||||||
→ `{callee, arg}`. New pin file `structural_recursion_pin.rs`
|
|
||||||
(loop_recur_pin harness verbatim). `diagnostic.rs` untouched
|
|
||||||
(plan-correctly: `code()` is the registry). RED→GREEN clean.
|
|
||||||
|
|
||||||
- iter it.2.2: the pass + helpers next to `verify_loop_body`:
|
|
||||||
`adt_type_head` / `adt_param_positions` / `ctor_bound_names` /
|
|
||||||
`collect_rec_calls_walk` (single `smaller`-threaded walk folding
|
|
||||||
collection + guardedness, `tail==false` grandfather in the App
|
|
||||||
arm, stops at `Term::Lam`) / `call_guarded_at` /
|
|
||||||
`rec_call_arg_display`. `check_fn`/`check_def` gained a
|
|
||||||
`module_fns: &[&FnDef]` param (plan-anticipated in Step 3.2 for
|
|
||||||
mutual grouping; threaded from the per-module loop; synthetic
|
|
||||||
instance-method fns pass `&[]`). Wired
|
|
||||||
`verify_structural_recursion(f, &env, module_fns)?;` immediately
|
|
||||||
after `verify_tail_positions`. Two over-rejection classes found
|
|
||||||
against the corpus and fixed within the task's own acceptance
|
|
||||||
constraint (see Concerns §1, §2). Status DONE_WITH_CONCERNS.
|
|
||||||
|
|
||||||
- iter it.2.3: ADT-family union-find (`TypeUnionFind`, inline
|
|
||||||
`BTreeMap`-backed, path-halving, no dependency) +
|
|
||||||
`mutual_structural_group` returning a `MutualGroup { members,
|
|
||||||
family_valid }` (a plan-pseudo-vs-real refinement — the plan's
|
|
||||||
`Vec<&FnDef>` could not express the cross-family-negative; an
|
|
||||||
empty group silently drops the cross-call instead of rejecting
|
|
||||||
it). `collect_direct_app_var_callees` for call-graph adjacency
|
|
||||||
(same lam boundary). `verify_structural_recursion` reworked to a
|
|
||||||
single `guarded` closure: self-calls vs callee-cand, cross-calls
|
|
||||||
guarded only if `family_valid`. tree/forest clean, cross-family
|
|
||||||
rejected. RED→GREEN.
|
|
||||||
|
|
||||||
- iter it.2.4: `term_contains_loop` (exhaustive Term match;
|
|
||||||
`Loop=>true`, `Lam=>false`, all else recurse incl. Recur args).
|
|
||||||
Injection in `check_fn` before the declared-vs-raised reconcile.
|
|
||||||
Lam-arrow coherence wired at BOTH the `Term::Lam` synth
|
|
||||||
sub-effect site AND the `Term::LetRec` synth sub-effect site
|
|
||||||
(the plan named only the Lam site; recon line drifted and there
|
|
||||||
are now two structurally-identical reconcile sites — a
|
|
||||||
`Term::LetRec` clause is a fn-equivalent, same as the it.1
|
|
||||||
tail-position treatment, so a loop in a deferred LetRec body must
|
|
||||||
also carry Diverge; injecting at both is the sound completion,
|
|
||||||
not an extra). Four it.1 loop fixtures + two HOF signatures
|
|
||||||
updated to declare `!Diverge` (see Concerns §3). Status
|
|
||||||
DONE_WITH_CONCERNS.
|
|
||||||
|
|
||||||
- iter it.2.5: DESIGN.md Decision 3 rewritten (`Diverge` no longer
|
|
||||||
nominal; carried by loop-bearing / Diverge-calling fns; structural
|
|
||||||
recursion pure+total; `IO` description kept + sharpened) +
|
|
||||||
§Data-model hook sentence → present tense, it.3 retirement
|
|
||||||
retained. Drift anchors (`"t":"loop"`/`"t":"recur"`) untouched —
|
|
||||||
`design_schema_drift`/`schema_coverage`/`spec_drift` green.
|
|
||||||
|
|
||||||
- iter it.2.6: acceptance gate. Full workspace green; all 7 named
|
|
||||||
spec-acceptance pins green; `mut_counter.ail` still runs `55`
|
|
||||||
(grandfathered, unmigrated); all 20 tail-app corpus fixtures
|
|
||||||
grandfathered-clean (zero `non-structural-recursion`).
|
|
||||||
|
|
||||||
- Phase 3 (E2E): `examples/struct_rec_sum_e2e.ail` +
|
|
||||||
`struct_rec_sum_runs_and_prints_15` — an it.2-clean structural
|
|
||||||
recursion (plain non-tail, no `!Diverge`, no `tail-app`) that
|
|
||||||
runs to 15, proving the "total" verdict is behaviourally sound,
|
|
||||||
not just a typecheck assertion. Plus
|
|
||||||
`loop_needs_diverge_runs_and_prints_55` — the Diverge-path twin,
|
|
||||||
proving the Diverge injection is a typecheck-only obligation with
|
|
||||||
zero codegen impact (the loop lowers/runs identically to it.1).
|
|
||||||
|
|
||||||
## Concerns
|
|
||||||
|
|
||||||
- **§1 — Spec/it.2-boundary defect: no-ADT-candidate recursion.**
|
|
||||||
The plan's load-bearing invariant ("the 21 tail-app corpus
|
|
||||||
fixtures stay clean through it.2 because `collect_rec_calls` only
|
|
||||||
collects `tail==false` calls") is insufficient: corpus fixtures
|
|
||||||
like `bench_latency_explicit.ail`'s `build_tree(depth: Int)`
|
|
||||||
recurse **non-tail** (inside a `term-ctor`) on a **primitive**
|
|
||||||
arg — the `tail==false` grandfather does not exempt them, and
|
|
||||||
they have no ADT candidate position. Strict DD-2 would reject
|
|
||||||
~18 corpus fixtures in it.2, contradicting the additive mandate.
|
|
||||||
Resolution (does NOT weaken the ADT check): a def with no ADT
|
|
||||||
candidate position AND no mutual cycle has no *structural*
|
|
||||||
position to verify — it is integer-counter / other-shaped
|
|
||||||
recursion whose migration to `(loop …)` is the destructive it.3
|
|
||||||
corpus pass, not it.2's job. it.2 only rejects *misuse of an ADT
|
|
||||||
structural position*; the spec's load-bearing negative
|
|
||||||
(`f(xs: IntList) = … f(xs) …`, HAS an ADT candidate) still fires.
|
|
||||||
This is the honest reading of DD-2's "for each candidate
|
|
||||||
structural position" (vacuous when there are none); the plan's
|
|
||||||
pseudo-code only handled `calls.is_empty()`, not
|
|
||||||
`cand.is_empty()`. Recorded as a resolved it.2/it.3 boundary
|
|
||||||
clarification — the it.3 planner should expect these to be the
|
|
||||||
corpus-migration set, and a spec-tightening pass may want to
|
|
||||||
state the no-candidate boundary explicitly.
|
|
||||||
|
|
||||||
- **§2 — Two RC-regression fixtures needed the spec's transitional
|
|
||||||
grandfather applied.** `rc_pin_recurse_implicit.ail` and
|
|
||||||
`rc_let_alias_implicit_param.ail` (NOT in the 20-fixture tail-app
|
|
||||||
set) contain `loop(n: Int, t: Tree) = … (app loop (- n 1) t)` —
|
|
||||||
an ADT param (`t: Tree`) held constant while decrementing an Int,
|
|
||||||
recursing **non-tail**. This is structurally identical to the
|
|
||||||
required negative (`f(xs)=…f(xs)…`): both pass the ADT param
|
|
||||||
unchanged at its candidate position. It therefore correctly fires
|
|
||||||
`NonStructuralRecursion` and CANNOT be exempted by broadening the
|
|
||||||
grandfather without also letting the required negative through
|
|
||||||
(which would weaken the check — explicitly forbidden by the
|
|
||||||
carrier). The recursive call is in tail position, so the
|
|
||||||
spec-prescribed transitional treatment applies: mark it
|
|
||||||
`tail-app` (the it.2 "Transitional grandfather" — identical to
|
|
||||||
the idiom 20 other corpus fixtures use; it.3 migrates to
|
|
||||||
`(loop …)`). The 18d.4 / 18g regressions these fixtures guard
|
|
||||||
live in `pin`/`pin_aliased`'s match arm-close, unaffected by the
|
|
||||||
`loop` recursion's lowering — so this is NOT adapting-a-test-to-
|
|
||||||
dodge-a-bug (the check is correct; the fixture joins the
|
|
||||||
transitional grandfather, with an in-fixture comment recording
|
|
||||||
why). Both fixtures' RC==GC e2e regression guards still pass.
|
|
||||||
|
|
||||||
- **§3 — it.1 loop fixtures gained `!Diverge` (plan-in-scope, full
|
|
||||||
enumeration vs plan's two examples).** Plan Step 4.4 named
|
|
||||||
`loop_counter.ail` + `loop_in_lambda_e2e.ail` explicitly; the
|
|
||||||
same in-scope action applies to all it.1 loop fixtures.
|
|
||||||
`loop_counter.ail` (loop in `main` body) → `(effects IO Diverge)`.
|
|
||||||
`loop_smoke.ail` `count_to` (loop in fn body) → `(effects
|
|
||||||
Diverge)`. `loop_nested_in_lambda.ail` / `loop_in_lambda_e2e.ail`
|
|
||||||
(loop in a *lam* body) → `(effects Diverge)` on the **lam**, with
|
|
||||||
the enclosing fn / HOF param-and-return fn-types threaded to
|
|
||||||
carry `!Diverge` (effects are part of the function type — forced,
|
|
||||||
not optional). The lam-boundary rule held: `main` in
|
|
||||||
`loop_in_lambda_e2e` does NOT carry Diverge from its own body
|
|
||||||
(the loop is behind the lam edge) but DOES via callee-effect
|
|
||||||
propagation through `apply` (free per DD-4, identical to `!IO`).
|
|
||||||
The injection was not weakened. Both it.1 loop e2e tests
|
|
||||||
(55 / 49) still pass; round-trip green on every modified `.ail`.
|
|
||||||
|
|
||||||
- **§4 — `MutualGroup` is a plan-pseudo-vs-real-API refinement.**
|
|
||||||
The plan's `mutual_structural_group -> Vec<&FnDef>` could not
|
|
||||||
express "cross-family mutual recursion must be rejected" — an
|
|
||||||
empty return drops the cross-call from collection instead of
|
|
||||||
flagging it. Split into `{ members: HashSet<String>,
|
|
||||||
family_valid: bool }`: membership drives collection (cross-calls
|
|
||||||
always collected), validity drives the verdict (a cross-call into
|
|
||||||
a family-invalid cycle is unconditionally unguarded). Minimal
|
|
||||||
shape satisfying both plan requirements; same documented
|
|
||||||
substitution class as it.1's repeated plan-pseudo-vs-real notes.
|
|
||||||
|
|
||||||
## Known debt
|
|
||||||
|
|
||||||
- The structural check's `rec_call_arg_display` has no real
|
|
||||||
term-pretty-printer (only `type_to_string` exists in
|
|
||||||
`ailang_core::pretty`); non-`Var` offending args render as terse
|
|
||||||
tags (`Ctor(…)`, `fn(…)`, `<expr>`). The canonical non-structural
|
|
||||||
case is always a bare `Var` (the param passed unchanged), so this
|
|
||||||
is cosmetic on the diagnostic only; a future term-pretty-printer
|
|
||||||
would improve the `arg` field. Not drift — recorded for
|
|
||||||
visibility.
|
|
||||||
- Three exhaustive Term-walks now exist in the it.2 region
|
|
||||||
(`collect_rec_calls_walk`, `collect_direct_app_var_callees`,
|
|
||||||
`term_contains_loop`). Each collects a genuinely different thing
|
|
||||||
with different threading; a shared generic walker would be
|
|
||||||
speculative abstraction per the AILang quality bar. Recorded so a
|
|
||||||
future reader does not mistake the triplication for an oversight.
|
|
||||||
- `mutual_structural_group`'s reaches/component BFS is O(n²)-ish
|
|
||||||
over a module's fns, run once per fn-check. Corpus modules are
|
|
||||||
<40 defs; not a hot path. No action; recorded for visibility.
|
|
||||||
|
|
||||||
## Blocked detail
|
|
||||||
|
|
||||||
None — all 6 tasks completed; the two spec/plan boundary defects
|
|
||||||
(Concerns §1, §2) were resolved inside the task's own stated
|
|
||||||
acceptance constraints (corpus stays clean, structural check not
|
|
||||||
weakened), not escalated, because the carrier explicitly anticipated
|
|
||||||
corpus-breakage handling and the resolutions are the spec's own
|
|
||||||
transitional model rather than judgement substitutions.
|
|
||||||
|
|
||||||
## Files touched
|
|
||||||
|
|
||||||
Check core: `crates/ailang-check/src/lib.rs` (CheckError variant +
|
|
||||||
code/ctx; `module_fns` thread through check_def/check_fn;
|
|
||||||
`verify_structural_recursion` + helpers + `TypeUnionFind` +
|
|
||||||
`MutualGroup` + `term_contains_loop`; Diverge injection at
|
|
||||||
check_fn / Term::Lam / Term::LetRec sites).
|
|
||||||
Tests (new): `crates/ailang-check/tests/structural_recursion_pin.rs`
|
|
||||||
(9 pins). Carve-out: `crates/ailang-core/tests/carve_out_inventory.rs`
|
|
||||||
(EXPECTED 17→20 + header comment rewritten accurate).
|
|
||||||
E2E: `crates/ail/tests/e2e.rs` (2 new Phase-3 tests).
|
|
||||||
Spec: `docs/DESIGN.md` (Decision 3 + §Data-model hook).
|
|
||||||
Fixtures (new): `examples/struct_rec_list_len.ail`,
|
|
||||||
`examples/struct_rec_foldl_sum.ail`,
|
|
||||||
`examples/struct_rec_tree_forest.ail`,
|
|
||||||
`examples/struct_rec_sum_e2e.ail`,
|
|
||||||
`examples/loop_needs_diverge.ail`,
|
|
||||||
`examples/test_non_structural_recursion.ail.json`,
|
|
||||||
`examples/test_mutual_cross_family.ail.json`,
|
|
||||||
`examples/test_loop_missing_diverge.ail.json`.
|
|
||||||
Fixtures (modified): `examples/loop_counter.ail`,
|
|
||||||
`examples/loop_smoke.ail`, `examples/loop_nested_in_lambda.ail`,
|
|
||||||
`examples/loop_in_lambda_e2e.ail` (all four: it.1 loop fixtures →
|
|
||||||
`!Diverge`); `examples/rc_pin_recurse_implicit.ail`,
|
|
||||||
`examples/rc_let_alias_implicit_param.ail` (transitional `tail-app`
|
|
||||||
grandfather on the `loop` fn — Concerns §2).
|
|
||||||
|
|
||||||
## Stats
|
|
||||||
|
|
||||||
bench/orchestrator-stats/2026-05-15-iter-it.2.json
|
|
||||||
@@ -1,187 +0,0 @@
|
|||||||
# iter it.3 — retire tail-app/tail-do + corpus migration
|
|
||||||
|
|
||||||
**Date:** 2026-05-15
|
|
||||||
**Started from:** c992eb93df4c5ec0a0686c924535cb9951be552f
|
|
||||||
**Status:** BLOCKED
|
|
||||||
**Tasks completed:** 1 of 7 (Task 1 complete; BLOCKED at the
|
|
||||||
Task-1.3 → Task-2 boundary before any destructive task)
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
Third and terminal iteration of the iteration-discipline milestone
|
|
||||||
(destructive: retire `tail-app`/`tail-do`, migrate the corpus).
|
|
||||||
Task 1 (the non-destructive pre-migration oracle + the spec-delegated
|
|
||||||
class-(b) live enumeration) ran to completion: 40 recursive corpus
|
|
||||||
fixtures classified, 40 behavioural oracles captured under
|
|
||||||
`bench/it3-oracle/`, `MANIFEST.tsv` written. Task 1.3's mandated
|
|
||||||
live sweep surfaced a **real spec-signal** that the plan's own
|
|
||||||
self-review (lines 550-560) and the carrier explicitly defined as a
|
|
||||||
BLOCKED-to-Boss condition, not a silent reshape: **six fixtures
|
|
||||||
contain a recursive fn that resists BOTH a natural `(loop)`/`recur`
|
|
||||||
form AND a structural-plain form.** Per DD-2/DD-3 (migrate before
|
|
||||||
remove; Task 4's completeness gate must not be softened and a RED
|
|
||||||
there is a Task-2 defect, never a restored exemption) there is **no
|
|
||||||
in-plan migration target** for these six. The destructive tasks
|
|
||||||
(4/5) were therefore not started — correctly, since the migration
|
|
||||||
they depend on is impossible for this subset and DD-3 forbids
|
|
||||||
working around it. No production code was modified; the working
|
|
||||||
tree contains only the new `bench/it3-oracle/` artefacts.
|
|
||||||
|
|
||||||
## Per-task notes
|
|
||||||
|
|
||||||
- iter it.3.1: Pre-migration oracle + migration-set enumeration —
|
|
||||||
**complete.** `cargo build -p ail` clean at HEAD c992eb9.
|
|
||||||
Class-(a): 23 `(tail-app|tail-do)` fixtures (recon-confirmed).
|
|
||||||
Class-(b) live sweep (Task 1.3, spec-delegated): read
|
|
||||||
`verify_structural_recursion` + the two it.2 exemptions
|
|
||||||
(`lib.rs:3027` `if !*tail` grandfather; `lib.rs:3197`
|
|
||||||
`if cand.is_empty() && group.members.is_empty()` no-ADT skip),
|
|
||||||
then classified every self-recursive corpus fn against the
|
|
||||||
post-Task-4 verdict logic (`lib.rs:3219-3267`). Authoritative
|
|
||||||
result: 13 no-ADT-candidate single-recursion fns are
|
|
||||||
loop-migratable; 11 fixtures recurse structurally on an ADT param
|
|
||||||
(clean before AND after Task 4 — no migration, only the redundant
|
|
||||||
`tail-app` marker if class-(a) is dropped); **6 fixtures have a
|
|
||||||
no-ADT-candidate DOUBLE non-tail recursion (binary-tree build)
|
|
||||||
that is neither structural nor loop-expressible.** Oracles
|
|
||||||
captured for all 40 (`bench/it3-oracle/<stem>.out` + `MANIFEST.tsv`).
|
|
||||||
Task 1.5 fallout list recorded below for the eventual unblocked
|
|
||||||
re-dispatch. No code changed (Task 1 is non-destructive by spec).
|
|
||||||
|
|
||||||
- iter it.3.2 … it.3.7: not started. Task 2 (migration) has no
|
|
||||||
valid action for the 6 blocked fixtures; Tasks 4/5 are
|
|
||||||
destructive and gated behind a complete Task-2 migration that
|
|
||||||
cannot be completed. Halted at the spec-signal per carrier.
|
|
||||||
|
|
||||||
## Concerns
|
|
||||||
|
|
||||||
- **The RC-RSS / 18g.1 load-bearing risk (DD-4 / Task 5.5 / 5b) was
|
|
||||||
NOT reached.** The iter blocked before Task 5, so the 18g.1
|
|
||||||
husk-dec deletion and the RC-RSS bench gate were never exercised.
|
|
||||||
No RC-RSS evidence exists from this dispatch. The milestone-close
|
|
||||||
audit must NOT treat the RC-RSS contingency as resolved — it is
|
|
||||||
untested. When the spec defect below is resolved and it.3 is
|
|
||||||
re-dispatched, Task 5.5 (and conditionally 5b) is still the
|
|
||||||
milestone's load-bearing risk and must be run + its evidence
|
|
||||||
journalled then.
|
|
||||||
|
|
||||||
## Known debt
|
|
||||||
|
|
||||||
- `bench/it3-oracle/` currently holds 40 `.out` snapshots +
|
|
||||||
`MANIFEST.tsv` (41 files). They are the Task-1 deliverable and
|
|
||||||
stay valid for a re-dispatch from the same `start_sha`. Boss
|
|
||||||
call at commit whether to keep them under `bench/` (plan §"Files
|
|
||||||
this plan creates": "deleted in Task 6 … or kept under `bench/`
|
|
||||||
if cheap") — but do NOT delete before the re-dispatch; the
|
|
||||||
re-dispatched Task 7.3 diffs against them.
|
|
||||||
- The 11 class-(a) `plaincall` fixtures (structural on an ADT param,
|
|
||||||
carrying only a redundant `tail-app` marker) and the 11
|
|
||||||
`already-structural` fixtures need no semantic migration — only
|
|
||||||
marker-stripping (class-a) — and are unaffected by the blocker.
|
|
||||||
Recorded so the re-dispatch does not re-derive this.
|
|
||||||
|
|
||||||
## Blocked detail
|
|
||||||
|
|
||||||
**Task:** 1 (the Task-1.3 spec-delegated sweep) → halts entry to
|
|
||||||
Task 2 / Task 4.
|
|
||||||
|
|
||||||
**Reason:** `spec-ambiguous` / spec defect surfaced by the mandated
|
|
||||||
live sweep — six corpus fixtures contain a recursive fn that is
|
|
||||||
**neither structural recursion (no ADT-typed parameter — `cand`
|
|
||||||
empty in `verify_structural_recursion`) nor expressible as
|
|
||||||
`(loop …)`/`recur`** (the recursion is two non-tail self-calls
|
|
||||||
building both children of a binary `Tree`; `recur` is
|
|
||||||
tail-position-only with a single back-edge, so a balanced-tree
|
|
||||||
build cannot be a tail loop). After Task 4 removes the
|
|
||||||
no-ADT-candidate skip (`lib.rs:3197`, mandated by DD-3 and the
|
|
||||||
spec's it.3 acceptance bullet "no grandfather/no-candidate skip
|
|
||||||
remains"), each of these deterministically fires
|
|
||||||
`CheckError::NonStructuralRecursion` (verdict logic `lib.rs:3219-3267`:
|
|
||||||
`cand` empty ⇒ `self_clear=false` ⇒ NSR). DD-3 forbids softening
|
|
||||||
Task 4 or restoring the exemption; the carrier and project memory
|
|
||||||
(`feedback_dont_adapt_tests_to_bugs`) forbid reshaping the
|
|
||||||
LLM-natural fixture (e.g. changing `depth: Int` to an ADT-encoded
|
|
||||||
`Nat`, or hand-rolling an explicit stack/continuation). There is
|
|
||||||
**no in-plan migration target**, so the destructive split cannot
|
|
||||||
proceed.
|
|
||||||
|
|
||||||
**The six fixtures (fn :: shape):**
|
|
||||||
|
|
||||||
1. `examples/bench_tree_walk.ail` :: `build_tree`
|
|
||||||
(`(if (== depth 0) (Tree Leaf) (Tree Node 1 (build_tree (- depth 1)) (build_tree (- depth 1))))`)
|
|
||||||
2. `examples/bench_latency_explicit.ail` :: `build_tree` (same shape; file is class-(a) for other fns but `build_tree` itself is non-tail double recursion)
|
|
||||||
3. `examples/bench_latency_implicit.ail` :: `build_tree` (same)
|
|
||||||
4. `examples/rc_let_owned_app_leak.ail` :: `build` (same Tree shape on `d: Int`)
|
|
||||||
5. `examples/rc_pin_recurse_implicit.ail` :: `build` (same; the RC-regression-guard fixture from it.2 Concerns §2)
|
|
||||||
6. `examples/rc_let_alias_implicit_param.ail` :: `build` (same; the RC-regression-guard fixture from it.2 Concerns §2)
|
|
||||||
|
|
||||||
This is exactly the spec-signal the it.2 journal §1 foreshadowed
|
|
||||||
(it named `bench_latency_explicit:build_tree` as a no-ADT-candidate
|
|
||||||
case "whose migration to `(loop …)` is the destructive it.3 corpus
|
|
||||||
pass") and the plan self-review's "Recorded risk" anticipated
|
|
||||||
("a genuine non-structural recursion with no natural loop form …
|
|
||||||
surfaces as a Task-2 BLOCKED to the Boss with the fixture named").
|
|
||||||
The spec/plan assumed every no-ADT-candidate recursion is a
|
|
||||||
loopable counter/accumulator; balanced-binary-tree construction by
|
|
||||||
double recursion on a depth counter is the counter-example.
|
|
||||||
|
|
||||||
**Suggested next step:** Boss-level spec decision required (this is
|
|
||||||
not an implementer or planner judgement call — see
|
|
||||||
`feedback_spec_over_plan_patches`: a spec defect goes back through
|
|
||||||
`brainstorm`, not a fourth plan patch). Candidate resolutions for
|
|
||||||
the Boss to weigh, language-first:
|
|
||||||
(a) **Add a structural accommodation for tree recursion** — extend
|
|
||||||
`verify_structural_recursion` so a non-tail recursive call on a
|
|
||||||
*structurally-derived smaller value* is guarded even when the
|
|
||||||
decreasing quantity is a primitive depth counter feeding an ADT
|
|
||||||
build (i.e. recognise the `build_tree` shape as bounded). This
|
|
||||||
keeps the six fixtures verbatim and is the only option that does
|
|
||||||
not reshape LLM-natural code, but it widens it.2's structural
|
|
||||||
checker — a Decision-10/Decision-3 spec change, brainstorm-gated.
|
|
||||||
(b) **Carve the six into a permanent allow-list / carve-out** (like
|
|
||||||
the existing `.ail.json` carve-outs) with an explicit
|
|
||||||
"constructor-blocked recursion, depth-bounded by construction"
|
|
||||||
annotation, leaving the no-ADT-candidate skip retired for
|
|
||||||
everything else. Requires a spec sentence defining the carve-out
|
|
||||||
predicate.
|
|
||||||
(c) **Retire the bench fixtures that exist only to exercise the
|
|
||||||
removed `musttail`/RC-RSS path** (`bench_latency_*`,
|
|
||||||
`rc_pin_recurse_implicit`, `rc_let_alias_implicit_param`,
|
|
||||||
`rc_let_owned_app_leak`) and replace `bench_tree_walk`'s
|
|
||||||
`build_tree` coverage with a structural equivalent — but this is
|
|
||||||
the "adapt the fixture to dodge" path the carrier and
|
|
||||||
`feedback_dont_adapt_tests_to_bugs` explicitly forbid for
|
|
||||||
language-limitation cases; named only for completeness, not
|
|
||||||
recommended.
|
|
||||||
Once the Boss resolves the spec, re-dispatch it.3 from the same
|
|
||||||
`start_sha` (the `bench/it3-oracle/` artefacts remain valid).
|
|
||||||
|
|
||||||
## Files touched
|
|
||||||
|
|
||||||
Created (untracked, non-code):
|
|
||||||
- `bench/it3-oracle/MANIFEST.tsv` (40-fixture classification)
|
|
||||||
- `bench/it3-oracle/<stem>.out` × 40 (behavioural oracle:
|
|
||||||
`ail run` stdout for `main` fixtures, `ail check` output for
|
|
||||||
check-only)
|
|
||||||
|
|
||||||
No production code, no spec, no test files modified (Task 1 is
|
|
||||||
non-destructive by plan; the BLOCKED halted before any code-edit
|
|
||||||
task).
|
|
||||||
|
|
||||||
## Task 1.5 fallout map (recorded for the unblocked re-dispatch)
|
|
||||||
|
|
||||||
Tail-app-specific tests the plan's Task 6.4 will delete:
|
|
||||||
`crates/ailang-prose` `app_tail_renders_with_keyword`,
|
|
||||||
`binop_with_tail_flag_keeps_prefix_form`,
|
|
||||||
`not_with_tail_flag_keeps_prefix_form`;
|
|
||||||
`crates/ailang-core/tests/spec_drift.rs::spec_mentions_tail_variants`;
|
|
||||||
`crates/ailang-check` `TailCallNotInTailPosition` pins
|
|
||||||
(`tail_call_in_tail_position_is_accepted` /
|
|
||||||
`..._non_tail_position_is_rejected` if present); any
|
|
||||||
`iter14e_*musttail*` e2e. (Enumeration deferred to the
|
|
||||||
re-dispatch's Task 1.5 against the then-current tree, since the
|
|
||||||
exact test names will drift; recorded here as the recon seed.)
|
|
||||||
|
|
||||||
## Stats
|
|
||||||
|
|
||||||
bench/orchestrator-stats/2026-05-15-iter-it.3.json
|
|
||||||
@@ -1,215 +0,0 @@
|
|||||||
# iter mut.1 — schema + surface for local mutable state
|
|
||||||
|
|
||||||
**Date:** 2026-05-15
|
|
||||||
**Started from:** 60e4559e31d7f09de77e2679aab2d524de257a61
|
|
||||||
**Status:** DONE
|
|
||||||
**Tasks completed:** 6 of 6
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
Lands the foundational AST + surface forms for the mut-local
|
|
||||||
milestone (first milestone on the Stateful-islands roadmap path).
|
|
||||||
Two new `Term` variants (`Mut` and `Assign`) plus the nested
|
|
||||||
`MutVar` struct enter `ailang-core::ast`; canonical-JSON serde
|
|
||||||
round-trips them by virtue of the existing `#[serde(tag = "t")]`
|
|
||||||
machinery. Form A gains `(mut …)` / `(var …)` / `(assign …)`
|
|
||||||
productions in the parser and the corresponding write arms in the
|
|
||||||
printer; the parser right-folds the trailing body sequence into
|
|
||||||
`Term::Seq` so the canonical JSON-AST always sees a single
|
|
||||||
`body: Term`. Every `Term` exhaustive match in the workspace (~25
|
|
||||||
walker sites) gains substantive arms, except the two dispatch
|
|
||||||
entry points (`synth` in `ailang-check`, `lower_term` in
|
|
||||||
`ailang-codegen`) which stub with `CheckError::Internal` /
|
|
||||||
`CodegenError::Internal` per the spec's "out of iteration"
|
|
||||||
boundary — typecheck recognition lands in mut.2, codegen lowering
|
|
||||||
in mut.3. The three drift tests (`design_schema_drift`,
|
|
||||||
`spec_drift`, `schema_coverage`) all flip green after the
|
|
||||||
exemplar / variant-tag / visitor extensions, and `examples/mut.ail`
|
|
||||||
ships as the round-trip fixture covering empty / single-var /
|
|
||||||
two-var / nested-shadow / Bool / Unit cases. Full
|
|
||||||
`cargo test --workspace` 579/579 green (was 564 + 2 new AST pins +
|
|
||||||
4 new parser pins + 9 from the extended drift exemplars and the
|
|
||||||
mut.ail-corpus contributions netting to +15).
|
|
||||||
|
|
||||||
## Per-task notes
|
|
||||||
|
|
||||||
- iter mut.1.1 — AST extension: added `Term::Mut { vars: Vec<MutVar>,
|
|
||||||
body: Box<Term> }`, `Term::Assign { name: String, value: Box<Term> }`,
|
|
||||||
and the `MutVar { name, ty, init }` struct to `crates/ailang-core/src/ast.rs`.
|
|
||||||
Two new unit tests pin the canonical-bytes of the empty-vars mut
|
|
||||||
serialisation and the Assign round-trip. `MutVar` carries Arm's
|
|
||||||
derives (`Debug, Clone, Serialize, Deserialize`) — the plan's
|
|
||||||
literal pseudo-code asked for `PartialEq + Eq` but its own
|
|
||||||
justification ("derives match Arm's for cross-tree consistency")
|
|
||||||
points at Arm, which does not derive those. Concern recorded;
|
|
||||||
no functional impact (the Task 1 tests do not depend on
|
|
||||||
`PartialEq`, and `Term` itself uses a custom `PartialEq` impl on
|
|
||||||
`Type` only).
|
|
||||||
|
|
||||||
- iter mut.1.2 — substantive walker arms: 9 sites in `desugar.rs`,
|
|
||||||
2 in `workspace.rs`, 3 in `ailang-check/src/lib.rs` (1 in
|
|
||||||
`substitute_rigids_in_term`, 1 in `verify_tail_positions`, 1
|
|
||||||
variant-name string emitter inside `synth`'s `Term::ReuseAs` sub-
|
|
||||||
match), 2 in `lift.rs`, 3 in `linearity.rs` (one being a
|
|
||||||
variant-name string emitter), 2 in `mono.rs`, 1 each in
|
|
||||||
`pre_desugar_validation.rs`, `reuse_shape.rs`, `uniqueness.rs`,
|
|
||||||
3 in `escape.rs`, 1 in `lambda.rs`, 1 in `codegen/src/lib.rs`
|
|
||||||
(the `synth_with_extras` type-synthesis helper), 3 in
|
|
||||||
`ailang-prose/src/lib.rs`, 2 in `ail/src/main.rs`. Plus one
|
|
||||||
unenumerated site in `crates/ail/tests/codegen_import_map_fallback_pin.rs`
|
|
||||||
(an exhaustive walker inside a test) that the build required.
|
|
||||||
Each arm follows the appropriate shape per existing convention
|
|
||||||
at its site: rebuilder shapes for `substitute_*` / `subst_*` /
|
|
||||||
`desugar_term` / `lift_in_term` / `substitute_rigids_in_term` /
|
|
||||||
`rewrite_term`, visitor shapes for `collect_used_in_term` /
|
|
||||||
`free_vars_in_term` / `find_non_callee_use` / `walk_term*` /
|
|
||||||
`verify_tail_positions` / `term_has_letrec` / the linearity
|
|
||||||
walkers / `walk` in `reuse_shape.rs` / `walk` in `uniqueness.rs`
|
|
||||||
/ `walk` in `escape.rs` / `collect_captures` / `count_free_var` /
|
|
||||||
`subst_var_with_term` (rebuilder-shape in prose). Shadowing
|
|
||||||
semantics across mut-var bindings replicates the
|
|
||||||
`Term::Let`/`Term::Lam`/`Term::LetRec`-shaped convention at each
|
|
||||||
site: var-init terms see the outer scope plus already-declared
|
|
||||||
vars; later var-inits and the body see all earlier var bindings.
|
|
||||||
|
|
||||||
- iter mut.1.3 — dispatch stubs: typecheck dispatch at `synth` in
|
|
||||||
`ailang-check/src/lib.rs` (Plan line 2572 was wrong — that line
|
|
||||||
is `verify_tail_positions`, which is substantive walker
|
|
||||||
territory; the actual dispatch entry is `synth` at line 3403's
|
|
||||||
former `Term::ReuseAs` arm, where the two new arms now sit)
|
|
||||||
returns `CheckError::Internal("Term::Mut/Assign not yet
|
|
||||||
supported in typecheck (deferred to iter mut.2)")`. Codegen
|
|
||||||
dispatch at `lower_term` in `ailang-codegen/src/lib.rs:1649`
|
|
||||||
returns `CodegenError::Internal("Term::Mut/Assign not yet
|
|
||||||
supported in codegen (deferred to iter mut.3)")`. Both errors
|
|
||||||
use the single-field tuple-variant shape (`Internal(String)`).
|
|
||||||
|
|
||||||
- iter mut.1.4 — Form A parser + printer: dispatcher arms added
|
|
||||||
for `"mut" => parse_mut` and `"assign" => parse_assign` in
|
|
||||||
`parse_term`'s head-keyword match; the two helpers parse
|
|
||||||
positionally (mut: pull leading `(var ...)` entries, then ≥ 1
|
|
||||||
body terms right-folded into `Term::Seq`; assign: positional
|
|
||||||
ident-then-value pair). Plan pseudo-code used helper names
|
|
||||||
(`peek_is_open_paren_with_head`, `expect_head`, etc.) that don't
|
|
||||||
exist; substituted the actual API (`expect_lparen`,
|
|
||||||
`expect_keyword`, `expect_ident`, `parse_type`, `parse_term`,
|
|
||||||
`expect_rparen`, `peek_head_ident`, `matches!(self.peek(),
|
|
||||||
Tok::RParen)`). Four RED-first parser tests added covering empty
|
|
||||||
mut + 1-var-1-assign-1-final + missing-body rejection (both
|
|
||||||
shapes). Print arms in `print.rs` walk the right-spine of
|
|
||||||
`Term::Seq` and emit each lhs as a top-level statement; the
|
|
||||||
terminal expression closes the `(mut …)` form. EBNF prologue in
|
|
||||||
`parse.rs` extended with `mut-term`, `var-decl`, `assign-term`
|
|
||||||
productions. The print arms had to land during the Task 1+2
|
|
||||||
build-unblock phase rather than waiting for Task 4 (they are
|
|
||||||
exhaustive-match neighbours of the synth/lower_term dispatchers);
|
|
||||||
this is a benign sequencing shift and the plan's "Task 2 build-
|
|
||||||
green verification gate" still holds.
|
|
||||||
|
|
||||||
- iter mut.1.5 — drift + coverage extensions: two new exemplars
|
|
||||||
added to `design_schema_drift.rs` and two new match arms; two
|
|
||||||
new `VariantTag` entries (`TermMut`, `TermAssign`),
|
|
||||||
`EXPECTED_VARIANTS` extended, two new `visit_term` arms in
|
|
||||||
`schema_coverage.rs`; two new exemplars + two new match arms in
|
|
||||||
`spec_drift.rs`. `form_a.md` gains three new lines in the
|
|
||||||
Parenthesised-forms block plus a prose paragraph naming the
|
|
||||||
empty-vars and post-vars-body invariants and the diagnostic
|
|
||||||
codes. `DESIGN.md` §"Term (expression)" gains the two jsonc
|
|
||||||
schema blocks immediately after the `reuse-as` block plus a
|
|
||||||
closing prose paragraph naming the mut.1 dispatch-stub semantics
|
|
||||||
and the deferred mut.2 / mut.3 iterations. Post-edit:
|
|
||||||
`design_schema_drift` and `spec_drift` both green;
|
|
||||||
`schema_coverage` is RED on the missing `examples/mut.ail`
|
|
||||||
fixture, per plan Step 6 expectation.
|
|
||||||
|
|
||||||
- iter mut.1.6 — fixture + round-trip: `examples/mut.ail` shipped
|
|
||||||
with the six fns from plan Step 1 verbatim (empty / single-var
|
|
||||||
with assign / two-var with combine / nested-shadow / Bool /
|
|
||||||
Unit). The corpus-globbing round-trip test
|
|
||||||
(`parse_then_print_then_parse_is_idempotent_on_every_ail_fixture`)
|
|
||||||
and the parse-determinism test pick the fixture up automatically;
|
|
||||||
both green. `schema_coverage` also green now that both new
|
|
||||||
variants are observed in the corpus. Full `cargo test
|
|
||||||
--workspace` 579/579 green.
|
|
||||||
|
|
||||||
## Concerns
|
|
||||||
|
|
||||||
- `MutVar` derives diverge from the plan's literal pseudo-code
|
|
||||||
(`Debug, Clone, PartialEq, Eq, Serialize, Deserialize`); the
|
|
||||||
shipped form is `Debug, Clone, Serialize, Deserialize` to match
|
|
||||||
Arm's pattern. The plan's own justification ("match Arm's
|
|
||||||
derives for cross-tree consistency") points at Arm which does
|
|
||||||
not derive PartialEq/Eq. Functional impact: none in mut.1
|
|
||||||
(the Task 1 tests do not depend on PartialEq, and `Term` uses
|
|
||||||
a custom PartialEq impl scoped to `Type` only). A future iter
|
|
||||||
that needs to compare `MutVar` (e.g. for in-place rewriting in
|
|
||||||
mut.3 codegen) can add the derives at that time without breaking
|
|
||||||
any mut.1 test.
|
|
||||||
|
|
||||||
- Plan recon for the typecheck dispatch entry was misindexed.
|
|
||||||
The plan listed `crates/ailang-check/src/lib.rs:2572` as the
|
|
||||||
"check_term dispatch — STUBBED" site; that line is actually in
|
|
||||||
`verify_tail_positions`, a substantive walker. The real
|
|
||||||
typecheck dispatch entry is `synth` (defined at lib.rs:2613,
|
|
||||||
with its `Term::ReuseAs` arm at line 3403 before the new mut
|
|
||||||
arms). The stub was placed at the actual dispatch entry; line
|
|
||||||
2572 received a substantive arm. The variant-name string
|
|
||||||
emitter sub-match referenced as line 3430 is inside `synth`'s
|
|
||||||
former `Term::ReuseAs` arm and was extended as Task 2 Step 13
|
|
||||||
asks. Net: the spec intent (stub at typecheck dispatch) is
|
|
||||||
preserved; only the line-number routing differs.
|
|
||||||
|
|
||||||
- One test-side walker site (`crates/ail/tests/codegen_import_map_fallback_pin.rs:62`)
|
|
||||||
carries an exhaustive match on `Term` and was not enumerated
|
|
||||||
by the plan's Task 2 file list. The site is the body-walker of
|
|
||||||
a regression-pin test from iter 24.tidy; it required two new
|
|
||||||
arms to build green. Added with appropriate visitor-shape
|
|
||||||
recursion. The plan-recon miss is recorded here as a
|
|
||||||
documentary item; no behavioural impact.
|
|
||||||
|
|
||||||
## Known debt
|
|
||||||
|
|
||||||
- Prose-side `(mut ...)` rendering in `ailang-prose/src/lib.rs` is
|
|
||||||
a minimal-correctness shape (`mut { var <name> = <init>; ...; <body> }`).
|
|
||||||
The prose surface for mut blocks is not yet fully designed; a
|
|
||||||
future prose-iter once the LLM-author signal arrives will refine
|
|
||||||
it. This is a deliberate placeholder, not drift — recording for
|
|
||||||
visibility.
|
|
||||||
|
|
||||||
- `subst_var` in `desugar.rs` does NOT rename the `Term::Assign.name`
|
|
||||||
field. The desugar pass's `subst_var` rewrites let-rec captures
|
|
||||||
(KnownType / LetBound / MatchArm), none of which can be mut-vars
|
|
||||||
(mut-vars are first-order scalars, not captureable). Leaving
|
|
||||||
the assign-name untouched is semantically correct; documented in
|
|
||||||
the arm's doc-comment. Mentioned here for visibility.
|
|
||||||
|
|
||||||
## Files touched
|
|
||||||
|
|
||||||
- `crates/ailang-core/src/ast.rs` — `Term::Mut`, `Term::Assign`, `MutVar`, two new unit tests
|
|
||||||
- `crates/ailang-core/src/desugar.rs` — 9 walker arms
|
|
||||||
- `crates/ailang-core/src/workspace.rs` — 2 walker arms
|
|
||||||
- `crates/ailang-check/src/lib.rs` — 3 walker arms (substantive + variant-name) + 2 dispatch stubs in `synth`
|
|
||||||
- `crates/ailang-check/src/lift.rs` — 2 walker arms
|
|
||||||
- `crates/ailang-check/src/linearity.rs` — 3 walker arms (2 substantive + 1 variant-name)
|
|
||||||
- `crates/ailang-check/src/mono.rs` — 2 walker arms (1 mut-ref rebuilder, 1 visitor)
|
|
||||||
- `crates/ailang-check/src/pre_desugar_validation.rs` — 1 walker arm
|
|
||||||
- `crates/ailang-check/src/reuse_shape.rs` — 1 walker arm
|
|
||||||
- `crates/ailang-check/src/uniqueness.rs` — 1 walker arm
|
|
||||||
- `crates/ailang-codegen/src/escape.rs` — 3 walker arms
|
|
||||||
- `crates/ailang-codegen/src/lambda.rs` — 1 walker arm
|
|
||||||
- `crates/ailang-codegen/src/lib.rs` — 1 walker arm (`synth_with_extras`) + 2 dispatch stubs in `lower_term`
|
|
||||||
- `crates/ailang-prose/src/lib.rs` — 3 walker arms (1 substantive, 1 `count_free_var`, 1 `subst_var_with_term` rebuilder)
|
|
||||||
- `crates/ail/src/main.rs` — 2 walker arms (dep-walker + `rewrite_term`)
|
|
||||||
- `crates/ail/tests/codegen_import_map_fallback_pin.rs` — 1 walker arm (test-side, unenumerated by plan)
|
|
||||||
- `crates/ailang-surface/src/parse.rs` — dispatcher arms, `parse_mut` / `parse_assign` helpers, EBNF prologue extension, 4 new parser tests
|
|
||||||
- `crates/ailang-surface/src/print.rs` — 2 `write_term` arms
|
|
||||||
- `crates/ailang-core/tests/design_schema_drift.rs` — 2 exemplars + 2 match arms
|
|
||||||
- `crates/ailang-core/tests/schema_coverage.rs` — 2 VariantTag + EXPECTED_VARIANTS + 2 visit_term arms
|
|
||||||
- `crates/ailang-core/tests/spec_drift.rs` — 2 exemplars + 2 match arms
|
|
||||||
- `crates/ailang-core/specs/form_a.md` — 3 new productions + prose paragraph
|
|
||||||
- `docs/DESIGN.md` — 2 jsonc schema blocks + prose paragraph
|
|
||||||
- `examples/mut.ail` — round-trip fixture (6 fns)
|
|
||||||
|
|
||||||
## Stats
|
|
||||||
|
|
||||||
bench/orchestrator-stats/2026-05-15-iter-mut.1.json
|
|
||||||
@@ -1,256 +0,0 @@
|
|||||||
# iter mut.2 — typecheck recognition of `Term::Mut` and `Term::Assign`
|
|
||||||
|
|
||||||
**Date:** 2026-05-15
|
|
||||||
**Started from:** 3a5b1d33472e5c35c4917702e30fa5a12b749e31
|
|
||||||
**Status:** DONE
|
|
||||||
**Tasks completed:** 7 of 7
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
Replaces the mut.1 dispatch stubs in `synth` (`ailang-check`) for
|
|
||||||
`Term::Mut` and `Term::Assign` with real typecheck logic. Three new
|
|
||||||
`CheckError` variants ship (`MutAssignOutOfScope`,
|
|
||||||
`AssignTypeMismatch`, `UnsupportedMutVarType`) with kebab-coded
|
|
||||||
`code()` arms and structured `ctx()` payloads, following the
|
|
||||||
cli-diag-human bracketed-`[code]` convention. A new
|
|
||||||
`mut_scope_stack: &mut Vec<IndexMap<String, Type>>` parameter
|
|
||||||
threads through every `synth` call site (24 sites across lib.rs +
|
|
||||||
mono.rs:712 / mono.rs:1337 + lift.rs:723 + builtins.rs in two test
|
|
||||||
helpers); each entry-point caller (`check_fn`, `check_const`, mono
|
|
||||||
re-synth, lift letrec-capture synth) opens a fresh empty stack at
|
|
||||||
top-of-body. `Term::Var` resolution prepends the mut-scope check
|
|
||||||
ahead of the existing locals/globals chain (innermost-first wins on
|
|
||||||
shadowing). `Term::Mut` pushes a frame after gating each
|
|
||||||
`MutVar.ty` against `is_supported_mut_var_type` (Int / Float /
|
|
||||||
Bool / Unit), synths inits in the in-progress scope, unifies init
|
|
||||||
type with declared, and synths the body in the extended scope.
|
|
||||||
`Term::Assign` resolves the target name through the stack, returns
|
|
||||||
`MutAssignOutOfScope` (with a flat-deduplicated `available` list)
|
|
||||||
on miss, compares value type against the declared via
|
|
||||||
`subst.apply` + equality (manual to preserve the
|
|
||||||
`AssignTypeMismatch` diagnostic identity), and yields `Type::unit()`
|
|
||||||
on success. Five `.ail.json` fixtures + a 5-test driver
|
|
||||||
(`mut_typecheck_pin.rs`) cover each diagnostic plus the positive
|
|
||||||
nested-shadow case; the carve-out inventory at
|
|
||||||
`carve_out_inventory.rs` is extended 7 → 12 to accept them.
|
|
||||||
`examples/mut.ail` (the mut.1 six-fn round-trip fixture) now
|
|
||||||
typechecks clean via `ail check`. Codegen stubs unchanged —
|
|
||||||
`examples/mut.ail` still cannot run end-to-end; that lands in mut.3.
|
|
||||||
Full `cargo test --workspace`: 592 passed / 0 failed (was 579 at
|
|
||||||
start of mut.2; +13 = 5 driver pins + 7 lib.rs `mod tests` pins +
|
|
||||||
1 inventory test count unchanged — the inventory entry count went
|
|
||||||
from `seven` to `twelve` but the test itself was already counted).
|
|
||||||
|
|
||||||
## Per-task notes
|
|
||||||
|
|
||||||
- iter mut.2.1 — `CheckError` variants + `code()` + `ctx()`:
|
|
||||||
Three new variants land before the `Internal` variant with
|
|
||||||
thiserror `#[error("[code] message")]` attributes per the
|
|
||||||
cli-diag-human convention. `code()` arms return the canonical
|
|
||||||
kebab strings (`mut-assign-out-of-scope`, `assign-type-mismatch`,
|
|
||||||
`mut-var-unsupported-type`). `ctx()` arms emit the structured
|
|
||||||
payloads named in the plan (`{name, available}`,
|
|
||||||
`{name, expected, actual}`, `{name, type}`). One in-crate pin
|
|
||||||
(`mut_typecheck_diagnostics_have_kebab_codes`) gates the code()
|
|
||||||
+ ctx() shape; RED → GREEN observed.
|
|
||||||
|
|
||||||
- iter mut.2.2 — `synth` signature threads `mut_scope_stack`:
|
|
||||||
New parameter `mut_scope_stack: &mut Vec<IndexMap<String, Type>>`
|
|
||||||
sits immediately after `locals` (canonical mid-position; matches
|
|
||||||
the existing convention for per-fn-body lexical state). 18
|
|
||||||
recursive call sites in `lib.rs::synth` updated via
|
|
||||||
pattern-by-pattern `replace_all` (each first-arg form had a
|
|
||||||
unique-enough wrapper). Entry-point callers in `check_fn:1934`
|
|
||||||
and `check_const:2687` declare a fresh empty stack. mono.rs's
|
|
||||||
two re-synth sites at 712 + 1337 also declare a fresh empty
|
|
||||||
stack (they begin from a top-of-body position). Plan-recon
|
|
||||||
missed two more call sites: `lift.rs:723` (letrec-capture
|
|
||||||
re-synth) and `builtins.rs` (two test helpers
|
|
||||||
`synth_in_builtins_env` + a FloatPatternNotAllowed pin) — both
|
|
||||||
required for build green; both get fresh empty stacks. The
|
|
||||||
in-test mq.3 call site at `lib.rs:6663` also got the new
|
|
||||||
parameter. Build was red structurally (E0061 at 23+ sites) until
|
|
||||||
every call site was updated, then green.
|
|
||||||
|
|
||||||
- iter mut.2.3 — `Term::Var` prepends mut-scope resolution: A 5-
|
|
||||||
line block at the top of the Var arm walks
|
|
||||||
`mut_scope_stack.iter().rev()` innermost-first, returning the
|
|
||||||
matched type cloned. Mut-vars are monomorphic per spec §"var
|
|
||||||
element types" — no Forall instantiation, no FreeFnCall. Two
|
|
||||||
pins (`mut_var_resolution_shadows_outer_local`,
|
|
||||||
`mut_var_resolution_picks_innermost_frame`) directly populate
|
|
||||||
the stack to isolate this change from the Mut-arm work in
|
|
||||||
Task 4 (the plan's literal Let { Mut { ... } } shape would have
|
|
||||||
hit the still-stubbed Mut arm at this stage; the property
|
|
||||||
protected — mut-scope wins over locals + innermost-wins on
|
|
||||||
shadowing — is identical). TDD discipline gap: the Var-arm edit
|
|
||||||
landed before the pin tests were observed RED on disk; the
|
|
||||||
reasoning is analytical (the pre-edit code consults
|
|
||||||
`locals.get(name)` first, so with `locals = { x → Int }` the
|
|
||||||
Var arm returned Int, failing the assert-Float; same logic for
|
|
||||||
the two-frame test). Recorded as Concern.
|
|
||||||
|
|
||||||
- iter mut.2.4 — `Term::Mut` typecheck arm: Replaces the stub
|
|
||||||
with real logic per plan Step 3. A file-scope helper
|
|
||||||
`is_supported_mut_var_type(&Type) -> bool` gates each var's
|
|
||||||
declared type to `Int | Float | Bool | Unit` (no args). Per-var
|
|
||||||
loop synths the init in the outer scope plus the in-progress
|
|
||||||
frame (the in-flight frame is cloned and pushed during the init
|
|
||||||
synth, then popped — keeps `mut_scope_stack` truthful at every
|
|
||||||
nested call), unifies init type with declared, inserts the
|
|
||||||
(name → declared ty) entry into the frame. After all vars: push
|
|
||||||
the completed frame, synth body in extended scope, pop frame
|
|
||||||
(regardless of body outcome — the `body_result` is captured
|
|
||||||
before pop). Two pins:
|
|
||||||
`mut_block_typechecks_with_int_var_and_returns_int` (positive,
|
|
||||||
initially RED because Assign was still stubbed — flipped GREEN
|
|
||||||
by Task 5) and `mut_block_rejects_str_var_with_unsupported_type`
|
|
||||||
(negative, GREEN at Task 4 close).
|
|
||||||
|
|
||||||
- iter mut.2.5 — `Term::Assign` typecheck arm: Replaces the stub
|
|
||||||
with real logic per plan Step 3. Resolves the target name via
|
|
||||||
`mut_scope_stack.iter().rev().find_map(|f| f.get(name).cloned())`.
|
|
||||||
None case: flattens every name in every frame innermost-first,
|
|
||||||
dedup-preserving-order via a manual `contains` check (small N,
|
|
||||||
no need for a Set), returns `MutAssignOutOfScope`. Some case:
|
|
||||||
synths the value, compares `subst.apply(&declared)` vs
|
|
||||||
`subst.apply(&value)` via `!=` (manual equality, NOT `unify` —
|
|
||||||
unify would have emitted `TypeMismatch`, and the spec demands
|
|
||||||
the distinct `AssignTypeMismatch` code), returns
|
|
||||||
`AssignTypeMismatch` with rendered type strings on mismatch or
|
|
||||||
`Ok(Type::unit())` on match. Three pins:
|
|
||||||
`assign_to_in_scope_mut_var_synths_unit` (positive),
|
|
||||||
`assign_to_out_of_scope_name_fires_diagnostic` (None case),
|
|
||||||
`assign_with_wrong_value_type_fires_diagnostic` (Some-mismatch
|
|
||||||
case). RED → GREEN observed for all three.
|
|
||||||
|
|
||||||
- iter mut.2.6 — Five fixtures + driver test: Five `.ail.json`
|
|
||||||
fixtures land under `examples/` (carve-out style, per the
|
|
||||||
Boss decision in the carrier overriding the spec's literal
|
|
||||||
`crates/ailang-check/tests/`-side placement). Each fixture is
|
|
||||||
minimal — single-fn `main`, no imports, body shape exactly the
|
|
||||||
one the plan named. The driver test
|
|
||||||
`crates/ailang-check/tests/mut_typecheck_pin.rs` uses the
|
|
||||||
canonical `load_workspace` + `check_workspace` +
|
|
||||||
`diagnostics.iter().map(|d| d.code.clone()).collect()` pattern
|
|
||||||
from `method_collision_pin.rs` and `typeclass_22b2.rs` — no
|
|
||||||
`todo!()` survives. Carve-out inventory extended from seven to
|
|
||||||
twelve to accept the five new fixtures (alphabetised; the
|
|
||||||
comment block names mut.2 as the lineage). All 5 driver tests
|
|
||||||
GREEN; carve-out inventory GREEN.
|
|
||||||
|
|
||||||
- iter mut.2.7 — Verify `examples/mut.ail` typechecks clean:
|
|
||||||
`cargo run --quiet --bin ail -- check examples/mut.ail` reports
|
|
||||||
`ok (26 symbols across 2 modules)` — zero diagnostics across
|
|
||||||
all six fns (`mut_empty`, `mut_single_var`, `mut_two_vars`,
|
|
||||||
`mut_nested_shadow`, `mut_returns_bool`, `mut_returns_unit`).
|
|
||||||
Full `cargo test --workspace`: 592 / 0.
|
|
||||||
|
|
||||||
## Concerns
|
|
||||||
|
|
||||||
- Task 3 TDD-ordering gap: the `Term::Var` arm prepend landed
|
|
||||||
before the two pin tests were observed RED on disk. The
|
|
||||||
reasoning that the pre-edit code would have failed both pins
|
|
||||||
is analytical (locals-first resolution returns Int when
|
|
||||||
`locals = { x → Int }`, contradicting both `assert_eq!(Float)`
|
|
||||||
and `assert_eq!(Bool)`). The discipline gap is recorded
|
|
||||||
per the implementer's Iron Law ("the test you wrote in RED MUST
|
|
||||||
pass; no other test may regress" — and the spirit-not-ritual
|
|
||||||
clause that tests-after prove nothing about whether they would
|
|
||||||
have caught the regression pre-edit). Subsequent tasks
|
|
||||||
(4 + 5) restored the RED-first sequencing; on those, the Mut /
|
|
||||||
Assign stubs already emit `CheckError::Internal` so the pins
|
|
||||||
were observed RED with the canonical pattern. No correctness
|
|
||||||
impact; the pins protect the property correctly post-edit.
|
|
||||||
|
|
||||||
- Plan-recon missed three synth call sites beyond the four the
|
|
||||||
plan enumerated (lib.rs 1885 + 2634; mono.rs 712 + 1337). The
|
|
||||||
build-red structural signal flushed them out via E0061:
|
|
||||||
`lib.rs:6663` (in-test mq.3 helper), `lift.rs:723` (letrec-
|
|
||||||
capture re-synth), `builtins.rs` (`synth_in_builtins_env` at
|
|
||||||
351 + a FloatPatternNotAllowed test pin at 594). Each gets a
|
|
||||||
fresh empty stack with the same "begins from top-of-body"
|
|
||||||
rationale as mono.rs. No behavioural impact; documentary
|
|
||||||
concern for the planner's next plan-recon dispatch.
|
|
||||||
|
|
||||||
- The carve-out inventory extension was strictly required (the
|
|
||||||
inventory test enforces a closed list of `examples/*.ail.json`
|
|
||||||
files) but was not explicitly named in the Boss carrier or the
|
|
||||||
plan. Recording here so the planner's recon for future iters
|
|
||||||
knows to enumerate the side-effect when fixtures land under
|
|
||||||
`examples/`. The boundary call: the fixtures match §C4(a)'s
|
|
||||||
precedent (negative typecheck fixtures as `.ail.json` so the
|
|
||||||
diagnostic-code assertion is the load-bearing form, not the
|
|
||||||
surface). The positive `test_mut_nested_shadow_legal.ail.json`
|
|
||||||
is the one stretch — its load-bearing assertion is "zero
|
|
||||||
diagnostics", which the form-A `.ail` form could equally
|
|
||||||
carry. Landing it as `.ail.json` keeps all five mut.2 fixtures
|
|
||||||
on one driver and one extension on the carve-out list; the
|
|
||||||
alternative (splitting positive to `.ail`, negatives to
|
|
||||||
`.ail.json`) would have produced two drivers or a hybrid
|
|
||||||
loader. Conservative call documented for visibility.
|
|
||||||
|
|
||||||
## Known debt
|
|
||||||
|
|
||||||
- Codegen stubs unchanged in mut.2 per the spec's iteration
|
|
||||||
boundary. A `Term::Mut` or `Term::Assign` reaching
|
|
||||||
`lower_term` still produces `CodegenError::Internal`. The
|
|
||||||
full mut.ail E2E (build + run) lands in mut.3. The spec's
|
|
||||||
Acceptance criterion #3 (`mut_counter.ail` + `mut_sum_floats.ail`
|
|
||||||
build + run + print expected stdout) is mut.3-scoped.
|
|
||||||
|
|
||||||
- The Mut-arm's in-progress-frame clone-on-each-init is O(n²)
|
|
||||||
over the var count. n is realistically ≤ 2 (the mut.ail
|
|
||||||
fixture's `mut_two_vars` is the largest in the corpus). Not
|
|
||||||
worth a more elaborate structure (e.g. pushing a single in-
|
|
||||||
progress frame and mutating it across inits); the clone-pop
|
|
||||||
shape keeps the stack truthful at every nested call and makes
|
|
||||||
the per-var init scope obvious. Recording for visibility.
|
|
||||||
|
|
||||||
## Files touched
|
|
||||||
|
|
||||||
Modified:
|
|
||||||
- `crates/ailang-check/src/lib.rs` — 3 new CheckError variants +
|
|
||||||
3 code() arms + 3 ctx() arms; `synth` signature gains
|
|
||||||
`mut_scope_stack` after `locals`; 18 recursive synth call
|
|
||||||
sites updated; 2 entry-point callers (`check_fn`,
|
|
||||||
`check_const`) declare fresh empty stacks; in-test synth call
|
|
||||||
(mq.3 helper) gets new param; `Term::Var` arm prepends
|
|
||||||
mut-scope resolution (5-line block); `Term::Mut` stub replaced
|
|
||||||
with real logic (push/pop frame, per-var init, body synth);
|
|
||||||
`Term::Assign` stub replaced with real logic
|
|
||||||
(find/None-MutAssignOutOfScope/Some-AssignTypeMismatch-or-Unit);
|
|
||||||
file-scope helper `is_supported_mut_var_type`; 7 new tests in
|
|
||||||
`mod tests` (`mut_typecheck_diagnostics_have_kebab_codes`,
|
|
||||||
`mut_var_resolution_shadows_outer_local`,
|
|
||||||
`mut_var_resolution_picks_innermost_frame`,
|
|
||||||
`mut_block_typechecks_with_int_var_and_returns_int`,
|
|
||||||
`mut_block_rejects_str_var_with_unsupported_type`,
|
|
||||||
`assign_to_in_scope_mut_var_synths_unit`,
|
|
||||||
`assign_to_out_of_scope_name_fires_diagnostic`,
|
|
||||||
`assign_with_wrong_value_type_fires_diagnostic`) plus the
|
|
||||||
`synth_standalone` helper.
|
|
||||||
- `crates/ailang-check/src/mono.rs` — 2 re-synth call sites
|
|
||||||
(712, 1337) each get a fresh `mut_scope_stack`.
|
|
||||||
- `crates/ailang-check/src/lift.rs` — letrec-capture re-synth at
|
|
||||||
:723 gets a fresh `mut_scope_stack`.
|
|
||||||
- `crates/ailang-check/src/builtins.rs` — 2 in-test synth calls
|
|
||||||
(synth_in_builtins_env + FloatPatternNotAllowed pin) get a
|
|
||||||
fresh `mut_scope_stack`.
|
|
||||||
- `crates/ailang-core/tests/carve_out_inventory.rs` — EXPECTED
|
|
||||||
list 7 → 12, comment-block updated naming mut.2 as the
|
|
||||||
lineage of the 5 new entries.
|
|
||||||
|
|
||||||
New:
|
|
||||||
- `crates/ailang-check/tests/mut_typecheck_pin.rs` — 5-test
|
|
||||||
driver via canonical load_workspace + check_workspace +
|
|
||||||
collect-codes pattern.
|
|
||||||
- `examples/test_mut_assign_out_of_scope.ail.json`
|
|
||||||
- `examples/test_mut_assign_outside_mut.ail.json`
|
|
||||||
- `examples/test_mut_assign_type_mismatch.ail.json`
|
|
||||||
- `examples/test_mut_nested_shadow_legal.ail.json`
|
|
||||||
- `examples/test_mut_var_unsupported_type.ail.json`
|
|
||||||
|
|
||||||
## Stats
|
|
||||||
|
|
||||||
bench/orchestrator-stats/2026-05-15-iter-mut.2.json
|
|
||||||
@@ -1,139 +0,0 @@
|
|||||||
# iter mut.3 — codegen + e2e for Term::Mut / Term::Assign
|
|
||||||
|
|
||||||
**Date:** 2026-05-15
|
|
||||||
**Started from:** b057789b55a86cd664e2d7fee2c998bb855cad98
|
|
||||||
**Status:** DONE
|
|
||||||
**Tasks completed:** 9 of 9
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
Replaced the iter mut.1 codegen stubs for `Term::Mut` and `Term::Assign`
|
|
||||||
with real LLVM lowering: mut-vars become `alloca` slots hoisted to the
|
|
||||||
fn's entry block via a per-fn side buffer `pending_entry_allocas`
|
|
||||||
spliced into `self.body` at a byte marker captured during
|
|
||||||
`start_block("entry")`; `Term::Assign` lowers to `store`; references to
|
|
||||||
a mut-var name lower to `load` through a new lookup precedence in both
|
|
||||||
`lower_term`'s `Term::Var` arm and `synth_with_extras`'s `Term::Var`
|
|
||||||
arm (precedence: extras → mut-vars → locals → globals → builtins,
|
|
||||||
symmetric to the typecheck-side `mut_scope_stack` walk in
|
|
||||||
`ailang-check`). Lambda emission saves/restores the three new
|
|
||||||
`Emitter` fields across the thunk boundary and splices the thunk's own
|
|
||||||
hoisted allocas at its own entry marker so nested mut blocks inside
|
|
||||||
lambdas hoist into the lambda's entry, not the outer fn's. Two e2e
|
|
||||||
fixtures shipped (`mut_counter.ail` Int / `mut_sum_floats.ail` Float),
|
|
||||||
both printing `55` end-to-end (Float's `%g` formatter strips the `.0`).
|
|
||||||
DESIGN.md gains a "Local mutable state" bullet under "What **is**
|
|
||||||
supported". Closes the mut-local milestone end-to-end; the audit step
|
|
||||||
follows.
|
|
||||||
|
|
||||||
## Per-task notes
|
|
||||||
|
|
||||||
- iter mut.3.1: three `Emitter` fields (`mut_var_allocas`,
|
|
||||||
`pending_entry_allocas`, `entry_block_end_marker`) added with
|
|
||||||
doc comments; initialised in `Emitter::new`; cleared per-fn at the
|
|
||||||
top of `emit_fn` next to the existing per-fn-reset block.
|
|
||||||
- iter mut.3.2: `start_block` extended — when `label == "entry"` it
|
|
||||||
captures `self.body.len()` as `entry_block_end_marker` (the splice
|
|
||||||
point for `pending_entry_allocas`).
|
|
||||||
- iter mut.3.3: `Term::Var` arm of `lower_term` gained a mut-var
|
|
||||||
lookup before the `self.locals` walk — on hit, emits
|
|
||||||
`%v_N = load <llvm_ty>, ptr <alloca_ssa>` and returns the load SSA
|
|
||||||
+ LLVM type.
|
|
||||||
- iter mut.3.4: `Term::Mut` arm of `lower_term` real lowering — for
|
|
||||||
each var: alloca into the side buffer, lower init in outer-plus-
|
|
||||||
earlier-vars scope (matches typecheck ordering at lib.rs:3578),
|
|
||||||
store init into alloca, then install the binding (saving any prior
|
|
||||||
entry for restoration on block exit). Lowers body, restores saved
|
|
||||||
bindings (unconditional restoration even on error path).
|
|
||||||
- iter mut.3.5: `Term::Assign` arm of `lower_term` real lowering —
|
|
||||||
look up the alloca in `mut_var_allocas`, lower value, emit store,
|
|
||||||
return the canonical Unit SSA form `("0", "i8")` matching
|
|
||||||
`Literal::Unit` in the same `match`. Tasks 4 and 5 were applied
|
|
||||||
in one `Edit` call because both arms were adjacent stubs that
|
|
||||||
needed simultaneous replacement (the `match` arm signature changed
|
|
||||||
from `{ .. }` to `{ name, value }` / `{ vars, body }`); the review
|
|
||||||
separation still held — each arm was spec-checked independently.
|
|
||||||
- iter mut.3.6: splice of `pending_entry_allocas` at the marker —
|
|
||||||
inserted into `self.body` at the end of `emit_fn` (after body
|
|
||||||
finalisation, before the deferred-thunks flush). Marker absence
|
|
||||||
while `pending` is non-empty fires `CodegenError::Internal` with
|
|
||||||
the fn name.
|
|
||||||
- iter mut.3.7: `examples/mut_counter.ail` (Int) and
|
|
||||||
`examples/mut_sum_floats.ail` (Float) authored; both use an
|
|
||||||
accumulator-style tail-recursive helper since while-loops are
|
|
||||||
deferred. Initial draft used the natural `(app + lo (tail-app ...))`
|
|
||||||
shape and was rejected by the tail-call analysis ("call marked
|
|
||||||
`tail` is not in tail position") — restructured to pass `acc` as
|
|
||||||
a third arg and yield it directly in the base case. Both fixtures
|
|
||||||
typecheck clean and print `55` (Float via `%g` strips `.0`).
|
|
||||||
- iter mut.3.8: `mut_counter_prints_55` + `mut_sum_floats_prints_55`
|
|
||||||
appended to `crates/ail/tests/e2e.rs`. The plan's placeholder
|
|
||||||
`expected = "55"` resolved by inspection of `examples/floats.ail`
|
|
||||||
+ `crates/ail/tests/floats_e2e.rs` (where `1.5 + 2.5` prints as
|
|
||||||
`4`, confirming `%g`-driven trailing-zero strip — so `55.0`
|
|
||||||
prints as `55`); test pinned to `"55"`.
|
|
||||||
- iter mut.3.9: DESIGN.md "Local mutable state" bullet appended
|
|
||||||
after the "Parameterised ADTs" bullet at the end of the "What
|
|
||||||
**is** supported" subsection. Schema-drift tests pass (the bullet
|
|
||||||
lives outside §"Data model").
|
|
||||||
|
|
||||||
Two beyond-plan adjustments emerged during execution and were
|
|
||||||
absorbed inline (recorded as Concerns below):
|
|
||||||
|
|
||||||
- `synth_with_extras`'s `Term::Var` arm also needed mut-var lookup
|
|
||||||
precedence. The plan's Task 3 only touched `lower_term`'s
|
|
||||||
`Term::Var` arm; without the parallel `synth_with_extras` update,
|
|
||||||
every polymorphic-call-site arg-type derivation that reaches a
|
|
||||||
mut-var reference fails with `UnknownVar`. Discovered by running
|
|
||||||
`examples/mut.ail` through codegen and observing the
|
|
||||||
`mut_single_var` case fail at `(app + x 1)`.
|
|
||||||
- Lambda emission (`lambda.rs`) needed save/restore of the three
|
|
||||||
new fields PLUS an in-thunk splice of `pending_entry_allocas` at
|
|
||||||
the thunk's own entry marker. Without this, a `Term::Mut` inside
|
|
||||||
a lambda body would push its alloca into the outer fn's side
|
|
||||||
buffer (or worse, leak into whichever fn's body the splice runs
|
|
||||||
on). Mirrors how `lambda.rs` already saves/restores
|
|
||||||
`non_escape` / `moved_slots` / `current_param_modes` across the
|
|
||||||
thunk boundary.
|
|
||||||
|
|
||||||
## Concerns
|
|
||||||
|
|
||||||
- Synth-side `Term::Var` mut-var-lookup parallel was not in the
|
|
||||||
plan but is structurally required. Recorded as a plan defect to
|
|
||||||
feed into the iter-mut.4 lessons (if any) and into the milestone-
|
|
||||||
close audit.
|
|
||||||
- Lambda-boundary save/restore + in-thunk splice was not in the
|
|
||||||
plan but is structurally required for correctness of any
|
|
||||||
`Term::Mut` inside a lambda body. Same defect category as above.
|
|
||||||
|
|
||||||
## Known debt
|
|
||||||
|
|
||||||
- None for the milestone-local scope. The spec's deferred items
|
|
||||||
(while-loops, heap-RC mut-var element types, `ref a` + `!Mut`
|
|
||||||
effect, `Stateful a b` + `pipe`) are explicitly named as future
|
|
||||||
milestones in the new DESIGN.md bullet.
|
|
||||||
|
|
||||||
## Blocked detail
|
|
||||||
|
|
||||||
n/a — Status: DONE.
|
|
||||||
|
|
||||||
## Files touched
|
|
||||||
|
|
||||||
- `crates/ailang-codegen/src/lib.rs` — three `Emitter` fields, init
|
|
||||||
+ per-fn reset, `start_block` marker capture, `Term::Var` arm
|
|
||||||
mut-var precedence (both `lower_term` and `synth_with_extras`),
|
|
||||||
`Term::Mut` + `Term::Assign` arms in `lower_term`, splice at end
|
|
||||||
of `emit_fn`.
|
|
||||||
- `crates/ailang-codegen/src/lambda.rs` — save/restore of the three
|
|
||||||
new fields across the thunk boundary, in-thunk splice of
|
|
||||||
`pending_entry_allocas`.
|
|
||||||
- `crates/ail/tests/e2e.rs` — two new tests pinning
|
|
||||||
`mut_counter.ail` → `"55"` and `mut_sum_floats.ail` → `"55"`.
|
|
||||||
- `docs/DESIGN.md` — "Local mutable state" bullet at end of
|
|
||||||
"What **is** supported" subsection.
|
|
||||||
- `examples/mut_counter.ail` — new Int fixture.
|
|
||||||
- `examples/mut_sum_floats.ail` — new Float fixture.
|
|
||||||
|
|
||||||
## Stats
|
|
||||||
|
|
||||||
bench/orchestrator-stats/2026-05-15-iter-mut.3.json
|
|
||||||
@@ -1,137 +0,0 @@
|
|||||||
# iter mut.4-tidy — close mut-local audit drift
|
|
||||||
|
|
||||||
**Date:** 2026-05-15
|
|
||||||
**Status:** DONE
|
|
||||||
**Spec / Plan:** `docs/specs/2026-05-15-mut-local.md` (no spec changes
|
|
||||||
beyond a §"Out of scope" amendment); `docs/plans/2026-05-15-iter-mut.4-tidy.md`.
|
|
||||||
**Started from:** 6966cce
|
|
||||||
**Trigger:** audit close on the mut-local milestone.
|
|
||||||
|
|
||||||
## What this iter shipped
|
|
||||||
|
|
||||||
Tidy work closing the audit drift the architect surfaced after
|
|
||||||
mut.3 landed. Two `[high]` items, two `[medium]` items, plus a
|
|
||||||
substantive bench regression on `bench/compile_check.py` `check_ms`.
|
|
||||||
|
|
||||||
### Architect [high] 1 — lambda-captures-of-mut-var diagnostic
|
|
||||||
|
|
||||||
The spec's "mut-vars cannot escape their enclosing block"
|
|
||||||
invariant was not enforced by any check pass — codegen detected
|
|
||||||
the violation via a `CodegenError::Internal` whose comment blamed
|
|
||||||
the typechecker for letting it through. The fix lands the
|
|
||||||
diagnostic at the typecheck layer:
|
|
||||||
|
|
||||||
- New variant `CheckError::MutVarCapturedByLambda { name: String }`
|
|
||||||
with kebab-case code `mut-var-captured-by-lambda`, `ctx()`
|
|
||||||
emitting `{"name": <id>}`, and the canonical bracketed-`[code]:`
|
|
||||||
Display attribute.
|
|
||||||
- `Term::Lam`'s synth arm now walks the lambda body's free vars
|
|
||||||
(via the existing `ailang_core::desugar::free_vars_in_term`
|
|
||||||
helper, which already honours pattern-binding subtraction in
|
|
||||||
`Term::Match` arms) and rejects any free var whose name appears
|
|
||||||
in any enclosing `mut_scope_stack` frame.
|
|
||||||
- The walk is gated by `!mut_scope_stack.is_empty()` so lambdas
|
|
||||||
outside any mut block pay no overhead.
|
|
||||||
|
|
||||||
### Architect [high] 2 — codegen comment cleanup
|
|
||||||
|
|
||||||
The codegen lambda.rs path that previously raised
|
|
||||||
`CodegenError::Internal("lambda capture {c} not in locals —
|
|
||||||
typechecker bug?")` now uses `unreachable!` with a message
|
|
||||||
naming the typecheck rejection that gates it. The companion
|
|
||||||
comment block at `lambda.rs:474` was rewritten to state
|
|
||||||
the current reality (typecheck rejects via
|
|
||||||
`MutVarCapturedByLambda`) instead of the stale promise that
|
|
||||||
"mut.2 typecheck will enforce this" (mut.2 didn't).
|
|
||||||
|
|
||||||
### Architect [medium] 3 + 4 — stale mut.1-stub history comments
|
|
||||||
|
|
||||||
- `docs/DESIGN.md` §"Term (expression)" mut/assign block: the
|
|
||||||
trailing paragraph describing the iter mut.1 stub state was
|
|
||||||
replaced with one describing the current end-to-end mut.3
|
|
||||||
reality. The `// Iter mut.1` comment on the `{"t": "assign"}`
|
|
||||||
jsonc fragment was updated to drop the "deferred to mut.2/mut.3"
|
|
||||||
language.
|
|
||||||
- `crates/ailang-codegen/src/lib.rs:1749-1758` — the comment block
|
|
||||||
above the real `Term::Mut` codegen arm still described the mut.1
|
|
||||||
stub. Removed entirely; the real `Iter mut.3` comment immediately
|
|
||||||
below it is now the only descriptive header.
|
|
||||||
|
|
||||||
### Bench regression ratify — `check_ms` ~30-50% uniform shift
|
|
||||||
|
|
||||||
`bench/compile_check.py` flags an 11-fixture-wide `check_ms`
|
|
||||||
regression on the mut-local milestone (30-50% relative; ~0.5ms
|
|
||||||
absolute on a 1.4-1.5ms baseline). The pattern is **uniform across
|
|
||||||
fixtures of very different Var counts** (`hello.ail` 7 lines and
|
|
||||||
`bench_list_sum_explicit.ail` 100+ lines both shift by ~0.5ms),
|
|
||||||
which indicates a fixed-cost-per-`ail check`-invocation tax rather
|
|
||||||
than a hot-path-per-Var cost.
|
|
||||||
|
|
||||||
Root-cause analysis:
|
|
||||||
|
|
||||||
- **Hypothesis A: `mut_scope_stack` walk at every Var resolution.**
|
|
||||||
Tested by the Task 3 short-circuit (`if !mut_scope_stack.is_empty()`).
|
|
||||||
Re-bench: regression unchanged. **Falsified.**
|
|
||||||
- **Hypothesis B: synth-parameter-passing overhead.** Threading
|
|
||||||
`&mut Vec<IndexMap<...>>` through ~19 recursive synth calls per
|
|
||||||
Term-tree walk. Each call pays a 64-bit reference push to the
|
|
||||||
stack frame. Not directly testable without rolling the
|
|
||||||
threading back. Plausible but speculative.
|
|
||||||
- **Hypothesis C: binary size / startup cost.** mut.* added
|
|
||||||
~1400 lines of code across `ailang-check` and `ailang-codegen`.
|
|
||||||
Larger binary → slightly slower process startup, dynamic linker
|
|
||||||
resolution, instruction-cache warmup. ~0.5ms is in the right
|
|
||||||
ballpark for that kind of cost on a small binary. The uniform-
|
|
||||||
across-fixtures pattern is consistent with this — it's not
|
|
||||||
Var-proportional.
|
|
||||||
|
|
||||||
The pragmatic call: hypothesis B and C are both consistent with the
|
|
||||||
observation; neither has a cheap remedy at this milestone. The
|
|
||||||
short-circuit in Task 3 closes hypothesis A's contribution if any,
|
|
||||||
and remains the right shape for future workloads that DO have
|
|
||||||
non-empty `mut_scope_stack`. The remaining shift is the cost of
|
|
||||||
the feature.
|
|
||||||
|
|
||||||
**Disposition: ratify** as a fixed-cost feature tax. The journal
|
|
||||||
records this disposition; the `bench/compile_check.py` baseline is
|
|
||||||
updated to the post-mut.4-tidy numbers. Future bench observations
|
|
||||||
will tolerance-check against the new baseline. If a future iter
|
|
||||||
finds a substantive hot-path that retroactively explains the shift,
|
|
||||||
the ratify can be revisited.
|
|
||||||
|
|
||||||
`bench/check.py` regressions are tail-latency metrics
|
|
||||||
(`latency.implicit_at_rc.p99_9_us / max_us`) consistent with the
|
|
||||||
"established noise envelope, 15th consecutive observation" finding
|
|
||||||
from audit-pd (2026-05-14). No ratify needed — they fall under
|
|
||||||
the existing noise carve-out.
|
|
||||||
|
|
||||||
`bench/cross_lang.py` is clean (25/0/0/25). Runtime is unchanged
|
|
||||||
by mut-local.
|
|
||||||
|
|
||||||
## Working tree
|
|
||||||
|
|
||||||
Eight files: 7 modified + 1 new (`examples/test_mut_var_captured_by_lambda.ail.json`).
|
|
||||||
|
|
||||||
## Tests
|
|
||||||
|
|
||||||
594 → 598 green (4 new: the variant code-pin smoke test, the
|
|
||||||
positive `lambda_outside_mut_still_typechecks_clean` regression
|
|
||||||
guard, the negative `lambda_inside_mut_capturing_mut_var_emits_mut_var_captured_by_lambda`
|
|
||||||
in lib.rs, and the integration-test
|
|
||||||
`lambda_capturing_mut_var_emits_mut_var_captured_by_lambda`).
|
|
||||||
|
|
||||||
## Concerns
|
|
||||||
|
|
||||||
- **`Term::Match` pattern binding over-approximation.** The
|
|
||||||
`free_vars_in_term` walker correctly subtracts pattern-bound
|
|
||||||
names; reused without modification. No false positives
|
|
||||||
encountered in the existing test corpus.
|
|
||||||
- **Bench-ratify decision.** Documented above. The 30-50%
|
|
||||||
uniform shift is plausible but not proven to be feature-cost.
|
|
||||||
A future bencher dispatch could test hypothesis B by branching
|
|
||||||
off a "remove the mut_scope_stack parameter, use a thread-local
|
|
||||||
/ Env field instead" prototype and measuring the difference. Out
|
|
||||||
of scope for this tidy.
|
|
||||||
- **`bench/compile_check.py` baseline updated** in this commit.
|
|
||||||
The audit-skill discipline ("NO BASELINE UPDATE WITHOUT A
|
|
||||||
PAIRED JOURNAL RATIFY ENTRY") is satisfied by this entry.
|
|
||||||
@@ -1,107 +0,0 @@
|
|||||||
# audit — iteration-discipline-revert milestone close
|
|
||||||
|
|
||||||
**Date:** 2026-05-16
|
|
||||||
**Milestone:** iteration-discipline-revert (HEAD `37ac704`)
|
|
||||||
**Verdict:** Milestone-iteration-discipline-revert tidy — **clean**
|
|
||||||
|
|
||||||
## Step 1 — Architect drift review
|
|
||||||
|
|
||||||
The revert is byte-pristine: all 18 reverted production source files
|
|
||||||
+ 5 reverted test files byte-identical to `git show 1ff7e81:<path>`
|
|
||||||
(`crates/ailang-check/src/lib.rs` fully so); zero residual it.1/it.2
|
|
||||||
production surface (`Term::Loop`/`Recur`/`LoopBinder`/
|
|
||||||
`verify_structural_recursion`/`term_contains_loop`/`module_fns`/
|
|
||||||
`"Diverge"`-injection / the five `Recur*`+`NonStructuralRecursion`
|
|
||||||
variants all absent from `crates/*/src/`). DESIGN.md coherent:
|
|
||||||
Decision 3 & 8 byte-identical to oracle, no loop/recur in §"Data
|
|
||||||
model", clause 3 retained (hunk-edited, not wholesale-restored — the
|
|
||||||
`1ff7e81` DESIGN.md had zero clause-3 text) with its worked example
|
|
||||||
de-claimed to explicit hypothetical, `skills/brainstorm/SKILL.md`
|
|
||||||
de-claimed in lockstep, F1/F4 idiom note present and guarded by a
|
|
||||||
passing `design_schema_drift.rs` test. Roadmap: Iteration-discipline
|
|
||||||
block + blocking-fork section gone, single deferred P2 entry with
|
|
||||||
correct `depends on:`/`context:`, F3 todo + Stateful-islands intact,
|
|
||||||
old spec carries the superseded header.
|
|
||||||
|
|
||||||
One `[medium]` drift item:
|
|
||||||
|
|
||||||
- `docs/roadmap.md` Stateful-islands `[~]` scope bullet still
|
|
||||||
asserted *"the 'Iteration discipline' P1 milestone supersedes the
|
|
||||||
earlier 'while-loops legal' scope … repetition is structural
|
|
||||||
recursion or named `loop`/`recur`"* — a dangling cross-reference
|
|
||||||
into the now-reverted milestone from a **live planning doc** that
|
|
||||||
would mislead the next brainstorm into treating `loop`/`recur` as
|
|
||||||
the committed repetition story. **Resolved inline** (Boss
|
|
||||||
doc-hygiene, full context already loaded): reworded to keep the
|
|
||||||
real, still-true scope guard (AILang has no `while`/`for`; this
|
|
||||||
milestone introduces none; repetition stays recursion exactly as
|
|
||||||
the language has it today; a `mut` block is a sealed expression,
|
|
||||||
never a loop) and drop the reverted-milestone reference.
|
|
||||||
|
|
||||||
## Step 2 — Bench-regression check
|
|
||||||
|
|
||||||
- `bench/compile_check.py` — exit 0 (24/24 stable).
|
|
||||||
- `bench/cross_lang.py` — exit 0 (25/25 stable; notably
|
|
||||||
`bench_list_sum.ail_bump_s` only +2.44% vs baseline).
|
|
||||||
- `bench/check.py` — exit 1: single metric
|
|
||||||
`throughput.bench_list_sum.bump_s` baseline 0.046s → 0.052s,
|
|
||||||
+12.71% (tol 10%); the 4 "improvements" are `gc_over_bump` ratio
|
|
||||||
shifts downstream of that one number.
|
|
||||||
|
|
||||||
**Localisation (bencher, hypothesis-driven).** Built release `ail`
|
|
||||||
at HEAD `37ac704` and at the byte-oracle `1ff7e81` (read-only
|
|
||||||
`git worktree`, removed after). The compiled `bench_list_sum`
|
|
||||||
bump binaries are **`cmp`-identical** (byte-for-byte same machine
|
|
||||||
code, identical stdout). Interleaved 3 trials × 60 runs/arm: head
|
|
||||||
median 0.051233s vs oracle median 0.051206s; head−oracle delta
|
|
||||||
oscillates around zero (+0.05% / +0.61% / −0.20%, within ~1.5%
|
|
||||||
stdev); **both** arms read ~+11% over the 0.046 baseline on this
|
|
||||||
machine *now*. Verdict: **environmental/hardware drift relative to
|
|
||||||
the 2026-05-09 baseline-capture machine state, NOT a revert
|
|
||||||
regression** — confirmed independently of the architect byte-diff
|
|
||||||
and the implement Task-11 behavioural-equivalence sweep (third
|
|
||||||
independent proof the revert's codegen == `1ff7e81`'s codegen).
|
|
||||||
|
|
||||||
## Step 3 — Classification & resolution
|
|
||||||
|
|
||||||
- Architect `[medium]` roadmap dangling xref → **fix**, done inline
|
|
||||||
(Boss doc-hygiene; no separate tidy iteration warranted for a
|
|
||||||
one-bullet prose correction with a single correct outcome).
|
|
||||||
- `check.py` exit 1 → **carry-on**, baseline **NOT ratified**.
|
|
||||||
Ratify is categorically wrong here: nothing *intentionally* moved
|
|
||||||
the metric (the revert returned codegen to byte-identical
|
|
||||||
`1ff7e81`), and the audit Iron Law forbids a baseline bump without
|
|
||||||
an iter that intentionally moved it. Bumping 0.052 in would bake a
|
|
||||||
noise/environment value into `bench/baseline.json`. The spec's
|
|
||||||
"bench PRISTINE, not ratified" mandate is **satisfied**: pristine
|
|
||||||
means the revert introduced no regression, and it provably did not
|
|
||||||
(byte-identical binaries vs the clean `1ff7e81` oracle).
|
|
||||||
- The pre-existing `*.bump_s` baseline-vs-hardware staleness (the
|
|
||||||
`1ff7e81` oracle itself trips the same +11% on current hardware —
|
|
||||||
predates this milestone, revert-foreign) recorded as a separate
|
|
||||||
**P2 roadmap `[todo]`** (bench-harness recalibration; no language
|
|
||||||
change), so it is tracked without being mis-attributed to the
|
|
||||||
revert or laundered through a ratify.
|
|
||||||
|
|
||||||
## Fieldtest
|
|
||||||
|
|
||||||
**Skipped** (Boss judgement; fieldtest is non-mandatory). A revert
|
|
||||||
restores the already-field-tested pre-`9973546` user-visible surface
|
|
||||||
(structural / tail recursion; `tail-app` intact) — the surface that
|
|
||||||
the mut-local fieldtest already exercised and that started this
|
|
||||||
corrective arc. A fieldtest here would re-test a known, well-trodden
|
|
||||||
state and add no signal; the implement Task-11 byte-equivalence
|
|
||||||
sweep over 164 fixtures is the stronger correctness proof for a
|
|
||||||
revert.
|
|
||||||
|
|
||||||
## Close
|
|
||||||
|
|
||||||
Milestone **iteration-discipline-revert** closed clean:
|
|
||||||
byte-pristine vs `1ff7e81` across all production surface; behaviour
|
|
||||||
byte-identical over 164 surviving fixtures; `cargo test --workspace`
|
|
||||||
600/0; one roadmap drift fixed inline; the single `check.py` metric
|
|
||||||
localised to environmental variance (baseline untouched) with the
|
|
||||||
pre-existing staleness filed forward. AILang's iteration story is
|
|
||||||
exactly the pre-`9973546` state; the genuine total-recursion
|
|
||||||
ambition is preserved on the roadmap, sequenced behind a future
|
|
||||||
`Nat`/refinement-types milestone.
|
|
||||||
@@ -1,81 +0,0 @@
|
|||||||
# iter effect-doc-honesty — effect-system documentation-honesty tidy
|
|
||||||
|
|
||||||
**Date:** 2026-05-16
|
|
||||||
**Started from:** c41e0e5a9e48fd0099eda8c286f10ec7a30e8e8a
|
|
||||||
**Status:** DONE
|
|
||||||
**Tasks completed:** 6 of 6
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
Documentation-honesty tidy: corrected three false effect-system
|
|
||||||
claims plus their two satellite mentions across the project docs,
|
|
||||||
guarded by one new doc-presence regression pin. No language,
|
|
||||||
checker, or codegen code changed — only a doc-comment, two markdown
|
|
||||||
spec files, an internal CLI guidance string, and a new integration
|
|
||||||
test. `ail check`/`ail run` behaviour is byte-unchanged by
|
|
||||||
construction; `cargo test --workspace` 600 → 604 (the four new pin
|
|
||||||
tests, zero regressions), `cargo build --workspace` clean. The
|
|
||||||
three sentinel drift/coverage tests (`design_schema_drift`,
|
|
||||||
`spec_drift`, `schema_coverage`) stay green, empirically confirming
|
|
||||||
none scans the effect-prose region.
|
|
||||||
|
|
||||||
## Per-task notes
|
|
||||||
|
|
||||||
- iter effect-doc-honesty.1: new pin `crates/ailang-core/tests/effect_doc_honesty_pin.rs`
|
|
||||||
(4 tests asserting fiction strings absent + corrected anchors
|
|
||||||
present across the 4 edited files); authored first, ran RED 0/4
|
|
||||||
for the right reason (TDD: RED before corrections).
|
|
||||||
- iter effect-doc-honesty.2: `docs/DESIGN.md` Decision 3 row-polymorphic
|
|
||||||
block → flat/unordered/closed set-equality + Diverge-reserved
|
|
||||||
wording; line-2722 "IO and Diverge ops" bullet → IO-only +
|
|
||||||
Diverge-reserved bullet. `design_md_effect_prose_is_true` GREEN.
|
|
||||||
- iter effect-doc-honesty.3: `crates/ailang-core/src/ast.rs` `Term::Do`
|
|
||||||
doc-comment effect-handler-table-at-link-time fiction → real
|
|
||||||
mechanism (typecheck against `Env::effect_ops`, `lower_effect_op`
|
|
||||||
literal-match codegen). `term_do_doc_comment_is_true` GREEN.
|
|
||||||
- iter effect-doc-honesty.4: `crates/ailang-core/specs/form_a.md`
|
|
||||||
line-226 "currently `IO` and `Diverge`" → IO-built-in-op /
|
|
||||||
Diverge-reserved; rule-3 "`Diverge` for `diverge/*`" →
|
|
||||||
io/print_str-is-the-only-built-in-op. `form_a_spec_effect_names_are_true`
|
|
||||||
GREEN.
|
|
||||||
- iter effect-doc-honesty.5: `crates/ail/src/main.rs` merge-prose
|
|
||||||
CONTRACT effect-annotation bullet dropped the non-existent
|
|
||||||
`(effects Diverge)` example. Whole pin 4/4 GREEN.
|
|
||||||
- iter effect-doc-honesty.6: full verification — whole pin 4/4,
|
|
||||||
`cargo test --workspace` 604/0 (600 prior + 4), drift/coverage
|
|
||||||
trio green, `cargo build --workspace` clean.
|
|
||||||
|
|
||||||
## Concerns
|
|
||||||
|
|
||||||
- Task 2: the plan's verbatim Task-2 replacement body soft-wrapped
|
|
||||||
the phrase "flat, unordered,\nclosed set of effect names"
|
|
||||||
mid-phrase, which contradicts Task-1's single-line pin substring
|
|
||||||
`"flat, unordered, closed set of effect names"` AND the plan's
|
|
||||||
own self-review item 6 ("every pin substring is single-line — the
|
|
||||||
documented iter-revert grep-wrap failure mode is avoided"). The
|
|
||||||
two authoritative artefacts (exact replacement text vs. exact
|
|
||||||
pin) could not both be applied verbatim. Resolved by preserving
|
|
||||||
the exact corrected *wording* (byte-identical to the plan's
|
|
||||||
intent) and the exact pin (Task 1, unchanged), moving only the
|
|
||||||
soft-wrap column so the asserted phrase stays contiguous on one
|
|
||||||
line. No wording added/dropped/changed. This is a planner
|
|
||||||
self-review-item-3 cross-check gap (the asserted-substring vs.
|
|
||||||
replacement-body check did not account for the replacement body's
|
|
||||||
own line wrap) — orchestrator may want to tighten the planner's
|
|
||||||
pin/replacement-body wrap cross-check.
|
|
||||||
|
|
||||||
## Known debt
|
|
||||||
|
|
||||||
- (none) — the tidy is self-contained; no deferred follow-up.
|
|
||||||
|
|
||||||
## Files touched
|
|
||||||
|
|
||||||
- `crates/ailang-core/tests/effect_doc_honesty_pin.rs` (new — the pin)
|
|
||||||
- `docs/DESIGN.md` (Decision 3 + "What is not supported" bullet)
|
|
||||||
- `crates/ailang-core/src/ast.rs` (`Term::Do` doc-comment)
|
|
||||||
- `crates/ailang-core/specs/form_a.md` (line 226 + rule 3)
|
|
||||||
- `crates/ail/src/main.rs` (merge-prose CONTRACT string)
|
|
||||||
|
|
||||||
## Stats
|
|
||||||
|
|
||||||
bench/orchestrator-stats/2026-05-16-iter-effect-doc-honesty.json
|
|
||||||
@@ -1,174 +0,0 @@
|
|||||||
# iter revert — Iteration-discipline milestone surgical forward revert
|
|
||||||
|
|
||||||
**Date:** 2026-05-16
|
|
||||||
**Started from:** abf00131c10ff9287b419439c565480c96fa861b
|
|
||||||
**Status:** DONE
|
|
||||||
**Tasks completed:** 11 of 11
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
The Iteration-discipline milestone (it.1 `96db54d` + it.2 `a4be1e5`)
|
|
||||||
is fully backed out by a single forward iteration, with `1ff7e81`
|
|
||||||
(the last commit before milestone-start `9973546`) as the per-region
|
|
||||||
byte oracle. `main` was never rewound — every restore is a forward
|
|
||||||
working-tree edit, net-new it.1/it.2 surface is `rm`'d, DESIGN.md is
|
|
||||||
hunk-edited in place (clause 3 / criterion section KEPT, never
|
|
||||||
wholesale-restored). Feature-acceptance clause 3 and the F3 todo are
|
|
||||||
retained; the clause-3 worked example is de-claimed from "shipped" to
|
|
||||||
hypothetical-illustration form (in DESIGN.md and brainstorm SKILL.md
|
|
||||||
in lockstep); one honest F1/F4 documentation note is added (the only
|
|
||||||
net addition) and guarded by a doc-presence test. The correctness
|
|
||||||
gate (Task 11) proved compiler behaviour **pristine**: all 164
|
|
||||||
surviving `1ff7e81`-era fixtures `ail check`/`ail run` byte-identical
|
|
||||||
to their pre-milestone behaviour; `cargo test --workspace` 600/0;
|
|
||||||
zero residual it.1/it.2 production surface. AILang's iteration story
|
|
||||||
is exactly the pre-`9973546` state (structural / tail recursion;
|
|
||||||
`tail-app` intact); the genuine total-recursion ambition is preserved
|
|
||||||
on the roadmap as a deferred P2 milestone sequenced behind a future
|
|
||||||
`Nat`/refinement-types milestone.
|
|
||||||
|
|
||||||
## Per-task notes
|
|
||||||
|
|
||||||
- revert.1: AST node family + drift anchors + DESIGN.md §Data-model —
|
|
||||||
removed `Term::Loop`/`Term::Recur`/`struct LoopBinder` + 2 serde
|
|
||||||
units (ast.rs byte-identical to oracle); loop/recur exemplars +
|
|
||||||
match arms removed from design_schema_drift.rs / spec_drift.rs /
|
|
||||||
schema_coverage.rs (byte-identical); DESIGN.md §Data-model
|
|
||||||
loop/recur JSON + it.1/it.2 prose removed (region byte-identical).
|
|
||||||
- revert.2: `CheckError` surface — 5 variants + doc comments, 5
|
|
||||||
`code()` arms, 3 dedicated `ctx()` arms deleted; `_ =>` catch-all
|
|
||||||
preserved; diagnostic.rs confirmed genuine no-op.
|
|
||||||
- revert.3: it.2 guardedness pass + Diverge-injection — STOP-gate
|
|
||||||
cleared (all 6 named helpers it.2-net-new at oracle → all delete,
|
|
||||||
no ambiguity). Removed the entire 687-line guardedness cluster
|
|
||||||
(verify_structural_recursion, term_contains_loop, adt_families,
|
|
||||||
referenced_con_names, mutual_structural_group, adt_param_positions,
|
|
||||||
RecCall, TypeUnionFind, MutualGroup, et al.) **plus** the
|
|
||||||
transitively-it.2 `module_fns` plumbing through
|
|
||||||
check_module/check_def/check_fn signatures (oracle proved net-new;
|
|
||||||
resolved via the plan's Oracle convention + recon-by-name mandate —
|
|
||||||
see Concerns). 3 Diverge-injection sites restored byte-identical;
|
|
||||||
lib.rs `"Diverge"` count 0 == oracle.
|
|
||||||
- revert.4: it.1 check surface — synth Loop/Recur arms +
|
|
||||||
verify_loop_body deleted; loop_stack param + ~30 threaded calls + 9
|
|
||||||
local decls removed; verify_tail_positions survives, it.1 arms
|
|
||||||
removed, byte-identical to oracle; substitute_rigids_in_term +
|
|
||||||
post-mono name arms cleaned. Result:
|
|
||||||
`crates/ailang-check/src/lib.rs` FULLY byte-identical to `1ff7e81`.
|
|
||||||
- revert.5: 9 remaining walker files (lift/mono/linearity/uniqueness/
|
|
||||||
reuse_shape/pre_desugar_validation/builtins + core desugar/
|
|
||||||
workspace) restored to oracle, all byte-identical; pre-restore diff
|
|
||||||
classification proved every hunk it.1/it.2-only.
|
|
||||||
- revert.6: surface + prose + form-A grammar — parse.rs/print.rs/
|
|
||||||
prose lib.rs/form_a.md restored to oracle byte-identical; each
|
|
||||||
touched only by 96db54d (it.1) since 1ff7e81; surface+prose build
|
|
||||||
clean.
|
|
||||||
- revert.7: codegen — lib.rs/escape.rs/lambda.rs + ail/main.rs
|
|
||||||
restored to oracle byte-identical; `block_terminated = true` count
|
|
||||||
== 8 == oracle (it.1 parallel setter gone; the 4 tail-driven + 4
|
|
||||||
control-flow setters survive). `cargo build --workspace` clean
|
|
||||||
(first full-build gate, lockstep clustering worked as designed).
|
|
||||||
- revert.8: STOP-gate re-verify cleared (live `git diff --stat`
|
|
||||||
matched plan's net-new/modified/absent classification exactly).
|
|
||||||
Deleted 2 pin files + 16 fixtures + bench/it3-oracle via plain
|
|
||||||
`rm` (no git-index touch). 2 RC fixtures restored byte-identical.
|
|
||||||
e2e.rs + codegen_import_map_fallback_pin.rs + carve_out_inventory.rs
|
|
||||||
restored byte-identical (carve = "Twelve carve-outs", last entry
|
|
||||||
test_mut_var_captured_by_lambda.ail.json). it.{1,2,3} stats json
|
|
||||||
KEPT. cargo test --workspace 599/0.
|
|
||||||
- revert.9: DESIGN.md Decision 3 → 1ff7e81 placeholder (byte-identical,
|
|
||||||
hunk-only); clause-3 worked example de-claimed to hypothetical form
|
|
||||||
(criterion section + clause-3 discriminator KEPT); brainstorm
|
|
||||||
SKILL.md de-claimed in lockstep; F1/F4 note added verbatim;
|
|
||||||
doc-presence test added verbatim, PASSES.
|
|
||||||
- revert.10: roadmap Iteration-discipline [~] block + blocking-fork
|
|
||||||
sub-section removed; deferred "Iteration-totality story" entry
|
|
||||||
added verbatim at top of P2 (per plan + spec sequencing); F3 +
|
|
||||||
Stateful-islands untouched; superseded header prepended to the old
|
|
||||||
spec; Step-4 STOP-check clean (no residual A1 amendment).
|
|
||||||
- revert.11 (correctness gate): 1ff7e81 ref compiler built in
|
|
||||||
throwaway worktree; 164-fixture check/run oracle captured;
|
|
||||||
post-revert compiler diffed byte-for-byte — fail=0, ZERO DRIFT,
|
|
||||||
behaviour PRISTINE. cargo test --workspace 600/0. Residual
|
|
||||||
it.1/it.2 production-surface grep clean. Worktree removed; main
|
|
||||||
HEAD never moved.
|
|
||||||
|
|
||||||
## Concerns
|
|
||||||
|
|
||||||
- revert.3: the plan's Task-3 file-map enumerated the guardedness
|
|
||||||
helpers + call site but did NOT explicitly list the `module_fns`
|
|
||||||
signature threading through check_module/check_def/check_fn. The
|
|
||||||
`1ff7e81` oracle unambiguously proved that threading it.2-net-new;
|
|
||||||
leaving it would have been dead it.2 surface referencing deleted
|
|
||||||
helpers. Resolved correctly via the plan's own "Oracle convention"
|
|
||||||
+ "re-locate by symbol name, oracle is authority on every byte"
|
|
||||||
mandate + the spec §Data-flow ("restored to pure pre-it.1 role").
|
|
||||||
This is the recon-map-drifts-but-oracle-is-authority mechanism
|
|
||||||
working as designed, not a plan defect — no bounce. Flagged so the
|
|
||||||
Boss is aware the plan file-map under-enumerated one signature
|
|
||||||
edge; the end-state is verified correct (lib.rs byte-identical to
|
|
||||||
oracle).
|
|
||||||
- revert.9: the plan's Task-9 Step-6 single-line grep heuristic
|
|
||||||
(`grep -c 'clauses 1 and 2 yet fail clause 3' ...`) returned 0
|
|
||||||
because the verbatim-applied replacement text line-wraps ("yet
|
|
||||||
fail\n clause 3"). The substantive acceptance criterion
|
|
||||||
(clause-3 block retained, only de-claimed) is positively verified
|
|
||||||
multiline + by the intact `## Feature-acceptance criterion` section
|
|
||||||
and "criterion 3 is the discriminator" sentence. Not a defect;
|
|
||||||
flagged so the Boss does not misread the heuristic.
|
|
||||||
|
|
||||||
## Known debt
|
|
||||||
|
|
||||||
(none — the revert is byte-pristine against `1ff7e81` for all
|
|
||||||
production surface; behaviour is byte-identical over the surviving
|
|
||||||
corpus.)
|
|
||||||
|
|
||||||
## Blocked detail
|
|
||||||
|
|
||||||
(none — DONE.)
|
|
||||||
|
|
||||||
## Files touched
|
|
||||||
|
|
||||||
90 working-tree paths (Boss commits all as one cohesive revert iter):
|
|
||||||
|
|
||||||
- Deleted (59): `crates/ailang-check/tests/loop_recur_pin.rs`,
|
|
||||||
`crates/ailang-check/tests/structural_recursion_pin.rs`; 16 it.1/it.2
|
|
||||||
fixtures under `examples/` (loop_*.ail, struct_rec_*.ail,
|
|
||||||
test_recur_*.ail.json, test_loop_missing_diverge / test_mutual_cross_family
|
|
||||||
/ test_non_structural_recursion .ail.json); `bench/it3-oracle/`
|
|
||||||
(MANIFEST.tsv + 40 .out).
|
|
||||||
- Modified — source revert to 1ff7e81 (byte-identical):
|
|
||||||
`crates/ailang-core/src/ast.rs`, `desugar.rs`, `workspace.rs`;
|
|
||||||
`crates/ailang-check/src/lib.rs`, `lift.rs`, `mono.rs`,
|
|
||||||
`linearity.rs`, `uniqueness.rs`, `reuse_shape.rs`,
|
|
||||||
`pre_desugar_validation.rs`, `builtins.rs`;
|
|
||||||
`crates/ailang-codegen/src/lib.rs`, `escape.rs`, `lambda.rs`;
|
|
||||||
`crates/ail/src/main.rs`; `crates/ailang-surface/src/parse.rs`,
|
|
||||||
`print.rs`; `crates/ailang-prose/src/lib.rs`;
|
|
||||||
`crates/ailang-core/specs/form_a.md`.
|
|
||||||
- Modified — tests revert to 1ff7e81 (byte-identical):
|
|
||||||
`crates/ailang-core/tests/spec_drift.rs`, `schema_coverage.rs`,
|
|
||||||
`carve_out_inventory.rs`; `crates/ail/tests/e2e.rs`,
|
|
||||||
`codegen_import_map_fallback_pin.rs`.
|
|
||||||
- Modified — net-add / hunk-edit:
|
|
||||||
`crates/ailang-core/tests/design_schema_drift.rs` (loop/recur
|
|
||||||
anchors removed + new doc-presence test added),
|
|
||||||
`docs/DESIGN.md` (Decision 3 placeholder + clause-3 de-claim +
|
|
||||||
F1/F4 note; hunk-edited, clause 3 kept), `docs/roadmap.md`
|
|
||||||
(Iteration-discipline block removed, deferred P2 entry added),
|
|
||||||
`docs/specs/2026-05-15-iteration-discipline.md` (superseded
|
|
||||||
header), `skills/brainstorm/SKILL.md` (worked-example de-claim).
|
|
||||||
- Modified — fixtures restored to 1ff7e81:
|
|
||||||
`examples/rc_pin_recurse_implicit.ail`,
|
|
||||||
`examples/rc_let_alias_implicit_param.ail`.
|
|
||||||
- KEPT (no-op, Boss decision):
|
|
||||||
`bench/orchestrator-stats/2026-05-15-iter-it.{1,2,3}.json`.
|
|
||||||
|
|
||||||
E2E coverage: none (revert iteration — the Task-11 behavioural-
|
|
||||||
equivalence sweep over all 164 surviving fixtures is a stronger
|
|
||||||
correctness proof than new E2E; the doc-presence test guards the
|
|
||||||
only net-new artefact).
|
|
||||||
|
|
||||||
## Stats
|
|
||||||
|
|
||||||
bench/orchestrator-stats/2026-05-16-iter-revert.json
|
|
||||||
@@ -1,180 +0,0 @@
|
|||||||
# iter loop-recur.1 — Additive AST-Node Foundation (Term::Loop / Term::Recur / LoopBinder)
|
|
||||||
|
|
||||||
**Date:** 2026-05-17
|
|
||||||
**Started from:** a5eebcd5fd9d703cc4f8a6374f95e00061d86c55
|
|
||||||
**Status:** DONE
|
|
||||||
**Tasks completed:** 7 of 7
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
`loop` / `recur` become real, parseable, printable, round-trippable,
|
|
||||||
hash-stable, strictly-additive AST nodes across all six crates plus
|
|
||||||
the drift tests — with NO typecheck semantics and NO real codegen
|
|
||||||
lowering (those are loop-recur iters 2 and 3). Added `Term::Loop {
|
|
||||||
binders, body }`, `Term::Recur { args }`, and `struct LoopBinder`
|
|
||||||
(mirroring `MutVar` exactly); Form-A `(loop (NAME TYPE INIT)* BODY+)`
|
|
||||||
/ `(recur ARG*)` parse + print + grammar/notes; prose render /
|
|
||||||
count_free_var / subst_var arms; ~30 structural pass-through walker
|
|
||||||
arms; the two semantic dispatch points stubbed with
|
|
||||||
`CheckError::Internal` (`synth`) / `CodegenError::Internal`
|
|
||||||
(`lower_term`) exactly as mut.1 did; DESIGN.md + form_a.md schema
|
|
||||||
blocks; drift/coverage/hash anchors; and the spec's worked `sum_to`
|
|
||||||
program as a green `examples/loop_sum_to.ail` round-trip fixture.
|
|
||||||
`cargo test --workspace` 608 green / 0 red; the iter13a + new
|
|
||||||
loop-recur hash pins both green (additivity proven — pre-existing
|
|
||||||
canonical-JSON hashes byte-stable); all `tail`-filtered tests green
|
|
||||||
(Boss-call-1 operational evidence).
|
|
||||||
|
|
||||||
## Per-task notes
|
|
||||||
|
|
||||||
- iter loop-recur.1.1: Core AST — `Term::Loop`/`Term::Recur` variants
|
|
||||||
+ `struct LoopBinder` + 2 serde round-trip unit tests. RED-first
|
|
||||||
(`error[E0599]`); GREEN after Task 4 (desugar/workspace are *in*
|
|
||||||
ailang-core, so the lib's own exhaustive matches gate it — see
|
|
||||||
Concerns). Both serde tests green.
|
|
||||||
- iter loop-recur.1.2: Surface parser (`parse_loop`/`parse_recur` +
|
|
||||||
head dispatch + unknown-head message), printer arms, form_a.md
|
|
||||||
grammar+notes. Binder/body boundary disambiguated via an
|
|
||||||
`is_term_head_kw` discriminator (the plan's flagged "one genuine
|
|
||||||
parser judgement"). `parse_loop_and_recur_round_trip_via_term`
|
|
||||||
green (exactly 2 binders + `if` body + 2-arg recur).
|
|
||||||
- iter loop-recur.1.3: Prose projection — `write_term_prec`,
|
|
||||||
`count_free_var`, `subst_var_with_term` Loop/Recur arms (verbatim
|
|
||||||
plan code, lexical-shadow-aware mirroring the Mut arms).
|
|
||||||
- iter loop-recur.1.4: ailang-core walkers — 9 desugar.rs sites + 2
|
|
||||||
workspace.rs sites. `cargo build -p ailang-core` GREEN.
|
|
||||||
- iter loop-recur.1.5: ailang-check — 14 steps across 7 files
|
|
||||||
including the Boss-call-1 `verify_tail_positions` two mandatory
|
|
||||||
additive arms and the Boss-call-2 `synth` `CheckError::Internal`
|
|
||||||
stub. `cargo build -p ailang-check` GREEN.
|
|
||||||
- iter loop-recur.1.6: ailang-codegen + ail/main.rs — `lower_term`
|
|
||||||
`CodegenError::Internal` stub + real structural pass-through at
|
|
||||||
the pure analysis walkers (lambda/escape ×3) + `synth_with_extras`
|
|
||||||
pass-through + main.rs walk_term/rewrite_term. `cargo build
|
|
||||||
--workspace` GREEN.
|
|
||||||
- iter loop-recur.1.7: DESIGN.md + form_a.md schema blocks;
|
|
||||||
design_schema_drift + spec_drift + schema_coverage anchors; new
|
|
||||||
loop_recur hash pin; `examples/loop_sum_to.ail` positive fixture.
|
|
||||||
Full suite 608/0; tail-app non-regression green.
|
|
||||||
|
|
||||||
## Boss design calls (mirrored from the plan header at iter close)
|
|
||||||
|
|
||||||
1. **`verify_tail_positions` "byte-unchanged" = tail-app role
|
|
||||||
unchanged, NOT source frozen.** `verify_tail_positions`
|
|
||||||
(`crates/ailang-check/src/lib.rs`) is an exhaustive no-`_`-wildcard
|
|
||||||
`match`-on-`Term`; the two additive variants *require* two new
|
|
||||||
arms there or `ailang-check` does not compile. The added arms
|
|
||||||
only define how the tail-app walker descends through two
|
|
||||||
brand-new node kinds that no pre-existing fixture contains —
|
|
||||||
zero behaviour change for any pre-existing construct. The
|
|
||||||
acceptance evidence is the tail-app non-regression test (all
|
|
||||||
`tail`-filtered tests green), NOT a frozen-source diff. mut.1 set
|
|
||||||
the exact precedent (it added `Term::Mut`/`Term::Assign` arms
|
|
||||||
inside the same function). Settled; not reopened — the
|
|
||||||
spec-compliance phase did NOT emit `unclear`/BLOCKED on the
|
|
||||||
anticipated "spec says byte-unchanged but the function changed"
|
|
||||||
tension, per the plan header's authority.
|
|
||||||
2. **Codegen IS in iter-1 scope as stubs/pass-throughs (mirrors
|
|
||||||
mut.1 site-for-site).** "No codegen in iter 1" means no real
|
|
||||||
loop-header/phi/back-edge lowering (that is iter 3). But
|
|
||||||
`ailang-codegen` carries no-wildcard exhaustive `Term` matches;
|
|
||||||
the workspace would not compile without arms. Resolved exactly
|
|
||||||
as mut.1: `CodegenError::Internal` stub at the `lower_term`
|
|
||||||
dispatch + real structural pass-through at the pure analysis
|
|
||||||
walkers (lambda.rs collect_captures, escape.rs walk/escapes/
|
|
||||||
collect_free_vars) and `synth_with_extras`. No real loop lowering
|
|
||||||
written this iter.
|
|
||||||
|
|
||||||
## Concerns
|
|
||||||
|
|
||||||
- DONE_WITH_CONCERNS (iter loop-recur.1.1/1.2/1.4): three
|
|
||||||
plan-pseudo-vs-reality substitutions, all of the recurring
|
|
||||||
"specs need concrete code / plan pseudo vs reality" class
|
|
||||||
(`feedback_specs_need_concrete_code`, `feedback_plan_pseudo_vs_reality`),
|
|
||||||
each resolved by preserving the arm/test's evident intent and
|
|
||||||
fixing to the real codebase:
|
|
||||||
1. **Task-1 serde expected-bytes literal.** The plan asserted
|
|
||||||
`"type":{"t":"con","name":"Int","args":[]}`; the real canonical
|
|
||||||
`Type::Con` form is `{"k":"con","name":"Int"}` (discriminator
|
|
||||||
`"k"`, empty `args` omitted — established since mut.2). The
|
|
||||||
test's stated intent (pin one-binder Loop canonical bytes;
|
|
||||||
prove `binders` has no `skip_serializing_if`) is preserved;
|
|
||||||
only the mis-transcribed expected string was corrected. NOT a
|
|
||||||
bug-dodge — `{"k":"con",…}` is the canonical form every Type
|
|
||||||
fixture uses; the plan author mis-recalled the Type JSON.
|
|
||||||
2. **Bare `Int` vs `(con Int)` in binder triples.** The spec's
|
|
||||||
worked example and the plan's Step-1 test source / Step-6
|
|
||||||
fixture write `(loop (acc Int 0) …)` with bare `Int`. The real
|
|
||||||
`parse_type` parses a bare `Int` ident as `Type::Var{"Int"}`
|
|
||||||
(a type *variable*), not the Int constructor — semantically
|
|
||||||
wrong and inconsistent with every `mut` fixture
|
|
||||||
(`(var x (con Int) 0)`), the spec schema, and the Task-1 serde
|
|
||||||
pin. Corrected to `(con Int)` in the parser test source and the
|
|
||||||
`examples/loop_sum_to.ail` fixture. This is the LLM-natural AND
|
|
||||||
correct Form-A type (not a fixture-adapts-to-bug case — every
|
|
||||||
sibling fixture uses `(con Int)`).
|
|
||||||
3. **`ailang_core::ast::LoopBinder` path inside ailang-core.** The
|
|
||||||
plan's literal desugar.rs/workspace.rs arms used the
|
|
||||||
downstream-crate path `ailang_core::ast::LoopBinder`; inside
|
|
||||||
ailang-core itself (`use crate::ast::*;`) the correct path is
|
|
||||||
unqualified `LoopBinder` (consistent with the adjacent bare
|
|
||||||
`MutVar` usage). `subst_call_with_extras`'s plan arm also used
|
|
||||||
`(target, extras)`; the real signature is `(name, lifted,
|
|
||||||
extras)` — mirrored from the local Mut arm per the plan's own
|
|
||||||
instruction. ailang-check (downstream) correctly keeps the
|
|
||||||
`ailang_core::ast::LoopBinder` path.
|
|
||||||
|
|
||||||
## Boss addendum (post-orchestrator, at commit)
|
|
||||||
|
|
||||||
Concern 2 surfaced that the bare-`Int`-in-binder imprecision is not
|
|
||||||
just in the plan/test but in the **committed specs themselves**:
|
|
||||||
`docs/specs/2026-05-17-loop-recur.md`'s headline clause-1 worked
|
|
||||||
example + the must-fail `bad_recur` fixture, and
|
|
||||||
`docs/specs/2026-05-17-llm-surface-discipline.md` §5's illustrative
|
|
||||||
anchor, all wrote `(loop (acc Int 0) (i Int 1) …)`. Bare `Int`
|
|
||||||
parses as `Type::Var{"Int"}`, so the spec's *headline evidence* —
|
|
||||||
the program an LLM author would copy verbatim — does not parse to
|
|
||||||
the typed loop it claims. Same doc-honesty class as the mono.rs
|
|
||||||
header / effect-doc-honesty: a spec must show code that actually
|
|
||||||
parses to what it asserts. Boss forward-fixed all three snippets to
|
|
||||||
`(con Int)` (the established convention every `mut` fixture + the
|
|
||||||
spec's own schema use); folded into this iter's commit. No design,
|
|
||||||
semantics, schema, or load-bearing assumption changed — a
|
|
||||||
surface-syntax precision fix, so no re-brainstorm / re-grounding.
|
|
||||||
|
|
||||||
## Known debt
|
|
||||||
|
|
||||||
- No additional E2E fixture written (Phase 3). iter-1 ships NO
|
|
||||||
typecheck and NO codegen by design; an `ail run` e2e would only
|
|
||||||
exercise the deliberately-temporary `synth`/`lower_term`
|
|
||||||
`Internal` stubs (a negative-value test asserting a transient
|
|
||||||
error). The milestone invariant worth protecting at iter-1 —
|
|
||||||
additivity (hash pins), round-trip (the new fixture), drift trio,
|
|
||||||
tail-app non-regression — is fully covered by tests added in
|
|
||||||
Tasks 1/2/7. The spec's positive sum_to-runs-to-a-value E2E is
|
|
||||||
explicitly iter-3 scope.
|
|
||||||
|
|
||||||
## Files touched
|
|
||||||
|
|
||||||
- AST + core walkers: `crates/ailang-core/src/ast.rs`,
|
|
||||||
`desugar.rs`, `workspace.rs`
|
|
||||||
- Surface: `crates/ailang-surface/src/parse.rs`, `print.rs`;
|
|
||||||
`crates/ailang-core/specs/form_a.md`
|
|
||||||
- Prose: `crates/ailang-prose/src/lib.rs`
|
|
||||||
- Check: `crates/ailang-check/src/{lib,lift,mono,linearity,
|
|
||||||
uniqueness,reuse_shape,pre_desugar_validation}.rs`
|
|
||||||
- Codegen + CLI: `crates/ailang-codegen/src/{lambda,escape,lib}.rs`,
|
|
||||||
`crates/ail/src/main.rs`
|
|
||||||
- Docs/drift/coverage/hash: `docs/DESIGN.md`,
|
|
||||||
`crates/ailang-core/tests/{design_schema_drift,spec_drift,
|
|
||||||
schema_coverage,hash_pin}.rs`,
|
|
||||||
`crates/ail/tests/codegen_import_map_fallback_pin.rs`
|
|
||||||
(recon-undercount: an integration-test exhaustive `Term` match
|
|
||||||
not in the plan's site inventory; same additive-arm class,
|
|
||||||
mirrors the Mut defensive-recursion shape — Boss-call-2-class
|
|
||||||
"recon under-scoped it")
|
|
||||||
- New fixture: `examples/loop_sum_to.ail`
|
|
||||||
|
|
||||||
## Stats
|
|
||||||
|
|
||||||
bench/orchestrator-stats/2026-05-17-iter-loop-recur.1.json
|
|
||||||
@@ -1,185 +0,0 @@
|
|||||||
# iter loop-recur.2 — Typecheck Semantics (Component 4)
|
|
||||||
|
|
||||||
**Date:** 2026-05-17
|
|
||||||
**Started from:** 5ac57fe8dec2d83ba135dd5ff6dd9caa1165c700
|
|
||||||
**Status:** DONE
|
|
||||||
**Tasks completed:** 6 of 6
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
The iter-1 `synth` `CheckError::Internal` stub for `Term::Loop` /
|
|
||||||
`Term::Recur` is replaced with real typecheck semantics: binder
|
|
||||||
typing (each init synthed in outer scope + already-declared binder
|
|
||||||
names of THIS loop; loop's static type = body type), positional
|
|
||||||
`recur` arity + per-arg type checking via a new `loop_stack: &mut
|
|
||||||
Vec<Vec<Type>>` frame threaded exactly as mut.2's `mut_scope_stack`,
|
|
||||||
the four `Recur*` `CheckError` variants (bracket-`[code]`-free
|
|
||||||
Display per F2) + `code()` + two `ctx()` arms, and a NEW private
|
|
||||||
`verify_loop_body` tail-position pass (a *sibling* of the
|
|
||||||
spec-frozen `verify_tail_positions`, run after `synth` in `check_fn`
|
|
||||||
so synth-raised codes take precedence by pass ordering). Four
|
|
||||||
negative `.ail.json` fixtures fire their codes point-exactly
|
|
||||||
(`assert_eq!`, non-vacuous); the iter-1 `loop_sum_to.ail`
|
|
||||||
round-trip fixture now ALSO typechecks clean; an infinite `loop`
|
|
||||||
typechecks (no termination claim). NO codegen this iter (the iter-1
|
|
||||||
`lower_term` `CodegenError::Internal` stub stays — iter 3); NO
|
|
||||||
`Diverge`/`verify_structural_recursion`/guardedness (spec deliberate
|
|
||||||
boundary). `verify_tail_positions` is byte-frozen (0 deletions).
|
|
||||||
`cargo test --workspace` 616 green / 0 red (iter-1 baseline 608).
|
|
||||||
|
|
||||||
## Per-task notes
|
|
||||||
|
|
||||||
- iter loop-recur.2.1: the four `Recur*` `CheckError` variants
|
|
||||||
(`RecurOutsideLoop`, `RecurArityMismatch{expected,got}`,
|
|
||||||
`RecurTypeMismatch{position,expected,got}`,
|
|
||||||
`RecurNotInTailPosition`) + 4 `code()` arms + 2 `ctx()` arms
|
|
||||||
(Arity/Type only; OutsideLoop/NotInTailPosition use the `{}`
|
|
||||||
catch-all). RED-first via `recur_checkerror_codes_are_exact`
|
|
||||||
(observed 4× E0599); GREEN exact. Bracket-`[code]`-free Display.
|
|
||||||
- iter loop-recur.2.2: `synth` signature gains
|
|
||||||
`loop_stack: &mut Vec<Vec<Type>>` after `mut_scope_stack`;
|
|
||||||
threaded at every recursive call site (17 single-line canonical +
|
|
||||||
3 multi-line `Term::Mut`-init/body & `Term::Assign`-value + 2
|
|
||||||
letrec-capture `&mut body_effects`-shape) + `check_const` (fresh
|
|
||||||
empty stack) + cross-module callers + test-module callers. The
|
|
||||||
compile-driven sweep (`cargo build`/`cargo test --no-run`) was the
|
|
||||||
exhaustive caller oracle as the plan specifies; iterated to 0
|
|
||||||
errors. Stub intact, lib suite green.
|
|
||||||
- iter loop-recur.2.3: iter-1 `Internal` stub replaced with the two
|
|
||||||
real `Term::Loop`/`Term::Recur` arms. Binder-name save/restore
|
|
||||||
mirrors `Term::Let` (`lib.rs:3205-3218`) verbatim; per-arg type
|
|
||||||
check mirrors `Term::Assign` (`lib.rs:3716-3730`) verbatim per
|
|
||||||
Boss call 1; `recur`'s own type = `Subst::fresh(counter)`.
|
|
||||||
RED `loop_sum_to_typechecks_clean` observed `["internal"]`;
|
|
||||||
GREEN zero diagnostics.
|
|
||||||
- iter loop-recur.2.4: new private `verify_loop_body(t, in_loop_tail)`
|
|
||||||
immediately after `verify_tail_positions`'s closing brace —
|
|
||||||
exhaustive no-`_` match over all 16 `Term` variants (verified
|
|
||||||
equal to `verify_tail_positions`' variant set). Invoked from
|
|
||||||
`check_fn` after `verify_tail_positions(&f.body, true)?;`. RED
|
|
||||||
`recur_not_in_tail_position` observed `left: []`; GREEN
|
|
||||||
`["recur-not-in-tail-position"]`, positive still clean.
|
|
||||||
- iter loop-recur.2.5: 3 remaining negative fixtures
|
|
||||||
(`test_recur_outside_loop` / `_arity_mismatch` / `_type_mismatch`)
|
|
||||||
+ 3 pins (all 5 green) + `carve_out_inventory` 13→17 with header
|
|
||||||
refresh (`Twelve…mut.2` → `Seventeen…loop-recur.2`, pre-existing
|
|
||||||
Twelve-vs-13 mut.4-tidy drift corrected in passing) + ct1 F2
|
|
||||||
sibling `check_human_mode_renders_recur_diagnostic_code_exactly_once`.
|
|
||||||
- iter loop-recur.2.6: `examples/loop_forever.ail` (loop whose only
|
|
||||||
path is `recur`, no exit) typechecks clean — asserts the spec
|
|
||||||
"no termination claim is made or enforced". Form-A grammar
|
|
||||||
verified verbatim against the shipped `loop_sum_to.ail`. Full
|
|
||||||
suite 616/0; tail-app non-regression green (9 `tail`-named tests
|
|
||||||
ok incl. the iter-1 verify_tail_positions guards).
|
|
||||||
|
|
||||||
## Boss design calls (mirrored from the plan header at iter close)
|
|
||||||
|
|
||||||
1. **`RecurTypeMismatch` is an Assign-style structural pre-check,
|
|
||||||
NOT a `unify`-propagate.** `Term::Recur` per-arg checking mirrors
|
|
||||||
the `Term::Assign` arm (`lib.rs:3716-3730`) verbatim in
|
|
||||||
mechanism: synth the arg, `subst.apply` both the arg type and the
|
|
||||||
binder type, structural `!=`, on mismatch
|
|
||||||
`Err(RecurTypeMismatch{position,expected,got})`. A
|
|
||||||
`unify`-propagate would surface the generic `type-mismatch` code
|
|
||||||
and fail the spec acceptance ("dedicated `recur-type-mismatch`
|
|
||||||
fires point-exactly"). Rationale semantic (one in-repo mechanism
|
|
||||||
for "declared-vs-actual at a binding site with its own
|
|
||||||
point-exact code", consistent with `AssignTypeMismatch`), not
|
|
||||||
effort. Implemented verbatim; the `test_recur_type_mismatch`
|
|
||||||
fixture fires exactly `["recur-type-mismatch"]`.
|
|
||||||
2. **`loop_stack` element type is `Vec<Type>` (ordered binder
|
|
||||||
types), NOT `IndexMap<String,Type>`.** `recur` rebinds binders
|
|
||||||
*positionally*, so the frame models the ordered binder types for
|
|
||||||
arity (`.len()`) + per-position type checks. Binder *names* enter
|
|
||||||
the ordinary `locals: &mut IndexMap<String,Type>` exactly as
|
|
||||||
`Term::Let` binds its name (save/restore in reverse on loop
|
|
||||||
exit). The spec phrase "threaded exactly as mut.2's
|
|
||||||
`mut_scope_stack`" governs the *threading discipline*
|
|
||||||
(a `&mut Vec<…>` pushed/popped around the body, passed through
|
|
||||||
every recursive `synth`), not the element type. Semantic, not
|
|
||||||
effort. Implemented verbatim.
|
|
||||||
3. **Diagnostic-code precedence is by pass ordering, no explicit
|
|
||||||
logic.** `RecurOutsideLoop`/`RecurArityMismatch`/`RecurTypeMismatch`
|
|
||||||
are raised in `synth` (runs at the `check_fn` synth call);
|
|
||||||
`RecurNotInTailPosition` is raised in `verify_loop_body`, invoked
|
|
||||||
*after* `verify_tail_positions(&f.body, true)?;`. A `recur`
|
|
||||||
outside any loop therefore fires `RecurOutsideLoop` (synth,
|
|
||||||
first), not `RecurNotInTailPosition`. No precedence logic added;
|
|
||||||
the pass sequence IS the mechanism. `verify_loop_body` is entered
|
|
||||||
only on synth success, so every `recur` reached there is already
|
|
||||||
inside a loop with matching arity/types — the pass adds only the
|
|
||||||
tail rule. `verify_tail_positions` is byte-frozen (0 deletions);
|
|
||||||
`verify_loop_body` is a new sibling, never a repurpose.
|
|
||||||
|
|
||||||
## Concerns
|
|
||||||
|
|
||||||
- DONE_WITH_CONCERNS (iter loop-recur.2.2): **plan ordering defect —
|
|
||||||
`check_fn` threading must be in Task 2, not Task 4.** The plan's
|
|
||||||
Task 2 Step 3 bullet says "check_fn :1970: handled in Task 4 Step
|
|
||||||
4 (it declares its own fresh loop_stack)", but Task 2 Step 4's
|
|
||||||
gate is `cargo build -p ailang-check` reaching `Finished` with 0
|
|
||||||
errors. That gate is *unsatisfiable* while `check_fn` remains an
|
|
||||||
unthreaded `synth` caller (hard `error[E0061]`). Resolution:
|
|
||||||
`check_fn`'s `loop_stack` declaration + synth-call threading
|
|
||||||
(verbatim the plan's Task 4 Step 3 block + Step 4 first half) were
|
|
||||||
pulled into Task 2 where the compile gate forces them; only the
|
|
||||||
`verify_loop_body` *invocation* (Task 4 Step 4 second half)
|
|
||||||
remained for Task 4. This is the unique resolution satisfying both
|
|
||||||
Task 2 Step 4 AND preserving all plan code verbatim — the "fix to
|
|
||||||
compile while preserving evident intent + Boss calls" repair the
|
|
||||||
carrier explicitly authorises. Correctness: the declaration code
|
|
||||||
is byte-identical to the plan's Task 4 Step 3 block; no semantic
|
|
||||||
change, only sequencing. Recurring planner-gap class (a
|
|
||||||
compile-gate task whose gate depends on a caller the plan defers
|
|
||||||
past the gate) — candidate for a planner Step-5 self-review check.
|
|
||||||
- DONE_WITH_CONCERNS (iter loop-recur.2.2): **recon under-counted
|
|
||||||
cross-module `synth` callers** — `builtins.rs` ×2 (test helpers),
|
|
||||||
`lift.rs:746` (letrec-capture re-entry), `mono.rs:720` &
|
|
||||||
`mono.rs:1361` (mono re-synth) were not in the plan's site
|
|
||||||
inventory (`:3161…:3717` + the 8 test sites). Exactly the
|
|
||||||
mut.2-class recon-undercount the iter-1 journal flagged
|
|
||||||
(Boss-call-2-class "recon under-scoped it"). Resolved by mirroring
|
|
||||||
the established mut.2 fresh-stack pattern at each (declare a fresh
|
|
||||||
empty `loop_stack` alongside the existing fresh `mut_scope_stack`,
|
|
||||||
pass `&mut loop_stack` immediately after `&mut mut_scope_stack`).
|
|
||||||
Observation, not a correctness risk — the compile-driven sweep IS
|
|
||||||
the plan's designated exhaustive oracle and surfaced every site;
|
|
||||||
the plan's literal site list is advisory, the compiler is
|
|
||||||
authoritative (as the plan itself states).
|
|
||||||
|
|
||||||
## Known debt
|
|
||||||
|
|
||||||
- No additional `ail run`/binary-stdout E2E fixture written (Phase
|
|
||||||
3). iter-2 ships typecheck semantics only; the `lower_term`
|
|
||||||
`CodegenError::Internal` stub stays by design (real loop lowering
|
|
||||||
+ the positive sum_to-runs-to-a-value / deep-`n` E2E is explicitly
|
|
||||||
iter-3 scope). An `ail run` this iter would only exercise the
|
|
||||||
deliberately-transient codegen stub (a negative-value test of a
|
|
||||||
known-temporary error) — same justified Phase-3 reasoning as
|
|
||||||
iter-1. The iter-2 invariants worth protecting (four codes
|
|
||||||
point-exact, positive + infinite typecheck clean,
|
|
||||||
carve-out/tail-app non-regression) are fully pinned by the tests
|
|
||||||
added in Tasks 1/3/4/5/6 + the ct1 F2 sibling.
|
|
||||||
|
|
||||||
## Files touched
|
|
||||||
|
|
||||||
- Check core: `crates/ailang-check/src/lib.rs` (4 variants +
|
|
||||||
code/ctx, `synth` signature + threading, real Loop/Recur arms,
|
|
||||||
new `verify_loop_body`, `check_fn` + `check_const` wiring),
|
|
||||||
`crates/ailang-check/src/lift.rs`, `crates/ailang-check/src/mono.rs`,
|
|
||||||
`crates/ailang-check/src/builtins.rs` (cross-module synth-caller
|
|
||||||
threading)
|
|
||||||
- Tests/fixtures: `crates/ailang-check/tests/loop_recur_typecheck_pin.rs`
|
|
||||||
(new — 7 tests), `crates/ail/tests/ct1_check_cli.rs` (F2 sibling),
|
|
||||||
`crates/ailang-core/tests/carve_out_inventory.rs` (13→17 + header)
|
|
||||||
- New fixtures: `examples/test_recur_outside_loop.ail.json`,
|
|
||||||
`examples/test_recur_arity_mismatch.ail.json`,
|
|
||||||
`examples/test_recur_type_mismatch.ail.json`,
|
|
||||||
`examples/test_recur_not_in_tail_position.ail.json`,
|
|
||||||
`examples/loop_forever.ail`
|
|
||||||
- Reused (no new file): `examples/loop_sum_to.ail` (iter-1
|
|
||||||
round-trip fixture, now also the positive `ail check` evidence)
|
|
||||||
|
|
||||||
## Stats
|
|
||||||
|
|
||||||
bench/orchestrator-stats/2026-05-17-iter-loop-recur.2.json
|
|
||||||
@@ -1,188 +0,0 @@
|
|||||||
# iter loop-recur.3 — Codegen (Component 5) + run-to-value E2E
|
|
||||||
|
|
||||||
**Date:** 2026-05-17
|
|
||||||
**Started from:** eae73bf320440f6b031715e72c1f20b0ac86e024
|
|
||||||
**Status:** DONE
|
|
||||||
**Tasks completed:** 3 of 3
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
The **terminal iteration** of the standalone-`loop`/`recur` milestone:
|
|
||||||
the iter-1 `lower_term` `CodegenError::Internal` stub for
|
|
||||||
`Term::Loop`/`Term::Recur` is replaced with real LLVM-IR lowering, so
|
|
||||||
a `sum_to`-class loop program builds and runs to the correct printed
|
|
||||||
value, a deep-`n` variant is safe by construction, and an infinite
|
|
||||||
loop compiles. Loop binders are loop-carried values lowered as
|
|
||||||
entry-block allocas (the mut.3 `pending_entry_allocas` mechanism)
|
|
||||||
registered in the existing `mut_var_allocas` map, so the existing
|
|
||||||
`Term::Var` load path resolves them with zero new Var code
|
|
||||||
(representation-sharing); a fresh `loop.header.<id>` block is reached
|
|
||||||
by an unconditional `br` from the pre-header, `recur` lowers each arg
|
|
||||||
to SSA *before* any store (simultaneous positional rebind), stores
|
|
||||||
into the binder allocas, back-edges `br` to the header, and sets the
|
|
||||||
single existing `block_terminated` field at its OWN new emit site so
|
|
||||||
the loop's exit value is the emergent product of the existing
|
|
||||||
`if`/`match` join (no separate loop-result phi/exit-block). A new
|
|
||||||
`loop_frames` codegen stack lets `recur` find its target; it is
|
|
||||||
saved/reset/restored at the single lambda-lowering boundary exactly
|
|
||||||
as mut.3's `mut_var_allocas` triple. `clang -O2` mem2reg promotes
|
|
||||||
the binder allocas to the phi nodes the spec's implementation-shape
|
|
||||||
describes. This is a **codegen-only iteration**: no schema/AST/serde
|
|
||||||
change, no typecheck change; hash pins (`loop_recur` + iter13a) and
|
|
||||||
the drift trio + `carve_out_inventory` stay green untouched
|
|
||||||
(confirmed, not modified — empirical proof none scans the codegen
|
|
||||||
region). `cargo test --workspace` 616 → 619 / 0 red (the 3 new e2e
|
|
||||||
tests). After this iter the milestone is structurally CLOSED;
|
|
||||||
milestone-close (audit/fieldtest) is the Boss's post-iter call.
|
|
||||||
|
|
||||||
## Per-task notes
|
|
||||||
|
|
||||||
- iter loop-recur.3.1: positive `sum_to` run-to-value E2E (RED →
|
|
||||||
GREEN). RED observed verbatim: `ail build` on `loop_sum_to_run.ail`
|
|
||||||
fails with the iter-1 stub `internal: Term::Loop/Term::Recur
|
|
||||||
lowering lands in loop-recur iter 3` (parse + iter-1/iter-2
|
|
||||||
typecheck pass; only codegen stubbed — confirmed via direct `ail
|
|
||||||
build`). GREEN: `loop_frames` field + init, lambda-boundary
|
|
||||||
save/reset/restore, the two real `Term::Loop`/`Term::Recur` arms
|
|
||||||
replacing the single stub arm. `loop_sum_to_run.ail` → `55`;
|
|
||||||
`cargo build -p ailang-codegen` Finished; full e2e suite 88/0.
|
|
||||||
Plan's literal arms compiled as-written — no plan-vs-reality
|
|
||||||
substitution needed (pre-grounding confirmed `mut_var_allocas:
|
|
||||||
(String, Type)` and `lower_term -> (String /*ssa*/, String
|
|
||||||
/*llvm_ty*/)` matched the plan's assumptions exactly).
|
|
||||||
- iter loop-recur.3.2: deep-`n` safe-by-construction +
|
|
||||||
infinite-loop-compiles (test+fixture only; the codegen mechanism
|
|
||||||
shipped in 3.1, so no separate RED). `loop_sum_to_deep.ail` runs
|
|
||||||
1e6 iterations via the back-edge → `500000500000` with no stack
|
|
||||||
growth (the spec clause-2 correctness claim made executable —
|
|
||||||
mem2reg promoted the binder allocas so the loop is an iterative
|
|
||||||
back-edge, not a stack-growing recursion). `loop_forever_build.ail`
|
|
||||||
(`spin`'s loop has no non-`recur` exit) compiles `ail build` exit
|
|
||||||
0 — `recur` sets `block_terminated`, propagating through
|
|
||||||
`emit_fn`'s `if !self.block_terminated` fall-through guard so
|
|
||||||
`spin` emits no `ret` and is a well-formed never-returning fn; the
|
|
||||||
binary is never executed (spec "typechecks AND compiles, no
|
|
||||||
termination claim"). Full e2e suite 90/0.
|
|
||||||
- iter loop-recur.3.3: regression gates — pure verification, ZERO
|
|
||||||
source changes (confirmed `git diff --name-only HEAD` = only the
|
|
||||||
3 T1/T2 source files). tail-app/tail-do/`verify_tail_positions`
|
|
||||||
non-regression green (the parallel `block_terminated` SET site
|
|
||||||
touched no existing SET/READ site — Boss call 4); `loop_recur` +
|
|
||||||
iter13a hash pins + drift trio + `carve_out_inventory` green
|
|
||||||
untouched (codegen-only iter, no schema change); full workspace
|
|
||||||
619 passed = exactly the loop-recur.2 baseline (616) + the 3 new
|
|
||||||
e2e tests, zero FAILED/error; `round_trip` green (the 3 new `.ail`
|
|
||||||
fixtures auto-discovered, parse→print→parse idempotent — Roundtrip
|
|
||||||
Invariant holds for runnable loop/recur programs).
|
|
||||||
|
|
||||||
## Boss design calls (mirrored from the plan header at iter close)
|
|
||||||
|
|
||||||
All four were settled in the plan header on
|
|
||||||
architectural-consistency / spec-pinned-invariant grounds (NOT
|
|
||||||
effort); each verified against actual read source before
|
|
||||||
implementation; none reopened, re-derived, or "improved" in any
|
|
||||||
phase:
|
|
||||||
|
|
||||||
1. **Loop binders → entry-block alloca + `mut_var_allocas` reuse,
|
|
||||||
NOT hand-emitted `phi`.** The spec's "phi" wording lives only in
|
|
||||||
the explicitly-*secondary* implementation-shape subsection. The
|
|
||||||
established in-repo loop-carried-value mechanism is
|
|
||||||
alloca-in-entry-block (mut.3 `pending_entry_allocas`) + `clang
|
|
||||||
-O2` mem2reg, which produces the identical optimized binary. The
|
|
||||||
linear-emit codegen architecture structurally cannot hand-emit a
|
|
||||||
header `phi` whose back-edge predecessors are discovered *during*
|
|
||||||
body lowering without a string-splice hack with no precedent (the
|
|
||||||
`if` arm sidesteps this by opening its join block *last* — a loop
|
|
||||||
header cannot be opened last). Rationale = architectural
|
|
||||||
consistency + absence of a linear-emit `phi` precedent + identical
|
|
||||||
-O2 output. Implemented verbatim; `loop_sum_to_run.ail`→55 and
|
|
||||||
`loop_sum_to_deep.ail`→500000500000 are the operational proof
|
|
||||||
mem2reg delivers the loop-carried-SSA intent.
|
|
||||||
2. **Binders ride the existing `mut_var_allocas` map + the existing
|
|
||||||
`Term::Var` load path (representation-sharing).** A loop binder's
|
|
||||||
codegen representation — a named alloca slot, loaded on each
|
|
||||||
`Term::Var` use, stored on `recur` — is structurally identical to
|
|
||||||
a mut-var's. The `Term::Loop` arm inserts each binder into
|
|
||||||
`mut_var_allocas` (value type `(String, Type)`, identical to the
|
|
||||||
`Term::Mut` precedent); the existing `lib.rs` `Term::Var` arm
|
|
||||||
resolves them via its pre-existing `mut_var_allocas.get(name)` →
|
|
||||||
`load` path **byte-unchanged** (zero edits to Var-resolution; no
|
|
||||||
parallel map). Verified against real source before implementing.
|
|
||||||
mut.3's shipped+E2E-green mut-var read path is the proof the load
|
|
||||||
works for loop binders.
|
|
||||||
3. **The loop's exit value is the emergent product of the existing
|
|
||||||
`if`/`match` join once `recur` sets `block_terminated` — no
|
|
||||||
separate loop-result phi/exit-block.** The body is typically
|
|
||||||
`(if c <exit> (recur …))`; the existing
|
|
||||||
one-branch-terminated logic at `lib.rs:1606-1613` already drops
|
|
||||||
the terminated (recur) branch and returns the non-recur branch's
|
|
||||||
`(ssa,ty)` directly — *iff* `recur` sets `block_terminated`. The
|
|
||||||
`Term::Loop` arm therefore returns `lower_term(body)`'s `(ssa,ty)`
|
|
||||||
directly; no separate loop-result phi was built. An infinite loop
|
|
||||||
(body = bare `recur`) compiles because `recur` sets
|
|
||||||
`block_terminated`, propagating through the `emit_fn`
|
|
||||||
`if !self.block_terminated` fall-through guard exactly as a
|
|
||||||
tail-app-terminated function.
|
|
||||||
4. **Reuse the single `block_terminated` field; `recur` sets it at
|
|
||||||
its OWN new emit site (a parallel SET call-site), zero edits to
|
|
||||||
any existing SET or READ site.** A back-edge `br` IS a block
|
|
||||||
terminator with exactly `block_terminated`'s semantics. `recur`'s
|
|
||||||
own `self.block_terminated = true;` is a NEW call-site parallel
|
|
||||||
to (not replacing) tail-app's SET at `lib.rs:2279`. The diff
|
|
||||||
confirms tail-app's SET and every READ site
|
|
||||||
(`:1124/1581/1594/1600-1613/...`) are byte-identical; the
|
|
||||||
tail-app/`verify_tail_positions` non-regression gate (T3 Step 1)
|
|
||||||
is the operational proof.
|
|
||||||
|
|
||||||
## Concerns
|
|
||||||
|
|
||||||
- DONE_WITH_CONCERNS (iter loop-recur.3.3): the plan's literal Task-3
|
|
||||||
Step-1 command `cargo test --workspace tail` filters on the
|
|
||||||
test-name substring `tail`, which matches **no** test in the
|
|
||||||
workspace (the real tail-app guards are
|
|
||||||
`ailang-check::lib::tail_call_in_{non_,}tail_position_is_*`,
|
|
||||||
`ailang-prose::lib` `*_tail_*`, e2e
|
|
||||||
`iter14e_print_list_recursion_emits_musttail`). Ran the gate via
|
|
||||||
the real test names instead (2 + 3 + 1, all green); the
|
|
||||||
authoritative non-regression gate is Step 3's full-workspace
|
|
||||||
619/0 which subsumes them. Recurring `feedback_plan_pseudo_vs_reality`
|
|
||||||
class (a literal verification command whose filter doesn't resolve
|
|
||||||
to its evident target); resolved by preserving the gate's evident
|
|
||||||
intent (tail-app/`verify_tail_positions` byte-unchanged proven),
|
|
||||||
no behaviour change. Observation, not a correctness risk — the
|
|
||||||
full-suite count `619 = 616 + 3` is independent confirmation
|
|
||||||
nothing regressed.
|
|
||||||
|
|
||||||
## Known debt
|
|
||||||
|
|
||||||
- None. The iteration's invariants (loop/recur lowers to real IR;
|
|
||||||
run-to-value 55; deep-`n` 500000500000 safe-by-construction;
|
|
||||||
infinite loop compiles; tail-app + hash pins + drift trio
|
|
||||||
byte-unchanged; round-trip on the 3 new fixtures) are fully pinned
|
|
||||||
by the 3 new e2e tests + the unchanged regression suite.
|
|
||||||
|
|
||||||
## Files touched
|
|
||||||
|
|
||||||
- Codegen: `crates/ailang-codegen/src/lib.rs` (`loop_frames` field +
|
|
||||||
init; the two real `Term::Loop`/`Term::Recur` arms replacing the
|
|
||||||
iter-1 single stub arm), `crates/ailang-codegen/src/lambda.rs`
|
|
||||||
(lambda-boundary `loop_frames` save/reset + restore, alongside the
|
|
||||||
mut.3 triple)
|
|
||||||
- Tests: `crates/ail/tests/e2e.rs` (3 new test fns: 2 build+run, 1
|
|
||||||
build-only)
|
|
||||||
- New fixtures: `examples/loop_sum_to_run.ail`,
|
|
||||||
`examples/loop_sum_to_deep.ail`, `examples/loop_forever_build.ail`
|
|
||||||
- No edit / byte-frozen (confirmed, not modified): every existing
|
|
||||||
`block_terminated` SET/READ site + tail-app/tail-do lowering +
|
|
||||||
`verify_tail_positions`; the iter-2 typecheck (`synth`
|
|
||||||
Loop/Recur arms, `verify_loop_body`, `loop_stack`, the four
|
|
||||||
`Recur*` variants); the iter-1 `synth_with_extras`
|
|
||||||
Loop→body-type / Recur→Unit pass-through; `hash_pin.rs`
|
|
||||||
(`loop_recur` + iter13a pins) + the drift trio +
|
|
||||||
`carve_out_inventory.rs` (codegen-only iter; the 3 new `.ail`
|
|
||||||
fixtures are round-trip-auto-covered, NOT carve-outs — inventory
|
|
||||||
stays 8)
|
|
||||||
|
|
||||||
## Stats
|
|
||||||
|
|
||||||
bench/orchestrator-stats/2026-05-17-iter-loop-recur.3.json
|
|
||||||
@@ -1,133 +0,0 @@
|
|||||||
# iter loop-recur.tidy — lambda capturing a loop binder must fail `ail check`, not panic codegen
|
|
||||||
|
|
||||||
**Date:** 2026-05-18
|
|
||||||
**Started from:** 39380d361d530a95a4f1c718c0e201f8312d3ac4
|
|
||||||
**Status:** DONE
|
|
||||||
**Tasks completed:** 1 of 1
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
A `Term::Lam` whose body captured an enclosing `Term::Loop` binder
|
|
||||||
passed `ail check` (exit 0) and then panicked `unreachable!()` at
|
|
||||||
`crates/ailang-codegen/src/lambda.rs:102` — type-correct input
|
|
||||||
crashing the compiler. This iter adds the symmetric escape guard:
|
|
||||||
such a capture now fails `ail check` with the stable kebab code
|
|
||||||
`loop-binder-captured-by-lambda`, exactly as mut.4-tidy did for
|
|
||||||
`mut-var-captured-by-lambda`. The fix is the minimal mechanism that
|
|
||||||
reuses the scope structure the `Term::Loop` arm already maintains.
|
|
||||||
|
|
||||||
## Per-task notes
|
|
||||||
|
|
||||||
- iter loop-recur.tidy.1: GREEN side of the debug RED test
|
|
||||||
`lambda_capturing_loop_binder_emits_loop_binder_captured_by_lambda`.
|
|
||||||
Added `CheckError::LoopBinderCapturedByLambda { name }`
|
|
||||||
(bracket-free F2 Display), its `code()` arm
|
|
||||||
(`loop-binder-captured-by-lambda`), and its `ctx()` arm
|
|
||||||
(`{name}`, mirroring `MutVarCapturedByLambda`). Extended
|
|
||||||
`loop_stack`'s element type from `Vec<Type>` to
|
|
||||||
`Vec<(String, Type)>` so the existing per-loop scope frame also
|
|
||||||
carries binder names (the `Term::Recur` arm reads `.1` for the
|
|
||||||
positional type check; the `Term::Lam` escape guard reads `.0`).
|
|
||||||
The `Term::Lam` arm's existing free-var scan (shared
|
|
||||||
`free_vars_in_term` walker) gained a parallel pass over
|
|
||||||
`loop_stack` next to the `mut_scope_stack` pass; its gate widened
|
|
||||||
to `!mut_scope_stack.is_empty() || !loop_stack.is_empty()`.
|
|
||||||
Mirrored the two mut.4-tidy in-source unit tests
|
|
||||||
(kebab-code test + synth-level capturing test). Lockstep:
|
|
||||||
`carve_out_inventory.rs` 17→18 (header count word + new bullet +
|
|
||||||
EXPECTED entry), `ct1_check_cli.rs` mut-F2 sibling `cases` gained
|
|
||||||
the new fixture (where `mut-var-captured-by-lambda` is asserted —
|
|
||||||
the exact precedent). `docs/DESIGN.md` one-line note adjacent to
|
|
||||||
the `MutVarCapturedByLambda` mention.
|
|
||||||
|
|
||||||
## Root cause
|
|
||||||
|
|
||||||
The `Term::Lam` escape guard iterated only `mut_scope_stack` to
|
|
||||||
reject lambda captures of alloca-resident locals. Loop binders are
|
|
||||||
inserted into the ordinary `locals` by the `Term::Loop` arm and
|
|
||||||
never entered any escape-checked scope, so a lambda body referencing
|
|
||||||
a loop binder was accepted by `ail check`. Codegen's lambda capture
|
|
||||||
resolver searches only `self.locals` after `mut_var_allocas` is
|
|
||||||
`std::mem::take`-emptied at the lambda frame boundary, so the
|
|
||||||
loop-binder reference resolved to nothing and hit `unreachable!()`.
|
|
||||||
Loop binders are alloca-resident under the same scheme as mut-vars
|
|
||||||
and obey the same no-escape-into-lambda rule.
|
|
||||||
|
|
||||||
## Design note — why extend `loop_stack` rather than add a parallel stack
|
|
||||||
|
|
||||||
The carrier offered "reuse whatever scope structure the `Term::Loop`
|
|
||||||
arm already maintains" as an explicit minimal option. `loop_stack`
|
|
||||||
is already threaded through every `synth` recursive call and is
|
|
||||||
pushed/popped at exactly the loop scope boundary. Extending its
|
|
||||||
frame element from `Type` to `(String, Type)` preserves all existing
|
|
||||||
push/pop discipline and `Term::Recur` positional-type semantics
|
|
||||||
unchanged — it is not a redesign. The alternative (a parallel
|
|
||||||
`mut_scope_stack`-shaped parameter) would have touched all 35
|
|
||||||
recursive `synth` call sites plus declarations, strictly larger.
|
|
||||||
Element-type extension is the minimal correct mechanism.
|
|
||||||
|
|
||||||
## Concerns
|
|
||||||
|
|
||||||
(none)
|
|
||||||
|
|
||||||
## Known debt
|
|
||||||
|
|
||||||
(none)
|
|
||||||
|
|
||||||
## Files touched
|
|
||||||
|
|
||||||
- `crates/ailang-check/src/lib.rs` — variant, code/ctx/Display, the
|
|
||||||
`Term::Loop` push + `Term::Recur` read + `Term::Lam` escape guard,
|
|
||||||
two in-source unit tests
|
|
||||||
- `crates/ailang-check/src/builtins.rs` — `loop_stack` decl type
|
|
||||||
- `crates/ailang-check/src/lift.rs` — `loop_stack` decl type
|
|
||||||
- `crates/ailang-check/src/mono.rs` — `loop_stack` decl type (x2)
|
|
||||||
- `crates/ailang-core/tests/carve_out_inventory.rs` — 17→18 lockstep
|
|
||||||
- `crates/ail/tests/ct1_check_cli.rs` — mut-F2 sibling `cases`
|
|
||||||
- `docs/DESIGN.md` — one-line symmetric-rule note
|
|
||||||
|
|
||||||
## Milestone-close audit resolution (Boss-side)
|
|
||||||
|
|
||||||
The loop/recur milestone-close `audit` (architect drift review +
|
|
||||||
bench gate) produced four items; this tidy commit resolves all:
|
|
||||||
|
|
||||||
- **`[high]` lambda-captures-loop-binder codegen crash** — fixed by
|
|
||||||
this iter's GREEN (the symmetric `loop-binder-captured-by-lambda`
|
|
||||||
guard). RED test committed separately as the audit trail
|
|
||||||
(`39380d3`); GREEN + the items below fold into this commit.
|
|
||||||
- **`[medium]` `mut_var_allocas` rustdoc lied about dual use** —
|
|
||||||
`crates/ailang-codegen/src/lib.rs` rustdoc said "Populated when
|
|
||||||
entering a `Term::Mut` block" / "matching the typecheck-side
|
|
||||||
mut-scope-stack precedence"; as of iter-3 loop binders ride the
|
|
||||||
same map. Boss-edited to present-tense dual-use truth (mut-vars
|
|
||||||
AND loop binders; the shared map is a representation detail, not
|
|
||||||
a scope-precedence claim). Doc-honesty class.
|
|
||||||
- **`[medium]` `Term::Loop` doc-comment stale "per-binder phi"** —
|
|
||||||
`crates/ailang-core/src/ast.rs` still described iter-1's
|
|
||||||
forward-looking `synth`/`lower_term` `Internal` stubs + "per-binder
|
|
||||||
phi"; iters 2/3 shipped real typecheck + alloca+mem2reg lowering
|
|
||||||
(Boss-call-1 explicitly rejected hand-emitted phi). Boss-edited to
|
|
||||||
the real shipped state. Doc-honesty class.
|
|
||||||
- **`[low]` recon-undercount 3× pattern** — added a P2 `[todo]` to
|
|
||||||
`docs/roadmap.md` (`ailang-plan-recon` cross-crate-caller-undercount
|
|
||||||
countermeasure; pairs with the planner Step-5 items 7+8 added this
|
|
||||||
milestone — those scrub the plan, this scrubs the recon). Not a
|
|
||||||
milestone-blocking fix; tracked forward.
|
|
||||||
- **Bench gate (audit Step 2)** — `bench/check.py` /
|
|
||||||
`compile_check.py` / `cross_lang.py` all exit 0; 25 metrics, 0
|
|
||||||
regressed, 25 stable. **Pristine** — exactly as the spec's
|
|
||||||
acceptance criteria expect for a strictly-additive surface no
|
|
||||||
hot-path bench exercises; the pre-existing P2 `*.bump_s`
|
|
||||||
hardware-staleness drift did not trip this run. **carry-on**: no
|
|
||||||
baseline update, no ratify entry (Iron Law — nothing intentionally
|
|
||||||
moved a metric).
|
|
||||||
|
|
||||||
**Milestone-loop-recur tidy: resolved.** Architect drift cleared
|
|
||||||
(the `[high]` is fixed code, the two `[medium]` are doc-honest, the
|
|
||||||
`[low]` is roadmap-tracked); bench pristine carry-on. The loop/recur
|
|
||||||
milestone is audit-clean; the only remaining pipeline step before
|
|
||||||
full close is `fieldtest` (user-visible Form-A surface).
|
|
||||||
|
|
||||||
## Stats
|
|
||||||
|
|
||||||
bench/orchestrator-stats/2026-05-17-iter-loop-recur.tidy.json
|
|
||||||
@@ -1,133 +0,0 @@
|
|||||||
# audit docs-honesty-lint — milestone close (CLEAN, carry-on)
|
|
||||||
|
|
||||||
**Date:** 2026-05-18
|
|
||||||
**Scope:** `928e1c0..e50b400` (spec `aff25cd`, plan `de66eb7`, iter
|
|
||||||
`7580d43`, INDEX `e50b400`)
|
|
||||||
**Status:** CLEAN — carry-on, no fix iteration, NO baseline ratify
|
|
||||||
**Milestone:** docs-honesty-lint (single iteration) — **CLOSED**
|
|
||||||
|
|
||||||
## Outcome
|
|
||||||
|
|
||||||
| Gate | Result |
|
|
||||||
|------|--------|
|
|
||||||
| Architect drift | `clean` (one `[medium]` standing advisory wart, see below) |
|
|
||||||
| `bench/check.py` | EXIT 1 — **causally exonerated**, do NOT ratify |
|
|
||||||
| `bench/compile_check.py` | EXIT 0 (24/24 stable) |
|
|
||||||
| `bench/cross_lang.py` | EXIT 0 (25/25 stable) |
|
|
||||||
| Recommendation | **carry-on** |
|
|
||||||
|
|
||||||
## Step 1 — Architect drift review
|
|
||||||
|
|
||||||
Status `clean`. Verified:
|
|
||||||
|
|
||||||
- **Scope claim exact.** `git diff 928e1c0..e50b400 --stat --
|
|
||||||
'crates/**/src/**' 'runtime/'` empty; sole `crates/` change is the
|
|
||||||
additive `crates/ailang-core/tests/docs_honesty_pin.rs` (RED-first,
|
|
||||||
now GREEN 4/4). The two lockstep-invariant pairs (`Pattern::Lit`/
|
|
||||||
`pre_desugar_validation`, `lower_app`/`is_static_callee`) untouched.
|
|
||||||
- **DESIGN.md edits are themselves honest.** All 14 corrections
|
|
||||||
genuinely present-tense; no Wunschdenken/post-mortem reintroduced.
|
|
||||||
All four protected-exception KEEPs survive un-stripped: `Diverge`
|
|
||||||
reserved (L2730), `Regions … considered and rejected` at the
|
|
||||||
Decision site (L1530), `a tiebreaker, not a rationale` (L1240),
|
|
||||||
`No deriving` (L1850). The `### What this document is — and the
|
|
||||||
honesty rule it holds itself to` meta-subsection is itself
|
|
||||||
present-tense (no "we will / planned").
|
|
||||||
- **Agent-def lockstep complete.** `skills/audit/agents/ailang-architect.md`
|
|
||||||
"four"→"five sweeps" purge verified (`grep "four sweeps…"` empty);
|
|
||||||
new "DESIGN.md honesty drift" bullet present and citing the
|
|
||||||
meta-subsection.
|
|
||||||
- **Sweep residue is exactly the 3 documented deliberate KEEPs.**
|
|
||||||
`bash bench/architect_sweeps.sh` EXIT 1 with: Sweep-1 L50
|
|
||||||
(`docs/journal-archive.md … pre-2026-05-11 history` — present-tense
|
|
||||||
doc-roles pointer, incidental date) + L2060 (the true
|
|
||||||
`(iter str-concat, 2026-05-13)` provenance tag — asserts nothing
|
|
||||||
false, consistent with the sibling `(iter 24.1)` tags); Sweep-5 L62
|
|
||||||
(the discriminator subsection's OWN forbidden-phrasing catalogue —
|
|
||||||
a self-referential false-positive, it IS the rule definition). Each
|
|
||||||
correctly adjudicated KEEP per the new bullet's discriminator.
|
|
||||||
|
|
||||||
**`[medium]` standing advisory wart (architect-flagged, orchestrator-
|
|
||||||
adjudicated → filed, not fixed now).** Sweep 5's regex matches the
|
|
||||||
discriminator subsection's own example catalogue (DESIGN.md L62)
|
|
||||||
permanently: every future `architect_sweeps.sh` run inherits a
|
|
||||||
guaranteed self-referential EXIT 1 that must be re-adjudicated by
|
|
||||||
hand. This does not introduce a *new* failure mode — the script has
|
|
||||||
never been EXIT 0 in practice (Sweeps 1-4 already retain legitimate
|
|
||||||
date-anchors; the script header states non-zero is advisory,
|
|
||||||
architect-adjudicated by design). It is a minor signal-erosion, not
|
|
||||||
current drift. Filed as a P3 roadmap todo (optional Sweep-5
|
|
||||||
self-anchor exclusion); explicitly NOT a fix iteration — consistent
|
|
||||||
with "trivial drift recorded, not ignored, but priced honestly at
|
|
||||||
P3".
|
|
||||||
|
|
||||||
## Step 2 — Bench-regression check
|
|
||||||
|
|
||||||
`check.py` EXIT 1, three firings: `throughput.bench_list_sum.bump_s`
|
|
||||||
+13.42% (tol 10%); `latency.implicit_at_rc.p99_9_us` +37.65% (tol
|
|
||||||
25%); `latency.implicit_at_rc.max_us` +134.70% (tol 30%). 58 stable,
|
|
||||||
2 improvements. `compile_check.py` EXIT 0; `cross_lang.py` EXIT 0 —
|
|
||||||
and `cross_lang.py` independently measures the *same* `bench_list_sum`
|
|
||||||
workload at `ail_bump_s +5.95% ok`, `ail_rc_s +5.08% ok` (the fixture
|
|
||||||
`check.py` calls a +13.42% REGRESSION reads within tolerance under a
|
|
||||||
fresher anchor / wider band).
|
|
||||||
|
|
||||||
## Step 3 — Bencher causal exoneration (decisive)
|
|
||||||
|
|
||||||
Hypothesis: the firings were *caused by this milestone's diff*.
|
|
||||||
Refuting test: HEAD bench binaries byte-identical to a pre-milestone
|
|
||||||
commit ⇒ no causal mechanism. Result (verbatim):
|
|
||||||
|
|
||||||
- `cmp` of `bench_list_sum_{bump,gc,rc}` + `bench_latency_implicit_{rc,gc}`
|
|
||||||
at HEAD `e50b400` vs `5bb7211` (last milestone close) vs `de66eb7`
|
|
||||||
(immediate pre-iter): **0 differing bytes, all 5 binaries identical
|
|
||||||
across both baselines.** `ail` release binary sha256 identical at
|
|
||||||
all three commits (zero compiled-source delta — the sole `crates/`
|
|
||||||
change is the non-bench test file).
|
|
||||||
- Latency re-measure on the byte-identical binary (5×`--runs 5`):
|
|
||||||
median/p99 rock-steady (≤0.6% / 1.1% spread); p99.9 swings
|
|
||||||
503.8→651.4, max 565.6→692.1 — classic stable-center / volatile-tail
|
|
||||||
on identical machine code. = the tracked-P2 `-n 5` tail-jitter
|
|
||||||
false-positive, **4th occurrence on a zero-runtime-change
|
|
||||||
milestone**.
|
|
||||||
- `bench_list_sum_bump` (byte-identical) 20-run median 0.0531s,
|
|
||||||
persistent ~+15% (not transient) = the tracked-P2 `*.bump_s`
|
|
||||||
environmental-baseline-staleness item; the 0.046s anchor is stale
|
|
||||||
vs this machine's current steady state.
|
|
||||||
|
|
||||||
**Verdict: EXONERATED (environmental, do NOT ratify).** Per the audit
|
|
||||||
Iron Law ("NO BASELINE UPDATE WITHOUT A PAIRED JOURNAL RATIFY ENTRY";
|
|
||||||
ratifying noise is forbidden) and the spec's zero-codegen invariant, a
|
|
||||||
`--update-baseline` is foreclosed. Neither firing family is
|
|
||||||
attributable to docs-honesty-lint.
|
|
||||||
|
|
||||||
## Step 4 — Resolution: carry-on (clean)
|
|
||||||
|
|
||||||
No fix iteration. No ratify. Milestone **CLOSED**. No fieldtest: this
|
|
||||||
milestone changed zero authoring surface (documentation prose + one
|
|
||||||
non-bench test file + an agent-def + a bench shell sweep) — the
|
|
||||||
fieldtest trigger (surface-touching milestone) does not apply; the
|
|
||||||
wrap-robust `docs_honesty_pin.rs` (4 tests) + Sweep 5 ARE the
|
|
||||||
milestone's regression coverage.
|
|
||||||
|
|
||||||
Two pre-existing standing P2 items remain orchestrator-owned and
|
|
||||||
were explicitly NOT bundled into this close (the bencher's
|
|
||||||
recommendation, honoured): (a) the stale `*.bump_s` anchor — a
|
|
||||||
deliberate quiescent-machine baseline refresh, separately journaled
|
|
||||||
when pursued, never a ratification rider on an unrelated close; (b)
|
|
||||||
the recurring `-n 5` tail false-positive — the durable fix is a
|
|
||||||
structural harness change (higher default N or a tail-metric gating
|
|
||||||
policy), already a tracked roadmap todo, not a bench-output artefact.
|
|
||||||
|
|
||||||
## Milestone close summary
|
|
||||||
|
|
||||||
docs-honesty-lint is fully ratified and CLOSED: spec (grounding-check
|
|
||||||
PASS 10/10) + plan + single iter + this audit. The canonical docs
|
|
||||||
(`docs/DESIGN.md`, `docs/PROSE_ROUNDTRIP.md`) now mirror only the
|
|
||||||
current state, present-tense; the tense+modality discriminator is
|
|
||||||
codified in DESIGN.md and the architect drift-checks against it via
|
|
||||||
Sweep 5 + the new bullet; the wrap-robust enumerated pin is the hard
|
|
||||||
forward gate. The procedural-enumeration design proved itself: the
|
|
||||||
Task-4 Step-14 catch-all surfaced one genuine extra FIX site (the
|
|
||||||
Form-B prose-projection bullet) that the spec's illustrative regex
|
|
||||||
and a frozen orchestrator list would both have missed.
|
|
||||||
@@ -1,155 +0,0 @@
|
|||||||
# audit embedding-abi-m1 — milestone close (CLEAN — carry-on, NO ratify)
|
|
||||||
|
|
||||||
**Date:** 2026-05-18
|
|
||||||
**Scope:** `064599e..e406d07`
|
|
||||||
**Verdict:** Milestone audit **CLEAN**. Architect `clean` (one
|
|
||||||
`[low]` evidence-adjudicated to carry-on); bench `check.py` exit 1
|
|
||||||
**decisively causally exonerated** (21/21 byte-identical bench
|
|
||||||
binaries vs pre-milestone), `compile_check.py` / `cross_lang.py`
|
|
||||||
exit 0. No fix iteration, **no baseline ratify**.
|
|
||||||
|
|
||||||
## Commit map
|
|
||||||
|
|
||||||
- `064599e` — pre-milestone HEAD (bench/architect diff baseline).
|
|
||||||
- `51160e9` spec, `b0bd7ef` plan — docs only.
|
|
||||||
- `123ae1b` — USER-authored roadmap-P2 ("DESIGN.md → `design/`
|
|
||||||
role-split"), `docs/roadmap.md` only, path-disjoint — explicitly
|
|
||||||
out of M1 audit scope (Boss roadmap maintenance, architect told
|
|
||||||
to skip it; it is not M1 code/doc work).
|
|
||||||
- `818177d` — iter partial (Tasks 1–3) + a Boss plan Repair
|
|
||||||
(Design-decision 6, the module≠stem fixture defect).
|
|
||||||
- `e406d07` — iter completion (Tasks 4–7) + INDEX line.
|
|
||||||
|
|
||||||
## Step 1 — Architect drift review
|
|
||||||
|
|
||||||
**Status: `clean`.** All four focus areas + standing concerns
|
|
||||||
verified against the `064599e..e406d07` diff:
|
|
||||||
|
|
||||||
- **Invariant 1 (clean core) holds semantically.** No
|
|
||||||
finance/`data-server` vocabulary, logic, or dependency entered
|
|
||||||
`ailang-core` / `ailang-codegen` / `runtime/`. `backtest_step`
|
|
||||||
is an opaque author-chosen string flowing through generic
|
|
||||||
`FnDef.export`; the only "backtest" occurrences in core/codegen
|
|
||||||
are test-fixture filenames + mangled-symbol assertions in test
|
|
||||||
files, not domain knowledge. AILang↔host meeting point correctly
|
|
||||||
deferred (no `ail-embed`, no runtime lifecycle API) — M1 scope
|
|
||||||
respected.
|
|
||||||
- **DESIGN.md honesty rule satisfied.** New §"Embedding ABI (M1)"
|
|
||||||
+ fn-JSON `"export"` line + `FnDef.export` rustdoc are
|
|
||||||
present-tense current state; "provisional until M3" / "M2 will
|
|
||||||
change this C signature" are correctly *labelled* reserved-
|
|
||||||
unimplemented, not stated as present fact, no
|
|
||||||
post-mortem/Wunschdenken. Sweep hits (DESIGN.md:50/:2060/:62)
|
|
||||||
all pre-existing, out of M1 range.
|
|
||||||
- **Lockstep intact.** `FnDef` ↔ DESIGN.md §Data-model fn-JSON ↔
|
|
||||||
`design_schema_drift.rs`/`spec_drift.rs` all carry the field.
|
|
||||||
The `@<sym>` forwarder is additive raw-IR emission *beside* the
|
|
||||||
unchanged `@ail_<module>_<fn>` mangling — does not route through
|
|
||||||
`lower_app`, so the `lower_app`/`is_static_callee` and
|
|
||||||
`Pattern::Lit` lockstep pairs are not engaged. Schema stays
|
|
||||||
exhaustively-constructed (~85 sites uniform `export: None`, no
|
|
||||||
`_ =>` / `..Default::default()` — plan Design-decision 1
|
|
||||||
honoured). In-source `missing_entry_main_is_error` pin (now
|
|
||||||
L3411) behaviourally byte-unchanged (only the mandatory field
|
|
||||||
added). `_adapter`/`_clos` closure pair untouched (spec "Out of
|
|
||||||
scope").
|
|
||||||
- **Design-decision 6 confirmed correct, not re-flagged.** The
|
|
||||||
gate/headline fixtures' `(module embed_*)` == file-stem; the
|
|
||||||
divergence from the spec's *illustrative* `(module backtest)` /
|
|
||||||
`(module bad)` blocks is expected under the loader's
|
|
||||||
module==stem invariant + spec Decision 2 (C symbol
|
|
||||||
`backtest_step` decoupled via `(export …)`). The fixtures
|
|
||||||
*demonstrate* the decoupling rather than violate the spec.
|
|
||||||
|
|
||||||
**Drift / debt — one item, adjudicated:**
|
|
||||||
|
|
||||||
- `[low]` ~85 inserted `export: None,` lines indented to the
|
|
||||||
struct-literal brace column rather than the field column
|
|
||||||
(`rustfmt`-divergent struct literals; compiles 611/0, semantics
|
|
||||||
unaffected). **Orchestrator adjudication → carry-on (NOT a fix,
|
|
||||||
NOT pending).** Evidence: `cargo fmt --all --check` reports
|
|
||||||
**1137 fmt-divergent locations tree-wide** (pre-existing
|
|
||||||
`builtins.rs` / `diagnostic.rs` / dozens of `lib.rs` sites
|
|
||||||
predating M1). rustfmt is demonstrably **not** a project
|
|
||||||
convention — the hand-formatted codebase tolerates 1137
|
|
||||||
divergences. The architect's premise ("a `cargo fmt` pass would
|
|
||||||
churn later") is therefore moot: no such gate exists or is
|
|
||||||
coming, and selectively rustfmt-aligning 85 of 1137 sites would
|
|
||||||
*impose* a standard the project does not hold, which is noise,
|
|
||||||
not tidy. This is an evidence-based adjudication of the
|
|
||||||
architect's finding (premise falsified), not "trivial, ignore
|
|
||||||
it". If AILang ever adopts a fmt gate that is a tree-wide
|
|
||||||
decision (1137 sites), unrelated to M1.
|
|
||||||
|
|
||||||
## Step 2 — Bench-regression check
|
|
||||||
|
|
||||||
| Script | Exit | Result |
|
|
||||||
|---|---|---|
|
|
||||||
| `bench/check.py` | **1** | 63 metrics; 7 regressed, 2 improved, 54 stable |
|
|
||||||
| `bench/compile_check.py` | 0 | 24 metrics; 0 regressed |
|
|
||||||
| `bench/cross_lang.py` | 0 | 25 metrics; 0 regressed |
|
|
||||||
|
|
||||||
**The 7 `check.py` firings:**
|
|
||||||
`throughput.bench_list_sum.bump_s` +12.27%,
|
|
||||||
`throughput.bench_hof_pipeline.bump_s` +12.67%,
|
|
||||||
`throughput.bench_list_sum_explicit.bump_s` +13.74%,
|
|
||||||
`latency.explicit_at_rc.p99_9_us` +36.13%,
|
|
||||||
`latency.explicit_at_rc.max_us` +101.43%,
|
|
||||||
`latency.implicit_at_rc.p99_9_us` +67.32%,
|
|
||||||
`latency.implicit_at_rc.max_us` +123.57%.
|
|
||||||
|
|
||||||
**Decisive causal exoneration (bencher localisation).** Hypothesis:
|
|
||||||
M1's `Target::Executable` default leaves the entire bench corpus's
|
|
||||||
emitted machine code byte-unchanged; the firings are pre-existing
|
|
||||||
environmental/sampling noise, not M1.
|
|
||||||
|
|
||||||
- **21/21 bench binaries `cmp`-byte-identical** between `064599e`
|
|
||||||
and `e406d07` (`bench_list_sum_bump` sha `dce18c288904c9f0` at
|
|
||||||
both commits; every one of the 6 fixtures × 3 modes + 3 latency
|
|
||||||
arms IDENTICAL, 0 DIFFER). `ail` self-hash differs (expected —
|
|
||||||
Rust source changed for the embedding ABI) but the *emitted
|
|
||||||
machine code* for every bench fixture is identical because every
|
|
||||||
fixture builds an executable via the unchanged `Target::Executable`
|
|
||||||
default. **Zero codegen/runtime causal mechanism exists.**
|
|
||||||
- The RC-latency tails reproduce *on the byte-identical oracle
|
|
||||||
binary itself*, swinging 3× run-to-run (`explicit_at_rc.max_us`
|
|
||||||
per-run 376→1200 us; `implicit_at_rc.max_us` 562→1742 us) while
|
|
||||||
median/p99 hold steady — the documented `-n 5` single-sample-
|
|
||||||
jitter signature.
|
|
||||||
- Fresh `check.py` re-run: the `*.bump_s` trio → ok, 4 of 7
|
|
||||||
original firings → ok, only `explicit_at_rc.max_us` still trips
|
|
||||||
(+26%, byte-oracle-proven environmental).
|
|
||||||
- Both firing families map (confirmed, not assumed) onto the two
|
|
||||||
pre-existing **roadmap-P2** tracked items: `*.bump_s` baseline-
|
|
||||||
vs-current-hardware staleness, and the RC-latency `*.max_us` /
|
|
||||||
`*.p99_9_us` `-n 5` structural false-positive (this is the ~4th
|
|
||||||
zero-runtime-change milestone close on which they fire and
|
|
||||||
collapse on identical-code re-run).
|
|
||||||
|
|
||||||
**Classification: carry-on, NO baseline ratify.** Per the audit
|
|
||||||
Iron Law a baseline update requires a paired JOURNAL ratify entry
|
|
||||||
naming an iter that *intentionally* moved the metric; M1 by
|
|
||||||
construction moved **zero** default-path bytes (21/21 binaries
|
|
||||||
identical), so a ratify would bake noise into `baseline.json` —
|
|
||||||
forbidden. The spec forecloses a zero-codegen baseline bump. The
|
|
||||||
residual flapping is pre-existing, already roadmap-P2-tracked; the
|
|
||||||
genuinely next-best action (re-capture `baseline.json` on current
|
|
||||||
hardware at higher N, or widen single-sample tail tolerances) is a
|
|
||||||
separate bench-harness iteration, **not** an M1 concern — the two
|
|
||||||
P2 items remain orchestrator-owned and are NOT bundled into this
|
|
||||||
close (bencher recommendation honoured, consistent with the
|
|
||||||
remove-mut-var-assign / docs-honesty-lint closes).
|
|
||||||
|
|
||||||
## Step 3/4 — Resolution
|
|
||||||
|
|
||||||
**carry-on (clean).** No fix iteration, no ratify, no commit beyond
|
|
||||||
this journal + its INDEX line. Architect clean; the sole `[low]` is
|
|
||||||
evidence-adjudicated non-actionable; bench firings decisively
|
|
||||||
non-causal (21/21 byte-identical binaries) and mapped to existing
|
|
||||||
P2 noise. Both bench gates that can pass on the merits do
|
|
||||||
(`compile_check`/`cross_lang` exit 0; `check.py` exit 1 causally
|
|
||||||
exonerated).
|
|
||||||
|
|
||||||
**M1 milestone audit CLEAN.** Next pipeline step: `fieldtest` —
|
|
||||||
M1 added user-visible authoring surface (`(export "<sym>")`), so
|
|
||||||
the surface-touch fieldtest gate applies before milestone close.
|
|
||||||
@@ -1,106 +0,0 @@
|
|||||||
# Audit — Embedding ABI — M2 (milestone close)
|
|
||||||
|
|
||||||
**Date:** 2026-05-18
|
|
||||||
**Scope:** `2db737c..fbeeade` (M2 = spec `1c58055` → plan `b3388c8` →
|
|
||||||
iter `c9a84b3` PARTIAL 4/9 + Boss spec-defect repair → iter `fbeeade`
|
|
||||||
DONE 5–9). HEAD `fbeeade`, working tree clean at audit time.
|
|
||||||
**Outcome:** DRIFT (one `[medium]` + coupled `[low]` doc-honesty
|
|
||||||
item → fix via tidy iteration). Bench `check.py` exit 1 — **all
|
|
||||||
firings causally exonerated as the two tracked roadmap-P2 noise
|
|
||||||
families; NO baseline ratified, NOT an M2 regression.**
|
|
||||||
|
|
||||||
## Step 1 — Architect drift review
|
|
||||||
|
|
||||||
Status: `drift_found`. No DESIGN.md/spec-contract drift, no
|
|
||||||
unrequested extras, Invariant 1 intact, internal-convention
|
|
||||||
byte-invariance held, Decision-10 atomicity note present-tense-
|
|
||||||
correct (sanitiser-verified this iter), spec→DESIGN.md→code lockstep
|
|
||||||
complete, `docs_honesty_pin.rs` green unmodified, the Boss
|
|
||||||
spec-defect repair correctly reflected in both spec and shipped
|
|
||||||
tests.
|
|
||||||
|
|
||||||
Actionable drift (one item, two coupled sites):
|
|
||||||
|
|
||||||
- **`[medium]` `docs/DESIGN.md:2266`** — the section header reads
|
|
||||||
`## Embedding ABI (M1)` while the body (`:2285–2295`) now
|
|
||||||
describes the post-M2 ctx ABI as present-tense current state
|
|
||||||
("Every exported entrypoint takes a mandatory leading
|
|
||||||
`ailang_ctx_t*` (M2)… the ctx-threaded C signature is the M2
|
|
||||||
shape"). Current-state-mirror honesty mismatch: a header that
|
|
||||||
misdescribes its own present-tense body. Not a Sweep-5 catch (the
|
|
||||||
body prose itself is correctly present-tense — the
|
|
||||||
docs-honesty-lint machinery does not auto-flag a header/body
|
|
||||||
desync; only a read catches it). The iter journal's Concerns +
|
|
||||||
Known-debt flagged it accurately and truthfully — Task 7's edit
|
|
||||||
regions were *deliberately* scope-limited (provisional-until-M3
|
|
||||||
sentence + Decision 10) to preserve the `docs_honesty_pin.rs:135`
|
|
||||||
verbatim "Export parameters are written **bare**…" assertion 12
|
|
||||||
lines below the header. Deferral reasoning sound; the mismatch is
|
|
||||||
real drift that must not persist past this milestone close.
|
|
||||||
- **`[low]` `docs/DESIGN.md:2354`** — schema-block cross-reference
|
|
||||||
comment still reads `see §"Embedding ABI (M1)"`; stale xref
|
|
||||||
coupled to the header above. Must move in lockstep with any
|
|
||||||
header rename so the fix is scoped header + xref +
|
|
||||||
`docs_honesty_pin.rs:134–136` `(M1)`-token audit together, not
|
|
||||||
the header alone.
|
|
||||||
|
|
||||||
Architect recommendation: one scoped tidy iteration.
|
|
||||||
|
|
||||||
## Step 2 — Bench regression
|
|
||||||
|
|
||||||
| Script | Exit |
|
|
||||||
|---|---|
|
|
||||||
| `bench/check.py` | **1** |
|
|
||||||
| `bench/compile_check.py` | 0 (clean — 24/24 stable) |
|
|
||||||
| `bench/cross_lang.py` | 0 (clean — 25/25 stable, **every `rc_over_c` ratio within tol**) |
|
|
||||||
|
|
||||||
`check.py` 63 metrics: 3 regressed, 2 improved-beyond-tol, 58 stable.
|
|
||||||
The 3 firings (verbatim): `throughput.bench_list_sum.bump_s`
|
|
||||||
0.046→0.052 +13.57% (tol 10%); `latency.explicit_at_rc.max_us`
|
|
||||||
413.0→751.5 +81.96% (tol 25%); `latency.implicit_at_rc.max_us`
|
|
||||||
477.3→661.4 +38.57% (tol 30%). Every `*.rc_s`/`*.gc_s` throughput and
|
|
||||||
every rc-latency median/p99/p99.9 stable within tol.
|
|
||||||
|
|
||||||
Because M2 genuinely changed the benched runtime path
|
|
||||||
(`runtime/rc.c`: the two RC-counter sites gained a `__thread` load +
|
|
||||||
null-branch ahead of `g_rc_*++`), the byte-identical exoneration
|
|
||||||
method did **not** apply — `ailang-bencher` was dispatched for
|
|
||||||
hypothesis-driven localisation (H0 = real M2 regression; H1 =
|
|
||||||
tracked-P2 noise, M2 bench-neutral).
|
|
||||||
|
|
||||||
**Bencher verdict: H0 refuted; all three firings
|
|
||||||
tracked-noise-causally-exonerated.** Method: swapped *only* `rc.c`
|
|
||||||
between `2db737c` and `fbeeade` (same `ail` binary, same fixtures),
|
|
||||||
disassembled the delta, interleaved core-pinned A/B.
|
|
||||||
- `bump_s`: causally **impossible** from M2 — `bump_malloc`
|
|
||||||
disassembly has 0 refs to `__ail_tls_ctx`/rc counters; the
|
|
||||||
M2-inert bump binary independently reproduces ~+12% vs the stale
|
|
||||||
0.046 baseline. Roadmap-P2 `*.bump_s` environmental staleness.
|
|
||||||
- M2's rc.c hunk compiled **branchless** (3 insns: `mov %fs`,
|
|
||||||
`test`, `cmovne`) and measured **free** (HEAD median −0.63% vs
|
|
||||||
pre-M2, inside intra-arm spread) — the H0 mechanism does not
|
|
||||||
exist; no per-alloc cost.
|
|
||||||
- `*.max_us`: median/p99 flat across rc.c variants (a systematic
|
|
||||||
per-alloc cost must move them and does not); max_us
|
|
||||||
non-reproducible within a single fixed binary (3.2× spread).
|
|
||||||
Roadmap-P2 `*.max_us` `-n 5` structural false-positive (now its
|
|
||||||
4th consecutive no-runtime-change-attributable firing).
|
|
||||||
|
|
||||||
Limitations (bencher-stated): single host; N=15 moderate for
|
|
||||||
max-tail — median/p99 verdict solid, max verdict rests on the
|
|
||||||
(strong, N-bounded) non-reproducibility argument; gc latency arm not
|
|
||||||
re-measured (no gc firing; rc/gc share the identical M2 hunk).
|
|
||||||
|
|
||||||
## Step 3 — Classification & resolution
|
|
||||||
|
|
||||||
| Item | Class | Resolution |
|
|
||||||
|---|---|---|
|
|
||||||
| `[medium]` DESIGN.md:2266 stale `(M1)` header + `[low]` :2354 xref | **fix** | one tidy iteration `embedding-abi-m2.tidy`: rename the `## Embedding ABI (M1)` header to drop the milestone tag (current-state-mirror), lockstep the `:2354` cross-reference and the `docs_honesty_pin.rs:134–136` comment/assert-message `(M1)` token — the pinned *substring* (`:135`, the "Export parameters are written **bare**…" sentence) stays verbatim untouched. planner → implement. |
|
|
||||||
| `check.py` exit 1 — 3 firings | **carry-on** | causally exonerated as the two pre-existing roadmap-P2 noise families (`*.bump_s` staleness; `*.max_us` `-n 5` false-positive). **No baseline ratified** (bencher wrote nothing to baseline; these are not M2 artefacts). The P2 items remain tracked on the roadmap; retiring the `bump_s` stale baseline is a separate roadmap-P2 action against that item, explicitly NOT this milestone. |
|
|
||||||
| `compile_check.py` / `cross_lang.py` | carry-on | clean (exit 0). |
|
|
||||||
|
|
||||||
**Milestone M2 is substantively closed and sound** (capability
|
|
||||||
sanitiser-verified, all contract invariants held). The one open
|
|
||||||
drift is a doc-honesty header desync, fixed by the tidy iteration
|
|
||||||
dispatched next; the roadmap `[~]`→`[x]` flip follows the tidy
|
|
||||||
landing clean. No ratify. No re-audit deferral.
|
|
||||||
@@ -1,136 +0,0 @@
|
|||||||
# audit Embedding ABI — M3 (milestone close)
|
|
||||||
|
|
||||||
**Date:** 2026-05-18
|
|
||||||
**Milestone range:** `9a609ae..4ea8bc5` (M2 roadmap-close → M3 DONE)
|
|
||||||
**Outcome:** DRIFT — one `[medium]` + one `[low]` doc-honesty item
|
|
||||||
→ a single docs-honesty tidy iteration; bench `check.py` exit 1
|
|
||||||
**decisively causally exonerated** (byte-identical generated IR),
|
|
||||||
NO ratify. compile_check / cross_lang clean.
|
|
||||||
|
|
||||||
## Step 1 — Architect drift review (`drift_found`)
|
|
||||||
|
|
||||||
Dispatched `ailang-architect` over `9a609ae..HEAD`.
|
|
||||||
|
|
||||||
**What holds (audited clean):**
|
|
||||||
- **Invariant 1 (clean core):** zero `Cargo.toml`/runtime dep
|
|
||||||
changes; `ailang-core`/`ailang-codegen`/`runtime/` touches are
|
|
||||||
only the spec-scoped widen + lockstep comments + pins. No
|
|
||||||
`data-server`/finance knowledge entered.
|
|
||||||
- **M1/M2 internal-convention byte-invariant held:** the M2
|
|
||||||
`Target::StaticLib` forwarder body and the internal
|
|
||||||
`@ail_<mod>_<fn>` / `_adapter` / `_clos` convention are *absent
|
|
||||||
from the diff* (untouched). The `llvm_scalar` / `is_c_abi_type`
|
|
||||||
widen is exactly the spec scope — no new `CheckError` variant, no
|
|
||||||
schema field, no Form-A change (M2-style honoured).
|
|
||||||
- **Frozen-layout SSOT consistent:** DESIGN.md
|
|
||||||
`### Frozen value layout` (`:2321-2359`) is present-tense and
|
|
||||||
matches both `match_lower.rs` `lower_ctor` (`size = 8 + n*8`,
|
|
||||||
tag@0, fields@8+i*8) and the enforcing byte-pin
|
|
||||||
(`@ailang_rc_alloc(i64 24)`, tag@0, @8/@16). All three lockstep
|
|
||||||
`// FROZEN ABI` pointers present and aimed at the SSOT. The Boss
|
|
||||||
spec-consistency repair note + §"Testing strategy" 3/4 + the
|
|
||||||
journal Boss-adjudication / Re-dispatch sections are mutually
|
|
||||||
consistent and honest about the M2-TLS cross-attribution (correct
|
|
||||||
behaviour, not a leak).
|
|
||||||
|
|
||||||
**Drift / debt:**
|
|
||||||
- **`[medium]` `docs/DESIGN.md:2299-2302`** — a surviving M1-era
|
|
||||||
paragraph still asserts modes "apply only to heap-shaped types,
|
|
||||||
which the **scalar-only rule above forbids** at an export
|
|
||||||
boundary anyway". `:2280-2284` was rewritten to accept a
|
|
||||||
single-ctor record, so there is no longer a scalar-only rule;
|
|
||||||
this sentence now contradicts the section's own freeze and the
|
|
||||||
`(own (con State))`/`(borrow (con State))` mode contract at
|
|
||||||
`:2345-2351`. Canonical-spec internal contradiction the milestone
|
|
||||||
owned. → **fix** (docs-honesty tidy).
|
|
||||||
- **`[low]` `crates/ailang-codegen/src/lib.rs:608-610`** — the
|
|
||||||
forwarder-body comment still says the gate "guarantees every
|
|
||||||
param and the ret are `Int`/`Float`; map Int→i64, Float→double",
|
|
||||||
now false (records→ptr). The architect classified this "carried
|
|
||||||
debt, not a scope miss" — the forwarder *body* is contractually
|
|
||||||
byte-frozen, so the comment was genuinely outside any M3 edit
|
|
||||||
region. Comment-only; correcting it changes no emitted IR (the
|
|
||||||
byte-pin + forwarder-IR pin assert IR, not source comments). →
|
|
||||||
**fix**, folded into the same tidy (doc-honesty hygiene; the IR
|
|
||||||
pins are the guard that generated code stays byte-frozen).
|
|
||||||
|
|
||||||
(Boss already caught + fixed in-line one sibling stale `//`
|
|
||||||
gate-comment block at `check/lib.rs:1917-1922` during the DONE
|
|
||||||
inspect; the architect found no further siblings.)
|
|
||||||
|
|
||||||
## Step 2 — Bench-regression trio
|
|
||||||
|
|
||||||
| script | exit | summary |
|
|
||||||
|---|---|---|
|
|
||||||
| `bench/check.py` | **1** | 63 metrics; 3 regressed, 2 improved, 58 stable |
|
|
||||||
| `bench/compile_check.py` | 0 | 24 metrics; 0 regressed, 24 stable |
|
|
||||||
| `bench/cross_lang.py` | 0 | 25 metrics; 0 regressed, 25 stable (every `rc_over_c` within tol) |
|
|
||||||
|
|
||||||
`check.py` exit 1 — the 3 firings:
|
|
||||||
|
|
||||||
| metric | baseline | actual | diff | tol |
|
|
||||||
|---|---|---|---|---|
|
|
||||||
| `throughput.bench_list_sum.bump_s` | 0.046 | 0.052 | **+12.62%** | 10.0% |
|
|
||||||
| `latency.implicit_at_rc.p99_9_us` | 452.0 | 580.6 | **+28.45%** | 25.0% |
|
|
||||||
| `latency.implicit_at_rc.max_us` | 477.3 | 645.2 | **+35.18%** | 30.0% |
|
|
||||||
|
|
||||||
Both are the two **standing tracked P2 noise families** (roadmap
|
|
||||||
P2): the `*.bump_s` stale-baseline (~+5–13% environmental band on
|
|
||||||
current hardware) and the `*.max_us`/`p99_9` `-n 5` tail-jitter
|
|
||||||
structural false-positive (signature: same benchmark's `median_us`
|
|
||||||
+5.11% ok, `p99_us` +15.08% ok — only the extreme tail quantiles
|
|
||||||
fire, the worst-of-~5000-samples-over-5-runs pattern).
|
|
||||||
|
|
||||||
**Causal exoneration (decisive, not hand-waved — M2-audit method).**
|
|
||||||
M3 changed *no* executable-path codegen (architect-confirmed: M2
|
|
||||||
forwarder body byte-unchanged; the only codegen touches are the
|
|
||||||
`llvm_scalar` record→ptr arm — reachable solely for an `(export)`
|
|
||||||
single-ctor record, and no `bench_*` program has an `(export)` — the
|
|
||||||
`ailang-check` `is_c_abi_type` widen, and lockstep *comments*). Built
|
|
||||||
`ail` at HEAD and at the pre-M3 commit `9a609ae` in an isolated
|
|
||||||
worktree; the **generated IR is byte-identical** (`cmp -s`) for every
|
|
||||||
firing benchmark and its neighbours:
|
|
||||||
|
|
||||||
```
|
|
||||||
bench_list_sum: IR BYTE-IDENTICAL (HEAD == 9a609ae)
|
|
||||||
bench_list_sum_explicit: IR BYTE-IDENTICAL
|
|
||||||
bench_latency_implicit: IR BYTE-IDENTICAL
|
|
||||||
bench_tree_walk: IR BYTE-IDENTICAL
|
|
||||||
```
|
|
||||||
|
|
||||||
The IR is what is handed to `clang -O2` (deterministic), so the
|
|
||||||
bench binaries are byte-identical and M3 causation is logically
|
|
||||||
impossible. **NO baseline ratify** (these are not M3 artefacts; the
|
|
||||||
two P2 items stay separately tracked). Bench → **carry-on**.
|
|
||||||
|
|
||||||
## Step 3 — Classification
|
|
||||||
|
|
||||||
| item | class | recommendation |
|
|
||||||
|---|---|---|
|
|
||||||
| `[medium]` DESIGN.md:2299-2302 scalar-only-rule contradiction | drift | **fix** — docs-honesty tidy |
|
|
||||||
| `[low]` codegen forwarder-comment Int/Float-only staleness | drift | **fix** — folded into the same tidy |
|
|
||||||
| `check.py` exit 1 (3 firings) | tracked-noise | **carry-on**, NO ratify (byte-identical-IR causal exoneration) |
|
|
||||||
| compile_check / cross_lang | clean | carry-on |
|
|
||||||
|
|
||||||
## Step 4 — Resolution (Boss)
|
|
||||||
|
|
||||||
- **Drift → one docs-honesty tidy iteration `embedding-abi-m3.tidy`**
|
|
||||||
(the M2-audit `[medium]+[low] doc-honesty → tidy` precedent
|
|
||||||
shape): reconcile the stale DESIGN.md:2299-2302 "scalar-only rule
|
|
||||||
above forbids" paragraph against the frozen SSOT + the own/borrow
|
|
||||||
mode contract, **pin-safe** (the `docs_honesty_pin.rs:135` pinned
|
|
||||||
sentence — "Export parameters are written **bare**…", shifted to
|
|
||||||
~`:2299` by the M3.1 DONE — sits in/adjacent to the edit zone;
|
|
||||||
the tidy plan must keep it byte-verbatim+contiguous, planner
|
|
||||||
Step-5 item-6 / M2.tidy precedent); plus the `[low]` codegen
|
|
||||||
forwarder-comment honesty fix (comment-only, the byte-pin +
|
|
||||||
forwarder-IR pin stay green as the guard that generated code is
|
|
||||||
untouched). Routed via `planner` → `implement`.
|
|
||||||
- **Bench → carry-on, NO ratify.** Causally exonerated; the two
|
|
||||||
tracked P2 noise todos stay as-is.
|
|
||||||
|
|
||||||
Milestone is substantively closed + sound (record crosses the C ABI
|
|
||||||
in/out, ownership follows the declared mode, the value layout is
|
|
||||||
frozen + byte-pin-enforced, all contract invariants held,
|
|
||||||
Invariant 1 clean). The roadmap M3 `[~]`→`[x]` follows the tidy
|
|
||||||
landing clean.
|
|
||||||
@@ -1,91 +0,0 @@
|
|||||||
# audit prose-loop-binders — Milestone-prose-loop-binders tidy (clean)
|
|
||||||
|
|
||||||
**Date:** 2026-05-18
|
|
||||||
**Milestone:** prose-loop-binders (single-iteration)
|
|
||||||
**Commit range:** `6cbd0fe..c9355d7` (prev close = loop/recur at `6cbd0fe`)
|
|
||||||
**Status:** CLOSED — clean, carry-on (no fix, no ratify)
|
|
||||||
|
|
||||||
## Step 1 — Architect drift review
|
|
||||||
|
|
||||||
`ailang-architect` over `6cbd0fe..HEAD`: **clean**, zero drift, zero
|
|
||||||
debt. Verified against the diff (not journal trust):
|
|
||||||
|
|
||||||
- Single hunk in `crates/ailang-prose/src/lib.rs` (the `Term::Loop`
|
|
||||||
arm of `write_term`, lines 936–953). The two non-render
|
|
||||||
`Term::Loop` sites (`count_free_var`, `subst_var_with_term`) and
|
|
||||||
all `Term::Recur` arms are absent from the diff — untouched.
|
|
||||||
- DESIGN.md and PROSE_ROUNDTRIP.md make **no** claim about how
|
|
||||||
prose renders `loop` (grep: no match), so the spec's "no doc
|
|
||||||
edit needed" is correct, not a skipped obligation. `DESIGN.md:714`
|
|
||||||
`loop(n-1)` is an ordinary recursive-call illustration in
|
|
||||||
Decision 8, unrelated to the `Term::Loop` construct — not stale.
|
|
||||||
- Diff touches only the four named code/fixture files plus expected
|
|
||||||
docs (spec/plan/journal/INDEX/roadmap/stats). No carve-out /
|
|
||||||
hash-pin / round-trip lockstep bypassed (prose is off the
|
|
||||||
Form-A↔JSON round-trip path by construction).
|
|
||||||
- The `architect_sweeps.sh` Sweep-1 DESIGN.md hits all pre-date
|
|
||||||
`6cbd0fe` and are outside this milestone's diff (zero DESIGN.md
|
|
||||||
lines changed) — flagged for the record, not attributable here.
|
|
||||||
|
|
||||||
Architect recommendation: carry on as planned.
|
|
||||||
|
|
||||||
## Step 2 — Bench-regression check
|
|
||||||
|
|
||||||
- `bench/compile_check.py` → **exit 0**, 24 metrics, 0 regressed.
|
|
||||||
- `bench/cross_lang.py` → **exit 0**, 25 metrics, 0 regressed.
|
|
||||||
Notably its independent `bench_list_sum.ail_bump_s` reads +1.50%
|
|
||||||
(±15% tol) → **ok** — corroborates that check.py's bump_s firing
|
|
||||||
is baseline-staleness, not a real bump regression.
|
|
||||||
- `bench/check.py` → exit 1. First run: 3 regressed
|
|
||||||
(`bench_list_sum.bump_s` +13.17%; `latency.implicit_at_rc.p99_9_us`
|
|
||||||
+37.30%; `latency.implicit_at_rc.max_us` +114.14%), 4 improved,
|
|
||||||
56 stable. Confirmatory identical re-run (no code change):
|
|
||||||
`p99_9_us` +10.22% → **ok**, `max_us` +22.17% → **ok** (median
|
|
||||||
and p99 within tol on both runs) — the two latency-tail firings
|
|
||||||
were single-sample 5-run RC-mode jitter, proven by re-run.
|
|
||||||
`bench_list_sum.bump_s` persisted at +11.79% (consistent with
|
|
||||||
the +13.17% first run): summary `63 metrics; 1 regressed`.
|
|
||||||
|
|
||||||
## Step 3 — Classification
|
|
||||||
|
|
||||||
| Item | Class | Rationale |
|
|
||||||
|------|-------|-----------|
|
|
||||||
| Architect drift | carry-on | clean — nothing actionable |
|
|
||||||
| `throughput.bench_list_sum.bump_s` +~12% | carry-on | The already-tracked P2 roadmap todo "`*.bump_s` throughput baseline is stale vs current hardware" — byte-oracle-proven environmental (not codegen) at the 2026-05-16 iteration-discipline-revert audit. Causally impossible to attribute to a projection-only `ailang-prose` change (prose is not in the bench runtime path; architect verified the diff is one render-arm + test fixtures). |
|
|
||||||
| `latency.implicit_at_rc` p99.9 / max | carry-on | Single-sample 5-run RC-mode tail jitter; vanished on identical re-run with median/p99 always within tolerance. Not a regression; the queued P3 "latency methodology rework" (histogram-based) exists precisely to de-noise this metric family. |
|
|
||||||
|
|
||||||
## Step 4 — Resolution
|
|
||||||
|
|
||||||
**Carry-on** on every item. No fix iteration, no baseline ratify.
|
|
||||||
|
|
||||||
Explicitly **not ratified**: the `*.bump_s` baseline is NOT bumped
|
|
||||||
here. The P2 roadmap todo scopes a holistic recapture+recalibration
|
|
||||||
of the entire `*.bump_s` set on current hardware from a known-clean
|
|
||||||
commit as a dedicated bench-harness iteration; opportunistically
|
|
||||||
bumping one metric's baseline inside an unrelated projection-only
|
|
||||||
milestone is exactly the "bump the baseline, the regression is
|
|
||||||
expected" rationalisation the audit Iron Law forbids, and would
|
|
||||||
fragment the recapture the P2 todo owns. The pointer stands; the
|
|
||||||
todo is unchanged.
|
|
||||||
|
|
||||||
## Fieldtest applicability
|
|
||||||
|
|
||||||
**Not applicable / not dispatched.** `fieldtest` is Boss-dispatched
|
|
||||||
for a milestone that touched the *authoring* surface — the
|
|
||||||
fieldtester writes `.ail` Form-A programs and runs them through the
|
|
||||||
public `ail` CLI. This milestone changed only the Form-B *reading*
|
|
||||||
projection (`ail prose` output); the Form-A authoring surface, the
|
|
||||||
typechecker, codegen, and runtime are byte-unchanged. A fieldtester
|
|
||||||
authoring `.ail` programs would not exercise the prose-render change
|
|
||||||
at all — there is no friction/bug/spec-gap axis for it to probe.
|
|
||||||
The two committed whole-file byte-equality `.prose.txt` snapshots +
|
|
||||||
the live-CLI `ail prose | diff` acceptance checks (iter journal) are
|
|
||||||
the end-to-end gate for a projection-only milestone.
|
|
||||||
|
|
||||||
## Milestone close
|
|
||||||
|
|
||||||
The **prose-loop-binders** milestone is **CLOSED, clean**. Architect
|
|
||||||
drift clear; bench carry-on (sole persistent firing is the tracked
|
|
||||||
P2 `*.bump_s` environmental staleness, latency-tail proven sampling
|
|
||||||
noise by re-run). Roadmap P0 entry flipped to closed; no follow-up
|
|
||||||
iteration spawned.
|
|
||||||
@@ -1,108 +0,0 @@
|
|||||||
# audit — remove-mut-var-assign milestone close
|
|
||||||
|
|
||||||
**Date:** 2026-05-18
|
|
||||||
**Scope:** `48e7774..07f0802` (prev close → the single terminal iter)
|
|
||||||
**Outcome:** CLEAN after one inline `.tidy` fix; bench causally
|
|
||||||
exonerated; two P2 roadmap todos filed/strengthened. Fieldtest
|
|
||||||
remains (surface-touching milestone).
|
|
||||||
|
|
||||||
## Step 1 — Architect drift review
|
|
||||||
|
|
||||||
**`[high]` — `crates/ailang-core/specs/form_a.md` (EBNF 286-288,
|
|
||||||
prose 308-328, dangling `like mut` at 332).** The live in-tree
|
|
||||||
Form-A grammar reference still documented the full `mut`/`var`/
|
|
||||||
`assign` construct. The spec (Components #2, Architecture table
|
|
||||||
row 2) explicitly named "form_a.md" for deletion; the
|
|
||||||
schema-vs-doc honesty invariant the milestone advertises was
|
|
||||||
broken. **Root cause = the recon-undercount class (4th
|
|
||||||
recurrence), and specifically a Boss error:** at plan time the
|
|
||||||
Boss grepped `docs/form_a.md` (dir-scoped), got "No such file",
|
|
||||||
concluded "form_a.md does not exist", and propagated that
|
|
||||||
unverified non-existence claim into the recon brief. The file
|
|
||||||
lives at `crates/ailang-core/specs/form_a.md` and is actively
|
|
||||||
maintained (form-a.tidy edited it) — not frozen historical
|
|
||||||
record. A compile-driven sweep cannot catch this: a stale doc
|
|
||||||
never produces `E0004`/`E0061`.
|
|
||||||
|
|
||||||
**Resolution — inline Boss `.tidy`, not planner+implement.**
|
|
||||||
Rationale (CLAUDE.md "When NOT to delegate": context already
|
|
||||||
loaded, an agent would redo all milestone reading for a ~40-line
|
|
||||||
single-file doc deletion whose design was already settled by the
|
|
||||||
approved spec): deleted the three EBNF productions (keeping
|
|
||||||
`reuse-as`→`loop`→`recur`), the three mut/var/assign prose
|
|
||||||
bullets, and reworded the surviving `loop` bullet so its
|
|
||||||
"right-folded into `Term::Seq`" no longer back-references the
|
|
||||||
deleted `mut`. Full review-and-commit discipline kept (no
|
|
||||||
discipline shed for the carve-out): verified tree-wide that the
|
|
||||||
only remaining `Term::Mut`/`Term::Assign`/`MutVar` strings in
|
|
||||||
`crates/` + `docs/DESIGN.md` + `docs/roadmap.md` are the roadmap
|
|
||||||
entry *describing the milestone itself* (not residue), and
|
|
||||||
form_a.md's two remaining `mut`/`var` substrings are the unrelated
|
|
||||||
`[unbound-var]` / `reuse-as-source-not-bare-var` diagnostic names.
|
|
||||||
Grammar + notes read coherently after the cut.
|
|
||||||
|
|
||||||
**`[medium]` — process-drift, recon-undercount 4th recurrence.**
|
|
||||||
The existing loop-recur.tidy P2 (`ailang-plan-recon`
|
|
||||||
cross-crate-caller-undercount) is demonstrably insufficient: the
|
|
||||||
class now recurs through a *new* site type each time (test bodies,
|
|
||||||
drift pins, carve-out files, and now a spec-named non-compiled
|
|
||||||
doc + an unverified existence claim feeding the recon brief).
|
|
||||||
Escalated that P2 in place — broadened from "cross-crate caller"
|
|
||||||
to a structural recon-contract defect with two named gaps
|
|
||||||
(non-compile-checked spec-named sites; unverified "X does not
|
|
||||||
exist" claims inheriting into a recon brief), and a concrete
|
|
||||||
agent-definition fix (compile-driven enumeration as authoritative
|
|
||||||
code-site set **plus** an `ls`-verified spec-named-path existence
|
|
||||||
table). Not a code defect; an agent-discipline fix, parked P2.
|
|
||||||
|
|
||||||
## Step 2 — Bench-regression
|
|
||||||
|
|
||||||
`bench/check.py` **exit 1** (3 firings); `compile_check.py`
|
|
||||||
**exit 0** (24/24 stable — no check-time tax, consistent with a
|
|
||||||
pure removal); `cross_lang.py` **exit 0** (25/25 stable, incl. the
|
|
||||||
independent `bench_list_sum.ail_bump_s` +2.95% ok).
|
|
||||||
|
|
||||||
check.py firings: `throughput.bench_list_sum.bump_s` +12.51%;
|
|
||||||
`latency.explicit_at_rc.max_us` +108.38%;
|
|
||||||
`latency.implicit_at_rc.max_us` +66.46%. All medians/p99 stable.
|
|
||||||
|
|
||||||
**Bencher localisation — causal exoneration (decisive).** Built
|
|
||||||
`ail` at HEAD and at pre-milestone `48e7774`; the three
|
|
||||||
firing-relevant bench binaries (`list_sum_bump`, `lat_expl_rc`,
|
|
||||||
`lat_impl_rc`) are **`cmp`-byte-identical** across the milestone.
|
|
||||||
The −2520-line removal diff does not alter one byte of these
|
|
||||||
fixtures' machine code (none contain `mut`); no causal mechanism
|
|
||||||
exists. By elimination every firing is non-code-caused.
|
|
||||||
|
|
||||||
Per-firing verdict: `bump_s` = the tracked P2 environmental
|
|
||||||
staleness (stable ~+11–14% across 4 runs, byte-identical binary,
|
|
||||||
cross_lang corroborates); both `*.max_us` = single-sample `-n 5`
|
|
||||||
jitter (`explicit_at_rc.max_us` collapsed +108%→**-2.30% ok** by
|
|
||||||
an identical-code rerun3; `implicit_at_rc.max_us` swings
|
|
||||||
477→1172→843→756 on the identical binary while its median holds
|
|
||||||
+3.1…3.9%).
|
|
||||||
|
|
||||||
**Resolution: PRISTINE met on the merits. NO baseline ratify.**
|
|
||||||
The Iron Law forbids ratifying noise into the baseline and the
|
|
||||||
spec explicitly forecloses opportunistic ratification here; the
|
|
||||||
non-pristine exit code is bench-harness noise, not a code defect,
|
|
||||||
and does not block close. Filed a **new, distinct P2** (separate
|
|
||||||
from the `*.bump_s` staleness P2): `check.py` RC-latency
|
|
||||||
`*.max_us` at `-n 5` is a structural false-positive — 3-for-3 on
|
|
||||||
consecutive no-runtime-change milestones — recommending drop
|
|
||||||
`max_us` from gating / raise `-n` / cpuset-pin (mitigation choice
|
|
||||||
left to a future iteration).
|
|
||||||
|
|
||||||
## Step 3/4 — Classification & resolution
|
|
||||||
|
|
||||||
| Item | Class | Outcome |
|
|
||||||
|------|-------|---------|
|
|
||||||
| form_a.md construct doc | fix | inline Boss `.tidy` (this commit) |
|
|
||||||
| recon-undercount 4th recurrence | process-drift | P2 escalated in place (roadmap) |
|
|
||||||
| `bump_s` +12.51% | carry-on | tracked P2 (no ratify) |
|
|
||||||
| `*.max_us` ×2 | carry-on | new P2 filed (no ratify) |
|
|
||||||
|
|
||||||
Milestone audit **CLEAN** post-tidy. Next pipeline step:
|
|
||||||
`fieldtest` (the removal is a user-visible Form-A surface change —
|
|
||||||
`mut`/`var`/`assign` no longer lex; an LLM author must now reach
|
|
||||||
for `let`/`if`/`loop`/`recur`).
|
|
||||||
@@ -1,122 +0,0 @@
|
|||||||
# 2026-05-18 — brainstorm embedding-abi-m4 → RETIRED (never speced)
|
|
||||||
|
|
||||||
`/boss` picked the top P0 item "Embedding ABI — M4: sequence
|
|
||||||
crossing via the existing `List` ADT" (a new milestone, no spec).
|
|
||||||
User green-lit a continue-here brainstorm. The brainstorm
|
|
||||||
terminated **without a spec** at the Step-2/3 Q&A: the milestone's
|
|
||||||
premise collapsed under its own feature-acceptance gate. This file
|
|
||||||
records why, so the retired entry is not silently erased.
|
|
||||||
|
|
||||||
## What was explored before the collapse
|
|
||||||
|
|
||||||
Plan-recon (`ailang-plan-recon`) returned a full fact sheet. Two
|
|
||||||
user forks were resolved in Q&A and are recorded here as context
|
|
||||||
(now moot, but they shaped the analysis):
|
|
||||||
|
|
||||||
- **own-only** for the `List` parameter — kernel consumes each
|
|
||||||
chunk; its existing internal recursive drop frees it; no
|
|
||||||
host-side recursive-free symbol (M3 explicitly deferred that as
|
|
||||||
the build-ahead trap it refused). Keeps M4 "no language change".
|
|
||||||
- **`List Record` only** — element an M3-shaped single-ctor
|
|
||||||
all-scalar record crossing as ptr-to-M3-box, one uniform
|
|
||||||
cons-cell layout; sidesteps the poly/mono inline-scalar-element
|
|
||||||
divergence the recon flagged (open question #3).
|
|
||||||
|
|
||||||
Approach **A (structural recognition)** — extend `is_c_abi_type`
|
|
||||||
with a recursive list-shaped arm (2 ctors: one nullary + one
|
|
||||||
`(C-ABI, recursive-self)`), no name match, consistent with M3's
|
|
||||||
structural single-ctor rule and AILang's content-addressing
|
|
||||||
thesis — was the recommendation. B (name-anchored) rejected on
|
|
||||||
language grounds (name fragility; no canonical `List` — 8
|
|
||||||
divergent decls, no `std/`); C (land a `std_list` SSOT first)
|
|
||||||
rejected on scope grounds (a separate milestone; breaks the
|
|
||||||
established self-contained embed-fixture discipline).
|
|
||||||
|
|
||||||
## Why M4 was struck (the substantive reason — feature-acceptance clause 2)
|
|
||||||
|
|
||||||
The user asked "what is M4 even *for*?" then "but the host has to
|
|
||||||
build the list first, no?" then narrowed: a *minimal* data-server
|
|
||||||
binding would be satisfied by the host pushing each tick
|
|
||||||
individually — no list at all.
|
|
||||||
|
|
||||||
Held against `docs/DESIGN.md` §"Feature-acceptance criterion":
|
|
||||||
|
|
||||||
- **Clause 2 (improves correctness / removes redundancy) — FAILS.**
|
|
||||||
The shipped M3 export gate `is_c_abi_type`
|
|
||||||
(`crates/ailang-check/src/lib.rs:1934-1953`) runs as a **per-
|
|
||||||
parameter loop** (`for p in ¶m_tys { if !is_c_abi_type(p,
|
|
||||||
&env) … }`) accepting, *independently per param*, a C scalar OR
|
|
||||||
a single-ctor all-scalar record. The forwarder's `llvm_scalar`
|
|
||||||
maps every non-scalar `Type::Con` → `ptr` and passes it
|
|
||||||
straight through (M3-frozen). Therefore `(State, Tick) -> State`
|
|
||||||
with both as single-ctor all-scalar records is **already
|
|
||||||
gate-accepted and forwarder-supported today** — the minimal
|
|
||||||
data-server binding is M3 (shipped) + a host-side per-tick loop
|
|
||||||
over each chunk's records. Cons-list crossing would *add* a
|
|
||||||
2N+1-box-per-chunk host builder and pull in the deferred
|
|
||||||
flat-array perf debt (the cons-list IS the P2-deferred perf
|
|
||||||
problem), removing no redundancy.
|
|
||||||
- **No named consumer.** M5's adapter (the sole data-server↔AILang
|
|
||||||
meeting point, Invariant 1) can wire `while let next_chunk` by
|
|
||||||
unrolling each chunk host-side into per-tick calls — a clean
|
|
||||||
adapter, not a layering violation; the adapter is exactly where
|
|
||||||
host data shape meets the kernel. Whole-chunk in-kernel
|
|
||||||
visibility buys nothing: `State` threads across calls regardless
|
|
||||||
of chunk boundaries, so a windowed indicator behaves identically
|
|
||||||
whether ticks arrive one-per-call or chunk-per-call; chunk
|
|
||||||
boundaries are a host artefact, semantically invisible to the
|
|
||||||
fold.
|
|
||||||
|
|
||||||
M4 (cons-list crossing) is therefore **speculative infra by
|
|
||||||
AILang's own criterion**: it builds something no named consumer
|
|
||||||
needs and adds cost rather than removing redundancy. The
|
|
||||||
brainstorm discipline at this point is *not* to ratify a known-
|
|
||||||
unneeded shape into the roadmap (Common Rationalisations:
|
|
||||||
"Approaches A,B,C — proceed with A" → surface the mis-framing),
|
|
||||||
so no spec was written, no grounding-check dispatched, no planner
|
|
||||||
handoff.
|
|
||||||
|
|
||||||
## Honest correction (lesson)
|
|
||||||
|
|
||||||
Mid-Q&A I asserted "M5 cannot wire the real data-server loop
|
|
||||||
without M4." That was wrong and I corrected it on the record: M5
|
|
||||||
*can* wire it with M3 + a host-side `for tick in chunk` loop. The
|
|
||||||
earlier "O(records) vs O(chunks) FFI calls" framing was also
|
|
||||||
misleading — the per-tick forwarder is thin; the expensive thing
|
|
||||||
in the M4 alternative is the cons-list marshalling the scalar loop
|
|
||||||
does not have at all. Re-deriving against the code (not defending
|
|
||||||
the roadmap I had written) is what surfaced the clause-2 failure;
|
|
||||||
this is the "user suggestions ≠ directives, form own judgment"
|
|
||||||
discipline working in the user's favour.
|
|
||||||
|
|
||||||
## Decision / outcome
|
|
||||||
|
|
||||||
- **M4 retired** in `docs/roadmap.md` (kept one cycle as a struck
|
|
||||||
entry for context, then delete — never `[x]`, never shipped).
|
|
||||||
- **M5 reconciled**: `depends on:` M4 → M3 (+ the Tick-coverage
|
|
||||||
todo); body now states the adapter unrolls each chunk host-side
|
|
||||||
into per-tick `(State, Tick) -> State` calls; the friction
|
|
||||||
harvest now feeds the host-per-tick-FFI vs. batch-crossing perf
|
|
||||||
decision (the P2 flat-array item) rather than an array-vs-cons
|
|
||||||
decision.
|
|
||||||
- **Residual = a `[todo]`**: Tick-coverage on M3 — an E2E +
|
|
||||||
fixture pinning the two-record-param per-tick crossing
|
|
||||||
`(State, Tick) -> State` on the shipped M3 ABI. Capability is
|
|
||||||
present today but only E2E-proven for a *single* record param
|
|
||||||
(`State`): every shipped M3 fixture
|
|
||||||
(`embed_backtest_step_record{,_borrow}.ail`,
|
|
||||||
`embed_export_record_ok.ail`) pushes a scalar `Float` sample,
|
|
||||||
none a record `Tick`. A second record param is the identical
|
|
||||||
mechanism (same gate arm, same `→ptr`, same forwarder, zero new
|
|
||||||
code path) — worth pinning by fixture, but a test backfill, not
|
|
||||||
a milestone (no brainstorm). This is the actual "minimal
|
|
||||||
data-server binding" the user asked for.
|
|
||||||
|
|
||||||
## Forward note (not edited now — avoid scope creep)
|
|
||||||
|
|
||||||
The P2 "Flat array/slice primitive — performance" item's framing
|
|
||||||
("1024 RC cons-cells per chunk on the hot path") is now partly
|
|
||||||
stale — the cons-list path is dropped, so the residual perf
|
|
||||||
question is host-side per-tick FFI vs. an eventual batch crossing.
|
|
||||||
Flagged here; reconcile that entry's wording when it is picked up,
|
|
||||||
not now.
|
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
# iter bugfix-over-strict-mode-ctor-rebuild-consume — over-strict-mode FP on ctor-rebuild-from-primitive-fields
|
|
||||||
|
|
||||||
**Date:** 2026-05-18
|
|
||||||
**Started from:** a11cb7cc9f912a0d7ee8a56b3f53255bd101e327
|
|
||||||
**Status:** DONE
|
|
||||||
**Tasks completed:** 1 of 1
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
The `over-strict-mode` lint spuriously fired `(borrow ...) would
|
|
||||||
suffice` on `(own (con T))` params whose body destructures `T` into
|
|
||||||
purely *primitive* fields and then feeds those fields into a
|
|
||||||
`Term::Ctor` that builds a fresh allocation. The dismantle+rebuild
|
|
||||||
is a genuine consume of the scrutinee's box (a borrowed value's
|
|
||||||
payload cannot be taken apart and re-packaged), but the lint's
|
|
||||||
consume-detection only recognised a *heap-typed* pattern-binder
|
|
||||||
being moved out — primitive fields are filtered by
|
|
||||||
`pattern_has_consumed_heap_binder`, so the rebuild was invisible.
|
|
||||||
This is the false positive behind `embed_backtest_step_tick.ail`
|
|
||||||
warning on both `st` and `tick`. The fix adds a second recognition
|
|
||||||
path in `any_sub_binder_consumed_for`: for a `match p` arm that
|
|
||||||
destructures binders out of `p`'s ctor, if any of those binders
|
|
||||||
(primitive or not) is mentioned anywhere inside a `Term::Ctor`'s
|
|
||||||
argument subtrees in the arm body, the param is genuinely consumed
|
|
||||||
and the lint stays silent. Over-strict-only: a param merely read
|
|
||||||
(no ctor build) still warns, proven by a negative control. No
|
|
||||||
existing test changed; doc-honesty corrections applied to the two
|
|
||||||
stale comments that mis-attributed this defect to nested `match`.
|
|
||||||
|
|
||||||
## Per-task notes
|
|
||||||
|
|
||||||
- iter bugfix-over-strict-mode-ctor-rebuild-consume.1: added
|
|
||||||
`collect_pattern_binders`-based ctor-rebuild-consume recognition
|
|
||||||
to `any_sub_binder_consumed_for`'s matched-scrutinee arm loop in
|
|
||||||
`crates/ailang-check/src/linearity.rs`, plus two pure private
|
|
||||||
helpers (`ctor_uses_any_binder` — finds a fresh-allocation
|
|
||||||
`Term::Ctor` reachable in the arm body; `term_mentions_any_binder`
|
|
||||||
— deep free-var scan of that ctor's args, so the binder reaching
|
|
||||||
the ctor *through* an expression like `(+ acc px)` is recognised,
|
|
||||||
not only a bare `Var` arg). Corrected the stale "Known limit" doc
|
|
||||||
on `any_sub_binder_consumed_for` and the matching "Known debt:
|
|
||||||
deeply-nested-match-on-sub-binder" block in the module header —
|
|
||||||
both previously mis-attributed the FP to a nested match; the real
|
|
||||||
mechanism is the unrecognised ctor-rebuild consume, independent of
|
|
||||||
match nesting.
|
|
||||||
|
|
||||||
## Concerns
|
|
||||||
|
|
||||||
(none)
|
|
||||||
|
|
||||||
## Known debt
|
|
||||||
|
|
||||||
- `term_mentions_any_binder`'s binder scan is by name and does not
|
|
||||||
track shadowing. A destructured primitive binder shadowed and
|
|
||||||
rebound before reaching the ctor would still be counted. This is
|
|
||||||
not a corpus shape, and the resulting imprecision is strictly in
|
|
||||||
the over-strict (extra-silence) direction the cause explicitly
|
|
||||||
permits — never under-strict, so no soundness risk. Documented in
|
|
||||||
the function's own doc comment; left as-is per the minimal-fix
|
|
||||||
constraint.
|
|
||||||
|
|
||||||
## Blocked detail
|
|
||||||
|
|
||||||
(not blocked)
|
|
||||||
|
|
||||||
## Files touched
|
|
||||||
|
|
||||||
- crates/ailang-check/src/linearity.rs (+166 / −20): lint-detection
|
|
||||||
fix + two helpers + two doc-honesty corrections. Check-only; no
|
|
||||||
codegen / runtime / ABI / schema / DESIGN.md change.
|
|
||||||
|
|
||||||
## Stats
|
|
||||||
|
|
||||||
bench/orchestrator-stats/2026-05-18-iter-bugfix-over-strict-mode-ctor-rebuild-consume.json
|
|
||||||
@@ -1,131 +0,0 @@
|
|||||||
# iter docs-honesty-lint.1 — canonical docs made present-tense-honest, guarded by a wrap-robust pin + Sweep 5
|
|
||||||
|
|
||||||
**Date:** 2026-05-18
|
|
||||||
**Started from:** de66eb7ab2e457bfe7e5dbe2f87c5da15a786cd7
|
|
||||||
**Status:** DONE
|
|
||||||
**Tasks completed:** 6 of 6
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
`docs/DESIGN.md` and `docs/PROSE_ROUNDTRIP.md` carried Wunschdenken
|
|
||||||
(forward intent stated as fact) and non-citation post-mortem /
|
|
||||||
doc-archaeology narrative. This iter strips both classes, codifies
|
|
||||||
the honesty discriminator as a `### What this document is — and the
|
|
||||||
honesty rule it holds itself to` meta-subsection inside DESIGN.md
|
|
||||||
§"Project ecosystem", and guards the invariant two ways: a
|
|
||||||
wrap-robust enumerated absent/present regression pin
|
|
||||||
(`crates/ailang-core/tests/docs_honesty_pin.rs`, 4 tests) and a
|
|
||||||
wrap-robust advisory Sweep 5 in `bench/architect_sweeps.sh` with all
|
|
||||||
sweep-count lockstep wording + a new `ailang-architect.md` "DESIGN.md
|
|
||||||
honesty drift" bullet. Genuine forward intent (LLM tool-use / MCP /
|
|
||||||
LSP) relocated to `docs/roadmap.md` P3. No language/checker/codegen/
|
|
||||||
runtime change — the sole `crates/` change is the additive pin file;
|
|
||||||
`ail check`/`run`/`build` are byte-unchanged by construction.
|
|
||||||
|
|
||||||
## Per-task notes
|
|
||||||
|
|
||||||
- iter docs-honesty-lint.1.1: created the wrap-robust RED pin
|
|
||||||
(`docs_honesty_pin.rs`, `norm()` whitespace-collapse helper, 4
|
|
||||||
tests). Verified RED for the right reasons (compiles, 4 fail on the
|
|
||||||
enumerated still-present strings + the absent discriminator).
|
|
||||||
- iter docs-honesty-lint.1.2: appended Sweep 5 to
|
|
||||||
`architect_sweeps.sh` + tail "All four"→"All five" + header
|
|
||||||
"four"→"five sweeps"; added the "DESIGN.md honesty drift" architect
|
|
||||||
bullet and fixed the sibling "four sweeps"→"five / Sweeps 1-4"
|
|
||||||
wording. Sweep 5 confirmed firing on the uncorrected tree
|
|
||||||
(EXIT=1). Concern-1 lockstep shipped whole, no split.
|
|
||||||
- iter docs-honesty-lint.1.3: inserted the discriminator
|
|
||||||
meta-subsection. Tree had drifted (a `- **Tests**:` bullet now
|
|
||||||
follows the Docs bullet, no blank line; closing prose is "When the
|
|
||||||
language grows…"); anchored on the verbatim closing-prose line per
|
|
||||||
the plan's own Step-1 "quote it, do not assume" caveat.
|
|
||||||
- iter docs-honesty-lint.1.4: applied the 13 byte-exact corrections
|
|
||||||
(each verbatim "current text" re-confirmed by grep first; lines had
|
|
||||||
drifted +~22 from Task 3). Step-14 catch-all surfaced one genuine
|
|
||||||
extra FIX site (see Concerns); classified + handled per procedure.
|
|
||||||
- iter docs-honesty-lint.1.5: stripped the PROSE_ROUNDTRIP tool-use/
|
|
||||||
MCP Wunschdenken paragraph (present-tense replacement); appended
|
|
||||||
the relocated forward-intent idea to roadmap P3 with spec
|
|
||||||
provenance. Full pin GREEN (4/4).
|
|
||||||
- iter docs-honesty-lint.1.6: full verification — pin GREEN isolated;
|
|
||||||
workspace zero failures (609 passed, delta = +4 pin tests only);
|
|
||||||
sentinel trio (design_schema_drift/spec_drift/schema_coverage)
|
|
||||||
green; Sweep 5 reduced to the single self-referential L62 KEEP;
|
|
||||||
build clean; `ONLY THE PIN` scope confirmation.
|
|
||||||
|
|
||||||
## Concerns
|
|
||||||
|
|
||||||
- T3: plan Task-1 pin string `whether the document asserts something
|
|
||||||
exists, works, or changed that does not` omitted the `**…**`
|
|
||||||
Markdown bold that plan Task-3 body wrapped around `asserts …
|
|
||||||
not`. Plan self-review item 3 cross-check missed it. Resolved by
|
|
||||||
dropping the incidental bold from the inserted body (the pin is the
|
|
||||||
contract, written first; the emphasis is non-load-bearing). Prose
|
|
||||||
meaning + present-tense framing unchanged. Plan-internal
|
|
||||||
inconsistency, not a tree-drift; flagged for planner calibration.
|
|
||||||
- T4: Step-14 catch-all surfaced one genuine extra Sweep-5 FIX site
|
|
||||||
not in Steps 1-13 — DESIGN.md Form-B class/instance prose-projection
|
|
||||||
bullet ("was deferred from milestone 22 entirely; … A future iter
|
|
||||||
ships the full prose projection if/when …"). The Boss's "zero extra
|
|
||||||
hits" pre-verification was against the spec's illustrative regex;
|
|
||||||
the plan's broader Sweep-5 regex correctly catches this soft-wrapped
|
|
||||||
site. Handled exactly per Step-14: rewritten present-tense (kept the
|
|
||||||
load-bearing facts — one-way renderer, no parser by design,
|
|
||||||
placeholder render in `crates/ailang-prose/src/lib.rs`) + matching
|
|
||||||
absent-string asserts + present-anchor added to the pin. This is the
|
|
||||||
procedural catch-all working as designed.
|
|
||||||
|
|
||||||
## Catch-all classification (Task 4 Step 14)
|
|
||||||
|
|
||||||
- KEEP: DESIGN.md L62 `- **Forward intent** ("planned / will back /
|
|
||||||
on the path to / if-when X arrives")` — the discriminator
|
|
||||||
subsection's own enumerated catalogue of forbidden phrasings;
|
|
||||||
asserts nothing false, present-tense rule definition. A
|
|
||||||
self-referential Sweep-5 false-positive (the regex matches the
|
|
||||||
literal "on the path to" inside the rule's own example list).
|
|
||||||
- FIX: DESIGN.md L2093 Form-B prose-projection bullet — see Concerns;
|
|
||||||
before→after recorded there; pin pair added.
|
|
||||||
|
|
||||||
## Known debt
|
|
||||||
|
|
||||||
- (none) — the iter is self-contained; the pin + Sweep 5 are the
|
|
||||||
forward guard against regrowth.
|
|
||||||
|
|
||||||
## Post-iter sweep output (Task 6 Step 4)
|
|
||||||
|
|
||||||
```
|
|
||||||
=== Sweep: 1 (history anchors) ===
|
|
||||||
50: `docs/journal-archive.md` (archived monolith for pre-2026-05-11 history),
|
|
||||||
2060: 2026-05-13) — combines two `Str` values into a single owned `Str`.
|
|
||||||
|
|
||||||
=== Sweep: 5 (honesty: wunschdenken + non-citation post-mortem) ===
|
|
||||||
62:- **Forward intent** ("planned / will back / on the path to /
|
|
||||||
|
|
||||||
EXIT=1
|
|
||||||
```
|
|
||||||
|
|
||||||
Sweep-1 residue = the two documented deliberate KEEPs (L50 doc-roles
|
|
||||||
pointer date, L2060 true `(iter str-concat, 2026-05-13)` provenance).
|
|
||||||
Sweep-5 residue = the single L62 self-referential discriminator-
|
|
||||||
catalogue KEEP. EXIT=1 is expected + architect-adjudicated per plan
|
|
||||||
Task 6 Step 4 (deliberate-KEEP date-anchors remain by design).
|
|
||||||
|
|
||||||
## Blocked detail
|
|
||||||
|
|
||||||
(none — DONE)
|
|
||||||
|
|
||||||
## Files touched
|
|
||||||
|
|
||||||
- `crates/ailang-core/tests/docs_honesty_pin.rs` (new — the pin, 4
|
|
||||||
tests; extended in T4 Step 14 per the mandated catch-all procedure)
|
|
||||||
- `bench/architect_sweeps.sh` (Sweep 5 + header/tail lockstep)
|
|
||||||
- `skills/audit/agents/ailang-architect.md` ("four"→"five" + new
|
|
||||||
"DESIGN.md honesty drift" bullet)
|
|
||||||
- `docs/DESIGN.md` (discriminator subsection + 13 enumerated
|
|
||||||
corrections + 1 catch-all correction)
|
|
||||||
- `docs/PROSE_ROUNDTRIP.md` (tool-use/MCP Wunschdenken stripped)
|
|
||||||
- `docs/roadmap.md` (relocated forward-intent idea, P3)
|
|
||||||
|
|
||||||
## Stats
|
|
||||||
|
|
||||||
bench/orchestrator-stats/2026-05-18-iter-docs-honesty-lint.1.json
|
|
||||||
@@ -1,474 +0,0 @@
|
|||||||
# iter embedding-abi-m1.1 — Embedding ABI M1: linkable artifact + scalar C entrypoint
|
|
||||||
|
|
||||||
**Date:** 2026-05-18
|
|
||||||
**Started from:** b0bd7efeaff137af22bb12f5e751260804e770c3
|
|
||||||
(Tasks 1–3 committed at 818177d; Tasks 4–7 re-dispatched from 818177d)
|
|
||||||
**Status:** DONE — all 7 tasks complete. Tasks 1–3 committed
|
|
||||||
(818177d); Tasks 4–7 implemented on the corrected plan and sit
|
|
||||||
unstaged in the working tree. The E2E coherent-stop proof
|
|
||||||
(`embed_e2e.rs`: `cc` link + run, `s == 25`, exit 0) is GREEN.
|
|
||||||
**Tasks completed:** 7 of 7 (Tasks 1–3 DONE + committed; Task 4
|
|
||||||
initially BLOCKED on a Boss plan-defect — fixture module≠stem —
|
|
||||||
resolved by Boss plan Repair [Design-decision 6] + re-dispatch;
|
|
||||||
Tasks 4–7 then completed cleanly on the corrected plan)
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
The Embedding-ABI M1 iter is complete across all 5 pipeline
|
|
||||||
layers. Tasks 1–3 landed in the first dispatch and were committed
|
|
||||||
(818177d) as a verified known-good subset: the CLI build-path
|
|
||||||
`MissingEntryMain` baseline pin (Task 1), the additive
|
|
||||||
`FnDef.export: Option<String>` schema field threaded across the
|
|
||||||
whole workspace via compile-driven enumeration with a hash-stability
|
|
||||||
golden pin (Task 2), and the Form-A `(export "<sym>")` surface
|
|
||||||
modifier with full parse/print/grammar lockstep and a byte-identical
|
|
||||||
round-trip (Task 3).
|
|
||||||
|
|
||||||
Task 4 was BLOCKED in the first dispatch on a Boss plan-defect
|
|
||||||
(fixtures declared `module ≠ file-stem`; the loader hard-enforces
|
|
||||||
`module == file-stem` at `workspace.rs:438`). The Boss Repaired the
|
|
||||||
plan (Design-decision 6: fixture `(module …)` == file stem; the
|
|
||||||
C symbol stays `backtest_step` via `(export …)`, demonstrating spec
|
|
||||||
Decision 2's mangling-decoupling) and re-dispatched Tasks 4–7. On
|
|
||||||
the corrected plan all four landed cleanly: the check-side
|
|
||||||
scalar-only + effect-free export gate with two new `CheckError`
|
|
||||||
variants (`export-non-scalar-signature` / `export-has-effects`, the
|
|
||||||
clause-3 discriminator *in code*, Task 4); the codegen
|
|
||||||
`Target::StaticLib` mode suppressing `@main`/`MissingEntryMain` and
|
|
||||||
emitting one external `@<sym>` forwarder per export to
|
|
||||||
`@ail_<module>_<fn>` (Task 5); the CLI `ail build --emit=staticlib`
|
|
||||||
two-archive emit (`libembed_backtest_step.a` + `libailang_rt.a`,
|
|
||||||
Task 6); and the C-host E2E coherent-stop proof — `cc` links both
|
|
||||||
archives, calls `backtest_step(0,3)=9`, `backtest_step(9,4)=25`,
|
|
||||||
the `s == 25` assertion holds, exit 0 (Task 7), plus the DESIGN.md
|
|
||||||
§"Embedding ABI (M1)" current-state subsection. Full workspace
|
|
||||||
green (0 failures); `roundtrip_cli` green; `compile_check.py` /
|
|
||||||
`cross_lang.py` exit 0; `check.py` fires only the tracked P2
|
|
||||||
`*.bump_s` / `*.max_us` known-noise envelope (plan Step 6: audit-
|
|
||||||
adjudicated, not a plan-level gate — the default-executable
|
|
||||||
codegen/runtime path is byte-unchanged). No clean-core dependency
|
|
||||||
added (Invariant 1).
|
|
||||||
|
|
||||||
## Per-task notes
|
|
||||||
|
|
||||||
- iter embedding-abi-m1.1.1: CLI build-path `MissingEntryMain`
|
|
||||||
baseline pin. Created `examples/embed_noentry_baseline.ail`
|
|
||||||
(main-free module) + `crates/ail/tests/embed_missing_main_baseline.rs`
|
|
||||||
(triple-assertion: non-zero exit + verbatim `has no \`main\` def`
|
|
||||||
Display substring [codegen lib.rs:129] + absent binary). GREEN on
|
|
||||||
write; not-a-no-op confirmed; `roundtrip_cli` green with the new
|
|
||||||
fixture. DONE.
|
|
||||||
- iter embedding-abi-m1.1.2: additive `FnDef.export` schema field +
|
|
||||||
workspace-wide threading. Added the field to `FnDef` after `doc`
|
|
||||||
(verbatim plan doc-comment, `#[serde(default,
|
|
||||||
skip_serializing_if = "Option::is_none")]`). Compile-driven
|
|
||||||
enumeration via `cargo build --workspace --all-targets
|
|
||||||
--keep-going` over two dependency-ordered waves threaded
|
|
||||||
`export: None` into ~85 `FnDef`/`AstFnDef` literals across 18
|
|
||||||
files incl. all named blind-spot sites (codegen lib.rs:3320
|
|
||||||
`missing_entry_main_is_error`, design_schema_drift.rs:278,
|
|
||||||
spec_drift.rs:256, hash_pin.rs, the in-source `#[cfg(test)]
|
|
||||||
mod tests` literals, the `mono.rs` `AstFnDef` alias, the
|
|
||||||
`parse.rs`/`print.rs` literals). `cargo build --workspace`
|
|
||||||
exits 0 (planner self-review item 7 gate met). DESIGN.md
|
|
||||||
fn-JSON `"export"` line + drift test green (Recon Q2 confirmed:
|
|
||||||
no nested-struct-key anchor forced). Hash pin green (golden
|
|
||||||
`b4662aa70839f60b`). Full workspace test gate: 611/0.
|
|
||||||
DONE_WITH_CONCERNS.
|
|
||||||
- iter embedding-abi-m1.1.3: Form-A `(export "<sym>")` surface
|
|
||||||
modifier. `parse_export` helper (faithful `parse_doc` mirror);
|
|
||||||
`parse_fn` threaded (`let mut export`, `Some("export")` arm with
|
|
||||||
duplicate-clause guard, unknown-attr string updated, `Ok(FnDef{…})`
|
|
||||||
uses parsed `export`); `write_fn_def` export emission block (doc →
|
|
||||||
export → suppress → type order); form_a.md production +
|
|
||||||
`(export STRING)?` + prose paragraph. RED verified
|
|
||||||
(`unknown fn attribute \`export\``), then GREEN: round-trip gate
|
|
||||||
byte-identical, surface crate 72/0, ailang-core 111/0. DONE.
|
|
||||||
- iter embedding-abi-m1.1.4 (FIRST dispatch — BLOCKED, audit
|
|
||||||
trail): wrote the five plan fixtures + the verbatim gate pin; ran
|
|
||||||
it → RED for the WRONG reason (a `ModuleNameMismatch` load
|
|
||||||
failure, not the absence of the gate). BLOCKED — see Blocked
|
|
||||||
detail + Boss resolution. No gate production code written; the
|
|
||||||
WIP fixtures + RED test were discarded by the Boss for the
|
|
||||||
re-dispatch.
|
|
||||||
- iter embedding-abi-m1.1.4 (RE-dispatch — DONE): check-side
|
|
||||||
export-signature gate on the corrected plan (Design-decision 6,
|
|
||||||
module==stem). Recreated the five fixtures (module == file stem;
|
|
||||||
`embed_export_float_ok.ail` uses `(app * a b)` — no `*.` head)
|
|
||||||
+ the verbatim gate pin `crates/ailang-check/tests/embed_export_gate.rs`.
|
|
||||||
RED verified for the RIGHT reason (fixtures load cleanly, 4
|
|
||||||
`*_rejected` fail because no gate exists, 2 positives already
|
|
||||||
pass). Added two `CheckError` Error variants
|
|
||||||
(`ExportNonScalarSignature(String,&'static str,String)` /
|
|
||||||
`ExportHasEffects(String,Vec<String>)`) after `ConstHasEffects`,
|
|
||||||
their two `code()` arms, and the gate in `check_fn` after the
|
|
||||||
param/ret well-formedness checks (gate order params→ret→effects,
|
|
||||||
first-violation-wins, unconditional on `f.export.is_some()`).
|
|
||||||
GREEN 6/6; combined fixture fails signature-first. Round-trip +
|
|
||||||
full workspace green. DONE.
|
|
||||||
- iter embedding-abi-m1.1.5: codegen `Target::StaticLib`. Added the
|
|
||||||
`Target` enum next to `AllocStrategy`; threaded `target` through
|
|
||||||
`lower_workspace_inner`; `lower_workspace_with_alloc` /
|
|
||||||
`lower_workspace` pin `Target::Executable`; new public
|
|
||||||
`lower_workspace_staticlib_with_alloc`. Gated the
|
|
||||||
`MissingEntryMain` shape-check + the `@main` trampoline behind
|
|
||||||
`Target::Executable`; the `StaticLib` arm emits one external
|
|
||||||
`define {cret} @<sym>(...)` per `(export …)` fn forwarding to
|
|
||||||
`@ail_<module>_<fn>` via the new `fn_scalar_sig` / `llvm_scalar`
|
|
||||||
helpers. RED→GREEN IR-shape pin (no `@main`, has `@backtest_step`,
|
|
||||||
forwards to `@ail_embed_backtest_step_step`; exe target still
|
|
||||||
`MissingEntryMain`). In-source `missing_entry_main_is_error` pin
|
|
||||||
byte-unchanged; codegen crate 0 failures. DONE_WITH_CONCERNS
|
|
||||||
(the `emit_ir`-routes-via-`lower_workspace` plan note).
|
|
||||||
- iter embedding-abi-m1.1.6: CLI `ail build --emit=staticlib`.
|
|
||||||
Added the `--emit` clap field (`value_parser = ["exe",
|
|
||||||
"staticlib"]`, default `exe`) after `alloc`; branched
|
|
||||||
`Cmd::Build` (`emit == "staticlib"` → `build_staticlib`); added
|
|
||||||
`build_staticlib` (verbatim `build_to:2239–2294` shared prefix +
|
|
||||||
`has_export` guard + `lower_workspace_staticlib_with_alloc` +
|
|
||||||
two-archive `clang -c`/`ar rcs` tail) and the new shared `run_cmd`
|
|
||||||
helper (confirmed no pre-existing equivalent). RED→GREEN CLI pin
|
|
||||||
(`libembed_backtest_step.a` + `libailang_rt.a` produced;
|
|
||||||
export-less module rejected with the zero-export error).
|
|
||||||
Default-exe regression (floats E2E + Task-1 baseline) green.
|
|
||||||
DONE.
|
|
||||||
- iter embedding-abi-m1.1.7: E2E host harness + DESIGN.md
|
|
||||||
§"Embedding ABI (M1)". Wrote `crates/ail/tests/embed/host.c`
|
|
||||||
(spec headline host, verbatim) + `crates/ail/tests/embed_e2e.rs`.
|
|
||||||
E2E GREEN — `cc` links `libembed_backtest_step.a` +
|
|
||||||
`libailang_rt.a`, runs the host, `backtest_step(0,3)=9` /
|
|
||||||
`backtest_step(9,4)=25`, `s == 25` assertion holds, exit 0 (the
|
|
||||||
milestone coherent stop). Added the DESIGN.md §"Embedding ABI
|
|
||||||
(M1)" subsection (verbatim prose, at `##` level for consistency
|
|
||||||
with §"Mangling scheme"). Drift tests 8/8 + 8/8; full workspace
|
|
||||||
0 failures; bench trio per plan Step 6. DONE_WITH_CONCERNS (the
|
|
||||||
dead-`Path`-import correction + the `###`-vs-`##` heading-level
|
|
||||||
resolution).
|
|
||||||
|
|
||||||
## Concerns
|
|
||||||
|
|
||||||
- iter embedding-abi-m1.1.2: the plan's RED-pin symbol
|
|
||||||
`ailang_core::hash::content_hash_fn(&FnDef)` does not exist. The
|
|
||||||
real entry point is `def_hash(&Def) -> String` (the one
|
|
||||||
`crates/ailang-core/tests/hash_pin.rs` uses). Resolved by
|
|
||||||
mirroring `hash_pin.rs` exactly (wrap the `FnDef` in
|
|
||||||
`Def::Fn(...)`, call `def_hash`) — this is explicitly authorised
|
|
||||||
by the plan's own Step-1 NOTE ("the implementer mirrors the exact
|
|
||||||
hashing entry point that `hash_pin.rs` uses if the name differs").
|
|
||||||
Property and shape unchanged; recorded because the plan's literal
|
|
||||||
symbol was wrong (planner pseudo-vs-reality class — the
|
|
||||||
`feedback_specs_need_concrete_code` / `feedback_plan_pseudo_vs_reality`
|
|
||||||
family).
|
|
||||||
- iter embedding-abi-m1.1.4: the plan fixture `embed_export_float_ok.ail`
|
|
||||||
uses `(app *. a b)`. There is no `*.` Float-multiply head in
|
|
||||||
AILang — Float arithmetic uses the same typeclass-generic head as
|
|
||||||
Int (`examples/floats.ail` / `examples/mut_sum_floats.ail` both
|
|
||||||
use `(app + …)`; codegen lowers to `fadd/fmul double` by type).
|
|
||||||
Per the plan NOTE ("if the Float-multiply surface differs, use the
|
|
||||||
same head that example uses"), the fixture as written uses
|
|
||||||
`(app * a b)`. The fixture's role ("scalar+pure Float export
|
|
||||||
passes the gate") is preserved. Recorded; not yet exercised
|
|
||||||
(Task 4 BLOCKED before the gate ran).
|
|
||||||
- INFRA (not iter-authored): `docs/roadmap.md` became dirty in the
|
|
||||||
working tree during this run (a ~98-line P2 "DESIGN.md → design/
|
|
||||||
role-split" milestone proposal, mtime mid-session). Phase 0's
|
|
||||||
`git status --porcelain` was empty (clean start). Nothing in this
|
|
||||||
iter writes `roadmap.md`; it was modified by an external/concurrent
|
|
||||||
writer. It is path-disjoint from every iter file. Left untouched
|
|
||||||
(roadmap.md is Boss-owned per CLAUDE.md; main HEAD sacrosanct).
|
|
||||||
The Boss must decide independently whether to keep or discard it;
|
|
||||||
it is NOT part of this iter's commit. (RESOLVED at Boss
|
|
||||||
re-dispatch: committed separately as Boss roadmap maintenance —
|
|
||||||
see Boss resolution. Not part of the Tasks 4–7 working tree.)
|
|
||||||
- iter embedding-abi-m1.1.5 (plan pseudo-vs-reality, behaviour-
|
|
||||||
neutral): plan Task-5 Step 3(b) third bullet says "`emit_ir`
|
|
||||||
(line 197): its internal `lower_workspace_inner` call passes
|
|
||||||
`Target::Executable`". Reality: `emit_ir` does NOT call
|
|
||||||
`lower_workspace_inner` directly — it calls `lower_workspace`
|
|
||||||
(`codegen lib.rs:214`), which now passes
|
|
||||||
`lower_workspace_inner(ws, AllocStrategy::Gc, Target::Executable)`.
|
|
||||||
The plan's *intent* (the in-source `missing_entry_main_is_error`
|
|
||||||
pin, which calls `emit_ir`, stays byte-unchanged) is delivered
|
|
||||||
transitively and verified GREEN at Task-5 Step 7. Recorded
|
|
||||||
because the plan's literal call-chain description was inaccurate
|
|
||||||
(planner pseudo-vs-reality family); no behavioural deviation.
|
|
||||||
- iter embedding-abi-m1.1.7 (plan-verbatim transcription defect,
|
|
||||||
corrected): the plan's verbatim Task-7 `embed_e2e.rs` imports
|
|
||||||
`std::path::{Path, PathBuf}` but the test body uses only
|
|
||||||
`PathBuf` — a transcribed unused import that raises
|
|
||||||
`unused_imports` on every build. Implementer-mindset judgement:
|
|
||||||
a verbatim copy that introduces a compiler warning is not the
|
|
||||||
plan's intent; reduced to `use std::path::PathBuf;`. Test
|
|
||||||
behaviour byte-identical (E2E still GREEN). Recorded as a
|
|
||||||
deviation from plan-literal text in service of plan intent.
|
|
||||||
- iter embedding-abi-m1.1.7 (plan code-block vs plan prose, design
|
|
||||||
consistency): plan Task-7 Step 4's code block shows the new
|
|
||||||
section as `### Embedding ABI (M1)`, but the same step's prose
|
|
||||||
instruction says "at the same heading level as §"Mangling
|
|
||||||
scheme"" — §"Mangling scheme" is `## ` (level-2) in DESIGN.md.
|
|
||||||
Used `## Embedding ABI (M1)` for structural consistency with the
|
|
||||||
surrounding §"Mangling scheme" / §"Data model" level-2 sections,
|
|
||||||
following the plan's explicit prose directive over its
|
|
||||||
illustrative code-block `#`-count. Section content byte-verbatim.
|
|
||||||
- iter embedding-abi-m1.1 (cross-checks, plan-confirming, no
|
|
||||||
deviation): the six named implementer-judgement cross-checks were
|
|
||||||
executed. (a) `Type::Con` has exactly `{name: String, args:
|
|
||||||
Vec<Type>}` — no mode field; the plan's `matches!` guard is
|
|
||||||
correct. (b) `declared_effs` is already `Vec<String>` (per
|
|
||||||
`lib.rs` `declared_effs.into_iter().collect()`), so the plan's
|
|
||||||
conditional `.into_iter().collect()` conversion did NOT apply —
|
|
||||||
`.clone()` matches `ExportHasEffects(_, Vec<String>)` exactly.
|
|
||||||
(c) `synth::llvm_type` lowers `Int`→`"i64"`, `Float`→`"double"`
|
|
||||||
(`synth.rs:17,21`) — the plan's `llvm_scalar` is an exact mirror
|
|
||||||
for the 2-scalar M1 subset; forwarder `call` types match
|
|
||||||
`@ail_<module>_<fn>`'s emitted signature (also proven by the
|
|
||||||
Task-7 E2E real `cc` link + run). (d) no pre-existing `run_cmd`
|
|
||||||
helper in `main.rs` (only inline `Command::new` blocks) — adding
|
|
||||||
it was correct per the plan. (e) `ailang_core::Def` is crate-root
|
|
||||||
re-exported (`ailang-core lib.rs:76`) — `ailang_core::Def::Fn`
|
|
||||||
valid in `main.rs`. (f) `ailang_surface::load_workspace` +
|
|
||||||
`ailang_check::check_workspace` + `Diagnostic.code: String` are
|
|
||||||
the exact `main.rs:580–581` public symbols. All confirmations of
|
|
||||||
plan assumptions, not contradictions.
|
|
||||||
|
|
||||||
## Known debt
|
|
||||||
|
|
||||||
- None blocking. The M1 ABI is deliberately scalar-only and
|
|
||||||
provisional until M3 (no `Bool`/`Str`/ADT/record marshalling, no
|
|
||||||
per-thread context lifecycle API); this is reserved-unimplemented
|
|
||||||
scope, labelled in DESIGN.md §"Embedding ABI (M1)", not debt.
|
|
||||||
- `check.py` fires the tracked P2 `*.bump_s` / `*.max_us`
|
|
||||||
known-noise envelope (3 regressed / 2 improved beyond tolerance /
|
|
||||||
58 stable; e.g. `throughput.bench_list_sum.bump_s` +13.95%,
|
|
||||||
`latency.explicit_at_rc.max_us` +60.61%). This iter touched only
|
|
||||||
check-side gating, a codegen `Target` enum (every existing path
|
|
||||||
pinned `Executable`), and CLI dispatch — none on the runtime hot
|
|
||||||
path these metrics measure; the default-executable codegen/runtime
|
|
||||||
path is byte-unchanged (Task-5 Step 7/8 + Task-6 Step 6 confirm).
|
|
||||||
Per plan Step 6 this is milestone-close-`audit`-adjudicated, NOT
|
|
||||||
a plan-level gate. `compile_check.py` / `cross_lang.py` exit 0.
|
|
||||||
- `bench/architect_sweeps.sh` exits 1 both with and without the
|
|
||||||
DESIGN.md edit (pre-existing advisory residue, established
|
|
||||||
carry-on adjudicated at milestone-close `audit`); the new
|
|
||||||
§"Embedding ABI (M1)" introduced no new Wunschdenken / post-
|
|
||||||
mortem phrasing (the "provisional until M3" / "M2 will change"
|
|
||||||
clauses are labelled reserved-unimplemented, which the DESIGN.md
|
|
||||||
honesty rule explicitly permits).
|
|
||||||
|
|
||||||
## Blocked detail
|
|
||||||
|
|
||||||
> **RESOLVED (2026-05-18, re-dispatch).** This block was a
|
|
||||||
> first-dispatch event; it was resolved by the Boss plan Repair
|
|
||||||
> (see "## Boss resolution" below — Design-decision 6, Option 2,
|
|
||||||
> module==stem) and the Tasks 4–7 re-dispatch completed all four
|
|
||||||
> tasks cleanly. The detail below is **preserved verbatim as the
|
|
||||||
> iter's audit trail** and is no longer an open block. Iter status
|
|
||||||
> is DONE.
|
|
||||||
|
|
||||||
**Task:** 4 (check-side export-signature gate)
|
|
||||||
**Reason:** spec-ambiguous (a spec-internal contradiction that
|
|
||||||
rippling-binds Tasks 4–7, surfaced as `unclear` per the per-task
|
|
||||||
sub-status table → BLOCKED to Boss).
|
|
||||||
|
|
||||||
**Worker says:** The plan's Task-4 gate pin
|
|
||||||
(`crates/ailang-check/tests/embed_export_gate.rs`) is verbatim and
|
|
||||||
correct Rust; it calls `ailang_surface::load_workspace(&path)` on
|
|
||||||
each fixture (the exact call shape `crates/ail/src/main.rs:580–581`
|
|
||||||
uses). `load_workspace` →
|
|
||||||
`ailang_core::workspace::load_modules_with` →
|
|
||||||
`module_name_from_path` (`workspace.rs:1566`) hard-enforces
|
|
||||||
`in-file module name == file stem`, returning
|
|
||||||
`WorkspaceLoadError::ModuleNameMismatch` (`workspace.rs:182`)
|
|
||||||
otherwise. The plan's verbatim fixtures deliberately violate this:
|
|
||||||
|
|
||||||
- `examples/embed_backtest_step.ail` has `(module backtest …)` —
|
|
||||||
spec-mandated, because the entry module name `backtest` is what
|
|
||||||
produces `libbacktest.a` (Task 6), `@ail_backtest_step` and the
|
|
||||||
exported C symbol `backtest_step` (Tasks 5/7, the spec headline
|
|
||||||
host `host.c` declares `extern int64_t backtest_step(...)`).
|
|
||||||
Stem is `embed_backtest_step` ⇒ `ModuleNameMismatch`.
|
|
||||||
- `embed_export_str_param_rejected.ail` → module `bad_str_param`;
|
|
||||||
`embed_export_io_rejected.ail` → `bad_io`;
|
|
||||||
`embed_export_effectful_rejected.ail` → `bad`;
|
|
||||||
`embed_export_adt_ret_rejected.ail` → `bad_adt_ret`;
|
|
||||||
`embed_export_float_ok.ail` → `embed_float_ok`. All
|
|
||||||
stem-mismatched ⇒ every `check_codes()` call panics on load
|
|
||||||
before the gate is ever consulted.
|
|
||||||
|
|
||||||
These three plan facts cannot all hold simultaneously: (a) fixture
|
|
||||||
files named `examples/embed_*` (every Task 4/5/6/7 path cites these
|
|
||||||
exact names), (b) the spec-headline module/C-symbol naming
|
|
||||||
(`backtest` → `libbacktest.a` / `backtest_step`), (c) a
|
|
||||||
`load_workspace`-based check-side gate pin. `roundtrip_cli` (Task 1/3)
|
|
||||||
sidesteps this only because it drives `ail parse` (single-file, no
|
|
||||||
workspace stem-match). The contradiction is therefore invisible until
|
|
||||||
the first `load_workspace`-based fixture test — exactly Task 4.
|
|
||||||
|
|
||||||
This is not one of the six named implementer-judgement cross-checks
|
|
||||||
(Float `*.` head, public-symbol confirmation, `Type::Con` field set,
|
|
||||||
`declared_effs` element type, `run_cmd` pre-existence, `llvm_scalar`
|
|
||||||
callee-ABI). It is a structural spec/plan defect requiring a
|
|
||||||
Boss/spec decision among at least:
|
|
||||||
|
|
||||||
1. Rename each fixture **file** to its module stem
|
|
||||||
(`examples/backtest.ail`, `examples/bad_str_param.ail`, …) and
|
|
||||||
update every Task 4/5/6/7 path citation accordingly (preserves
|
|
||||||
the spec headline module/C-symbol naming; ripples ~12 plan
|
|
||||||
path references + the Task-6/7 `libbacktest.a` math still holds
|
|
||||||
since module stays `backtest`).
|
|
||||||
2. Rename each fixture **module** to its filename stem
|
|
||||||
(`(module embed_backtest_step …)`, …) and re-derive the spec's
|
|
||||||
C-symbol / `lib<entry>.a` story from `embed_backtest_step`
|
|
||||||
(changes the spec headline; `libembed_backtest_step.a`,
|
|
||||||
`@ail_embed_backtest_step_step` — almost certainly not what the
|
|
||||||
spec wants).
|
|
||||||
3. Change the Task-4 gate pin to a loader that does not
|
|
||||||
stem-enforce (e.g. parse the single file directly), which
|
|
||||||
diverges from the plan's verbatim `load_workspace`-based test
|
|
||||||
and from how `ail check` actually loads (`main.rs:580`).
|
|
||||||
|
|
||||||
Option 1 is the most spec-faithful (it keeps the headline module
|
|
||||||
`backtest` and only moves files), but it is a cross-task plan edit
|
|
||||||
(≥12 path citations) and therefore the planner's / Boss's call, not
|
|
||||||
an inline implementer substitution. I did NOT silently pick one
|
|
||||||
(reverted my own speculative fixture-module renames so the Boss sees
|
|
||||||
the contradiction unobscured); no gate production code was written.
|
|
||||||
|
|
||||||
**Suggested next step:** Boss adopts Option 1 — re-issue the plan
|
|
||||||
for Tasks 4–7 with fixture files renamed to their module stems
|
|
||||||
(`examples/backtest.ail` etc.) and the ~12 path citations updated;
|
|
||||||
keep module `backtest` so the spec's `libbacktest.a` /
|
|
||||||
`backtest_step` headline is preserved — then re-dispatch this iter
|
|
||||||
with `task_range: [4, 7]` (Tasks 1–3 are clean and can be committed
|
|
||||||
now or carried).
|
|
||||||
|
|
||||||
## Boss resolution (2026-05-18)
|
|
||||||
|
|
||||||
The BLOCK was verified accurate against source:
|
|
||||||
`crates/ailang-core/src/workspace.rs:438` (`entry_module.name !=
|
|
||||||
module_name_from_path(entry_path)` → `ModuleNameMismatch`, no
|
|
||||||
exception) — module name MUST equal file stem. The defect is a
|
|
||||||
**Boss plan-write error**: the plan's headline + gate fixtures
|
|
||||||
declared `(module backtest)` / `(module bad)` / … in `embed_*.ail`
|
|
||||||
files, malformed under that invariant. The orchestrator correctly
|
|
||||||
stopped at the Iron Law instead of guessing. Recon maps file
|
|
||||||
structure, not fixture-content invariants; the Boss source-
|
|
||||||
verification focused on code-edit anchors, not cross-checking each
|
|
||||||
fixture's module name against its filename against the loader —
|
|
||||||
this is the `feedback_specs_need_concrete_code` /
|
|
||||||
`feedback_plan_pseudo_vs_reality` family (the fixture as written
|
|
||||||
would not load), now also a fixture-well-formedness lesson.
|
|
||||||
|
|
||||||
**Decision: Option 2, overriding the orchestrator-recommended
|
|
||||||
Option 1.** (Agent recommendations are not directives — own
|
|
||||||
judgement formed first.) Keep the descriptive `embed_*` filenames;
|
|
||||||
rename the fixture *modules* to their file stems
|
|
||||||
(`embed_backtest_step`, `embed_export_str_param_rejected`, …).
|
|
||||||
Rationale from semantics, not effort:
|
|
||||||
|
|
||||||
- The spec's load-bearing contract (Decision 2, spec lines
|
|
||||||
52–59 / 167–169) is that the exported C symbol is *author-chosen
|
|
||||||
and decoupled from `ail_<module>` mangling* —
|
|
||||||
`(export "backtest_step")` is the contract; `host.c` asserts on
|
|
||||||
`backtest_step`, which is invariant under module rename. So
|
|
||||||
spec-faithfulness does **not** discriminate between the options;
|
|
||||||
`libbacktest.a` / `@ail_backtest_step` are *illustrative*
|
|
||||||
(build-output filename + internal mangling — the latter is
|
|
||||||
exactly what the spec decouples from), not frozen-ABI contracts.
|
|
||||||
The orchestrator's "Option 1 most spec-faithful" over-weighted
|
|
||||||
the illustrative `libbacktest.a` string.
|
|
||||||
- `examples/` is a flat shared corpus auto-enrolled in whole-corpus
|
|
||||||
tests (`roundtrip_cli`); Option 1's `examples/bad.ail` /
|
|
||||||
`backtest.ail` / `bad_io.ail` pollute it with collision-prone,
|
|
||||||
non-self-describing names. Option 2 keeps the self-describing
|
|
||||||
grouped `embed_*` set — a real maintainability argument in a
|
|
||||||
~100-file flat dir read by future LLM authors.
|
|
||||||
- Option 2 (module = stem, C symbol stays `backtest_step` via the
|
|
||||||
`(export …)` string) is a *concrete demonstration of the spec's
|
|
||||||
own Decision-2 decoupling thesis* — the first fixture proving
|
|
||||||
module-name ≠ C-symbol — not a violation of it. The spec's
|
|
||||||
`(module backtest)` / `(module bad)` blocks are shape-
|
|
||||||
illustrative (planner contract: spec owns shape, not bytes).
|
|
||||||
|
|
||||||
Consequence: entry module `embed_backtest_step` ⇒ archive
|
|
||||||
`libembed_backtest_step.a`, internal symbol
|
|
||||||
`@ail_embed_backtest_step_step`; the external C symbol
|
|
||||||
`backtest_step` and `host.c` are UNCHANGED (the actual coherent-
|
|
||||||
stop contract). Architect-at-close note recorded in the plan
|
|
||||||
(Design-decision 6): a fixture module name differing from the
|
|
||||||
spec's illustrative block is expected under module==stem +
|
|
||||||
Decision 2, not drift.
|
|
||||||
|
|
||||||
Actions taken: (1) plan `docs/plans/embedding-abi-m1.1.md` fixed —
|
|
||||||
Design-decision 6 added, Task-4 fixtures → module==stem, Task-5/6/7
|
|
||||||
dependent strings updated (`libembed_backtest_step.a`,
|
|
||||||
`@ail_embed_backtest_step_step`; C symbol `@backtest_step`
|
|
||||||
unchanged), the two iter-1 concerns folded (no `*.` Float head →
|
|
||||||
`(app * a b)`; `content_hash_fn` → `def_hash(&Def)`); (2) the
|
|
||||||
already-created headline `examples/embed_backtest_step.ail`
|
|
||||||
corrected to `(module embed_backtest_step)`; (3) the 5 blocked-
|
|
||||||
Task-4 fixtures + the RED `embed_export_gate.rs` discarded
|
|
||||||
(untracked WIP — recreated correctly by the re-dispatch from the
|
|
||||||
fixed plan); (4) Tasks 1–3 committed as a verified known-good
|
|
||||||
partial subset (`cargo build --workspace` exit 0, full workspace
|
|
||||||
green with zero FAILED after the WIP discard, `roundtrip_cli`
|
|
||||||
green); (5) orchestrator re-dispatched with `task_range: [4, 7]`
|
|
||||||
on the clean tree + corrected plan. The user's path-disjoint
|
|
||||||
roadmap addition (P2 "DESIGN.md → `design/` role-split") was
|
|
||||||
committed separately as Boss roadmap maintenance, not bundled
|
|
||||||
into this iter.
|
|
||||||
|
|
||||||
## Files touched
|
|
||||||
|
|
||||||
### Committed at 818177d (Tasks 1–3, first dispatch)
|
|
||||||
|
|
||||||
Schema / canonical docs:
|
|
||||||
- crates/ailang-core/src/ast.rs (FnDef.export field)
|
|
||||||
- docs/DESIGN.md (fn-JSON "export" line)
|
|
||||||
- crates/ailang-core/specs/form_a.md (grammar production + prose)
|
|
||||||
|
|
||||||
Workspace-wide compile-gate threading (~85 `export: None,` literals):
|
|
||||||
- crates/ailang-core/src/desugar.rs, pretty.rs
|
|
||||||
- crates/ailang-core/tests/{design_schema_drift,hash_pin,spec_drift}.rs
|
|
||||||
- crates/ailang-check/src/{lib,lift,linearity,mono,reuse_shape,suppress_filter,uniqueness}.rs
|
|
||||||
- crates/ailang-check/tests/workspace.rs
|
|
||||||
- crates/ailang-codegen/src/lib.rs
|
|
||||||
- crates/ailang-prose/src/lib.rs
|
|
||||||
|
|
||||||
Surface modifier (Task 3):
|
|
||||||
- crates/ailang-surface/src/parse.rs (parse_export + parse_fn thread)
|
|
||||||
- crates/ailang-surface/src/print.rs (write_fn_def export block)
|
|
||||||
|
|
||||||
New files committed at 818177d:
|
|
||||||
- examples/embed_noentry_baseline.ail
|
|
||||||
- examples/embed_backtest_step.ail (corrected to `(module embed_backtest_step)`)
|
|
||||||
- crates/ail/tests/embed_missing_main_baseline.rs
|
|
||||||
- crates/ailang-core/tests/embed_export_hash_stable.rs
|
|
||||||
|
|
||||||
### Tasks 4–7 re-dispatch working tree (UNCOMMITTED — this hand-off)
|
|
||||||
|
|
||||||
Modified (4 source/doc files):
|
|
||||||
- crates/ailang-check/src/lib.rs (2 CheckError variants + 2 code() arms + check_fn gate; +51)
|
|
||||||
- crates/ailang-codegen/src/lib.rs (Target enum + threaded target + forwarder loop + fn_scalar_sig/llvm_scalar; +130/-22)
|
|
||||||
- crates/ail/src/main.rs (--emit clap field + Cmd::Build branch + build_staticlib + run_cmd; +123)
|
|
||||||
- docs/DESIGN.md (new §"Embedding ABI (M1)" subsection; +24)
|
|
||||||
|
|
||||||
New files (untracked, this re-dispatch):
|
|
||||||
- examples/embed_export_str_param_rejected.ail
|
|
||||||
- examples/embed_export_adt_ret_rejected.ail
|
|
||||||
- examples/embed_export_io_rejected.ail
|
|
||||||
- examples/embed_export_effectful_rejected.ail
|
|
||||||
- examples/embed_export_float_ok.ail
|
|
||||||
- crates/ailang-check/tests/embed_export_gate.rs (GREEN 6/6)
|
|
||||||
- crates/ailang-codegen/tests/embed_staticlib_lowering.rs (GREEN 2/2)
|
|
||||||
- crates/ail/tests/embed_staticlib_cli.rs (GREEN 2/2)
|
|
||||||
- crates/ail/tests/embed/host.c
|
|
||||||
- crates/ail/tests/embed_e2e.rs (GREEN 1/1 — coherent-stop proof)
|
|
||||||
|
|
||||||
(`docs/roadmap.md` is NOT in this iter's working tree — the
|
|
||||||
first-dispatch external modification was Boss-committed separately
|
|
||||||
as roadmap maintenance, per the Boss resolution.)
|
|
||||||
|
|
||||||
## Stats
|
|
||||||
|
|
||||||
bench/orchestrator-stats/2026-05-18-iter-embedding-abi-m1.1.json
|
|
||||||
@@ -1,214 +0,0 @@
|
|||||||
# iter embedding-abi-m2.1 — per-thread runtime context + concurrency safety
|
|
||||||
|
|
||||||
**Date:** 2026-05-18
|
|
||||||
**Started from:** c9a84b33b3e7806491153a5bd3037be1b8ef9d31 (Tasks 1–4 committed)
|
|
||||||
**Original start (first dispatch):** b3388c83506a314fbd87f3354f80ae8cc212f501
|
|
||||||
**Status:** DONE
|
|
||||||
**Tasks completed:** 9 of 9 (Tasks 1–4 in the first dispatch, committed at c9a84b3; Tasks 5–9 in this re-dispatch)
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
The per-thread embedding-ctx ABI is complete. Tasks 1–4 (first
|
|
||||||
dispatch, committed at c9a84b3): the FIXED-FIRST alloc-guard baseline
|
|
||||||
pin, the de-globalised `ailang_ctx_t` runtime (per-thread TLS slot +
|
|
||||||
conditional accounting, `g_rc_*`+atexit retained verbatim as the
|
|
||||||
null-ctx fallback), the codegen forwarder gaining a leading
|
|
||||||
`ptr %ctx` + `@__ail_tls_ctx` save/store/restore around the
|
|
||||||
byte-unchanged internal call, and the CLI RC-only guard. Tasks 5–9
|
|
||||||
(this re-dispatch): the per-thread-ctx scalar-swarm capability demo
|
|
||||||
(`swarm.c`, no SHARED_CTX, GREEN), the de-globalisation
|
|
||||||
negative-control standing test in the rc-accounting harness
|
|
||||||
(`rc_accounting_shared_ctx_is_flagged_by_tsan` — shared-ctx genuinely
|
|
||||||
raced under tsan, teeth proven), the M1 `host.c` migration to the ctx
|
|
||||||
ABI (`s == 25` holds through the ctx-threaded forwarder), the
|
|
||||||
DESIGN.md current-state update (provisional-until-M3 narrowed to
|
|
||||||
value/record layout; Decision-10 per-thread-ctx note; pinned
|
|
||||||
"Export parameters are written **bare**…" sentence + `(con Int)`
|
|
||||||
snippet preserved verbatim), and the full regression + bench gate
|
|
||||||
(workspace 0 failed; bench-trio causally exonerated). The milestone's
|
|
||||||
concurrency-safety invariant is sanitiser-verified.
|
|
||||||
|
|
||||||
## Boss spec-defect repair (2026-05-18)
|
|
||||||
|
|
||||||
The first dispatch (Tasks 1–4 clean) correctly **BLOCKED on Task 5**,
|
|
||||||
surfacing a genuine spec defect: the original Task 5 paired a
|
|
||||||
`-DSHARED_CTX` negative control onto `swarm.c`. That is structurally
|
|
||||||
impossible — `examples/embed_backtest_step.ail` is a non-allocating
|
|
||||||
scalar kernel (`state += sample*sample`); it never calls
|
|
||||||
`ailang_rc_alloc`/`ailang_rc_dec`, never writes a `ctx` field, and
|
|
||||||
`@__ail_tls_ctx` is `__thread` (per-thread, never shared even when the
|
|
||||||
`ailang_ctx_t*` value is passed to a shared ctx). A shared-ctx scalar
|
|
||||||
swarm therefore has no shared-memory write to race on; tsan correctly
|
|
||||||
stays clean. Asserting a structurally-unreachable race would have been
|
|
||||||
a vacuous (and dishonest) test. The Boss adjudicated: the
|
|
||||||
de-globalisation negative-control teeth belong to the **item-1
|
|
||||||
rc-accounting harness** (`rc_accounting_tsan.c -DSHARED_CTX`, where a
|
|
||||||
shared ctx genuinely races on `ctx->alloc_count`), NOT `swarm.c`. The
|
|
||||||
spec was amended in lockstep and the plan's Task 5 restructured:
|
|
||||||
`swarm.c` is the per-thread-ctx capability demo ONLY (one green
|
|
||||||
positive test); the negative control became
|
|
||||||
`rc_accounting_shared_ctx_is_flagged_by_tsan` added to the committed
|
|
||||||
`embed_rc_accounting_tsan.rs`. This re-dispatch implemented the
|
|
||||||
corrected Task 5 — no swarm.c negative control reintroduced. The
|
|
||||||
plan's Task 3 expected-string transcription error (recorded below)
|
|
||||||
was likewise Boss-corrected (`@ail_embed_backtest_step_step`, the
|
|
||||||
module-qualified convention-correct symbol).
|
|
||||||
|
|
||||||
## Per-task notes
|
|
||||||
|
|
||||||
- iter embedding-abi-m2.1.1 (first dispatch, committed): FIXED-FIRST
|
|
||||||
alloc-guard pin. New `crates/ail/tests/embed_staticlib_alloc_guard.rs`
|
|
||||||
(verbatim from plan); RED captured (build currently succeeds), the
|
|
||||||
deliverable. RED→GREEN across the iter (Task 4 closes it).
|
|
||||||
- iter embedding-abi-m2.1.2 (first dispatch, committed): `runtime/rc.c`
|
|
||||||
gains `ailang_ctx_t` {alloc_count, free_count} + `ailang_ctx_new`/
|
|
||||||
`ailang_ctx_free` + `__thread __ail_tls_ctx`; the two increment
|
|
||||||
sites become `if (_ctx) _ctx->… else g_rc_…`. `g_rc_*` statics +
|
|
||||||
atexit + constructor retained verbatim. New `rc_accounting_tsan.c` +
|
|
||||||
`embed_rc_accounting_tsan.rs`: RED (link error) → GREEN (per-ctx,
|
|
||||||
8×200000, tsan-clean).
|
|
||||||
- iter embedding-abi-m2.1.3 (first dispatch, committed): codegen
|
|
||||||
`Target::StaticLib` arm emits `@__ail_tls_ctx = external
|
|
||||||
thread_local global ptr` once; forwarder signature gains leading
|
|
||||||
`ptr %ctx`; body does load-save / store-ctx / unchanged
|
|
||||||
`call @ail_<mname>_<fn>(args)` / store-restore. `_adapter`/`_clos`
|
|
||||||
+ internal arg vector byte-untouched. Doc-comment narrowed to
|
|
||||||
value/record-layout provisionality. `embed_e2e` left RED by design
|
|
||||||
(Task 6 migrates it).
|
|
||||||
- iter embedding-abi-m2.1.4 (first dispatch, committed):
|
|
||||||
`crates/ail/src/main.rs build_staticlib` rejects `--alloc != Rc`
|
|
||||||
after the has_export check, before lowering. Task-1 pin RED→GREEN;
|
|
||||||
`embed_staticlib_cli` default path unaffected.
|
|
||||||
- iter embedding-abi-m2.1.5 (this re-dispatch): per-thread-ctx
|
|
||||||
capability demo. New `crates/ail/tests/embed/swarm.c` (no
|
|
||||||
SHARED_CTX) + `crates/ail/tests/embed_swarm_tsan.rs`;
|
|
||||||
`scalar_swarm_per_ctx_is_tsan_clean_and_matches_oracle` GREEN
|
|
||||||
(8 threads × 200k, tsan-clean, every result == serial oracle).
|
|
||||||
De-globalisation negative control
|
|
||||||
`rc_accounting_shared_ctx_is_flagged_by_tsan` appended to the
|
|
||||||
committed `embed_rc_accounting_tsan.rs` (reuses its `ws_root()`/
|
|
||||||
`cc()` helpers); GREEN — shared-ctx build genuinely raced under
|
|
||||||
tsan (teeth proven, not vacuous). No swarm.c negative control
|
|
||||||
reintroduced (the Boss spec-defect repair).
|
|
||||||
- iter embedding-abi-m2.1.6 (this re-dispatch): `crates/ail/tests/
|
|
||||||
embed/host.c` replaced with the ctx-ABI version (opaque
|
|
||||||
`ailang_ctx_t`, `ailang_ctx_new`/`_free` lifecycle, ctx-prefixed
|
|
||||||
`backtest_step`). Confirmed expected RED post-Task-3 first
|
|
||||||
(`s != 25` from the garbage-ctx arg shift). `embed_e2e.rs`
|
|
||||||
intentionally NOT modified: the M1 harness already links
|
|
||||||
`libailang_rt.a` by absolute path (`.arg(outdir.join(
|
|
||||||
"libailang_rt.a"))`), so the ctx symbols resolve without an
|
|
||||||
`-lailang_rt` add — the task block's own "no change is needed
|
|
||||||
beyond Step 2" conditional branch. GREEN, `s == 25` holds.
|
|
||||||
- iter embedding-abi-m2.1.7 (this re-dispatch): `docs/DESIGN.md` —
|
|
||||||
provisional-until-M3 sentence (~:2282) replaced with the M2 ctx
|
|
||||||
prose (mandatory leading `ailang_ctx_t*`, per-thread, RC-only
|
|
||||||
swarm artefact, value/record layout still provisional);
|
|
||||||
Decision-10 atomicity bullet (~:1528) gains the per-thread-ctx
|
|
||||||
soundness note. Pinned "Export parameters are written **bare**…"
|
|
||||||
sentence + canonical `(con Int)` snippet preserved verbatim (the
|
|
||||||
edit ends before them; no soft-wrap split risk). `docs_honesty_pin`
|
|
||||||
GREEN unmodified (5 passed), `design_schema_drift` GREEN (8
|
|
||||||
passed) — edits outside the `## Data model` scan window.
|
|
||||||
- iter embedding-abi-m2.1.8 (this re-dispatch): regression gate, no
|
|
||||||
code. `print_no_leak_pin` (1) + `e2e -- rc_stats` (4) GREEN —
|
|
||||||
null-ctx fallback prints unchanged. `embed_export_hash_stable`
|
|
||||||
(1, `fn_without_export_hash_is_unchanged`) + `embed_export_gate`
|
|
||||||
(6, all pass) GREEN — no schema field, export gate unchanged.
|
|
||||||
Full `cargo test --workspace`: every suite ok, 0 failing suites.
|
|
||||||
- iter embedding-abi-m2.1.9 (this re-dispatch): bench gate, no code.
|
|
||||||
`compile_check.py` exit=0 (24/24 stable); `cross_lang.py` exit=0
|
|
||||||
(25/25 stable); `check.py` exit=1 on exactly 4 tracked-P2 noise
|
|
||||||
metrics (`bench_list_sum.bump_s` +16.53%,
|
|
||||||
`latency.explicit_at_rc.max_us` +40.41%,
|
|
||||||
`latency.implicit_at_rc.p99_9_us` +31.66%,
|
|
||||||
`latency.implicit_at_rc.max_us` +92.35%). Causally exonerated by
|
|
||||||
the source-level invariant (see Concerns); no baseline ratified.
|
|
||||||
|
|
||||||
## Concerns
|
|
||||||
|
|
||||||
- iter embedding-abi-m2.1.3 (first dispatch — carried forward):
|
|
||||||
PLAN TRANSCRIPTION ERROR. Task 3 Step 1's plan code block asserted
|
|
||||||
`ir.contains("call i64 @ail_backtest_step(i64 %a0, i64 %a1)")`.
|
|
||||||
The internal symbol is `@ail_embed_backtest_step_step`
|
|
||||||
(module-qualified `@ail_<mname>_<fn>`), which the plan's own Step-4
|
|
||||||
parenthetical and Boss constraint 3 require to stay byte-unchanged.
|
|
||||||
Written instead with the convention-correct symbol (Boss-corrected
|
|
||||||
in the re-dispatched plan). The plan file should remain corrected.
|
|
||||||
- iter embedding-abi-m2.1.6: `embed_e2e.rs` is in the task's `Files:`
|
|
||||||
block but was intentionally left unchanged — the M1 harness already
|
|
||||||
links `libailang_rt.a` by absolute path, so the ctx symbols resolve
|
|
||||||
without an `-lailang_rt` add. This is the task block's own sanctioned
|
|
||||||
"no change is needed beyond Step 2" conditional branch, not a missed
|
|
||||||
requirement (observation, not correctness).
|
|
||||||
- iter embedding-abi-m2.1.7: the section header is still
|
|
||||||
`## Embedding ABI (M1)` while the body now describes the M2 ctx ABI
|
|
||||||
as current state — a header/content honesty mismatch. NOT fixed:
|
|
||||||
renaming the header is outside the task block's named edit regions
|
|
||||||
(`:2282–2285`, `:1528–1529`) and could perturb a doc-honesty pin
|
|
||||||
tracking the `(M1)` token; the controller curates scope, not the
|
|
||||||
implementer. Routed here for Boss adjudication — a candidate
|
|
||||||
follow-up (header rename + fieldtest/pin audit) but explicitly NOT
|
|
||||||
in this iter's plan scope.
|
|
||||||
- iter embedding-abi-m2.1.8: the task block expected
|
|
||||||
`embed_export_gate` to be "8 passed"; the suite at HEAD c9a84b3 has
|
|
||||||
exactly 6 tests, all passing, 0 filtered. The literal "8" is a
|
|
||||||
plan-transcription artefact (pre-existing — M2 added/removed no test
|
|
||||||
in this suite). The protected invariant (export gate unchanged by
|
|
||||||
M2) holds unambiguously; the implementer correctly did NOT pad the
|
|
||||||
suite to 8 (that would fabricate coverage). Plan number vs reality,
|
|
||||||
not a regression.
|
|
||||||
- iter embedding-abi-m2.1.9: `check.py` exit=1 on 4 metrics, all in
|
|
||||||
the tracked-P2 noise families (`*.bump_s`, `latency.*.max_us`,
|
|
||||||
`latency.*.p99_9_us`). CAUSALLY EXONERATED by a source-level
|
|
||||||
invariant stronger than the plan's binary `cmp`: this re-dispatch's
|
|
||||||
entire working-tree footprint is test fixtures + test drivers +
|
|
||||||
`docs/DESIGN.md` — ZERO files under `crates/*/src/` or `runtime/`.
|
|
||||||
The `ail` codegen binary and the linked `rc.c`/`str.c` runtime are
|
|
||||||
byte-identical to HEAD c9a84b3 by construction, so the firing
|
|
||||||
metrics cannot be caused by this iter. The plan's `git stash`/
|
|
||||||
`git stash pop` recipe was deliberately NOT run: on a re-dispatch
|
|
||||||
it would discard the iter's uncommitted Tasks 5–8 work and violate
|
|
||||||
the working-tree-as-handoff contract. No baseline ratified
|
|
||||||
(consistent with roadmap-P2 noise policy + the 2026-05-14 audit-pd
|
|
||||||
"baseline pristine, Nth consecutive observation" envelope).
|
|
||||||
|
|
||||||
## Known debt
|
|
||||||
|
|
||||||
- `## Embedding ABI (M1)` section header in DESIGN.md still says
|
|
||||||
"(M1)" though its body is now M2 current-state. Out of this iter's
|
|
||||||
plan scope (Task 7 named only `:2282–2285` and `:1528–1529`).
|
|
||||||
Candidate follow-up: header rename with a doc-honesty-pin audit
|
|
||||||
for any `(M1)` token dependency. Not touched to keep the diff
|
|
||||||
scope-compliant and avoid perturbing a pin.
|
|
||||||
|
|
||||||
## Blocked detail
|
|
||||||
|
|
||||||
None. All 9 tasks completed (Tasks 1–4 committed at c9a84b3 in the
|
|
||||||
first dispatch; Tasks 5–9 in this re-dispatch, all reaching
|
|
||||||
`approved` quality with zero review re-loops). The first dispatch's
|
|
||||||
Task-5 BLOCK was a genuine spec defect, Boss-adjudicated and repaired
|
|
||||||
before this re-dispatch (see "Boss spec-defect repair").
|
|
||||||
|
|
||||||
## Files touched (this re-dispatch — Tasks 5–9 working tree)
|
|
||||||
|
|
||||||
Modified:
|
|
||||||
- `crates/ail/tests/embed/host.c` (Task 6: ctx-ABI migration)
|
|
||||||
- `crates/ail/tests/embed_rc_accounting_tsan.rs` (Task 5: +
|
|
||||||
`rc_accounting_shared_ctx_is_flagged_by_tsan` negative control)
|
|
||||||
- `docs/DESIGN.md` (Task 7: M2 current-state + Decision-10 note)
|
|
||||||
|
|
||||||
Created:
|
|
||||||
- `crates/ail/tests/embed/swarm.c` (Task 5: per-thread-ctx
|
|
||||||
capability demo, no SHARED_CTX)
|
|
||||||
- `crates/ail/tests/embed_swarm_tsan.rs` (Task 5: one green
|
|
||||||
positive test)
|
|
||||||
|
|
||||||
(Tasks 1–4's files — `embed_staticlib_alloc_guard.rs`, `rc.c`,
|
|
||||||
codegen `lib.rs`, `embed_staticlib_lowering.rs`, `main.rs`,
|
|
||||||
`rc_accounting_tsan.c`, the Task-2 `embed_rc_accounting_tsan.rs`
|
|
||||||
base — are already committed at c9a84b3.)
|
|
||||||
|
|
||||||
## Stats
|
|
||||||
|
|
||||||
bench/orchestrator-stats/2026-05-18-iter-embedding-abi-m2.1.json
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
# iter embedding-abi-m2.tidy — lockstep section rename `Embedding ABI (M1)` → `Embedding ABI`
|
|
||||||
|
|
||||||
**Date:** 2026-05-18
|
|
||||||
**Started from:** d1241b12c759765f12713584ce2aab21e2a74f74
|
|
||||||
**Status:** DONE
|
|
||||||
**Tasks completed:** 1 of 1
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
Audit-driven docs-honesty tidy (no parent spec; source:
|
|
||||||
`docs/journals/2026-05-18-audit-embedding-abi-m2.md`, the M2
|
|
||||||
milestone-close audit). The DESIGN.md section `## Embedding ABI (M1)`
|
|
||||||
had a stale `(M1)` qualifier — its body now describes the post-M2 ctx
|
|
||||||
ABI present-tense, so the qualifier contradicts the current-state-mirror
|
|
||||||
discipline. Renamed the header and moved all four *live* coupled
|
|
||||||
references in lockstep so no pointer dangles at the vanished `(M1)`
|
|
||||||
title. Pure docs/test-string tidy: zero language / checker / codegen /
|
|
||||||
schema change; `ail check`/`run` byte-unchanged by construction. The
|
|
||||||
`docs_honesty_pin.rs:135` asserted substring is rename-immune (no
|
|
||||||
section-name token, matched file-wide via `d.contains`) and was left
|
|
||||||
byte-verbatim untouched; the pin staying green before AND after
|
|
||||||
empirically confirms the rename did not perturb it.
|
|
||||||
|
|
||||||
## Per-task notes
|
|
||||||
|
|
||||||
- iter embedding-abi-m2.tidy.1: lockstep rename across 5 live-coupling
|
|
||||||
sites in 3 files — `docs/DESIGN.md:2266` (section header) +
|
|
||||||
`docs/DESIGN.md:2354` (schema-block xref comment) +
|
|
||||||
`crates/ailang-core/tests/docs_honesty_pin.rs:134` (test comment) +
|
|
||||||
`:136` (assert-message) + `crates/ailang-core/specs/form_a.md:89`
|
|
||||||
(compiled-in forward-xref). Each site: one-line in-place `(M1)`-token
|
|
||||||
deletion, no reflow. Step-1 baseline grep showed exactly the 5 sites
|
|
||||||
present (`:135` correctly absent — no `(M1)` token); Step-6 grep
|
|
||||||
returned zero (exit 1). `docs_honesty_pin` 5 passed and
|
|
||||||
`design_schema_drift` 8 passed both before and after. Footprint
|
|
||||||
gated at exactly 3 files, 5 insertions / 5 deletions — no
|
|
||||||
`docs/journals/**`, `docs/specs/**`, `docs/plans/**`,
|
|
||||||
`bench/orchestrator-stats/**` touched (committed append-only history
|
|
||||||
retains `Embedding ABI (M1)` as correct record of the name at the
|
|
||||||
time; renaming it would falsify the record).
|
|
||||||
|
|
||||||
## Concerns
|
|
||||||
|
|
||||||
(none)
|
|
||||||
|
|
||||||
## Known debt
|
|
||||||
|
|
||||||
(none)
|
|
||||||
|
|
||||||
## Files touched
|
|
||||||
|
|
||||||
- `docs/DESIGN.md` (header `:2266`, schema-block xref `:2354`)
|
|
||||||
- `crates/ailang-core/tests/docs_honesty_pin.rs` (comment `:134`, assert-message `:136`; `:135` untouched)
|
|
||||||
- `crates/ailang-core/specs/form_a.md` (forward-xref `:89`)
|
|
||||||
|
|
||||||
## Stats
|
|
||||||
|
|
||||||
bench/orchestrator-stats/2026-05-18-iter-embedding-abi-m2.tidy.json
|
|
||||||
@@ -1,404 +0,0 @@
|
|||||||
# iter embedding-abi-m3.1 — freeze the value layout + record across the C ABI
|
|
||||||
|
|
||||||
**Date:** 2026-05-18
|
|
||||||
**Started from:** 15ee3c5c8fbb871a9cde6cc7d57c0801bae9ae85
|
|
||||||
**Status:** DONE (Tasks 1–5 GREEN after the Boss spec-consistency
|
|
||||||
repair below; Tasks 6–7 completed GREEN in the re-dispatch)
|
|
||||||
**Tasks completed:** 7 of 7 (Tasks 1–4 DONE/GREEN by the orchestrator;
|
|
||||||
Task 5 correctly BLOCKED on a genuine spec-defect, M2.1-precedent
|
|
||||||
class, resolved GREEN by the Boss spec-consistency repair —
|
|
||||||
global-leak-freedom proof model; Tasks 6–7 DONE/GREEN in the
|
|
||||||
`task_range:[6,7]` re-dispatch on the amended plan)
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
> **Boss note (read first):** the paragraphs below are the
|
|
||||||
> orchestrator's *first-dispatch* account (present-tense as written
|
|
||||||
> then) — Task 5's BLOCK was real and is preserved for its
|
|
||||||
> documentary value. It was resolved by the Boss spec-consistency
|
|
||||||
> repair and Tasks 6–7 completed in the re-dispatch; the iter is
|
|
||||||
> **DONE 7/7**. See "## Boss adjudication" and "## Re-dispatch" for
|
|
||||||
> the resolution. Do not read the "Task 5 is BLOCKED / Task 7 not
|
|
||||||
> reached" sentences below as the final state.
|
|
||||||
|
|
||||||
Tasks 1–4 are complete and GREEN in the working tree: the FIXED-FIRST
|
|
||||||
baseline pins (1a re-point annotation on `adt_ret_export_rejected`;
|
|
||||||
1b the new `@ailang_rc_alloc` heap-box byte-pin proving size=8+n*8 /
|
|
||||||
tag@0 / fields@8,16), the export-gate widen to a single-ctor scalar
|
|
||||||
record (`is_c_scalar` → two-level `is_c_abi_type`, 10/10 gate suite),
|
|
||||||
the codegen forwarder widen (`llvm_scalar` maps a gate-guaranteed
|
|
||||||
record `Type::Con` → `ptr`; M2 forwarder body byte-unchanged; 3/3
|
|
||||||
staticlib-lowering pins), and the `own` E2E record round-trip
|
|
||||||
(1,000,000-iter balanced ctx readback `allocs=1000000 frees=1000000
|
|
||||||
live=0`, exit 0 — the spec's "mechanically almost free" ownership
|
|
||||||
claim substantiated, ratified by `e2e.rs:1855`, NOT the Iter-18a
|
|
||||||
metadata-only pin).
|
|
||||||
|
|
||||||
Task 5 (the `borrow` E2E arm) is BLOCKED on a genuine
|
|
||||||
plan-assertion-model defect (detail below), structurally identical to
|
|
||||||
the M2.1 first-dispatch swarm.c/-DSHARED_CTX spec defect that the
|
|
||||||
Boss adjudicated and repaired. No production-code bug: the borrow
|
|
||||||
mode is leak-free and value-correct; the plan's *assertion shape* is
|
|
||||||
unsatisfiable given the M2 TLS-ctx accounting mechanism. Returned
|
|
||||||
BLOCKED for Boss adjudication per the Iron Law (never push past
|
|
||||||
BLOCKED by hand; spec/plan amendment is a Boss/brainstorm decision).
|
|
||||||
|
|
||||||
audit owns the bench + architect milestone-close (spec Testing items
|
|
||||||
8/9 — architect Invariant 1, bench trio); this implement run asserts
|
|
||||||
only the test-suite GREEN state an implement run can, and Task 7
|
|
||||||
(workspace-green gate) was not reached because Task 5 blocks the
|
|
||||||
sequence.
|
|
||||||
|
|
||||||
## Per-task notes
|
|
||||||
|
|
||||||
- iter embedding-abi-m3.1.1 (DONE_WITH_CONCERNS): re-point annotation
|
|
||||||
added verbatim above `adt_ret_export_rejected`
|
|
||||||
(`crates/ailang-check/tests/embed_export_gate.rs`); gate suite 6/6
|
|
||||||
inert. New byte-pin carrier `examples/embed_record_layout_carrier.ail`
|
|
||||||
+ new pin `crates/ailang-codegen/tests/embed_record_layout_pin.rs`
|
|
||||||
(`lower_workspace_with_alloc(&ws, AllocStrategy::Rc)`, mirroring
|
|
||||||
`embed_staticlib_lowering.rs`'s `load()` helper). Pin GREEN on the
|
|
||||||
pre-M3 tree. Carrier required two grounding corrections vs the
|
|
||||||
plan's literal pseudo-code (see Concerns).
|
|
||||||
- iter embedding-abi-m3.1.2 (DONE_WITH_CONCERNS): `is_c_scalar` →
|
|
||||||
two-level `is_c_abi_type` (`crates/ailang-check/src/lib.rs:1921+`),
|
|
||||||
both call sites (param loop + ret) repointed; `:452` `#[error]`
|
|
||||||
widened; `:458` correctly left byte-unchanged (no `M1` token). 5
|
|
||||||
fixtures (`embed_export_record_ok` + 3 `_rejected` + re-pointed
|
|
||||||
`embed_export_adt_ret_rejected` → multi-ctor+Str `Reading`) + 4 new
|
|
||||||
gate tests. RED observed (`record_export_passes` failed pre-widen);
|
|
||||||
GREEN 10/10; full `ailang-check` 0 failed.
|
|
||||||
- iter embedding-abi-m3.1.3 (DONE_WITH_CONCERNS): `llvm_scalar`
|
|
||||||
widened (`Float`→double, `Int`→i64, other `Type::Con`→`ptr`, `_`→
|
|
||||||
i64) + doc-comment lockstep; `fn_scalar_sig` clarifying comment only
|
|
||||||
(body recon-confirmed filter-free). 2 fixtures
|
|
||||||
(`embed_backtest_step_record{,_borrow}.ail`) + forwarder-IR pin
|
|
||||||
`staticlib_record_forwarder_is_ptr_passthrough`. RED observed
|
|
||||||
(State→i64 pre-widen); GREEN 3/3; full `ailang-codegen` 0 failed
|
|
||||||
(Task-1 byte-pin unchanged — box layout did not move).
|
|
||||||
- iter embedding-abi-m3.1.4 (DONE): `crates/ail/tests/embed/
|
|
||||||
record_roundtrip.c` + `crates/ail/tests/embed_record_e2e.rs`.
|
|
||||||
`build_link_run_embed` composed from verbatim-mirrored real
|
|
||||||
incantations (`embed_e2e.rs` build/link + `e2e.rs:2190`
|
|
||||||
`build_and_run_with_rc_stats` stats-parse — `embed_e2e.rs` exposes
|
|
||||||
no reusable helper; mirroring, not inventing). `own` E2E GREEN
|
|
||||||
immediately once Tasks 2+3 in tree (ctx readback `allocs=1000000
|
|
||||||
frees=1000000 live=0`, exit 0). No production code (plan: none
|
|
||||||
expected). Ratifier `e2e.rs::alloc_rc_own_param_dec_at_fn_return:
|
|
||||||
1855` cited; `borrow_own_demo_modes_are_metadata_only:1330` NOT
|
|
||||||
cited (Boss constraint 2 honoured).
|
|
||||||
- iter embedding-abi-m3.1.5 (BLOCKED — plan-assertion-model defect):
|
|
||||||
`record_roundtrip_borrow_alloc_eq_free` added to
|
|
||||||
`embed_record_e2e.rs`. RED and stays RED — see Blocked detail. Left
|
|
||||||
in the working tree as evidence (the M2.1-precedent handling: the
|
|
||||||
BLOCKED task's state is left for Boss adjudication, not discarded).
|
|
||||||
- iter embedding-abi-m3.1.6 / .7: NOT attempted in the first dispatch
|
|
||||||
(Task 5 was a hard predecessor in the plan's sequencing; `skip task
|
|
||||||
K, continue` is intentionally not a mode). Completed GREEN in the
|
|
||||||
`task_range:[6,7]` re-dispatch — see the Re-dispatch section below.
|
|
||||||
|
|
||||||
## Concerns
|
|
||||||
|
|
||||||
- iter embedding-abi-m3.1.1: PLAN PSEUDO-CODE DEFECT (class:
|
|
||||||
"plan pseudo-code vs reality"). The plan's carrier body used
|
|
||||||
`(app Pt a b)` for ADT construction — non-compiling (`ail check`:
|
|
||||||
`[not-a-function] mk: \`Pt\` is not a function`). AILang construction
|
|
||||||
is `(term-ctor <Type> <Ctor> args…)` (grounded on
|
|
||||||
`examples/box.ail:36`, `borrow_own_demo.ail`). Corrected to
|
|
||||||
`(term-ctor Pt Pt a b)`. Second correction: the plan's
|
|
||||||
`(app print …)` IO sink is unresolvable by the codegen library API
|
|
||||||
the byte-pin must use (`lower_workspace_with_alloc` does not run the
|
|
||||||
mono/prelude pass that resolves polymorphic `print` —
|
|
||||||
empirically: `UnknownVar("print")`; `io/print_int` is not a known
|
|
||||||
effect-op). Corrected to `(let s (app int_to_str …) (do io/print_str
|
|
||||||
s))` (monomorphic builtin, library-resolvable; grounded on
|
|
||||||
`int_to_str_drop_rc.ail`). Structural intent (single-ctor 2-Int
|
|
||||||
record forced onto the `@ailang_rc_alloc` heap path) preserved
|
|
||||||
exactly; plan Step-4/5 explicitly sanctions carrier adjustment to
|
|
||||||
force the heap box. Observation, not correctness — the byte-pin
|
|
||||||
asserts the literal emitted forms and is a genuine RED-on-offset-
|
|
||||||
move guard (proven enforceable would be Task 6 Step 7, not reached).
|
|
||||||
- iter embedding-abi-m3.1.2: same plan-pseudo-code class — all 5
|
|
||||||
fixture bodies used `(app Ctor …)`; corrected to `(term-ctor …)`.
|
|
||||||
The test assertion idiom was matched to the file's *existing*
|
|
||||||
`.iter().any(|c| c == …)` pattern (plan showed
|
|
||||||
`.contains(&"…".to_string())`); `record_export_passes` written as
|
|
||||||
`!any(gate-code)` not the plan's `.is_empty()` (a single
|
|
||||||
`over-strict-mode` warning makes the diagnostics vec non-empty —
|
|
||||||
`.is_empty()` would false-fail; the protected property is
|
|
||||||
"no gate rejection"). Semantic intent preserved.
|
|
||||||
- iter embedding-abi-m3.1.2 (carry to Task 6): the
|
|
||||||
`CheckError::ExportNonScalarSignature` rustdoc at
|
|
||||||
`crates/ailang-check/src/lib.rs:448-450` still says "M1's embedding
|
|
||||||
ABI is scalar-only; `Bool`/`Str`/every ADT is rejected" — now stale
|
|
||||||
(single-ctor scalar records are accepted). Outside Task 2's named
|
|
||||||
edit regions (`:452`/`:458` only); Task 6 owns the broader
|
|
||||||
stale-doc / "provisional until M3" sweep and should absorb this
|
|
||||||
line. Not gating; recorded for Boss/Task-6 scope.
|
|
||||||
- iter embedding-abi-m3.1.3: PLAN TRANSCRIPTION DEFECT (same class as
|
|
||||||
the M2.1 journal's `@ail_embed_backtest_step_step` correction). The
|
|
||||||
plan's fixtures used `(module backtest …)` while the file stems are
|
|
||||||
`embed_backtest_step_record{,_borrow}` — the loader enforces
|
|
||||||
module==file-stem (`[module-name-mismatch]`). Module names corrected
|
|
||||||
to the file stems; the forwarder-IR pin's internal-symbol assertion
|
|
||||||
corrected from the plan's draft `@ail_backtest_step` to the
|
|
||||||
convention-correct module-qualified
|
|
||||||
`@ail_embed_backtest_step_record_step` (per the plan's own Step-3
|
|
||||||
parenthetical "tighten to literal emitted forms" + Boss constraint /
|
|
||||||
M2.1 module-qualified-symbol mandate). `llvm_scalar` doc-comment
|
|
||||||
updated in lockstep with its widened body (coherence-required, not
|
|
||||||
surrounding cleanup — a function whose doc says "the only two M1
|
|
||||||
scalars" while its body maps a third case is self-contradictory
|
|
||||||
within the same task). `llvm_scalar` is now a slightly
|
|
||||||
over-narrow *name* (also maps records→ptr) — renaming is a 4-site
|
|
||||||
cross-cut out of Task-3 scope; the doc disambiguates; advisory Nit
|
|
||||||
only.
|
|
||||||
|
|
||||||
## Known debt
|
|
||||||
|
|
||||||
- `crates/ailang-check/src/lib.rs:448-450` stale variant rustdoc —
|
|
||||||
Task-6-scope (see Concerns); not touched to keep Task 2
|
|
||||||
scope-compliant.
|
|
||||||
- `llvm_scalar` identifier slightly narrower than its post-M3
|
|
||||||
behaviour — out of Task-3 scope; doc disambiguates.
|
|
||||||
- Tasks 6 (DESIGN.md SSOT + lockstep freeze pointers + the
|
|
||||||
match_lower.rs enforceability demonstration) and 7 (workspace-green
|
|
||||||
gate) unexecuted — blocked behind Task 5.
|
|
||||||
|
|
||||||
## Blocked detail
|
|
||||||
|
|
||||||
**Task:** 5 (`record_roundtrip_borrow_alloc_eq_free`).
|
|
||||||
**Reason:** worker-blocked — design/plan contradiction
|
|
||||||
(plan-assertion-model defect; the implementer-phase sub-status is
|
|
||||||
`BLOCKED`, mapped to the Boss per the SKILL.md sub-status table).
|
|
||||||
|
|
||||||
**Worker says (verbatim):** The plan's Task-5 assertion
|
|
||||||
`assert_eq!(stats.allocs, stats.frees, "borrow: kernel kept input;
|
|
||||||
host freed every input + the return")` is structurally unsatisfiable.
|
|
||||||
`stats` is defined (Task 4 Step 2, plan) as the *single*
|
|
||||||
`ailang_ctx_free` RC-stats readback — the kernel-internal ctx
|
|
||||||
accounting. In `borrow` mode the kernel allocates the new return box
|
|
||||||
during the forwarder call (lands on `ctx->alloc_count` because the
|
|
||||||
codegen forwarder publishes `ctx` into `__ail_tls_ctx` for the
|
|
||||||
synchronous call duration only) but does NOT consume/drop the borrowed
|
|
||||||
input (correct borrow semantics, ratified by
|
|
||||||
`e2e.rs::alloc_rc_borrow_only_recursive_list_drop:1671`). The
|
|
||||||
balancing frees are the host's per-iter `ailang_rc_dec(st)`, which run
|
|
||||||
*outside* any forwarder call → `__ail_tls_ctx` is NULL → they land on
|
|
||||||
`g_rc_free_count` (the atexit fallback line), NOT on `ctx`. There is
|
|
||||||
no public runtime API (`ailang_ctx_enter`/`set`/`bind`) for the C
|
|
||||||
host to publish a ctx to TLS around its own decs (`__thread
|
|
||||||
ailang_ctx_t *__ail_tls_ctx` is runtime-internal; the only writer is
|
|
||||||
the codegen-emitted forwarder, by M2 design). Empirical readback
|
|
||||||
(`AILANG_RC_STATS=1`, borrow): ctx line `allocs=1000000 frees=0
|
|
||||||
live=1000000`; g_rc atexit line `allocs=1 frees=1000001
|
|
||||||
live=-1000000`. Globally leak-free and value-correct (exit 0,
|
|
||||||
`assert(n==1000000)` held; `allocs_total=1000001 ==
|
|
||||||
frees_total=1000001`, net live=0). The plan's own assertion *message*
|
|
||||||
("host freed every input") contradicts its own `stats` *source* (the
|
|
||||||
ctx readback, which by construction cannot see host-side frees) — an
|
|
||||||
internal contradiction in the plan. This is NOT a production-code bug
|
|
||||||
(the spec's substantive invariant — ownership follows the declared
|
|
||||||
mode, no leak — holds); it is a plan-assertion-model defect of the
|
|
||||||
same class the M2.1 journal records (the swarm.c/-DSHARED_CTX
|
|
||||||
spec defect: a plan assertion structurally impossible given the M2
|
|
||||||
TLS-ctx mechanism, Boss-adjudicated + spec-amended, NOT
|
|
||||||
agent-self-amended). Per the Iron Law ("never push past BLOCKED by
|
|
||||||
hand") and memory ("when an iter needs a spec change, the spec is
|
|
||||||
wrong — update via brainstorm, not a self-patch"), returned BLOCKED
|
|
||||||
rather than silently substituting a global-balance / `live==0`
|
|
||||||
assertion (which would be the correct invariant but is a *different*
|
|
||||||
assertion than the plan scripts — a Boss/brainstorm adjudication).
|
|
||||||
|
|
||||||
**Suggested next step:** Boss adjudicates the spec/plan accounting
|
|
||||||
model — likely: amend the spec + Task 5 (and re-check Task 4's
|
|
||||||
coincidentally-passing ctx-line assertion) so the proven invariant is
|
|
||||||
global leak-freedom (`live==0` across the ctx readback AND the g_rc
|
|
||||||
atexit line, or `allocs_total==frees_total` summed) for *both* modes,
|
|
||||||
rather than per-line ctx `allocs==frees` (which only holds for `own`
|
|
||||||
by the accident that own-decs happen inside the forwarder call). Then
|
|
||||||
re-dispatch with `task_range:[5,7]` against the amended plan. Tasks
|
|
||||||
1–4 are sound and GREEN; the working tree (including the failing
|
|
||||||
Task-5 test as evidence) is left intact per the M2.1 precedent.
|
|
||||||
|
|
||||||
## Files touched
|
|
||||||
|
|
||||||
Modified:
|
|
||||||
- `crates/ailang-check/src/lib.rs` (Task 2: gate predicate + message)
|
|
||||||
- `crates/ailang-check/tests/embed_export_gate.rs` (Tasks 1,2)
|
|
||||||
- `crates/ailang-codegen/src/lib.rs` (Task 3: llvm_scalar +
|
|
||||||
fn_scalar_sig comment)
|
|
||||||
- `crates/ailang-codegen/tests/embed_staticlib_lowering.rs` (Task 3)
|
|
||||||
- `examples/embed_export_adt_ret_rejected.ail` (Task 2: re-point)
|
|
||||||
|
|
||||||
Created:
|
|
||||||
- `crates/ailang-codegen/tests/embed_record_layout_pin.rs` (Task 1)
|
|
||||||
- `examples/embed_record_layout_carrier.ail` (Task 1)
|
|
||||||
- `examples/embed_export_record_ok.ail` (Task 2)
|
|
||||||
- `examples/embed_export_str_field_record_rejected.ail` (Task 2)
|
|
||||||
- `examples/embed_export_list_field_record_rejected.ail` (Task 2)
|
|
||||||
- `examples/embed_export_multictor_rejected.ail` (Task 2)
|
|
||||||
- `examples/embed_backtest_step_record.ail` (Task 3)
|
|
||||||
- `examples/embed_backtest_step_record_borrow.ail` (Task 3)
|
|
||||||
- `crates/ail/tests/embed/record_roundtrip.c` (Task 4/5)
|
|
||||||
- `crates/ail/tests/embed_record_e2e.rs` (Task 4 GREEN +
|
|
||||||
Task 5 RED-evidence)
|
|
||||||
|
|
||||||
## Boss adjudication (2026-05-18 — M2.1-precedent class)
|
|
||||||
|
|
||||||
The orchestrator's Task-5 BLOCK was correct and well-reasoned, and
|
|
||||||
its diagnosis was independently verified by the Boss (not
|
|
||||||
agent-trusted): built+ran both E2E modes by hand, full
|
|
||||||
`AILANG_RC_STATS` stderr —
|
|
||||||
|
|
||||||
- own: ctx `allocs=1000000 frees=1000000 live=0`; g_rc `allocs=1 frees=1 live=0`; exit 0
|
|
||||||
- borrow: ctx `allocs=1000000 frees=0 live=1000000`; g_rc `allocs=1 frees=1000001 live=-1000000`; exit 0
|
|
||||||
|
|
||||||
Both modes are **globally** leak-free (Σallocs = Σfrees = 1,000,001,
|
|
||||||
Σ`live` = 0) and value-correct. The defect is purely the spec's
|
|
||||||
proof *instrument*: §"Testing strategy" items 3/4 + §"Coherent stop"
|
|
||||||
asserted the single `ailang_ctx_free` ctx readback shows
|
|
||||||
`allocs == frees`, which is false **by M2's shipped TLS-ctx design**
|
|
||||||
(ctx bound to `__ail_tls_ctx` only for the synchronous forwarder
|
|
||||||
call; the host's `make_state`/`dec` run outside it → `g_rc_*`). The
|
|
||||||
roadmap's own coherent-stop wording is "a record in/out with
|
|
||||||
**alloc==free** across many calls" = *global*, exactly what holds.
|
|
||||||
|
|
||||||
Adjudicated as a **Boss spec-consistency repair, not a brainstorm
|
|
||||||
bounce** — M2.1-precedent class (genuine spec defect; deliverable +
|
|
||||||
substantive invariant sound; only verification mechanics
|
|
||||||
mis-specified; first BLOCKED task in M3, not 2+-in-a-family). The
|
|
||||||
repair, folded into this partial (M2.1 `c9a84b3` shape):
|
|
||||||
|
|
||||||
1. Spec amended (dated repair note + §"Testing strategy" 3/4 +
|
|
||||||
§"Coherent stop"): proof model → **global leak-freedom**
|
|
||||||
(Σallocs == Σfrees across *all* `ailang_rc_stats:` lines,
|
|
||||||
Σ`live` = 0, exit 0) for both modes; the M2-TLS cross-attribution
|
|
||||||
documented as correct behaviour, not a leak. Strictly stronger
|
|
||||||
and honest. No fresh grounding-check (removes an over-strong
|
|
||||||
measurement assumption, adds none about compiler behaviour).
|
|
||||||
2. Plan amended (Task 4/5 + the harness step): the harness sums
|
|
||||||
*every* `ailang_rc_stats:` line, not just the first.
|
|
||||||
3. Code repair applied (mechanical, decided-invariant): the
|
|
||||||
`build_link_run_embed` parse now sums all stat lines; T4/T5
|
|
||||||
docstrings + assertion messages corrected to the global model.
|
|
||||||
Verified GREEN by the Boss: `embed_record_e2e` 2/2 (own+borrow),
|
|
||||||
gate 10/10, byte-pin 1/1, forwarder 3/3; regression-green set
|
|
||||||
(`embed_e2e`, `embed_staticlib_cli`, `embed_export_hash_stable`,
|
|
||||||
`design_schema_drift`, `docs_honesty_pin`) unmodified-green.
|
|
||||||
|
|
||||||
Plan-pseudo-code defects the orchestrator self-corrected inline
|
|
||||||
(`feedback_plan_pseudo_vs_reality` class — `(app Ctor …)` →
|
|
||||||
`(term-ctor …)` construction, library-`print` non-resolution,
|
|
||||||
module==file-stem) are noted in Concerns; a planner Step-5 scrub
|
|
||||||
item is warranted (recorded by the Boss outside this journal).
|
|
||||||
|
|
||||||
audit owns the bench + architect milestone-close (spec Testing
|
|
||||||
items 8/9). This run asserts only the test-suite GREEN state.
|
|
||||||
|
|
||||||
## Re-dispatch (2026-05-18 — `task_range:[6,7]`, Tasks 6–7 GREEN)
|
|
||||||
|
|
||||||
Re-dispatched from HEAD `d5c565d` (the committed PARTIAL 5/7 + Boss
|
|
||||||
spec-defect repair). Tasks 1–5 NOT re-implemented or re-verified.
|
|
||||||
|
|
||||||
- iter embedding-abi-m3.1.6 (DONE): the substantive freeze.
|
|
||||||
DESIGN.md §"Embedding ABI" — both "provisional until M3" sentences
|
|
||||||
rewritten to one-way-freeze wording (`:2280-2282` → embedding ABI
|
|
||||||
accepts `Int`/`Float`/single-ctor record + "**Frozen as of M3**";
|
|
||||||
`:2293-2295` → "**frozen as of M3** (see "Frozen value layout"
|
|
||||||
below)") and the new `### Frozen value layout (M3 — one-way
|
|
||||||
commitment)` SSOT subsection inserted before `## Data model` (byte
|
|
||||||
table `p-8` header / `p+0` tag / `p+8+i*8` fields, size `8+n*8`,
|
|
||||||
construction/ownership/free contract). Lockstep `// FROZEN ABI`
|
|
||||||
pointers added at the three independent encoders: `runtime/rc.c`
|
|
||||||
layout comment block, `match_lower.rs` `lower_ctor` `:107` size
|
|
||||||
site, `drop.rs` `:88` tag-load + `:113` field-offset. Three stale
|
|
||||||
rustdocs corrected — the two plan-named (`codegen/lib.rs:178`,
|
|
||||||
`ailang-core/ast.rs:211`) PLUS the Concerns carry-forward
|
|
||||||
`ailang-check/lib.rs:448-450` ("scalar-only…every ADT rejected" →
|
|
||||||
frozen-as-of-M3, Boss constraint 2). docs_honesty_pin GREEN
|
|
||||||
baseline AND after the rewrite (5/5 both); the pinned line
|
|
||||||
("Export parameters are written **bare**…") shifted `:2297`→`:2299`
|
|
||||||
but stays byte-verbatim+contiguous — the pin asserts content not
|
|
||||||
line, so pin-safe (planner Step-5 item-6 family honoured). Step-7
|
|
||||||
enforceability: perturbed `match_lower.rs` `size_bytes` `8`→`16`
|
|
||||||
(the unanchored sed-class literal hit both `:107` and `:400`; both
|
|
||||||
feed the byte-pin through `lower_ctor`'s heap path), byte-pin went
|
|
||||||
RED (`0 passed; 1 failed`, IR emitted `i64 32` not `i64 24`),
|
|
||||||
restored via `git checkout -- crates/ailang-codegen/src/match_lower.rs`
|
|
||||||
(working-tree restore only — main HEAD untouched; the single
|
|
||||||
legitimate git history-touching op of the run), byte-pin GREEN
|
|
||||||
again (1/1). The freeze is proven enforceable, not aspirational.
|
|
||||||
- iter embedding-abi-m3.1.7 (DONE): milestone-close verification
|
|
||||||
gate (no code change). Regression-green-unmodified set GREEN
|
|
||||||
(`embed_e2e` 1, `embed_rc_accounting_tsan` 2, `embed_swarm_tsan`
|
|
||||||
1, `embed_staticlib_cli` 2, `embed_staticlib_alloc_guard` 2,
|
|
||||||
`print_no_leak_pin` 1; `design_schema_drift` 8/8;
|
|
||||||
`embed_export_hash_stable` 1/1). Full workspace **639 passed, 0
|
|
||||||
failed**, zero compile errors; M3 net-new present + GREEN
|
|
||||||
(`embed_record_layout_pin` 1, `embed_export_gate` 10 = orig 6 + 4,
|
|
||||||
`embed_staticlib_lowering` 3 = orig 2 + 1, `embed_record_e2e` 2),
|
|
||||||
nothing pre-existing removed/weakened. `round_trip` 2/2 (new
|
|
||||||
`.ail` fixtures round-trip; no Form-A surface added).
|
|
||||||
|
|
||||||
## Concerns (re-dispatch addendum)
|
|
||||||
|
|
||||||
- iter embedding-abi-m3.1.7: PLAN TRANSCRIPTION DEFECT (class:
|
|
||||||
"plan pseudo-code vs reality" — same family as the iter's existing
|
|
||||||
`(app Ctor)`→`(term-ctor)` / module==file-stem Concerns). Plan
|
|
||||||
Task-7 Step 1 names `cargo test -p ailang-check --test
|
|
||||||
embed_export_hash_stable`, but that test target lives in
|
|
||||||
**`ailang-core`** (`crates/ailang-core/tests/embed_export_hash_
|
|
||||||
stable.rs`), not `ailang-check`. The substantive requirement
|
|
||||||
("no schema field added; hashes/drift byte-unchanged") is
|
|
||||||
unaffected — ran the pin in its actual crate, 1/1 GREEN. Crate
|
|
||||||
name corrected at execution; observation not correctness. The
|
|
||||||
recon "named this exact set" claim was wrong for this one target
|
|
||||||
(recurring `feedback_grounding_check_misses_insource_tests` /
|
|
||||||
`feedback_plan_pseudo_vs_reality` family — a planner Step-5 scrub
|
|
||||||
item, recorded here for Boss visibility).
|
|
||||||
- iter embedding-abi-m3.1.6: the plan places the Step-5 match_lower.rs
|
|
||||||
`// FROZEN ABI` comment edit and the Step-7 `git checkout --
|
|
||||||
match_lower.rs` enforceability restore in the SAME task on the SAME
|
|
||||||
file. The checkout necessarily reverts the uncommitted Step-5
|
|
||||||
comment along with the perturbation (it restores to HEAD `d5c565d`,
|
|
||||||
where neither exists). Re-applied the Step-5 comment after the
|
|
||||||
enforceability demo so Step 5 stays satisfied — this is a plan
|
|
||||||
step-ordering wrinkle, not a defect: the freeze demo MUST run on a
|
|
||||||
tree where the only delta is the perturbation, and the comment is
|
|
||||||
re-addable post-demo. Recorded for planner-scrub awareness.
|
|
||||||
|
|
||||||
## Known debt (re-dispatch addendum)
|
|
||||||
|
|
||||||
- `llvm_scalar` identifier still slightly narrower than its post-M3
|
|
||||||
behaviour (carried from the first dispatch's Concerns) — out of
|
|
||||||
scope for Tasks 6/7; the doc-comment disambiguates. Unchanged.
|
|
||||||
- Bench + architect milestone-close (spec Testing items 8/9) remain
|
|
||||||
the `audit` skill's job — NOT this implement run, as the original
|
|
||||||
account and Boss adjudication both record. audit is the next
|
|
||||||
pipeline step.
|
|
||||||
|
|
||||||
## Files touched (re-dispatch addendum — Tasks 6–7)
|
|
||||||
|
|
||||||
Modified (Task 6; all uncommitted in the working tree):
|
|
||||||
- `docs/DESIGN.md` (two "provisional until M3" rewrites + the new
|
|
||||||
"### Frozen value layout" SSOT subsection)
|
|
||||||
- `runtime/rc.c` (lockstep `// FROZEN ABI` comment in the layout
|
|
||||||
block)
|
|
||||||
- `crates/ailang-codegen/src/match_lower.rs` (`lower_ctor` `:107`
|
|
||||||
lockstep comment)
|
|
||||||
- `crates/ailang-codegen/src/drop.rs` (`:88` tag-load + `:113`
|
|
||||||
field-offset lockstep comments)
|
|
||||||
- `crates/ailang-codegen/src/lib.rs` (`Target::StaticLib` rustdoc
|
|
||||||
"provisional until M3" → frozen)
|
|
||||||
- `crates/ailang-core/src/ast.rs` (`FnDef.export` rustdoc
|
|
||||||
"provisional until M3" → frozen)
|
|
||||||
- `crates/ailang-check/src/lib.rs` (`ExportNonScalarSignature`
|
|
||||||
rustdoc "scalar-only…every ADT rejected" → frozen — Concerns
|
|
||||||
carry-forward)
|
|
||||||
|
|
||||||
Task 7: verification only, no files.
|
|
||||||
|
|
||||||
## Stats
|
|
||||||
|
|
||||||
bench/orchestrator-stats/2026-05-18-iter-embedding-abi-m3.1.json
|
|
||||||
@@ -1,97 +0,0 @@
|
|||||||
# iter embedding-abi-m3.tidy — close the two M3-audit doc-honesty DRIFT items
|
|
||||||
|
|
||||||
**Date:** 2026-05-18
|
|
||||||
**Started from:** 4fc34705528333de765251be9c26f7bb3182e175
|
|
||||||
**Status:** DONE
|
|
||||||
**Tasks completed:** 3 of 3
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
Post-audit docs-honesty tidy that reconciles the two DRIFT items the
|
|
||||||
M3 milestone-close audit (`docs/journals/2026-05-18-audit-embedding-abi-m3.md`,
|
|
||||||
committed `b8a60b1`) routed here, in the M2.tidy `[medium]+[low]
|
|
||||||
doc-honesty → tidy` precedent shape. Two surgical, independent text
|
|
||||||
edits, zero language/checker/codegen behaviour change. (1) DESIGN.md
|
|
||||||
§"Embedding ABI" `:2300-2301` — replaced *only* the contradicted
|
|
||||||
M1-era parenthetical (`modes apply only to heap-shaped types, which
|
|
||||||
the scalar-only rule above forbids at an export boundary anyway`),
|
|
||||||
which since the M3 frozen-layout SSOT accepts a single-ctor record
|
|
||||||
export type contradicted both that freeze and the `(own/borrow
|
|
||||||
(con T))` mode contract at `:2345-2351`; the replacement states the
|
|
||||||
present-tense truth and points at the frozen-value-layout SSOT. The
|
|
||||||
`docs_honesty_pin.rs:135`-pinned sentence ("Export parameters are
|
|
||||||
written **bare**: a scalar type carries no `own`/`borrow` mode",
|
|
||||||
`norm()`-whitespace-collapsed) shares physical line `:2300` with the
|
|
||||||
stale clause — the edit kept every pinned word contiguous; the pin
|
|
||||||
stayed GREEN (5 passed) across the edit, no RED→restore needed.
|
|
||||||
(2) `crates/ailang-codegen/src/lib.rs:608-610` — comment-only
|
|
||||||
honesty fix: the forwarder-body comment claimed the gate guarantees
|
|
||||||
`Int`/`Float`-only, false post-M3 (a gate-guaranteed single-ctor
|
|
||||||
record lowers to `ptr`); the IR byte-pin + 3 forwarder-IR pins
|
|
||||||
stayed byte-identically GREEN before and after, the guard that no
|
|
||||||
generated code moved. Four standing pins at exact recon baseline
|
|
||||||
(`docs_honesty_pin` 5, `design_schema_drift` 8,
|
|
||||||
`embed_record_layout_pin` 1, `embed_staticlib_lowering` 3); full
|
|
||||||
workspace 639/77 byte-unchanged from the M3-DONE baseline. Diff is
|
|
||||||
exactly the two intended files. This closes the M3 audit's
|
|
||||||
`[medium]`+`[low]` doc-honesty drift; bench was already carry-on at
|
|
||||||
the audit (decisively causally exonerated by byte-identical
|
|
||||||
generated IR — M3 changed no executable-path codegen; NO ratify).
|
|
||||||
No audit/fieldtest gate after this tidy — the four pins + the
|
|
||||||
639/77 baseline ARE the regression coverage (M2.tidy precedent).
|
|
||||||
|
|
||||||
## Per-task notes
|
|
||||||
|
|
||||||
- iter embedding-abi-m3.tidy.1: reconcile the contradicted DESIGN.md
|
|
||||||
parenthetical (`[medium]`). Replaced only the parenthetical on
|
|
||||||
`:2300-2301`; pinned bare-scalar sentence kept verbatim+contiguous
|
|
||||||
(`docs_honesty_pin` 5 passed, `design_schema_drift` 8 passed).
|
|
||||||
- iter embedding-abi-m3.tidy.2: honesty-fix the stale forwarder-body
|
|
||||||
comment (`[low]`). Comment-only edit at `lib.rs:608-610`;
|
|
||||||
`embed_record_layout_pin` 1 + `embed_staticlib_lowering` 3 passed
|
|
||||||
byte-identically before and after (no IR delta); full codegen
|
|
||||||
crate green.
|
|
||||||
- iter embedding-abi-m3.tidy.3: milestone-close verification gate.
|
|
||||||
Four standing pins at recon baseline; workspace 639/77 unchanged;
|
|
||||||
diff exactly two files (DESIGN.md, codegen/src/lib.rs). One
|
|
||||||
plan-vs-actual grep discrepancy recorded under Concerns (Step 1
|
|
||||||
pattern collides with the plan's own Task 2 replacement text;
|
|
||||||
substantive intent satisfied via the discriminating fragment).
|
|
||||||
|
|
||||||
## Concerns
|
|
||||||
|
|
||||||
- Task 3 Step 1's verification grep pattern `guarantees every param`
|
|
||||||
is a plan defect: it is a substring of the plan's *own* Task 2
|
|
||||||
prescribed NEW replacement text (`// The check-side export gate
|
|
||||||
guarantees every param and`), so it cannot return "no output" as
|
|
||||||
the plan's Step 1 expects without contradicting Task 2. The
|
|
||||||
substantive intent of Step 1 — the stale `Int`/`Float`-only
|
|
||||||
comment is gone — was verified via the discriminating fragments
|
|
||||||
instead: the stale `check-side gate (Task 4)` marker is ABSENT
|
|
||||||
(grep exit 1) and the stale `guarantees every param` + immediately
|
|
||||||
following `and the ret are \`Int\`/\`Float\`; map` two-line claim
|
|
||||||
is ABSENT (Pzo exit 1), while the new correct M3 comment with
|
|
||||||
`single-constructor` record framing is PRESENT (`:609`). The
|
|
||||||
implementation followed Task 2's verbatim replacement exactly as
|
|
||||||
mandated; the retained words "guarantees every param" are correct
|
|
||||||
(the gate genuinely still guarantees every param — the M3 change
|
|
||||||
is *what* it guarantees, not *whether*). No edit was made to
|
|
||||||
dodge the grep; the plan's pattern is the defect, not the code.
|
|
||||||
|
|
||||||
## Known debt
|
|
||||||
|
|
||||||
(none — the two audit-flagged drift items are both closed; no
|
|
||||||
deferred follow-up.)
|
|
||||||
|
|
||||||
## Blocked detail
|
|
||||||
|
|
||||||
(none — DONE.)
|
|
||||||
|
|
||||||
## Files touched
|
|
||||||
|
|
||||||
- `docs/DESIGN.md` — §"Embedding ABI" parenthetical replaced (`:2300-2301`)
|
|
||||||
- `crates/ailang-codegen/src/lib.rs` — `Target::StaticLib` forwarder-body comment honesty (`:608-610`)
|
|
||||||
|
|
||||||
## Stats
|
|
||||||
|
|
||||||
bench/orchestrator-stats/2026-05-18-iter-embedding-abi-m3.tidy.json
|
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
# iter emit-ir-staticlib — `ail emit-ir --emit=staticlib` (M1 fieldtest spec_gap#2)
|
|
||||||
|
|
||||||
**Date:** 2026-05-18
|
|
||||||
**Started from:** 03493c9b316038f6bfe7f86678457bfa223451a5
|
|
||||||
**Status:** DONE
|
|
||||||
**Tasks completed:** 4 of 4
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
Resolves M1 fieldtest [spec_gap]#2 ("no public path emits the
|
|
||||||
staticlib IR though Decision 5 makes IR readability a first-class
|
|
||||||
LLM affordance") by adding `--emit=staticlib` to `ail emit-ir`,
|
|
||||||
symmetric with the M1-shipped `ail build --emit=staticlib`. A
|
|
||||||
one-line codegen convenience `lower_workspace_staticlib(ws)`
|
|
||||||
(mirroring `lower_workspace`) routes through the M1-audited,
|
|
||||||
unchanged `Target::StaticLib` forwarder-emission path; the CLI
|
|
||||||
arm gains an `emit: String` clap field (symmetric with
|
|
||||||
`Cmd::Build`'s) and a zero-export guard byte-identical to
|
|
||||||
`build_staticlib`'s. DESIGN.md is *widened* (never narrowed) with
|
|
||||||
a present-tense affordance sentence in §"Embedding ABI (M1)" and a
|
|
||||||
synopsis correction that also fixes a pre-existing M1 omission for
|
|
||||||
`ail build --emit=staticlib`. No new doc pin (architecture
|
|
||||||
decision): the new E2E file pins the behaviour; the existing
|
|
||||||
`docs_honesty_pin` (5 tests) confirmed non-regressed.
|
|
||||||
|
|
||||||
## Per-task notes
|
|
||||||
|
|
||||||
- iter emit-ir-staticlib.1: codegen — added `pub fn
|
|
||||||
lower_workspace_staticlib` in `ailang-codegen/src/lib.rs` between
|
|
||||||
`lower_workspace` and `lower_workspace_inner`; delegates to
|
|
||||||
`lower_workspace_inner(ws, Gc, Target::StaticLib)`. `cargo build
|
|
||||||
-p ailang-codegen` clean.
|
|
||||||
- iter emit-ir-staticlib.2: CLI — RED-first E2E file
|
|
||||||
`crates/ail/tests/emit_ir_staticlib_cli.rs` (3 tests), observed
|
|
||||||
RED "1 passed; 2 failed" exactly as predicted; added `emit`
|
|
||||||
clap field to `Cmd::EmitIr`, destructured it in the arm, branched
|
|
||||||
the lowering call with the byte-identical zero-export guard
|
|
||||||
(Steps 3+4 honoured as a single compile unit — no cargo between
|
|
||||||
the E0027 window). GREEN: 3 passed; `embed_staticlib_cli` 2
|
|
||||||
passed (build-side symmetry, no regression).
|
|
||||||
- iter emit-ir-staticlib.3: DESIGN.md — added the present-tense
|
|
||||||
`--emit=staticlib` affordance sentence after the M1 export
|
|
||||||
snippet; synopsis: both `emit-ir` and `build` lines gained
|
|
||||||
`[--emit=staticlib]` (the `build` correction discharges a
|
|
||||||
pre-existing M1 current-state-honesty gap). `docs_honesty_pin`
|
|
||||||
5/5 green — no new pin, no regression.
|
|
||||||
- iter emit-ir-staticlib.4: regression gate — `ail` suite 186
|
|
||||||
passed / 0 failed; workspace `TOTAL_PASSED=626` = pre-iter 623
|
|
||||||
+ 3 (exactly the new tests, no pre-existing test lost); scope
|
|
||||||
check clean (only the 4 in-scope paths).
|
|
||||||
|
|
||||||
## Concerns
|
|
||||||
|
|
||||||
(none)
|
|
||||||
|
|
||||||
## Known debt
|
|
||||||
|
|
||||||
(none — scoped feature fully closed; no fixture minted, no doc
|
|
||||||
pin added, all by explicit plan decision.)
|
|
||||||
|
|
||||||
## Blocked detail
|
|
||||||
|
|
||||||
(none)
|
|
||||||
|
|
||||||
## Files touched
|
|
||||||
|
|
||||||
- `crates/ailang-codegen/src/lib.rs` — `lower_workspace_staticlib` convenience
|
|
||||||
- `crates/ail/src/main.rs` — `Cmd::EmitIr` `emit` clap field + arm branch + zero-export guard
|
|
||||||
- `crates/ail/tests/emit_ir_staticlib_cli.rs` — new, 3 E2E tests (positive forwarder-IR / zero-export guard / default-exe regression)
|
|
||||||
- `docs/DESIGN.md` — §"Embedding ABI (M1)" affordance sentence + CLI synopsis (both lines)
|
|
||||||
|
|
||||||
## Stats
|
|
||||||
|
|
||||||
bench/orchestrator-stats/2026-05-18-iter-emit-ir-staticlib.json
|
|
||||||
@@ -1,73 +0,0 @@
|
|||||||
# iter form-a-scalar-param-mode-carveout — form_a.md scalar-parameter mode carve-out (docs-honesty tidy)
|
|
||||||
|
|
||||||
**Date:** 2026-05-18
|
|
||||||
**Started from:** 4c266a64b478c6a6106958d577faea5ac30dfa3e
|
|
||||||
**Status:** DONE
|
|
||||||
**Tasks completed:** 4 of 4
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
Docs-honesty tidy in the `docs-honesty-lint` class resolving the
|
|
||||||
fieldtest `[friction]` + `[spec_gap]#1` from
|
|
||||||
`docs/specs/2026-05-18-fieldtest-embedding-abi-m1.md`:
|
|
||||||
`crates/ailang-core/specs/form_a.md` and `docs/DESIGN.md` §"Embedding
|
|
||||||
ABI (M1)" stated an unconditional "every `(fn ...)` parameter MUST
|
|
||||||
carry a mode" rule that contradicts the shipped checker behaviour
|
|
||||||
(scalar `Int`/`Bool`/`Unit`/`Str` params take — and require — no
|
|
||||||
mode, mirroring the existing return-type carve-out). RED-first: the
|
|
||||||
new pin `form_a_scalar_param_carveout_present_and_old_rule_absent`
|
|
||||||
was written asserting the post-edit state and failed correctly
|
|
||||||
(Task 1), then the four form_a.md sites + one DESIGN.md insertion
|
|
||||||
turned it green (Tasks 2–3). Zero language/checker/codegen change;
|
|
||||||
exactly three files touched. Workspace suite 622 → 623 (delta = +1,
|
|
||||||
the new pin), zero failures.
|
|
||||||
|
|
||||||
## Per-task notes
|
|
||||||
|
|
||||||
- iter form-a-scalar-param-mode-carveout.1: RED pin —
|
|
||||||
`use ailang_core::FORM_A_SPEC;` added + new `#[test] fn
|
|
||||||
form_a_scalar_param_carveout_present_and_old_rule_absent`
|
|
||||||
(4 ABSENT + 4 PRESENT form_a + 1 PRESENT DESIGN assertions);
|
|
||||||
failed at the first ABSENT check (site 3) as designed; 4
|
|
||||||
pre-existing pins unaffected (4 passed / 1 failed).
|
|
||||||
- iter form-a-scalar-param-mode-carveout.2: form_a.md four sites —
|
|
||||||
site 1 `### Function` prose, site 2 grammar-block comment, site 3
|
|
||||||
Schema-invariants item 1 (+ checker-behaviour rationale), site 4
|
|
||||||
Pitfalls bullet split into heap-shaped-forget + inverse
|
|
||||||
scalar-over-wrap. Pin then failed only on the DESIGN.md assertion;
|
|
||||||
spec_drift all 8 green (not a lockstep partner, confirmed).
|
|
||||||
- iter form-a-scalar-param-mode-carveout.3: DESIGN.md §"Embedding
|
|
||||||
ABI (M1)" — inserted the bare-scalar export-param rule + the
|
|
||||||
corpus-grounded `step` Form-A snippet (byte-identical to
|
|
||||||
`examples/embed_backtest_step.ail` lines 3-11). Pin fully green;
|
|
||||||
docs_honesty_pin 5 passed / 0 failed.
|
|
||||||
- iter form-a-scalar-param-mode-carveout.4: regression gate —
|
|
||||||
`cargo test -p ailang-core` 112 passed / 0 failed; workspace
|
|
||||||
TOTAL_PASSED=623 (baseline 622, +1 expected) / 0 failed; zero
|
|
||||||
src-code diff under ailang-check / ailang-codegen /
|
|
||||||
ailang-core/src / ailang-surface.
|
|
||||||
|
|
||||||
## Concerns
|
|
||||||
|
|
||||||
(none)
|
|
||||||
|
|
||||||
## Known debt
|
|
||||||
|
|
||||||
(none — site 5 form_a.md:521-524 already-correct few-shot annotation
|
|
||||||
left verbatim by design; the carve-out text deliberately reuses its
|
|
||||||
heap-shaped/primitive vocabulary so the document is internally
|
|
||||||
consistent end to end.)
|
|
||||||
|
|
||||||
## Blocked detail
|
|
||||||
|
|
||||||
(none — DONE)
|
|
||||||
|
|
||||||
## Files touched
|
|
||||||
|
|
||||||
- `crates/ailang-core/specs/form_a.md` (4 carve-out sites)
|
|
||||||
- `docs/DESIGN.md` (1 §"Embedding ABI (M1)" insertion)
|
|
||||||
- `crates/ailang-core/tests/docs_honesty_pin.rs` (1 `use` + 1 new `#[test]`)
|
|
||||||
|
|
||||||
## Stats
|
|
||||||
|
|
||||||
bench/orchestrator-stats/2026-05-18-iter-form-a-scalar-param-mode-carveout.json
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
# iter prose-loop-binders.1 — Form-B `loop` renders binders as a parenthesised init-list on the keyword
|
|
||||||
|
|
||||||
**Date:** 2026-05-18
|
|
||||||
**Started from:** 6533134dab888605c24fb2e50a65fcbf99c1897e
|
|
||||||
**Status:** DONE
|
|
||||||
**Tasks completed:** 2 of 2
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
Projection-only single-iteration milestone in `ailang-prose`. The
|
|
||||||
`Term::Loop` arm of `write_term` (`crates/ailang-prose/src/lib.rs`)
|
|
||||||
now renders loop binders as a parenthesised init-list on the keyword
|
|
||||||
line — `loop(acc = 0, i = 1) { … }` — instead of bare
|
|
||||||
`name = init;` statements inside the body block. The old shape
|
|
||||||
implied C/Rust re-init-every-iteration semantics; the new shape
|
|
||||||
mirrors the adjacent (untouched) `Term::Recur` arm's
|
|
||||||
`enumerate()` + `", "`-separator idiom and renders init expressions
|
|
||||||
at `level` (they sit on the keyword line, not the indented block).
|
|
||||||
RED-first: two committed `examples/*.prose.txt` snapshot fixtures
|
|
||||||
plus two `check_snapshot`-calling `#[test]` fns were added and
|
|
||||||
verified failing against the old renderer before the arm rewrite
|
|
||||||
made them pass. Loop/recur AST, Form-A, JSON-AST, typecheck,
|
|
||||||
codegen and the Form-A↔JSON round-trip invariant are untouched by
|
|
||||||
construction (single render-arm body rewrite, no signature change,
|
|
||||||
no caller touched).
|
|
||||||
|
|
||||||
## Per-task notes
|
|
||||||
|
|
||||||
- iter prose-loop-binders.1.1: RED — created
|
|
||||||
`examples/loop_sum_to_run.prose.txt` (multi-binder + non-recur
|
|
||||||
exit) and `examples/loop_forever_build.prose.txt` (single-binder
|
|
||||||
recur-only) with the exact Option-A bytes from the plan; appended
|
|
||||||
`snapshot_loop_sum_to_run` + `snapshot_loop_forever_build` after
|
|
||||||
`snapshot_ordering_match` in `tests/snapshot.rs`. `cargo test -p
|
|
||||||
ailang-prose snapshot_loop` → `0 passed; 2 failed; 8 filtered
|
|
||||||
out` (filter resolved to exactly the two new tests; both panic
|
|
||||||
with the expected `prose snapshot mismatch` message). RED
|
|
||||||
confirmed.
|
|
||||||
- iter prose-loop-binders.1.2: GREEN — rewrote the `Term::Loop`
|
|
||||||
arm at `lib.rs:938-955` verbatim per the plan (parenthesised
|
|
||||||
init-list, init at `level`, stale "not yet designed" comment
|
|
||||||
removed). `cargo test -p ailang-prose snapshot_loop` → `2
|
|
||||||
passed; 0 failed`; full `ailang-prose` suite → `10 passed; 0
|
|
||||||
failed` (no pre-existing snapshot regressed); both `ail prose |
|
|
||||||
diff` CLI byte-matches → `MATCH`; post-cleanup `git status
|
|
||||||
--porcelain` is EXACTLY the four expected paths.
|
|
||||||
|
|
||||||
## Concerns
|
|
||||||
|
|
||||||
(none)
|
|
||||||
|
|
||||||
## Known debt
|
|
||||||
|
|
||||||
(none — projection-only single-arm rewrite, fully gated by
|
|
||||||
whole-file byte-equality snapshots + live-CLI diff)
|
|
||||||
|
|
||||||
## Files touched
|
|
||||||
|
|
||||||
- `crates/ailang-prose/src/lib.rs` — `Term::Loop` arm of
|
|
||||||
`write_term` rewritten (single hunk, lines 938-953)
|
|
||||||
- `crates/ailang-prose/tests/snapshot.rs` — two `#[test]` fns
|
|
||||||
appended after `snapshot_ordering_match`
|
|
||||||
- `examples/loop_sum_to_run.prose.txt` — new committed snapshot
|
|
||||||
- `examples/loop_forever_build.prose.txt` — new committed snapshot
|
|
||||||
|
|
||||||
## Blocked detail
|
|
||||||
|
|
||||||
(n/a — Status DONE)
|
|
||||||
|
|
||||||
## Stats
|
|
||||||
|
|
||||||
bench/orchestrator-stats/2026-05-18-iter-prose-loop-binders.1.json
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user