Conversation
…ntraction Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SWBcNGLjNH2i4AqRsfyxaN
…on and groups by The program's `Direction` is now `Join`, with `joined` and `grouped` roles in place of `consumed`, `produced` and `joined`; `Pullback` is `Lookup`, a join with no group-by; a partition's `group` is `grouped`. Every refusal, docstring and page says join and group-by in place of consume, produce and land on. The YAML surface is unchanged. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SWBcNGLjNH2i4AqRsfyxaN
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SWBcNGLjNH2i4AqRsfyxaN
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SWBcNGLjNH2i4AqRsfyxaN
Main's new uses of Direction, Pullback and a partition's group take the renamed names. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SWBcNGLjNH2i4AqRsfyxaN
`GroupSum` and `Lookup` are one node, `Join`, and `sum(by=)` lowers to the existing `Sum` over it, its `over` the dims the join drops; `at` lowers to the bare `Join`. The columns a call names move to `JoinColumns`. The YAML surface is unchanged. Claude-Session: https://claude.ai/code/session_01SWBcNGLjNH2i4AqRsfyxaN Co-authored-by: Claude <noreply@anthropic.com>
The duplicate-keyword refusal, the name-or-list read, a translation's step count, a raw-model section and the predicate union each have one home. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017XwKgY5wZXv1bKgkfCW2q1
… grammar's nodes UnresolvedNameNode was NameNode and QuotedNode was KeywordNode under other names. A predicate call's kwargs are now typed as what they hold. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017XwKgY5wZXv1bKgkfCW2q1
… call site is The template check was a second walker that looked at names only. It is now the resolver, with the template's formals left bare, so a label parameter used as a value or an unknown relation column is refused at load. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017XwKgY5wZXv1bKgkfCW2q1
…n is A format's glossary() returns the rows and section() sets the title, so the Glossary class and three copies of each heading go. cases_row leaves the Format protocol, since only a format's own cases() read it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017XwKgY5wZXv1bKgkfCW2q1
… after lowering ArithmeticComparison was ExpressionComparison with its sides still in the core syntax tree. ExpressionComparison now takes its side type as a parameter, and program.ArithmeticComparison is gone. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017XwKgY5wZXv1bKgkfCW2q1
… use reads that node Namespace.named resolves an expressions: entry the first time anything reads it. Expansion inlines that node, and the resolver passes it through. Expansion no longer parses named expressions, the resolver no longer re-reads a cased expression's arms, and validation drops its deduplication of repeated arm errors. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017XwKgY5wZXv1bKgkfCW2q1
…yped tree `resolve_expression_text` in resolution.py is what validation.py's `_check_expression` was, and resolution's `_value` was the same function with `comparison=False, ceiling=None`. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DwZZXWXoJfSMabvXTUndtn
…e expansion only writes rows `Spec` computes `resolved` before it expands, so the expansion reads each link's typed tree instead of parsing the text again. The names a block references and the names it emits are checked with the other `Spec` reference rules, and its frame in `curve_frame`, which the typesetter reads too. `PiecewiseExpansionError` is gone: a block is refused as a `SchemaError` or a `DimensionError` like every other declaration. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DwZZXWXoJfSMabvXTUndtn
`piecewise.assumptions_of` returns `AssumptionBlock`s, which is what it made from its `Assumed` tuples; `Resolved.assumptions` holds `program.Holds`, which `ResolvedAssumption` had the fields of; the `Assumption` alias of `Holds` and the `LinkSign` alias of `ComparisonOperator` are gone. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DwZZXWXoJfSMabvXTUndtn
…s own nodes Resolution builds `program.Expression` nodes from the syntax tree, so the dim rules, the degree rules, the exclusivity check, the typesetter and lowering read one vocabulary. The parser keeps its eight syntax nodes; the ten typed ones, the node groups and the assertion arms that policed them are gone, and lowering packages declarations with each `Named` use of an `expressions:` entry inlined. The form of an `offset=`, `window=` and `edge=` is decided where it is read, in resolution. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DwZZXWXoJfSMabvXTUndtn
… sentence Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DwZZXWXoJfSMabvXTUndtn
`program.Holds` is renamed `Assumption`: the file's section is `assumptions:`, every other declaration class is a noun, and the alias that carried the noun stood in for kinds that never came. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DwZZXWXoJfSMabvXTUndtn
…ther each group is one row Merge #638's head into #605. Resolution builds program nodes, so every pass after it computes a frame from one node at a time. A Sum over a Join has no frame of its own where the join drops and adds one dimension, as rep_of does, so both calls lower to one Join whose frame is (operand - joined) | grouped. JoinColumns.one_row_per_group tells a lookup from a sum, for fan_in, degree, separability and the typesetter. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013M6RG2cnsrFCEbaUuU8KyJ
Documentation build overview
31 files changed ·
|
FBumann
added this pull request to stack #637
September 23, 2026 08:33
FBumann
marked this pull request as draft
September 23, 2026 08:33
…in opens A Join keeps each column it joins on and does not group by as an axis of its own, named relation.column, so a column dropped and a column added over one dimension stay two axes. sum(by=) lowers to a Sum over those axes, and at() to the bare Join, which opens none because its groups are one row. Each node's frame is its own again, and a reduction other than a sum can follow the same join. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013M6RG2cnsrFCEbaUuU8KyJ
FBumann
force-pushed
the
claude/mathspec-packages-review-urd4ya
branch
from
September 23, 2026 11:26
3270e48 to
f2675a2
Compare
FBumann
removed this pull request from stack #637
September 23, 2026 11:30
Merged against the tree this branch was written on, the head of #638 before its rebase, so the conflicts were the four places #643 and the join touch the same lines: fan_in reads through a Named and then asks a Sum, the relation read builds a join, and the dim tests carry the error class and the join's wording. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015h57WkBDnpxrknuJ5zZy9F
FBumann
changed the base branch from
claude/mathspec-packages-review-urd4ya
to
main
September 23, 2026 11:40
This was referenced Sep 23, 2026
FBumann
pushed a commit
that referenced
this pull request
Sep 23, 2026
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PHNF149r8B4tGmGUKmAdUd
FBumann
pushed a commit
that referenced
this pull request
Sep 23, 2026
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PHNF149r8B4tGmGUKmAdUd
Contributor
Author
Note The following content was generated by AI. Closed as superseded. #664 carries this PR's change, moved onto Generated by Claude Code |
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.
#605's join and group-by, on #638's resolver. A
Joinopens an axis namedrelation.columnfor each column it drops.sum(by=)is aSumover those axes, andat()is the bareJoin. Each node's frame is its own. #605 is unchanged.What this changes
The branch starts at #605's head (
53ba4a2) and merges #638's head (3270e48). The diff against #638 is #605's change, carried onto the resolver that builds program nodes. It keeps #605's two nodes and adds one thing: the axes a join opens.Direction,GroupSumandPullbackare gone.Join(operand, columns)is the one relation node.JoinColumnsholds thejoinedandgroupedroles, withdropped,added,kept,one_row_per_groupandaxesderived.zone_of.generator, and not for its dimension. A dimension name holds no dot, so an axis never meets one. The spelling is the one awhere:already reads a column with.at(x, by=R, over=a, into=b)lowers to the bareJoin.sum(x, by=R, over=a, into=b)lowers toSum(Join(x, cols), over=('R.a',)).Sum.overnames dimensions, or the axes of theJoinunder it.fan_in(Join)is one-to-one andSumis many-to-one, anddegreecountsSumandWindowSumas reductions. Separability reports aSumover a join's axes as a grouping, and a bareJoinas a coordinate read. The walk prints aSumover a join's axes with the domain condition, and a bareJoinat the leaves.PulledBackPredicate.directionis.columns.dimensions.pulled_back_dimsisjoin_dims, one frame rule for an expression and a predicate.Partition.groupisPartition.grouped, as in feat(language): a relation call is a join on the columns it names, and a sum through a relation is a sum over that join #605.contributing.mdnames the axes in its node table, andreading.mdnamesJoinColumnsonPulledBackPredicate.Why
#638 makes
dims_of, the walk and separability read program nodes, one node at a time. #605'sSum(Join(x), over=dropped_dims)names the join's dropped columns by their dimension, and two cases break:representativesums throughrep_of: { key: snapshot, values: { rep: snapshot } }. The join dropssnapshotand adds it again, so the two columns share one name. TheSumoversnapshotthen takes away the dim the row keeps. With the axis, the join gives{snapshot, rep_of.snapshot}and the sum gives{snapshot}.sum(at(q, by=zone_of, over=zone, into=u), over=u)has feat(language): a relation call is a join on the columns it names, and a sum through a relation is a sum over that join #605's grouped-sum shape. Separability then reports "groups u into u" and drops the lookup's read ofzone. Now it is aSumover the dimu, and a grouped sum is aSumoverzone_of.u, so the names tell them apart.With the join and the reduction separate again, another reduction can follow the same join. A
Meanover a join's axes is one new node. It is not in this PR.Guards, tests, gates, what was not done
Guards and the tests that fail without them
Each guard was deleted in turn, the suite run, and the tree restored.
join_dims' frametest_dimensions.pyframe cases, and the golden and gallery pagestest_dim_inference[its-lookup-keeps-the-joined-key-too],[a-from-list-reads-two-value-columns-at-once]test_docs.py, and the golden filestest_a_grouping_that_sums_the_axis_away_couples_itJoinas a lookuptest_a_grouping_that_sums_the_axis_away_couples_it, after it gained thecoupledandundecidedasserts; before, this guard survivedtest_a_join_opens_an_axis_for_the_column_it_drops_and_the_sum_over_it_closes_itI checked two things against #605's code in a separate worktree:
test_a_sum_over_a_lookup_is_a_sum_and_a_lookup_not_a_groupingfails there with'groups u into u — …' != 'sums over u — …'.test_dim_inference[a-map-into-its-own-dimension-keeps-the-frame]is the one dims test that fails.Degree has two new cases: two lookups multiplied are one term each, and a grouped sum is a reduction.
No guard refuses a join axis in a declaration's frame. Resolution always builds the
Sumover a groupingJoin, so a file cannot reach one.Coverage that moved
tests/test_lowering.py: the lowering cases, the zonal and history asserts, and the fan-in table expectSum(Join(...), ('relation.column',)).tests/typesetting/test_golden.py: the operator census readsatoff aJointhat opens no axis.CARRIERSnamesJoinColumns.tests/test_validation.py: feat(language): a where may read a predicate through a relation with at() #634's two predicateat()refusals assert feat(language): a relation call is a join on the columns it names, and a sum through a relation is a sum over that join #605's wording ("joins on", "groups by").Gates
Pixi is not reachable here (
pixi.shrefused), so the gates ran from a uv venv on Python 3.12 with the pinnedruffandpyrefly.pytest -q -n autoruff check,ruff format --checkpyrefly checktools.schema,tests.typesetting.goldentests/test_docs.py)typos,reuse lintprettier --checkon the changed pagesmkdocs build --strictdocs.python.orginventory dropped for the run (the proxy refuses it)compile-tex,zizmorNo check runs are on this PR. The workflows may not run on a base branch other than
main.History
1e4282dmerges refactor(language): an expression resolves straight into the program's own nodes #638 into feat(language): a relation call is a join on the columns it names, and a sum through a relation is a sum over that join #605 with the one-node design.0455e93replaces that design with the join axes. The PR diff against refactor(language): an expression resolves straight into the program's own nodes #638 is the second design alone.3270e48, not the earlier540d412. The two trees differ only inCHANGELOG.mdand the release manifest, and this merge takes refactor(language): an expression resolves straight into the program's own nodes #638's copies of those.Not done
Mean, or any reduction butSum. The design admits it. No file writes one yet.GroupSum,PullbackandDirectiontoday. With this shape it runs the join for aJoin, and the terminal group-by for theSumover its axes.🤖 Generated with Claude Code
https://claude.ai/code/session_013M6RG2cnsrFCEbaUuU8KyJ