Single-file projects are synthesized: working thread #915
Replies: 6 comments
Update — 2026-09-22: step 4 is complete, and the host now picks the envelopeThree things landed since the first post, and one of them changes the shape of the problem. The host chooses the envelope per provider
That resolves what was the open question here. A released process or WASM provider never sees So there are now three transitional mechanisms, not two, all waiting on that same explicit transition:
(1) and (3) are mirror images: one protects new guests from old hosts, the other protects old guests from new hosts. Neither removes itself. morphir-elm 0.2.0 is published
Its guard accepted only a top-level
With A workspace trap worth knowingA path dependency joins the workspace that reaches it unless excluded, and one workspace does not inherit another's exclusions.
Still open
|
The specs are now published, so this work is portable
Where a spec and this thread disagree, this thread is newer — the specs are published as written, not retrofitted. Still only on one machineThe eight implementation plans under
Two further design documents are also local-only and relate to already-shipped work: If any of those turns out to be load-bearing for future work rather than a record of past work, it should be published the same way before the worktree it lives in is discarded. |
Update, 2026-09-22: how capabilities reach an installed recordStep 5 gave the Elm MEP extension
The design is now #921, Spec: Capability statements across the extension lifecycle. In short: the guest is the only author of its capabilities; a new What changes for this sequence:
#921 is the working spec. The durable form goes into the MEP draft ( |
Ruling replaced: "the protocol stays at version 1 while pre-release"The settled item "The protocol stays at version 1. It is pre-release and is being refined in place." is replaced by a toolchain-wide default: every versioned contract uses SemVer 2.0 strings, and a prerelease version matches only exactly (#921, decision 12; The intent is unchanged: the workspace discovery protocol is still a draft that is refined in place. The draft is now stated in the version itself: the integer |
|
Correction to the comment above: the workspace discovery protocol moves from the integer |
Status, 2026-09-23: steps 5 and 6 deliveredSteps 5 and 6 of #917 are merged in #925 (squash Step 5: capability-driven selection and source collection
Step 6: the old CLI route is deleted The single-file Elm route, its module-header scanner and the Releases and pins
Still open
|
Uh oh!
There was an error while loading. Please reload this page.
Working thread for "a single file is a synthesized project" — the sequence that moves single-file compile identity out of the CLI and into the language providers. Use this to pick the work up across sessions and machines.
The decision, in one paragraph
A single file is not a special case. It is a synthesized project, and the provider owns synthesis: the file extension names the language, the source text names the module, and what counts as a legal module name is the language's business. The CLI used to do all three itself with a
matchon the file extension, which is why adding a language meant editing the CLI. Synthesis happens during workspace discovery, not at compile, so there is exactly one place identity can come from.Where it stands
DiscoveryPurpose,SourceSelection,ProjectOriginmorphir-elmvnextTypeScriptWhat is actually blocking
morphir-elmonvnextis the remaining piece of step 4.cli2/mep/protocol.ts:249requires a top-leveldocumentsarray and:439rejects the request outright when that guard fails. #208 added a transitional legacy envelope so hosts predating the change keep working, but that does nothing for an old guest: once the parent sendssources, the MEP Elm extension refuses every compile. It needs the TypeScript change, a rebuilt process artifact, and its own pin bump. Changes stay onvnext;mainis protected.Two transitional mechanisms, one shared removal condition
Both landed in morphir-rust#208 and both disappear together, but neither removes itself:
CompileRequestis either wholly legacy (top-leveldocuments, optionallysourceRootUri/sourceRoot) or wholly modern (sources: SourceSet { root, documents }). Mixing the two is an error that names the ambiguity — two envelopes each state a source root, and a request stating two roots has no honest answer for which one module names resolve against. SeeSourceEnvelopeinmorphir-extension-sdk/src/types.rs; its doc comment records what to delete..mise/tasks/test/cli-releasedrives every freshly built bundle through the morphir CLI release pinned in.config/morphir-cli-version— it is the declared compatibility check between the two repositories. It now skips only when publish fails withunknown fieldnaming a field in itsTRANSITIONAL_FIELDSlist that the descriptor really carries (currentlyworkspaceDiscovery). Any other failure is still a failure, and the success path warns when an entry has gone stale.Removal condition for both: a morphir release whose host sends
sourcesand whoserelease.jsonparser knowsworkspaceDiscovery, plus bumping.config/morphir-cli-versionto name it.Until then, Gleam's bundle is not publish-checked against a released CLI. That is a real reduction in coverage. It is narrow and loudly announced on every run, but if the release slips, this scaffolding quietly becomes permanent. Worth a decision about who owns removing it rather than discovering it later.
Carried into step 5
Recorded in the spec's Sequence section, and repeated here because that file is not in git (see below):
--package-nameis still not validated against the provider's package contract, which the spec requires. Both providers skip a project that already has a name, so an invalid name fails at compile, exactly as today. The provider only becomes the natural place for that check once step 5 routes names through discovery.workspace.selection.name-required), not by the provider that owns what distinct means for its language. Correct for every language today.exposedModulesunset, meaning "expose everything". The providers derive exposure from a single input. Nothing can construct that shape until step 5 wires source collection — which is also what makes enumerating a multi-source set's modules worth doing.A design finding worth acting on independently
itest's golden assertions are implemented on top of Rego, and need not be.prepare_assertionincrates/morphir/src/commands/itest/runner.rshandlesAssertionKind::Goldenby doing the comparison in Rust — reading the file, applying the selection, normalizing line endings, computing the mismatch — and then synthesizing a Rego program anyway:which is serialized into an
EvaluationRequestand shelled out tomorphir eval --request … --json. The mismatch it already computed is used only as error context. So every golden assertion spawns the evaluator to re-confirm an equality Rust already decided.Making golden assertions native would:
--no-default-features, converting several currently-ignored tests back into real coverageprovider: regobeing required metadata on scenarios that assert nothing in RegoThe counter-argument is uniformity: one evaluation path, one report shape, one
check_report. Going native means golden results bypass that, so the report contract would have to hold both.Related: #914 marks the 15 tests that genuinely need the evaluator with
cfg_attr(not(feature = "rego"), ignore = "…"), because--no-default-featuresis a documented, supported configuration (see #886) that was failing its own suite.Where the spec lives, and a caveat about it
.dev/docs/superpowers/specs/2026-09-21-single-file-projects-are-synthesized.md, with the plan alongside it in.dev/docs/superpowers/plans/..dev/is gitignored on purpose (.gitignore:82— "Local working area: temp scripts, agent and script outputs, superpowers specs/plans"). So the spec, including the carry-forwards above, exists only in whichever worktree it was written in. It is not in version control and will not survive that worktree being removed. If this sequence is going to outlive the machine it started on, the spec needs a home that is actually tracked — that is an open question, not a solved one.Settled, please don't re-litigate
CompilePackagekeeps its requiredname: String. Making it a sum (Named/Synthesized) or anOptionwas proposed and rejected in design review: synthesis happens at discovery, so an optional name at compile creates a second place a name can come from — the exact problem this sequence removes.All reactions