Conversation
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…no two regions claiming one coordinate Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`examples/commitment.yaml` is the formulation #2 factors. The state a unit carries into a snapshot has three regimes — a unit that is never off, the first snapshot, and every later one — and writing them at the constraint would fork `ramp_up` three ways. With the regimes named once, the inequality is written once. `tools/render_tex.py` picks it up like every other model, so the LaTeX gate compiles it, and it gets a page in the example gallery. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Documentation build overview
25 files changed ·
|
This was referenced Aug 29, 2026
FBumann
force-pushed
the
claude/the-waist-as-a-value
branch
from
August 30, 2026 07:13
0248207 to
a7be014
Compare
FBumann
force-pushed
the
claude/what-counts-as-a-verb
branch
from
August 30, 2026 07:16
81ae378 to
27407cd
Compare
Resolved import-block conflicts in lowering, program and test_lowering. boundedness now walks program nodes, so the Cases arm is ported from CasesNode to program.Cases. `_lower_where` is `resolution.where_of` on main, which folds an always-true mask to None, so the exclusivity tests build their masks the way validation does, unfolded.
…outside the cases (#251) The value wherever no `when` holds moves out of `cases:` and becomes the block's own `otherwise:`, so no name inside `cases:` is reserved and the cases carry no order. Five hand-written errors policing `default` are replaced by the closed schema's own: a required `otherwise:`, a required `when:` on every case, and `min_length=1` on `cases:`. The fallback's dims are checked where the cases' are, which the loop over `cases:` used to give it for free.
… is set as one the solver decides (#252) * fix(typesetting): a cased expression whose otherwise holds a variable is set as one the solver decides Reading only `cases:` for what a block is left the fallback out, so a quantity whose only variable sits there printed upright — the notation for something the model is handed rather than something it solves for. `previous_status` in the commitment example is exactly that shape, and its generated page disagreed with the hand-written one in the language reference. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XJMiQWNo9b5VD3Aac5gTXX * fix(language): an error inside a case names the expression that declares it, and is printed once A cased expression is expanded where its name stood, so a fault in one of its arms was reported against the constraint that pulled it in — as `Constraint 'ramp_up', case 'boundary'`, a case on a constraint that has none, once per constraint naming the expression. The arm now names its declaration, and the arm with no `when` is named `otherwise` rather than a case, which is what every other check already calls it. Two errors that differ at all carry different contexts, so an exact repeat is one fault seen twice and is printed once. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XJMiQWNo9b5VD3Aac5gTXX * fix(language): two integer bands with no integer between them are proved apart The cells for an ordered subject put a representative in every gap between the literals a pair of masks names, and the midpoint it chose was one no integer can be. So `n < 1` against `n > 0` on an `int` was refused with a witness of `n is 0.5` — a coordinate the data cannot produce, naming a rewrite the file had already made. A date was already handled this way; an integer is discrete for the same reason and now says so. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XJMiQWNo9b5VD3Aac5gTXX * test(exclusivity): the soundness fuzz draws two masks apart rather than one and its negation Every pair it generated was a complement by construction, so the grid assertion was `X and not X` — false at every point, under every implementation, for every set of cells. Collapsing each subject to a single cell, which is the most a cell-coverage bug could ever be, left both seeds green while nine other tests in the file went red. Drawn independently and filtered to the pairs the check proves apart, the same mutation now fails it 1295 times over. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XJMiQWNo9b5VD3Aac5gTXX * chore: drop an unreachable lookup guard and the default spelling the otherwise rename left behind `_lower_expression` looked its name up in the very mapping its only caller iterates, so the `KeyError` and its `did_you_mean` were a branch nothing reaches — and the one place in the file raising something other than `LanguageError`. Deleting it leaves the suite green, which is what the guard was worth. Beside it, five docstrings and a test name still called the fallback `default`, which the language no longer has. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XJMiQWNo9b5VD3Aac5gTXX --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
…o whichever engine reads the data Three facts decide which model a file and a table make together — where a dimension's members come from, the order they stand in, and that a coordinate appears at most once. Each was real and each was written down in a consumer, so a second consumer would have inherited none of them. Co-Authored-By: Claude <noreply@anthropic.com>
…oordinate, so a row lost in preparation is not read as a mask A table short of a coordinate and a table that never had one are identical in the data and mean opposite things. `coverage:` says which was meant, and a parameter declaring itself a mask is refused as a bound or a divisor — the two positions where absence has no reading — before any data arrives. Co-Authored-By: Claude <noreply@anthropic.com>
…library is templates rather than generated YAML merge takes the fragments and hands back one mapping to validate, resolve and lower exactly once. A fragment is merged before it is validated, so a template may name what a sibling declares without being a model on its own; the coordinate space is shared where fragments agree, the math is owned, and the objectives are summed. Co-Authored-By: Claude <noreply@anthropic.com>
…g being a path rather than a second convention merge read a string as YAML text where to_spec reads one as a path, so the front door and the composition disagreed about what a caller had handed over. A composed model is also held to round-tripping through to_yaml and printing in all three formats: composing may not reach a model no reviewer can open. Co-Authored-By: Claude <noreply@anthropic.com>
…one document they print The gallery showed one model per page, which a composition has none of. It now shows each fragment as written and then the document the merge prints, so the claim a reader checks is that four small files and one model are the same thing — and that the balance is the row it was before storage was added. Co-Authored-By: Claude <noreply@anthropic.com>
…k writes them The new files were the only YAML in the stack the formatter had not already written, and the check that missed it globbed markdown alone where the hook globs md, yml and yaml alike. Co-Authored-By: Claude <noreply@anthropic.com>
…are not models and say so render_tex renders every file under examples/, and a fragment naming what a sibling declares is a load error alone — which is the thing examples/composed/ exists to show. The model they make is rendered by the gallery and typeset in all three formats by the merge tests, so nothing goes unrendered. Co-Authored-By: Claude <noreply@anthropic.com>
… so a port nobody wired is not a term that vanishes A map short of a label lands its terms in no group at all, which is a shape a model wants and a wiring mistake in equal measure. `coverage:` says which, and a composed model declaring its coupling map total turns the second from a plausible answer into an error. The vocabulary is now one Coverage, shared with the parameter key it repeats. Co-Authored-By: Claude <noreply@anthropic.com>
…ge's surface The language has a test for what belongs in it and the ceiling has one for what may enter it; the API had neither, which is how a small surface grows a verb per feature. A verb decides nothing the language has not stated and needs nothing but the file, and the three properties it keeps are pure, total at load, and closed under composition. merge lost the third and nobody noticed: a lone objective came back parenthesised, so composing one fragment differed from the fragment and every nesting added a pair. It is returned as written now, and the idempotence and associativity the page argues from are pinned by tests. Co-Authored-By: Claude <noreply@anthropic.com>
…second consumer needs no second parser A consumer in Python imports the program; one in anything else had a single route to the same model, which was implementing the language again. to_json and from_json make the waist a value instead: everything in a program is resolved, so the document carries no grammar and a reader dispatches on a tag. The encoding is reflective, so a node added to the program is serialisable the day it exists, and the node fence already refuses a node no file reaches — the round trip therefore covers every node by construction. Reading is closed: a tag names a class in a registry or the document is refused, and a wire version this release does not know is refused by number rather than guessed at. Strict JSON, which is not free: an unbounded variable's bound is an infinity, so every model with a one-sided bound would otherwise have written Infinity and stopped a conforming reader. Co-Authored-By: Claude <noreply@anthropic.com>
FBumann
force-pushed
the
claude/what-counts-as-a-verb
branch
from
August 30, 2026 18:19
27407cd to
ab21495
Compare
FBumann
force-pushed
the
claude/the-waist-as-a-value
branch
from
August 30, 2026 18:19
a7be014 to
c1d5a2e
Compare
Contributor
Author
|
Not in scope for now |
FBumann
added a commit
that referenced
this pull request
Aug 31, 2026
…at a time, before a driver cuts one A rolling horizon or a myopic pathway asks one thing of a model before it starts: would windowing change the answer? separability reports the overlap two windows need and, where none would do, names each declaration and the construct tying the axis together. A reduction means opposite things by position — in a constraint a sum ties every window to every other, in the objective it is additively separable. Squashed from the four commits of claude/separable-along and rebased onto main alone, dropping the closed #247 serialisation base it was stacked on. Co-Authored-By: Claude <noreply@anthropic.com>
FBumann
added a commit
that referenced
this pull request
Aug 31, 2026
…bproblem is a call rather than a second file A myopic pathway, a rolling horizon and a Benders subproblem share one move: a variable stops being a decision and becomes a number somebody else chose. fix takes every name in one call and validates once at the end. Two translations are decisions, not copies: a where-masked variable becomes coverage: masked, and a binary or integer variable becomes an int parameter — never a float, never a bool that would read as a mask. Rebased onto the parameter-coverage branch (#243) it needs for Coverage, off the closed #247/#248 stack it was authored on; only the fix additions are kept. Co-Authored-By: Claude <noreply@anthropic.com>
FBumann
added a commit
that referenced
this pull request
Sep 2, 2026
…a time, and what each coordinate needs from its neighbours (#374) * feat(program): a model says whether a horizon may be solved a window at a time, before a driver cuts one A rolling horizon or a myopic pathway asks one thing of a model before it starts: would windowing change the answer? separability reports the overlap two windows need and, where none would do, names each declaration and the construct tying the axis together. A reduction means opposite things by position — in a constraint a sum ties every window to every other, in the objective it is additively separable. Squashed from the four commits of claude/separable-along and rebased onto main alone, dropping the closed #247 serialisation base it was stacked on. Co-Authored-By: Claude <noreply@anthropic.com> * feat(program): a model says whether an axis may be built a window at a time, and what each coordinate needs from its neighbours One dataclass and one attribute beside the Footprint the program already answers: program.separability[dim] says how far a row reads behind and ahead along the axis, which declarations tie it together and the modelling change that would lift each, which reaches only data can decide and the parameter or lookup that decides them, and which masks count a position a window would restart. windowable and independent are the two verdicts a rolling-horizon driver and a scenario sweep ask before cutting a slice. Carries #248 rebased onto alpha.67, where a mask is a Mask and its leaves come off Mask.atoms rather than a walk of its own. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FPBsP3UM6esCBREVmjMEhZ * refactor(program): an undecided reach says what it stands as, and the verdict folds a parameter's values in Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NgEyTfEbZzdTFavr43bRgr * refactor(program): a verdict reports only what a window reads ahead, and nothing reads it for a scenario sweep Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NgEyTfEbZzdTFavr43bRgr --------- Co-authored-by: Claude <noreply@anthropic.com>
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
Two verbs, and the API stops being Python-shaped.
A consumer written in Python imports
Programand is done. A consumer written in anything else has had exactly one route to the same model: implement the language a second time — a second parser, a second name resolution, a second dim algebra. That is the one thing hard rule 1 exists to forbid, and shipping the waist only as importable Python quietly guaranteed it.Everything in a program is already resolved, so the document carries no grammar, no expression strings and nothing to parse. A reader dispatches on a tag and builds a node.
Why this and not another verb
The companion page in #246 argues that capability here is measured on the far side — what a second consumer can build, not what a caller can call — and names this as the largest capability the API can gain, precisely because it is not a feature. One serialisation, and every non-Python consumer becomes possible without a line of language reimplemented. Nothing else on any list multiplies like that, and it adds nothing a caller must learn.
This PR is that argument cashed. It also settles the decision I flagged as needing to be made before there are readers, not after: what a consumer does with a document it does not understand.
Safe and predictable, concretely
Reading is closed. A tag names a class in a registry built by reflection over the two modules that define nodes. Nothing is imported, evaluated, or constructed by name from the document — a hostile document can name a node that does not exist and get an error, and cannot name anything else at all.
Refusal is by name, never by guess.
WIRE_VERSIONis the format's own and moves when the encoding changes, not when the language does — the two version things that would otherwise be conflated.The encoding is reflective, so it cannot go stale. A node added to the program is serialisable the day it exists, with no table to update. And
tests/test_program_nodes.pyalready refuses a node no file lowers to, so the round trip covers every node by construction rather than by a list somebody keeps in step.It is exact, not approximate. Round-tripped against the every-node fixture and every model in
examples/— equality on theProgram, field for field, because a consumer trusting this document instead of the language has nothing to compare a subtly different field against.The landmine, which is not exotic
allow_nan=False, and it costs something real. An unbounded variable's bound is-inf— so every model with a one-sided bound contains a non-finite float, and the obvious encoding writesInfinity, which is Python's extension and not JSON. A reader in a language with a strict parser would have stopped on the first realistic model.Non-finite floats are written
{"$": "float", "value": "-inf"}, and there is a test per shipped model asserting noInfinityorNaNappears and thatjson.loadsaccepts it.What lpspec removes or changes
Nothing today, and something worth having tomorrow.
Nothing, because
lpspecimportsmath_specand reads theProgramdirectly; this adds a route it does not need.Worth having, because the two lanes are differentially tested against each other and both consume the same resolved AST — which
ceiling.mdalready names as the gap: "a shared misreading passes the differential suite green." A document is what lets a third consumer, in another language, be compared against both. Theexamples/ports/corpus exists for that class of bug; this is what would let it be checked from outside Python.It is also what a Julia or Rust reader needs before it can exist at all, which is the adoption question rather than a code one.
Verified
Toolchain reconstructed at the versions
pixi.tomlpins:ruff format --check/ruff check(0.16.1, pinned) clean;pyrefly(1.2.0, pinned) 0 errors;pytest -q885 passed, 0 skipped (858 on the base — 27 new).reuse,typos,prettier --check,render_tex(27 models) andmkdocs build --strictwere run on the top of the stack (#250), which contains these commits.The new
reading.mdsection is executed, not just written:tests/test_reading_page.pyruns every Python block on that page against a real model and checks every# valueline. The round-trip claim there is one of them, which is why the page's claim count moved from 7 to 8.Not run:
zizmor,taplo(neither's files changed), and thetectonichalf ofcompile-tex.Mutation table
Each guard broken in turn on a clean tree, restored with
git checkout --:InfinityleaksThe last one is the reason the round trip asserts equality rather than resemblance: a
listwhere atuplebelongs compares unequal and changes nothing you would see by reading a document.Deliberately not done
No conformance corpus shipped as a fixture directory. The round-trip tests are the corpus, and committing generated documents beside them would be a second copy to regenerate — better once there is an external consumer to hand them to, which is when their shape will be known.
No schema for the document. A JSON Schema of the wire format is derivable from the same reflection and would be the natural next thing for a non-Python reader; it is not needed to write one, and guessing at its shape before a reader exists is what this repository calls YAGNI.
WIRE_VERSIONis 0 and the language version is untouched. They answer different questions and this PR deliberately does not couple them.Changed in review.
_registry()is@cached and_decodeno longer threads it through every recursive call — the reflection is per-release, not per-node, and the parameter was six mentions buying nothing. The one thing that did not survive the same pass: folding_encode's inlineisinstance(value, tuple) and hasattr(value, '_fields')into_is_named_tupleloses the narrowingpyreflyneeds to acceptvalue._fields, so the apparent duplication is earning its keep and stays.Stack: sixth, on #246 → #245 → #244 → #243 → #242 → #168.
Generated by Claude Code