effects: a written row is reconciled against what the body incurs #227
Loading…
Reference in a new issue
No description provided.
Delete branch "feat/227-effects-a-written-row-is-reconciled-against-wh"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
effects: a written row is reconciled against what the body incurs
Along the way:
Touches 135 files:
CLAUDE.md,plan.md,scripts/test-integration.sh,scripts/test.sh,scripts/training/.gitignore,src/rust/Cargo.lockand 129 more.Reads well. One question on
src/rust/crates/ck_resolve/src/effect_rows.rs, nothing blocking.@ -0,0 +55,4 @@/// E0506 was checked over, which is what keeps the two passes agreeing about/// what an entry means.#[must_use]pub fn collect_declared_rows(collect_declared_rowsis public. Does anything outside the crate call it, or is it pub because that was easier? Every public item is a promise.Nothing outside calls
collect_declared_rows. It is pub(crate) insrc/rust/crates/ck_resolve/src/effect_rows.rsnow.Read it too. No objection to
src/rust/crates/ck_resolve/src/effect_rows.rs.`docs/10-effects.md` said the compiler checks the row law. It did not. E0506 asked whether a row entry DENOTES and nothing asked whether the claim was TRUE, so a body could incur `Log` with no row, or name `Log` and never log, and both compiled. Both halves are now refused: direct ∖ declared -> E0413 incurred and not named declared ∖ direct -> E0509 named and not incurred THE LAW LIVES WITH THE GRAPH. `ck_effects::rows` is the consumer ruling #30 reserved that crate for, and it uses the `ck_diag` edge the crate had declared in its manifest and used in zero source files since it landed. Both directions compare against `direct_effects`, never `reaches`: comparing against the transitive set would demand exactly the restatement the law forbids. The transitive half is not idle — it is what makes E0509 actionable. Every real over-declaration in this tree is a row restating a callee's effect, so the message names the callee (`carries_only`'s question, asked through `callees`). Where no carrier is among the fed frames it says nothing about callees at all: the closure is app-only, so "nothing it calls incurs one" is a claim this compiler cannot always support. THE SEAMS ARE EXISTING ONES, NOT NEW PARALLEL WALKS. · `ck_syntax::effect_row_of` — one reader for a row's syntactic shape. Three callers were hand-rolling `child_node_of(node, EffectClause)`; the four row positions now share one definition and a fifth would inherit it. · `ck_resolve::effect_rows` — collects `FnId -> WrittenRow` off `module_fns`, already the canonical "every function this module declares". Actor slots are covered without being named (the §2a desugar hands them over as impl methods) and bodiless declarations are excluded without an exemption (`module_fns` skips traits and effect ops, so the law inherits the right domain instead of allow-listing to it). A fn with no row maps to an EMPTY row, never an absent key: "I incur nothing" is a claim, and it is the one E0413 most often refutes. · `ck_driver::effect_rows::Frames` — feeds the fixpoint from `ResolvedBody::resolutions`, the same map `interface_ops` reads, so provider DI and the effect graph cannot disagree about what a call is. An under-declaration anchors on the declaration's own name token, not on `fn_data(id).span`: N1 mints the `FnId` from the `.ckh`, so the interner's span points at the header while the body is in the `.ck`. The row is part of the SIGNATURE, though — E0403 checks impl against header — so a fn whose body incurs an effect declares it in both files or in neither. THE MIGRATION: 301 rows added, 9 removed, 127 files. Every removal was one shape, a row restating an effect a callee incurs, and `tests/corpus_test_run/greeter` is where that shape had been planted deliberately all along — the cell cited it at its pre-`0d6d1c13` path, which no checkout contains. TWO DEFECTS THIS SURFACED, NEITHER OF THEM THE ROW LAW'S: · `grammar_dsl/measure.rs` scanned `delimited_after(src, "effect", '{', '}')`, which matches the `effect` of a ROW and then measures the function's body as a declaration body. Corpus-wide rows made it report M14 = 24 at a `test fn`. Guarded on declaration position; `Grammar.ckx`'s table needed no change, because the measurement was false rather than shifted. · Spans on a desugared page point into generated text — an existing E0506 on a row at real line 28 reports 108, the same shift. Pre-existing and left alone, but E0413 will be the most-read diagnostic on those pages, so it wants its own slice. `effect_row_is_not_checked_yet` is renamed to `effect_row_is_checked_in_both_directions` (a rename, so `CELL_COUNT` holds at 440) and declares `typecheck: E0413, E0509, E0509` — the three violations its page carries, and not redundant: `quiet` falls to the direct set alone, while `spawn` really is on a path to `Log` and falls only to the rule that a row never restates a callee's. The cell carried no `// STATUS:` line while its own prose said it was BROKEN, so it sat in the unmarked set — invisible to `grep -l '^// STATUS: BROKEN'`, which is the undercount `UNMARKED_CELL_COUNT` warns about. Marking it is what put it in the fix queue, and it came straight back out: `BROKEN_CELL_COUNT` 44 -> 45 -> 44, `UNMARKED_CELL_COUNT` 146 -> 145. `ck_driver/src/load.rs` hit the R3 cap, so the effect-graph seam split to its own module. `ck_driver/src/lib.rs` went 1322 -> 1323 for one `mod` line; that red is still the standing one and still wants the driver-root split, not a cleanup. Gate: 4 legs green. Workspace sweep 1610 passed, 1 failed — the standing R3. `clippy --workspace --all-targets -D warnings` 0, `fmt` clean.Four removals, chosen by asking what actually reads each file rather than what each file says about itself: · scripts/training/unsloth_compiled_cache/ — 39 files, 1.8 MB, headed "Unsloth auto generated code" and version-stamped 2026.5.3. Swept into cc9a60ec with the training scaffolding; the library regenerates it on import when its version moves, so the committed copy could only ever go stale. Now gitignored, with the reason written down. · scripts/training/diagnose-hf.sh — one-off curl triage for a slow HuggingFace download. Referenced by no step of run.sh, no README, no ITERATION_LOG entry; the incident it diagnosed is no longer described anywhere. · scripts/tictactoe.sh, scripts/ttt_train_report.sh — a demo and a metrics report. Nothing outside their own headers called either. · scripts/cklang_ai.sh — 1951 lines rendering plan_training.md §7/§8/§9, a spec this repo does not contain. The two comments that cited cklang_ai.sh are corrected in the same commit, because a deletion that leaves its citations standing just moves the lie: ck_driver's `interactive` doc drops it from the caller list, and tensor_race/Main.ck names `od` as the reader of race.results rather than a script that is gone. The layout claim itself is unchanged — it was always about the bytes, not about who read them. Nothing else in scripts/ is dead. The four legs, test-integration.sh, both lib/ helpers and docs/verify.sh call each other; deploy.sh, clean.sh and ckeditor-integration.sh are cited from crate doc comments; offline-core16 is installed and the degraded core is still offline; every remaining training script is a numbered step of run.sh. Gates: four legs ALL GREEN (1354 unit / 106 apps / 440 cells / 35 book expectations), integration sweep 251 passed with the one known-red architecture entry, fmt clean, clippy 0 warnings.