Skip to content

refactor(language): the front end is a package, and the fence is a test - #367

Merged
FBumann merged 1 commit into
docs/architecture-diagram-accuracyfrom
refactor/language-package
Jul 31, 2026
Merged

refactor(language): the front end is a package, and the fence is a test#367
FBumann merged 1 commit into
docs/architecture-diagram-accuracyfrom
refactor/language-package

Conversation

@FBumann

@FBumann FBumann commented Jul 31, 2026

Copy link
Copy Markdown
Owner

Stacked on #363 (docs base) — that PR is doc-only, so review this one's second commit.

Why

Ten modules — from the file reader to the dim checker — never reach forward to a consumer of the AST they produce. I checked every from lpspec… edge including TYPE_CHECKING blocks: none of them imports lowering, piecewise, sources, api or relational.

That was true by habit and unenforced. Hard rule 0 claims "the layers are ordered, and imports prove it", but only the lazy-import half is checked (DELIBERATE_LAZY_IMPORTS). Nothing stopped expression_parser.py from importing lowering.py next month, and the doc would have quietly become wrong.

What

Nine modules move to src/lpspec/language/, and membership is read off the path the way relational/, linopy/ and typeset/ already are:

language/_yaml.py  schema.py  helpers.py  expression_parser.py
         where_parser.py  expansion.py  resolution.py
         dimensions.py  validation.py

test_language_never_reaches_a_consumer is the fence. It is the mirror of hard rule 2: the engine may not see the schema or the AST; the language may not see what is done with the AST it produces. What a model means cannot depend on what any consumer does with it — which is what keeps lps.check() a pass with no data and no plan, and a second consumer cheap rather than a second opinion.

What deliberately did not move

  • errors.py — the dependency-free leaf both fences point at. LANGUAGE_MAY_IMPORT = ENGINE_MAY_IMPORT, and moving it under language/ would put the language's import path on the engine's allowlist, which is exactly the near-zero import surface rule 2 protects.
  • lowering.py, piecewise.py, sources.py, api.py — each is legitimately on the line. piecewise.py is the interesting one: it emits declarations (language) but consults check_core_subset to do it (plan), which is also why it is the one declared lazy-import edge. The flat top level is where a both-sides module belongs.

No behaviour change: imports and paths only. git recorded all nine as renames.

Docs

docs/ARCHITECTURE.md: module map paths, the pipeline diagram's edge labels, hard rule 1 gains the closed-from-the-front clause, and the closing paragraph now says four subpackages — it said "Two subpackages" while listing three, which this makes worth fixing rather than worse.

Test plan

  • uv run pytest — 701 passed, 4 skipped, 1 xfailed
  • uv run ruff check . / ruff format --check . — clean (the N806 per-file-ignore for where_parser.py moved with it)
  • uv run pyrefly check <src files> — 0 errors
  • uv run mkdocs build --strict — builds

🤖 Generated with Claude Code

The ten modules from the file reader to the dim checker never reach forward
to a consumer — no `lowering`, no `piecewise`, no `sources`, no `api`, no
`relational`. That was true by habit and unenforced: hard rule 0 claims "the
layers are ordered, and imports prove it", but only the lazy-import half was
ever checked, so nothing stopped `expression_parser.py` from importing
`lowering.py` next month.

Move nine of them under `src/lpspec/language/` and enforce membership off the
path, the way `relational/`, `linopy/` and `typeset/` already are. It is the
mirror of hard rule 2: the engine may not see the schema or the AST, and now
the language may not see what is done with the AST it produces. That is what
keeps `lps.check()` a pass with no data and no plan, and a second consumer
cheap rather than a second opinion.

`errors.py` stays at the top level on purpose — it is the dependency-free
leaf *both* fences point at (`ENGINE_MAY_IMPORT`), and moving it in would put
the language's import path on the engine's list. `lowering.py`, `piecewise.py`,
`sources.py` and `api.py` stay too: each is legitimately on the line between
the halves, and the flat namespace is where that belongs.

No behaviour change — imports and paths only.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: d7398053-b853-47f7-8921-8d842ee25ae8

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@read-the-docs-community

Copy link
Copy Markdown

Documentation build overview

📚 lpspec | 🛠️ Build #33847054 | 📁 Comparing 56bb8f1 against latest (04dd46c)

  🔍 Preview build  

2 files changed
± ARCHITECTURE/index.html
± changelog/index.html

@FBumann
FBumann merged commit 85538e2 into docs/architecture-diagram-accuracy Jul 31, 2026
4 checks passed
@FBumann
FBumann deleted the refactor/language-package branch July 31, 2026 10:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant