prometeu-studio/discussion/lessons/DSC-0063-multi-frontend-synthetic-test-frontend/LSN-0065-synthetic-test-frontend-proves-pipeline-neutrality.md
bQUARKz 79a682b8ec
Some checks are pending
Intrepid/Prometeu/Studio/pipeline/pr-master Build started...
JaCoCo Coverage #### Project Overview No changes detected, that affect the code coverage. * Line Coverage: 62.10% (17811/28682) * Branch Coverage: 52.81% (6878/13025) * Lines of Code: 28682 * Cyclomatic Complexity: 11494 #### Quality Gates Summary Output truncated.
Test / Build skipped: 15, passed: 653
Intrepid/Prometeu/Studio/pipeline/head This commit looks good
implements PLN-0128
Add a test-only synth FrontendProvider that traverses compile() to
verified bytecode without PBS. Expose register/unregister on the static
registry and lock the obligation in specs 23 and 22.

Housekeep DSC-0063 with LSN-0065.
2026-09-19 03:03:37 +01:00

6.4 KiB

id ticket title created tags
LSN-0065 multi-frontend-synthetic-test-frontend Synthetic test frontend proves common pipeline neutrality 2026-09-19
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