Rule 6 refuses a Python modeling API. Two separate features now need something that emits math from Python, and docs/design/ceiling.md has been carrying the question as an aside in the Composition section:
Should anything ever be blessed, it is the schema level and not the plan level — that much is settled; what is not is whether to bless the seam at all.
One feature depending on an unanswered question is a footnote. Two arriving independently is the signal to answer it.
The two callers
Composition (#29, #30). Component libraries need namespacing and schema merge. Whatever genuinely is not data — variable port counts, runtime-unknown component types — belongs in a layer emitting more rows or more templates. That layer has nothing supported to call. A seam already exists in practice: api.load_schema accepts dict | MathSchema, so a programmatically built model already goes through validation, expansion, resolution and dim checking. It is undocumented and unversioned, which means it will be depended on and then broken.
Decomposition (ROADMAP Track 2d, #204, #39). Benders needs someone to write the cut. The cut is generated from duals by an algorithm; it is not sayable in YAML and should not be.
The distinction that may split them
These look like the same request and might not be, which is the first thing to settle:
| Caller |
Emits |
At what level |
Needs a name in the language? |
| Composition |
declarations — variables, constraints |
schema (MathSchema) |
yes — they are the model |
| Decomposition |
affine rows — (row, col, coeff) |
matrix, post-lowering |
no — a cut is anonymous by nature |
If that holds, decomposition does not force the schema seam. It forces a much narrower one: append COO rows to a live session, with no declaration, no name, no read-back obligation. That is a smaller thing to bless and it does not touch the language at all — arguably it is a sink capability rather than a seam.
Settling this is most of the work. It is possible the answer is "bless the narrow one, keep refusing the wide one".
The options, for the wide case
A — never bless. Ship a driver per use case: a decomposition driver, a component-library builder, each reading and writing internal structures with no public contract. Keeps rule 6 absolutely. Costs us a new shipped feature every time someone has a shape we did not anticipate, which is the failure mode rule 6 exists to avoid in the other direction.
B — bless a narrow, versioned schema emitter. Not a modeling API: no expression building, no fluent interface, no Model object. A documented way to hand load_schema a structure, versioned so it can change. The thing that already works, made real.
C — bless nothing, document the refusal. State that programmatic declaration is out and that generated YAML is the supported answer despite ceiling.md forbidding it. Honest but contradicts the current text; listed for completeness.
What decides it
Rule 6's purpose is that the model is the file — one artifact, readable, diffable, validated. Option B does not violate that if what it emits is a MathSchema that could have been written as YAML. It violates it if what it emits could not. That test — is the emitted thing sayable? — is probably the actual line, and it is checkable rather than a matter of taste.
Related: #38 (islands are the other thing that runs user Python, and are bounded rather than blessed), #67 (a version: key, which is the same versioning problem one level up).
Rule 6 refuses a Python modeling API. Two separate features now need something that emits math from Python, and
docs/design/ceiling.mdhas been carrying the question as an aside in the Composition section:One feature depending on an unanswered question is a footnote. Two arriving independently is the signal to answer it.
The two callers
Composition (#29, #30). Component libraries need namespacing and schema merge. Whatever genuinely is not data — variable port counts, runtime-unknown component types — belongs in a layer emitting more rows or more templates. That layer has nothing supported to call. A seam already exists in practice:
api.load_schemaacceptsdict | MathSchema, so a programmatically built model already goes through validation, expansion, resolution and dim checking. It is undocumented and unversioned, which means it will be depended on and then broken.Decomposition (ROADMAP Track 2d, #204, #39). Benders needs someone to write the cut. The cut is generated from duals by an algorithm; it is not sayable in YAML and should not be.
The distinction that may split them
These look like the same request and might not be, which is the first thing to settle:
MathSchema)(row, col, coeff)If that holds, decomposition does not force the schema seam. It forces a much narrower one: append COO rows to a live session, with no declaration, no name, no read-back obligation. That is a smaller thing to bless and it does not touch the language at all — arguably it is a sink capability rather than a seam.
Settling this is most of the work. It is possible the answer is "bless the narrow one, keep refusing the wide one".
The options, for the wide case
A — never bless. Ship a driver per use case: a decomposition driver, a component-library builder, each reading and writing internal structures with no public contract. Keeps rule 6 absolutely. Costs us a new shipped feature every time someone has a shape we did not anticipate, which is the failure mode rule 6 exists to avoid in the other direction.
B — bless a narrow, versioned schema emitter. Not a modeling API: no expression building, no fluent interface, no
Modelobject. A documented way to handload_schemaa structure, versioned so it can change. The thing that already works, made real.C — bless nothing, document the refusal. State that programmatic declaration is out and that generated YAML is the supported answer despite ceiling.md forbidding it. Honest but contradicts the current text; listed for completeness.
What decides it
Rule 6's purpose is that the model is the file — one artifact, readable, diffable, validated. Option B does not violate that if what it emits is a
MathSchemathat could have been written as YAML. It violates it if what it emits could not. That test — is the emitted thing sayable? — is probably the actual line, and it is checkable rather than a matter of taste.Related: #38 (islands are the other thing that runs user Python, and are bounded rather than blessed), #67 (a
version:key, which is the same versioning problem one level up).