chore(piecewise): a block is checked once, and its rows are written from what the check settled - #623
Merged
FBumann merged 2 commits intoSep 22, 2026
Conversation
…rom what the check settled What a block names by key, and the names it writes, are checked as the file loads with every other cross-declaration rule. The link expressions and the where are typed with the rest of the model, so the expansion no longer parses them itself. What needs those typed forms is decided in one function, check, which returns the facts the rows read; the emitters are plain functions over them and decide nothing. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GNZN57CR8oGTVpRmpXF8YZ
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GNZN57CR8oGTVpRmpXF8YZ
Documentation build overview
20 files changed ·
|
FBumann
merged commit Sep 22, 2026
e4962e6
into
claude/mathspec-piecewise-api-7j0wpt
5 checks passed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Note
The following content was generated by AI.
What this changes
Stacked on #569. Every check on a
piecewise:block has one home, and the emitters decide nothing: they write rows from facts a singlechecksettled. No model loads or refuses differently, and every emitted row is byte-identical.Three layers, in the order a file passes them:
PiecewiseBlock's validators, as beforeSpec._piecewise_shapesandSpec._piecewise_emitted_names, beside thesos:checks in_validate_referencesalong,dims:, each link's values parameter, the relation and columns a walk names, the dimension a split spans, the gate; and that no name the expansion writes is one the file declarespiecewise.check, returning aCurvewhere:fits the frameWhat went.
_Expansion, a class of thirty methods that interleaved emitting and checking, computed each link's row frame three times, and parsed every link expression and the gate itself. The link expressions and the where are now read offSpec.resolved, which types them with the rest of the model, so_expr_dims,_dims_of, its cache, the second parse of the gate, and the module's imports of the parser, the resolver and the degree check are gone.CurveMasktakes the resolved mask rather than resolving text, so it holds no namespace. The emitters are four functions over aCurve.Load order.
Spec._validate_expressionsresolves the file before expanding it, where it expanded first. A fault in a link is still named against the link the file wrote, by resolution's own context (piecewise 'x' link 0), and the guard tests assert that.Error classes, on the alpha stream. A refusal about what a block names by key, or a collision, is now a
SchemaErrorwith the file's other cross-declaration refusals, and a fault in a link expression is theSchemaErrorresolution raises, with the same wording.PiecewiseExpansionErroris left for what only the typed model can decide: a link that does not fit its row, or a where outside the frame. Six tests that pinnedPiecewiseExpansionErroron those cases pinLanguageError, the base of both, so their message assertions stand.What each moved refusal is still covered by
Every message is preserved verbatim, and each has a test matching on it:
undeclared parameter 'nope'test_a_malformed_block_is_refused[undeclared-parameter]must be binary,is not a declared variable…[activity-must-be-binary],test_a_gate_that_is_not_a_variable_is_refusedemitted sos 'cost_curve' collides,emitted assumption … collidestest_an_emitted_set_may_not_collide_with_a_declared_one,test_a_condition_a_method_states_is_a_name_the_block_emitsreferences undeclared relation,has no column for,over and into both reach…[a-walk-through-an-undeclared-relation],…[a-walk-that-does-not-name-both-ends]and the refinement tabledims carries,dims repeats,into names '…', the breakpoint dim…[splitting-along-the-breakpoint-dim]and the frame tableover variables,the divisor contains variables,which is degree 2,a dual exists only after a solve)LanguageError, still assertingpiecewise 'cost_curve' link 0in the messageOne new test:
test_every_assumption_a_block_may_derive_is_a_name_it_reserves. The collision check runs before the mask that decides which assumptions are written is typed, soNamesreserves all five suffixes inASSUMED, and the test holdsassumptions_ofto that list for every method.Gates, and what could not be run
pytest -q -n autoruff check,ruff format --checkpyrefly checkmissing-importfor pydantic and yaml stubs from the venv layout, the same count as the baseprettieron the one edited pagetypos,reuse lint,taplo,zizmor,compile-tex,mkdocs build --strictRun from a
uvenvironment on Python 3.12 with the pinnedruff==0.16.1andpyrefly==1.2.0, because this environment's proxy refusespixi.sh.piecewise.pyis 722 lines, from 786. The diff as a whole adds 53 lines, because the key checks that moved intomodel.pycarry their own docstrings there; what fell is the concept count, not the character count, and that is the trade this PR makes.Deliberately not done
PiecewiseBlock. They need no schema, and pydantic runs them before any cross-declaration rule, which is the narrowing-beforehand asked for.sos:'s checks are not touched. They already sit where the piecewise ones now do.Why
The expansion had grown into one class where every emitter re-derived facts and every fact carried a check. Reading a row meant reading the checks around it, and a check could not be found without knowing which emitter needed it. Deciding everything first, in one place per layer, is what lets each emitter be read on its own.
🤖 Generated with Claude Code
https://claude.ai/code/session_01GNZN57CR8oGTVpRmpXF8YZ
Generated by Claude Code