--- id: LSN-0065 ticket: multi-frontend-synthetic-test-frontend title: Synthetic test frontend proves common pipeline neutrality created: 2026-09-19 tags: [compiler, compiler-general, frontend, tests, backend, multi-frontend] --- # Synthetic test frontend proves common pipeline neutrality ## Original Problem The common pipeline already had `FrontendProvider`, `IRBackend` as the executable handoff, common lifecycle assembly, and `IRBackend -> IRVM -> PBX`. Direct construction tests proved the backend could lower a handmade `IRBackend`. Import guards proved common backend code did not depend on PBS packages. That was not enough. Nothing proved that a second provider, resolved by `languageId`, could enter through `DiscoverPhase` and `FrontendPhasePipelineStage`, assemble lifecycle, and finish `BuilderPipelineService.compile()` at verified bytecode without instantiating PBS, reading `.pbs` files, or loading PBS stdlib. `CompileOnlyFrontendProvider` already existed as a local test double. It was not registered, returned an empty `IRBackend`, and never called the pipeline. It did not close this gap. ## Consolidated Decision Pipeline neutrality is proved by a **test-only** compile-only `FrontendProvider`, not by a second product language and not by injecting IR into compile stages. Locked shape (former Option A): - `languageId` is `synth`. - The provider and `FrontendPhaseService` live only in `prometeu-build-pipeline` test sources. - `languageService()` is empty. There is no parser, grammar, semantic presentation, or stdlib catalog. - `sourceRoots` is `src`. `allowedExtensions` must not collide with `pbs` / `barrel`. The fixture must **not** create a source file of that extension. An empty `src/` directory must still exist because `WireProjectsPhase` resolves `toRealPath()`. - The compiler ignores source text and emits one module, one `frame` body that is a single `RET`, and an `IRLifecycleDeclaration` with exactly one `FRAME_ROOT` whose origin span is filled. - The handoff must not declare `FILE_INIT_FRAGMENT` or `PROJECT_INIT`, and must not pre-materialize wrappers, module-init, or `BOOT_GUARD`. The common assembler produces `__prometeu.lifecycle.frame_wrapper$mN` and the boot guard. - The IR must not contain host bindings, capabilities, intrinsics, PBS types, PBS stdlib, or `__pbs.` names. The acceptance path is `compile()` through `VerifyBytecode`. Writing `program.pbx` is not the criterion. `build()` only adds disk I/O. Registration stays on the existing static `FrontendRegistryService`. `register` and `unregister` are public. `unregister` refuses to remove the default PBS provider and is a no-op for unknown ids. Tests register `synth` for the duration of the run and unregister in `try/finally`. `bootstrapDefaults()`, `AppContainer`, and the Studio language catalog must not register `synth`. Do not inject a `Registry` into the pipeline, add a `frontends/synth` module, scan the classpath, or treat a dummy on-disk extension as a language. Do not replace `LifecycleAssemblerServiceTest`, `LowerToIRVMServiceTest`, or `IRBackendExecutableContractTest` with this fixture. A blank `prometeu.json` `language` field still defaults to PBS. The synthetic project must set `language: synth` explicitly. ## Final Implementation `FrontendRegistryService.register(FrontendProvider)` is the explicit registration API. `unregister(String languageId)` removes a non-PBS provider or no-ops if the id is absent. Duplicate `register` still fails. `bootstrapDefaults()` still registers only PBS. The test fixture is: - `p.studio.compiler.synth.SyntheticFrontendProvider` - `p.studio.compiler.synth.SyntheticFrontendPhaseService` `SyntheticFrontendPipelineTest` writes a temporary project (`language: synth`, `target: Game`, numeric `stdlib`, empty `src/`), registers the provider, calls `BuilderPipelineService.compile()`, and asserts: - the synthetic compiler ran; - resolved `languageId` is `synth`, not `pbs`; - diagnostics have no errors; - in-memory bytecode is non-empty and verified; - assembled entrypoint is `__prometeu.lifecycle.frame_wrapper$m0`; - a hidden boot guard is present; - no `__pbs.` names; - no `.pbs` / `.barrel` sources were required; - after `unregister`, `listProviders()` is PBS-only. Spec 23 section 8.3 records that a compile-only registered provider is a valid pipeline participant and that the common pipeline must not require PBS types, PBS sources, or PBS stdlib to emit verified bytecode. Spec 22 row `G23-8.3` binds that obligation to the synthetic pipeline test. PBS production code, Studio, and `AppContainer` were not changed. ## Examples Good: - register `synth`, compile a temp project with empty `src/`, unregister; - `FRAME_ROOT` with `FileId(0)` and a non-none span, even with no source file in `FileTable`; - assert the synthetic compiler was invoked instead of importing `PBSFrontendPhaseService` from build-pipeline tests. Bad: - omitting `language` in the manifest so discovery silently uses PBS; - creating `main.dummy` and calling that a second frontend; - putting the provider under `prometeu-compiler/frontends/`; - leaving `synth` registered after the test so later tests see two providers; - using this pipeline test as a substitute for assembler or IRVM unit tests. ## Pitfalls - `WireProjectsPhase` still needs `src/` on disk. Empty is not the same as missing. - Origin span `Span.none()` is rejected by the assembler. Use a real `FileId` even when no file was loaded. - `unregister("pbs")` must fail. A cleanup path that blindly unregisters the resolved language would delete the product default. - `listProviders()` feeds the Studio catalog. A leaked test registration would show `synth` as a new-project language. - Direct `IRBackend` construction tests still own backend-only proofs. The synthetic frontend owns the registry-plus-pipeline proof. Keep both. ## References - Decision: `DEC-0049` - Plan: `PLN-0128` - Related lessons: `LSN-0055`, `LSN-0056`, `LSN-0059`, `LSN-0062`, `LSN-0061` - `docs/specs/compiler/23. Compiler Pipeline Entry Points Specification.md` - `docs/specs/compiler/22. Backend Spec-to-Test Conformance Matrix.md` - `prometeu-compiler/prometeu-frontend-registry/src/main/java/p/studio/compiler/FrontendRegistryService.java` - `prometeu-compiler/prometeu-build-pipeline/src/test/java/p/studio/compiler/synth/SyntheticFrontendProvider.java` - `prometeu-compiler/prometeu-build-pipeline/src/test/java/p/studio/compiler/integration/SyntheticFrontendPipelineTest.java`