iter 22b.2: typecheck arms — 8 diagnostics shipped + skill-system post-mortem
This commit is contained in:
+190
@@ -11309,3 +11309,193 @@ relevant skill.
|
|||||||
- Per-skill TDD pressure-tests (RED + 3 GREEN/REFACTOR runs each)
|
- Per-skill TDD pressure-tests (RED + 3 GREEN/REFACTOR runs each)
|
||||||
on the build-out iteration. Deferred to a follow-up audit if the
|
on the build-out iteration. Deferred to a follow-up audit if the
|
||||||
first end-to-end exercise reveals gaps.
|
first end-to-end exercise reveals gaps.
|
||||||
|
|
||||||
|
## 2026-05-09 — Iteration 22b.2: typecheck arms
|
||||||
|
|
||||||
|
First milestone-iteration routed entirely through the new skill
|
||||||
|
pipeline (`brainstorm` was retrospective for milestone 22 in iter
|
||||||
|
22b.2 prelude; `plan` produced `docs/plans/2026-05-09-22b2-typecheck-arms.md`;
|
||||||
|
`implement` ran the 10 tasks plus E2E; `audit` will close the
|
||||||
|
milestone separately).
|
||||||
|
|
||||||
|
**What shipped (8 new diagnostics + schema + infra):**
|
||||||
|
|
||||||
|
- Schema: `Constraint { class, type_ }` struct; `Type::Forall` gains
|
||||||
|
`constraints: Vec<Constraint>` gated by
|
||||||
|
`#[serde(default, skip_serializing_if = "Vec::is_empty")]`. Pre-22b.2
|
||||||
|
fixtures hash bit-identical (regression test in `hash.rs`).
|
||||||
|
- Class-schema (3, in `validate_classdefs` running before
|
||||||
|
`build_registry`): `kind-mismatch` (HKT use forbidden, Decision 11
|
||||||
|
axis 5), `invalid-superclass-param` (superclass `type` must equal
|
||||||
|
class `param`, axis 1), `constraint-references-unbound-type-var`
|
||||||
|
(class-method constraint vars must be bound).
|
||||||
|
- Workspace coherence (3, in `build_registry`):
|
||||||
|
`overriding-non-existent-method` (instance bodies for undeclared
|
||||||
|
methods), `method-name-collision` (single code with `kind: "class-class"`
|
||||||
|
vs `"class-fn"` ctx field; backed by structural `enum Origin` not
|
||||||
|
string-prefix matching; fn-fn delegated to existing `DuplicateDef`),
|
||||||
|
`missing-superclass-instance` (post-loop superclass-chain walk with
|
||||||
|
`BTreeSet<&str>` cycle-termination guard).
|
||||||
|
- Per-FnDef typecheck (2, in `check_fn`): `missing-constraint`
|
||||||
|
(residual at class-method call site doesn't match expanded
|
||||||
|
declared constraints, where expansion walks the superclass chain
|
||||||
|
one step per Decision 11), `no-instance` (concrete-type residual
|
||||||
|
not in workspace registry; reuses `canonical::type_hash`).
|
||||||
|
- Infra: `ModuleGlobals { fns, class_methods }` two-channel split
|
||||||
|
(class methods get a separate map preserving definition order via
|
||||||
|
`IndexMap`); `ClassMethodEntry { class_name, class_param, method_ty,
|
||||||
|
defining_module }` accessor; `Env.class_methods`,
|
||||||
|
`Env.class_superclasses` (`BTreeMap<String, String>` — absence
|
||||||
|
means no superclass), `Env.workspace_registry` threaded through
|
||||||
|
`check_in_workspace`.
|
||||||
|
|
||||||
|
**Tests (cross-cutting + e2e):** 12 tests in
|
||||||
|
`crates/ail/tests/typeclass_22b2.rs` (4 task-level + 4 e2e
|
||||||
|
cross-module + 4 superclass-walk asymmetry). Workspace-load tests
|
||||||
|
under each diagnostic in `crates/ailang-core/src/workspace.rs`.
|
||||||
|
11 fixtures under `examples/test_22b2_*.ail.json`.
|
||||||
|
|
||||||
|
**Per-task subjects (mirrors commit messages):**
|
||||||
|
|
||||||
|
- iter 22b.2.1 — Constraint struct + Forall.constraints
|
||||||
|
- iter 22b.2.2 — kind-mismatch
|
||||||
|
- iter 22b.2.3 — invalid-superclass-param
|
||||||
|
- iter 22b.2.4 — constraint-references-unbound-type-var
|
||||||
|
- iter 22b.2.5 — overriding-non-existent-method
|
||||||
|
- iter 22b.2.6 — method-name-collision
|
||||||
|
- iter 22b.2.7 — missing-superclass-instance
|
||||||
|
- iter 22b.2.8 — register class methods in module globals
|
||||||
|
- iter 22b.2.9 — missing-constraint per-fn
|
||||||
|
- iter 22b.2.10 — no-instance per-fn
|
||||||
|
- iter 22b.2.e2e — cross-module class resolution + multi-fn aggregation
|
||||||
|
|
||||||
|
19 commits total on branch `iter-22b2-typecheck-arms` (10 task
|
||||||
|
commits + 8 round-2 fixes from the spec/quality review loops + 1
|
||||||
|
e2e commit). Two-stage review surfaced real issues the implementer
|
||||||
|
or task carrier alone wouldn't have caught — see "Skill-system
|
||||||
|
post-mortem" entry below.
|
||||||
|
|
||||||
|
**Known debt deliberately not touched:**
|
||||||
|
|
||||||
|
- The `constraint-references-unbound-type-var` walker is shallow:
|
||||||
|
it only inspects constraints whose `type_` is a bare `Type::Var`.
|
||||||
|
`(Bar, Maybe z)` with `z` unbound would slip past. Enough for
|
||||||
|
schema-level coherence per Decision 11's stated convention;
|
||||||
|
recursive walk is a follow-up if a real fixture needs it.
|
||||||
|
- Superclass-cycle DETECTION as a first-class diagnostic is
|
||||||
|
queued. The chain walks now terminate via `BTreeSet<&str>`
|
||||||
|
visited-set in `missing-superclass-instance` and via single-step
|
||||||
|
expansion in `expand_declared_constraints`; surfacing cycles as
|
||||||
|
their own error is a future arm.
|
||||||
|
- `Env.workspace_registry` is `Default::default()` in the
|
||||||
|
standalone `check_module` path. Acceptable because that path is
|
||||||
|
used only for diagnostics that don't need cross-module instance
|
||||||
|
lookup; if a future feature needs registry-backed checks in
|
||||||
|
module-only mode, the field must be made `Option<Registry>` or
|
||||||
|
fed from a builder.
|
||||||
|
|
||||||
|
**22b.3 next:** monomorphisation pass — synthesise FnDefs from
|
||||||
|
`(method, type-hash)` pairs, rewrite calls, with a synthetic
|
||||||
|
class+instance fixture for end-to-end mono validation before the
|
||||||
|
Prelude lands in 22b.4.
|
||||||
|
|
||||||
|
## 2026-05-09 — Skill-system post-mortem (first end-to-end run)
|
||||||
|
|
||||||
|
Iteration 22b.2 was the first milestone-iteration routed entirely
|
||||||
|
through the `brainstorm → plan → implement` pipeline (the build-out
|
||||||
|
iteration 22c.live shipped the system itself but didn't exercise
|
||||||
|
it on a real ten-task milestone). Observations:
|
||||||
|
|
||||||
|
**What the skill system caught that an unstructured run wouldn't:**
|
||||||
|
|
||||||
|
- **Spec drift in carrier construction.** Task 5 spec reviewer
|
||||||
|
flagged a placement issue (`OverridingNonExistentMethod` after
|
||||||
|
`MissingMethod` vs after `UnboundConstraintTypeVar`) that came
|
||||||
|
from the *orchestrator's* carrier text diverging from the literal
|
||||||
|
plan. The two-stage review made the divergence visible at the
|
||||||
|
point it landed, not three iters later. Cost: one fix-round
|
||||||
|
commit (`1f6d232`). Without spec review I'd have shipped the
|
||||||
|
drift.
|
||||||
|
- **Quality-stage caught real bugs spec-stage couldn't see.**
|
||||||
|
Task 6's first round used `prior.starts_with("class ")` to
|
||||||
|
decide the class-class-vs-class-fn discriminator — semantically
|
||||||
|
correct against the task text (so spec-compliant) but coupled
|
||||||
|
the discriminator to an ad-hoc display string. Quality reviewer
|
||||||
|
surfaced both that and the fn-fn-misreports-as-class-fn
|
||||||
|
correctness bug. The structural `enum Origin` rework
|
||||||
|
(`b252f83`) was ten lines of code but the only way the bug got
|
||||||
|
caught was by separating "did you build the right thing" from
|
||||||
|
"did you build it well".
|
||||||
|
- **Test-coverage gaps.** Task 9 shipped without a superclass-walk
|
||||||
|
exercise (the `expand_declared_constraints` code path was
|
||||||
|
unverified); quality stage flagged it as Important. Task 10
|
||||||
|
shipped without a positive `instance-present` test (registry
|
||||||
|
threading bugs would have slipped past); also flagged. In both
|
||||||
|
cases the per-task plan was complete but the implementer didn't
|
||||||
|
add cross-cutting coverage at task level. Quality review caught
|
||||||
|
the gap right when it would otherwise have rotted.
|
||||||
|
- **Termination guards.** Task 7's chain-walk had no cycle bound;
|
||||||
|
quality stage surfaced the hang risk. Implementer's first round
|
||||||
|
was a clean implementation of the plan, but the plan didn't
|
||||||
|
ask for a guard and a malicious workspace would have hung
|
||||||
|
`build_registry` indefinitely. Quality reviewer caught the
|
||||||
|
defensive-validation question and the fix landed in round 2.
|
||||||
|
|
||||||
|
**What the skill system cost:**
|
||||||
|
|
||||||
|
- 8 round-2 fix commits across the iter (out of 19 total). Every
|
||||||
|
one was a justified correction. Review-loop overhead is real but
|
||||||
|
the marginal cost per finding is much lower than the downstream
|
||||||
|
cost of catching those issues weeks later. Per-iter latency: ~50%
|
||||||
|
more wall-clock vs a single-pass implementation, but that wall-
|
||||||
|
clock is delegated subagent time, not orchestrator context.
|
||||||
|
- **Carrier-text duplication.** Each implementer + spec reviewer
|
||||||
|
+ quality reviewer dispatch carries the full task text and
|
||||||
|
scene-set. Token-cost-wise, this is the single biggest line item.
|
||||||
|
An optimisation to consider: have the spec reviewer accept a
|
||||||
|
"task_text_ref: docs/plans/<X>.md#task-N" and load the file
|
||||||
|
itself. But this would break the "agents do not open
|
||||||
|
docs/plans/" convention from `skills/README.md`. Trade-off worth
|
||||||
|
revisiting if iter cost balloons.
|
||||||
|
|
||||||
|
**What surprised me:**
|
||||||
|
|
||||||
|
- **Implementer self-corrections were strong.** Three implementer
|
||||||
|
rounds returned `DONE_WITH_CONCERNS` flagging an unrequested
|
||||||
|
edit they made because the task plan was underspecified (e.g.
|
||||||
|
Task 2's `round_trip.rs` skip-list extension; Task 6's
|
||||||
|
`"kind": "string"` → `"str"` fixture fix). These were always
|
||||||
|
correct. The DONE_WITH_CONCERNS protocol works — the
|
||||||
|
implementer doesn't push through silently, but doesn't demand
|
||||||
|
permission either when the call is clear.
|
||||||
|
- **Spec-stage failed cheaply.** When non_compliant fired, the
|
||||||
|
fix-rounds were always single-file edits. The two-stage split
|
||||||
|
prevented quality-review from spending time on diffs that would
|
||||||
|
later be moved or reverted.
|
||||||
|
- **Quality stage's "trust the implementer to choose the fix"
|
||||||
|
discipline held.** Reviewers consistently described issues
|
||||||
|
rather than prescribing fixes. The implementer made structurally
|
||||||
|
sound rework calls (e.g. `enum Origin` instead of patching the
|
||||||
|
prefix-string check in place). The reviewer instructions in
|
||||||
|
`ailang-quality-reviewer.md` were doing real work.
|
||||||
|
|
||||||
|
**Skill-system tweaks for the audit phase:**
|
||||||
|
|
||||||
|
- The `ailang-spec-reviewer` carrier should default to "the
|
||||||
|
literal plan task text" rather than orchestrator paraphrase.
|
||||||
|
Task 5's drift came from my paraphrase adding "after X". The
|
||||||
|
fix is for me as orchestrator to copy plan-task text verbatim
|
||||||
|
into the carrier; not a SKILL.md change but a discipline note.
|
||||||
|
- `ailang-tester` Step 3 was excellent for this iter: identified
|
||||||
|
cross-module gaps + multi-fn aggregation that per-task tests
|
||||||
|
missed, AND honestly reported when a candidate property wasn't
|
||||||
|
E2E-testable rather than padding the suite. Worth preserving
|
||||||
|
the "DONE with no new tests if coverage is exhaustive" carve-
|
||||||
|
out in the agent file.
|
||||||
|
|
||||||
|
**Net assessment:** the skill pipeline shipped 22b.2 with materially
|
||||||
|
higher quality than a single-pass run would have, at proportional
|
||||||
|
cost. First end-to-end exercise is a positive signal; the
|
||||||
|
remaining open follow-ups from 2026-05-09 (skill bugs surfaced by
|
||||||
|
real iter use) get queued as targeted SKILL.md tweaks rather than
|
||||||
|
a wholesale revisit.
|
||||||
|
|||||||
Reference in New Issue
Block a user