Merge pull request 'implements PLN-0129' (#23) from dev/multi-frontend-architectural-tests into master
All checks were successful
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: 654
Intrepid/Prometeu/Studio/pipeline/head This commit looks good

Reviewed-on: #23
This commit is contained in:
bquarkz 2026-09-19 02:40:48 +00:00
commit bb010d565e
21 changed files with 500 additions and 290 deletions

View File

@ -1,6 +1,6 @@
{"type":"meta","next_id":{"DSC":66,"AGD":69,"DEC":50,"PLN":129,"LSN":66,"CLSN":1}}
{"type":"meta","next_id":{"DSC":66,"AGD":69,"DEC":51,"PLN":130,"LSN":67,"CLSN":1}}
{"type":"discussion","id":"DSC-0065","status":"open","ticket":"multi-frontend-avoid-premature-abstractions","title":"Evitar abstracoes prematuras na preparacao multi-frontend","created_at":"2026-07-15","updated_at":"2026-07-15","tags":["compiler","compiler-general","studio","frontend","architecture","multi-frontend","simplicity"],"agendas":[{"id":"AGD-0068","file":"AGD-0068-multi-frontend-avoid-premature-abstractions.md","status":"open","created_at":"2026-07-15","updated_at":"2026-07-15"}],"decisions":[],"plans":[],"lessons":[]}
{"type":"discussion","id":"DSC-0064","status":"open","ticket":"multi-frontend-architectural-tests","title":"Testes arquiteturais para fronteiras multi-frontend","created_at":"2026-07-15","updated_at":"2026-07-15","tags":["compiler","compiler-general","studio","frontend","architecture","tests","multi-frontend"],"agendas":[{"id":"AGD-0067","file":"AGD-0067-multi-frontend-architectural-tests.md","status":"open","created_at":"2026-07-15","updated_at":"2026-07-15"}],"decisions":[],"plans":[],"lessons":[]}
{"type":"discussion","id":"DSC-0064","status":"done","ticket":"multi-frontend-architectural-tests","title":"Testes arquiteturais para fronteiras multi-frontend","created_at":"2026-07-15","updated_at":"2026-09-19","tags":["compiler","compiler-general","studio","frontend","architecture","tests","multi-frontend"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0066","file":"discussion/lessons/DSC-0064-multi-frontend-architectural-tests/LSN-0066-archunit-guards-java-multi-frontend-boundaries.md","status":"done","created_at":"2026-09-19","updated_at":"2026-09-19"}]}
{"type":"discussion","id":"DSC-0063","status":"done","ticket":"multi-frontend-synthetic-test-frontend","title":"Frontend sintetico de teste para provar neutralidade do pipeline","created_at":"2026-07-15","updated_at":"2026-09-19","tags":["compiler","compiler-general","frontend","tests","backend","multi-frontend"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0065","file":"discussion/lessons/DSC-0063-multi-frontend-synthetic-test-frontend/LSN-0065-synthetic-test-frontend-proves-pipeline-neutrality.md","status":"done","created_at":"2026-09-19","updated_at":"2026-09-19"}]}
{"type":"discussion","id":"DSC-0062","status":"done","ticket":"multi-frontend-pvm-neutrality","title":"Neutralidade da PVM e identificacao PBX independente de PBS","created_at":"2026-07-15","updated_at":"2026-09-18","tags":["vm-arch","runtime","pvm","pbx","compiler","multi-frontend"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0061","file":"discussion/lessons/DSC-0062-multi-frontend-pvm-neutrality/LSN-0061-pbx-runtime-boundary.md","status":"done","created_at":"2026-09-18","updated_at":"2026-09-18"}]}
{"type":"discussion","id":"DSC-0061","status":"done","ticket":"multi-frontend-sdk-canonical-definition","title":"Auditoria e centralizacao gradual da definicao canonica do SDK","created_at":"2026-07-15","updated_at":"2026-09-19","tags":["compiler","compiler-general","sdk","stdlib","hostcalls","intrinsics","multi-frontend"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0064","file":"discussion/lessons/DSC-0061-multi-frontend-sdk-canonical-definition/LSN-0064-runtime-owns-console-abi-pbs-is-delivery.md","status":"done","created_at":"2026-09-19","updated_at":"2026-09-19"}]}

View File

@ -0,0 +1,151 @@
---
id: LSN-0066
ticket: multi-frontend-architectural-tests
title: ArchUnit guards Java multi-frontend boundaries
created: 2026-09-19
tags: [compiler, compiler-general, studio, frontend, architecture, tests, multi-frontend]
---
# ArchUnit guards Java multi-frontend boundaries
## Original Problem
The multi-frontend boundaries already existed in specs and lessons: common
code selects a frontend by `languageId`, `IRBackend` is language-neutral,
lifecycle assembly is common, and PBS is one provider. The automated
protection did not match that model.
The old guards walked Java source and searched for substrings:
- `import p.studio.compiler.pbs`
- `new PBSFrontendPhaseService(`
- `new PbsEditorialSupportService(`
Those checks missed field, signature, and constructor coupling. They also
treated `p.studio.compiler.pbs` as the whole PBS surface. Facades such as
`PBSFrontendProvider`, `PBSDefinitions`, and `PBSFrontendPhaseService` live
in the PBS module but not in that package. `prometeu-app` and
`prometeu-lsp-v1` declared an `implementation` dependency on PBS without
importing any PBS type.
## Consolidated Decision
Java multi-frontend boundaries are enforced by **ArchUnit + JUnit 5**, not by
source substring search, except for the `__pbs.` literal scan that bytecode
cannot see.
Durable locks from DEC-0050:
- ArchUnit is test-only, pinned in `gradle/libs.versions.toml`.
- Rules live in the test-only Gradle project `:prometeu-architecture-tests`.
That project has no production sources. It is not a plugin loader.
- A PBS type is a class whose bytecode originates from
`:prometeu-compiler:frontends:prometeu-frontend-pbs`. Package
`p.studio.compiler.pbs` is not the complete boundary. Name prefixes
`PBS*` / `Pbs*` are not the identity.
- Production may depend on PBS types only in:
- the PBS module itself;
- `prometeu-frontend-registry` / `FrontendRegistryService`, and only on
`PBSFrontendProvider` and `PBSDefinitions`.
- `AppContainer` is not a PBS composition root. It may call
`FrontendRegistryService.bootstrapDefaults()`. It must not instantiate PBS
types.
- `synth` is not in the production allowlist and must not be registered in
`bootstrapDefaults()`.
- Runtime, PVM, and the Rust repository stay out of this discussion
(LSN-0061).
- `IRBackendExecutableContractTest` remains the public-contract shape audit.
ArchUnit covers module-origin dependence.
Do not migrate PBS facades into another package just to make the test
easier. Do not ban the word `PBS` in docs, fixtures, or comments.
## Final Implementation
`:prometeu-architecture-tests` imports production classes under `p.studio`
with `ImportOption.DoNotIncludeTests` and applies three ArchUnit rules:
1. classes outside the PBS module and the registry must not depend on
PBS-module types;
2. registry production may depend only on `PBSFrontendProvider` and
`PBSDefinitions` from that module;
3. frontend-api `p.studio.compiler.models` must not depend on PBS-module
types.
A separate JUnit test walks `build.gradle.kts` files and allows the PBS
project dependency only for:
- `:prometeu-compiler:prometeu-frontend-registry` as `implementation`/`api`;
- `:prometeu-architecture-tests` as `testImplementation` (needed to classify
class origin).
The `__pbs.` source scan moved to
`CommonBackendPbsPrefixScanTest` and still covers `backend/`, `lifecycle/`,
and `workspaces/stages/`.
Gradle graph cleanup:
- `prometeu-app` and `prometeu-lsp-v1` no longer `implementation` PBS.
Runtime classloading of `PBSFrontendProvider` still comes through the
registry's implementation dependency.
- `prometeu-studio` no longer `testImplementation` PBS. No studio test
compiled against PBS types.
Source-walk import/constructor tests were deleted.
`FrontendProviderBoundaryTest` keeps only
`compileOnlyFrontendProviderMayOmitLanguageService`.
Specs 19 §11 and 20 now say common/platform code must not depend on types
from the PBS frontend module. Matrix rows G19-11.6, G20-4.1.2, and G20-4.3.3
point at the ArchUnit rules and the `__pbs.` scan.
## Examples
Good:
- ArchUnit failure names the class that depends on a PBS-module type,
including a facade in package `p.studio.compiler`.
- Registry `bootstrapDefaults()` constructs `PBSFrontendProvider`. That is
the allowed composition root.
- App calls `FrontendRegistryService.bootstrapDefaults()` and has no PBS
project dependency.
Bad:
- Searching source for `import p.studio.compiler.pbs` and calling the
boundary protected.
- Treating `AppContainer` as the place that may `new PBSFrontendProvider()`.
- Identifying PBS by class name prefix, so a future `PbsLike` helper in
common code is banned or a PBS facade is missed.
- Adding ArchUnit to `java-common-conventions` for every module.
## Pitfalls
- Same Java package does not mean same Gradle module.
`FrontendRegistryService` and `PBSFrontendProvider` both sit in
`p.studio.compiler`. Origin is the module path or JAR name.
- `@AnalyzeClasses` classes must not mix `@ArchTest` and Jupiter `@Test` on
the same class. The ArchUnit engine owns `@ArchTest`.
- `DoNotIncludeTests` is required. The architecture-test sources themselves
reference PBS types in order to name the allowlist.
- Removing PBS from app/lsp compile classpath is safe only because registry
still `implementation`s PBS, which is runtime-transitive.
- `testImplementation` of PBS on studio is not a production leak, but if no
studio test needs PBS types it is still a lying graph. The allowlist test
rejects it.
- Do not point ArchUnit at the Rust runtime. `__pbs.` in Java source is a
string contract, not a type dependency.
## References
- Decision: `DEC-0050`
- Plan: `PLN-0129`
- Related lessons: `LSN-0055`, `LSN-0059`, `LSN-0062`, `LSN-0063`,
`LSN-0061`, `LSN-0065`
- `docs/specs/compiler/19. Verification and Safety Checks Specification.md`
- `docs/specs/compiler/20. IRBackend to IRVM Lowering Specification.md`
- `docs/specs/compiler/22. Backend Spec-to-Test Conformance Matrix.md`
- `prometeu-architecture-tests/src/test/java/p/studio/architecture/PbsModuleBoundaryArchTest.java`
- `prometeu-architecture-tests/src/test/java/p/studio/architecture/PbsGradleDependencyAllowlistTest.java`
- `prometeu-compiler/prometeu-frontend-registry/src/main/java/p/studio/compiler/FrontendRegistryService.java`
- `prometeu-compiler/prometeu-frontend-api/src/test/java/p/studio/compiler/models/IRBackendExecutableContractTest.java`

View File

@ -1,79 +0,0 @@
---
id: AGD-0067
ticket: multi-frontend-architectural-tests
title: Testes arquiteturais para fronteiras multi-frontend
status: open
created: 2026-07-15
resolved:
decision:
tags: [compiler, compiler-general, studio, frontend, architecture, tests, multi-frontend]
---
# Agenda - Criar testes arquiteturais
## Objetivo
Domain owner: `compiler/general`, com impacto em `studio` e `vm-arch`.
Definir testes que protejam fronteiras: backend comum sem PBS, IR comum sem AST PBS, Studio comum sem instanciacao PBS fora do composition root e runtime neutro.
## Contexto atual
O repo ja tem testes Java para backend, bytecode e IRVM. ArchUnit pode ser util, mas o alinhamento proibe adicionar biblioteca arquitetural quando testes simples bastarem.
## Escopo
- Escolher ferramenta minima.
- Definir regras por pacote.
- Adicionar testes que sejam baratos e claros.
## Fora de escopo
- Cobrir toda arquitetura do monorepo.
- Adicionar ArchUnit sem justificativa concreta.
- Bloquear referencias PBS em testes PBS ou no frontend PBS.
## Arquivos e componentes a inspecionar
- `build.gradle.kts` e version catalog se houver.
- `prometeu-compiler/prometeu-build-pipeline/src/test/java/...`
- `prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/...`
- `prometeu-studio/src/test/java/...`
- pacotes runtime/PVM quando presentes
## Alteracoes propostas
Opcao A: testes Java/reflection simples para imports, nomes de tipos e packages publicos.
Opcao B: ArchUnit caso a quantidade de regras e modulos justifique.
Recomendacao inicial: comecar com testes simples; reavaliar ArchUnit se as regras virarem frageis.
## Estrategia de implementacao
Definir allowlist de packages PBS, composition root e testes PBS. Escrever verificacoes independentes por dominio para falhas legiveis.
## Testes necessarios
- Backend/common nao importa `frontend.pbs`, `parser.pbs` ou `semantic.pbs`.
- IR comum nao expoe tipos PBS.
- Studio comum nao instancia servicos PBS fora da allowlist.
- Runtime nao usa nomenclatura ou semantica PBS.
## Criterios de aceitacao
- Regras rodam no build local.
- Falhas apontam arquivo/pacote responsavel.
- Excecoes autorizadas estao documentadas.
## Riscos
- Teste textual produzir falso positivo por docs ou fixtures.
- Criar barreira rigida antes de definir provider e lifecycle.
## Decisoes que devem ser registradas
- Ferramenta escolhida.
- Allowlist de packages.
- Ordem de introducao das regras.

View File

@ -210,4 +210,4 @@ This specification MUST NOT be read as authorizing:
- extracting PBS linking;
- or reopening lifecycle-assembly ownership.
Platform validators MUST NOT import `p.studio.compiler.pbs` and MUST NOT discover roles from `__pbs.` prefixes.
Platform validators MUST NOT depend on types from the PBS frontend module (`prometeu-frontend-pbs`). The package `p.studio.compiler.pbs` is not the complete boundary; facades shipped by that module remain PBS-owned. Platform validators MUST NOT discover roles from `__pbs.` prefixes.

View File

@ -92,7 +92,7 @@ The declaration MUST represent, using ordered collections and typed ids:
References MUST use `ModuleId`, `FileId`, `CallableId`, a typed role, and source attribution. The public shape MUST NOT expose maps, sets, callbacks, compiler services, or frontend-owned types.
`AssembleLifecyclePipelineStage` MUST run after `FrontendPhasePipelineStage` and before `LowerToIRVMPipelineStage`. `LifecycleAssemblerService` is the only owner of derived lifecycle artifacts: module init, project-init wrapper, published frame wrapper, physical entrypoint, and hidden boot guard. Those artifacts MUST use deterministic neutral internal identities. Common code MUST NOT discover roles from `__pbs.*` prefixes or depend on PBS packages.
`AssembleLifecyclePipelineStage` MUST run after `FrontendPhasePipelineStage` and before `LowerToIRVMPipelineStage`. `LifecycleAssemblerService` is the only owner of derived lifecycle artifacts: module init, project-init wrapper, published frame wrapper, physical entrypoint, and hidden boot guard. Those artifacts MUST use deterministic neutral internal identities. Common code MUST NOT discover roles from `__pbs.*` prefixes or depend on types from the PBS frontend module (`prometeu-frontend-pbs`). The package `p.studio.compiler.pbs` is not the complete boundary.
The declaration MUST remain available on the assembled `IRBackend` as typed provenance. Downstream inspection MUST use roles, ids, and origins rather than synthetic names.
@ -104,7 +104,7 @@ The common backend contract MUST NOT expose or require:
2. PBS parser cursors, parse contexts, parse nodes, or token kinds,
3. PBS semantic validator internals,
4. PBS editorial or language-service objects,
5. or any type under a language frontend package as part of the public backend handoff.
5. or any type from the PBS frontend module (`prometeu-frontend-pbs`) or an equivalent language frontend module as part of the public backend handoff.
## 4.2 Serializable-by-Design Contract Shape

View File

@ -45,17 +45,17 @@ to concrete positive/negative test evidence and current status.
| G19-11.3 | Platform diagnostics MUST use `COMMON_*` / `LOWER_IRVM_*` / `MARSHAL_*` and MUST copy origin/span when present. | `ValidationOwnershipSpecTest#spec19MustBeCompilerGeneralAndPublishValidationOwnership`; `LifecycleAssemblerServiceTest#assembleMustRejectMissingFrameRootWithoutRepair` | N/A | pass | Code families and span policy are normative in spec 19 section 11.1. |
| G19-11.4 | The same fact MAY exist as a language diagnostic and a platform diagnostic with distinct identities. | `ValidationOwnershipSpecTest#spec19MustBeCompilerGeneralAndPublishValidationOwnership` | N/A | pass | Dual reporting is required when a check reads both source and common contract. |
| G19-11.5 | Host admission MUST split source attribute shape from canonical `IRReservedMetadata` identity without moving the PBS validator in this plan. | `ValidationOwnershipSpecTest#spec19MustBeCompilerGeneralAndPublishValidationOwnership` | N/A | pass | Classified as platform; validator remains in the PBS package. |
| G19-11.6 | Platform validators MUST NOT import PBS packages or match `__pbs.` prefixes. | `CommonBackendArchitectureTest#commonBackendMustNotImportPbsFrontendPackages`; `CommonBackendArchitectureTest#commonLifecycleAndBackendMustNotDiscoverRolesFromPbsNamePrefixes` | N/A | pass | Guard covers backend, lifecycle, and all common pipeline stages. |
| G19-11.6 | Platform validators MUST NOT depend on PBS frontend module types or match `__pbs.` prefixes. | `PbsModuleBoundaryArchTest#productionOutsideAllowlistMustNotDependOnPbsModuleTypes`; `CommonBackendPbsPrefixScanTest#commonLifecycleAndBackendMustNotDiscoverRolesFromPbsNamePrefixes` | N/A | pass | ArchUnit covers PBS module origin, including facades outside `p.studio.compiler.pbs`; source scan still rejects `__pbs.` in backend, lifecycle, and common pipeline stages. |
| PBS12-11 | PBS diagnostics MUST NOT claim common structural lifecycle/IRVM failures as PBS required coverage. | `ValidationOwnershipSpecTest#pbsDiagnosticsMustNotClaimCommonStructuralFailures` | N/A | pass | PBS spec 12 points those failures at spec 19 section 11 and spec 20. |
| G20-4.1.1 | `IRBackend` MUST be the common frontend-to-backend executable handoff. | `LowerToIRVMServiceTest#lowerMustAcceptManuallyConstructedCommonIRBackend` | N/A | pass | Direct backend lowering from manually constructed common IRBackend proves PBS parser/frontend services are not required. |
| G20-4.1.2 | Common backend code MUST NOT depend on `p.studio.compiler.pbs`. | `CommonBackendArchitectureTest#commonBackendMustNotImportPbsFrontendPackages` | N/A | pass | Source-level architectural guard rejects PBS frontend imports in common backend packages. |
| G20-4.1.2 | Common backend code MUST NOT depend on types from the PBS frontend module. | `PbsModuleBoundaryArchTest#productionOutsideAllowlistMustNotDependOnPbsModuleTypes`; `PbsModuleBoundaryArchTest#irBackendModelsMustNotDependOnPbsModuleTypes` | N/A | pass | ArchUnit rejects PBS-module origin types in common production code, including facades outside `p.studio.compiler.pbs`. |
| G20-4.1.3 | Public `IRBackend` contract MUST NOT expose PBS AST, token, parser, semantic, or editorial types. | `IRBackendExecutableContractTest#publicIRBackendContractMustNotExposePbsTypes` | N/A | pass | Reflection guard covers public contract fields, constructors, methods, and record components. |
| G20-4.2.1 | Public `IRBackend` contract types MUST remain modelable as an acyclic, deterministic data graph. | `IRBackendExecutableContractTest#publicIRBackendContractMustExposeOnlySerializableShapes`; `IRBackendExecutableContractTest#aggregatorMustEmitDeterministicOrderedPublicContractShape` | N/A | pass | Covers public shape guardrails, generic type arguments, ordered aggregate emission, table remapping, and capability de-duplication order. |
| G20-4.2.2 | Public `IRBackend` contract types MUST NOT expose callbacks, services, mutable/unordered collection contracts, frontend-owned objects, process-dependent lazy values, cyclic public references, or wire-format commitments. | `IRBackendExecutableContractTest#publicIRBackendContractMustExposeOnlySerializableShapes` | N/A | pass | Reflection guard covers public contract fields, constructors, methods, record components, and generic type arguments. |
| G20-4.2.3 | Public `IRBackend` cross-object references MUST use explicit ids or stable symbolic keys. | `IRBackendExecutableContractTest#publicIRBackendContractAuditMustClassifyAllowedSupportTypes`; `IRBackendExecutableContractTest#publicIRBackendContractMustUseExplicitIdsOrStableKeysForKnownReferences` | N/A | pass | Current audit found no concrete public serialization leak requiring model correction. Existing `FileId`, `ModuleId`, `CallableId`, and `IntrinsicId` remain explicit table-scoped identifiers. |
| G20-4.3.1 | Executable `IRBackend` MUST carry a typed, ordered, id-based `IRLifecycleDeclaration` as provenance. | `IRBackendExecutableContractTest#aggregatorMustRemapTypedLifecycleReferencesAndPreserveDeclaredModuleOrder`; `LifecycleAssemblerServiceTest#assembleMustMaterializeNeutralLifecycleArtifactsInDeclaredOrder` | `IRBackendExecutableContractTest#aggregatorMustOmitLifecycleDeclarationWhenNoFileContributesOne` | pass | Declaration uses `ReadOnlyList`, typed ids, roles, and source attribution; aggregator remaps ids with explicit module order. |
| G20-4.3.2 | Missing lifecycle declaration MUST mean a deliberately non-executable handoff and MUST NOT synthesize entrypoints. | `LifecycleAssemblerServiceTest#assembleMustNoOpWhenLifecycleDeclarationIsAbsent`; `AssembleLifecyclePipelineStageTest#runMustNoOpForDeliberatelyNonExecutableHandoff` | N/A | pass | Assembler and stage leave the backend unchanged when the declaration is absent. |
| G20-4.3.3 | Common lifecycle assembly MUST be the exclusive owner of derived artifacts and MUST use neutral identities. | `LifecycleAssemblerServiceTest#assembleMustMaterializeNeutralLifecycleArtifactsInDeclaredOrder`; `CommonBackendArchitectureTest#commonLifecycleAndBackendMustNotDiscoverRolesFromPbsNamePrefixes` | `LifecycleAssemblerServiceTest#assembleMustRejectPreMaterializedDerivedArtifacts` | pass | Derived wrappers and boot guard are common-owned; architecture guard rejects PBS imports and `__pbs.` prefix matching. |
| G20-4.3.3 | Common lifecycle assembly MUST be the exclusive owner of derived artifacts and MUST use neutral identities. | `LifecycleAssemblerServiceTest#assembleMustMaterializeNeutralLifecycleArtifactsInDeclaredOrder`; `CommonBackendPbsPrefixScanTest#commonLifecycleAndBackendMustNotDiscoverRolesFromPbsNamePrefixes`; `PbsModuleBoundaryArchTest#productionOutsideAllowlistMustNotDependOnPbsModuleTypes` | `LifecycleAssemblerServiceTest#assembleMustRejectPreMaterializedDerivedArtifacts` | pass | Derived wrappers and boot guard are common-owned; ArchUnit rejects PBS-module types and the source scan rejects `__pbs.` prefix matching. |
| G20-6.2 | `IRVM_EXT` MUST declare structural metadata (`pops/pushes/is_branch/is_terminator`). | `IRVMValidatorTest#validateMustApplyStructuralMetadataForCustomInternalExtension`; `IRVMValidatorTest#validateMustRejectCustomInternalExtensionWhenStructuralMetadataUnderflowsStack`; `IRVMOp` record contract (`pops/pushes/branch/terminator/internal`) | N/A | pass | Dedicated extension fixtures now assert structural metadata is consumed by validation behavior. |
| G20-6.3 | `IRVM_EXT` MUST be eliminable before bytecode emission. | `OptimizeIRVMServiceTest#optimizeDefaultPassesMustEliminateUnreachableInternalExtensionBeforeEmission` | `EmitBytecodePipelineStageTest#runMustFailWhenInternalOpcodesRemain`; `EmitBytecodePipelineStageTest#runMustFailWhenInternalOpcodesRemainEvenWithNonEmptyEmissionPlan`; `IRVMValidatorTest#validateMustRejectInternalOpcodeWhenConfigured` | pass | Optimizer elimination path and emit-stage hard rejection path are both covered. |
| G20-6.4 | IRVM MUST preserve per-function slot and identity headers. | `IRVMProgramTest#constructorMustRejectModuleAndEmissionPlanMismatch` | `IRVMProgramTest#constructorMustRejectModuleAndEmissionPlanMismatch` | pass | Header mismatch is rejected deterministically. |

View File

@ -5,6 +5,7 @@ jackson = "2.18.2"
jacoco = "0.8.12"
lombok = "1.18.32"
junit-jupiter = "5.12.1"
archunit = "1.4.2"
slf4j = "2.0.7"
lsp4j = "1.0.0"
@ -19,6 +20,7 @@ apache-commons-collections = { group = "org.apache.commons", name = "commons-col
jacoco-core = { group = "org.jacoco", name = "org.jacoco.core", version.ref = "jacoco" }
lombok = { group = "org.projectlombok", name = "lombok", version.ref = "lombok" }
junit-jupiter = { group = "org.junit.jupiter", name = "junit-jupiter", version.ref = "junit-jupiter" }
archunit-junit5 = { group = "com.tngtech.archunit", name = "archunit-junit5", version.ref = "archunit" }
slf4j-api = { group = "org.slf4j", name = "slf4j-api", version.ref = "slf4j" }
slf4j-simple = { group = "org.slf4j", name = "slf4j-simple", version.ref = "slf4j" }
lsp4j = { group = "org.eclipse.lsp4j", name = "org.eclipse.lsp4j", version.ref = "lsp4j" }

View File

@ -7,7 +7,6 @@ dependencies {
implementation(project(":prometeu-infra"))
implementation(project(":prometeu-lsp:prometeu-lsp-api"))
implementation(project(":prometeu-lsp:prometeu-lsp-v1"))
implementation(project(":prometeu-compiler:frontends:prometeu-frontend-pbs"))
implementation(project(":prometeu-compiler:prometeu-frontend-registry"))
implementation(project(":prometeu-packer:prometeu-packer-api"))
implementation(project(":prometeu-packer:prometeu-packer-v1"))

View File

@ -0,0 +1,23 @@
plugins {
id("gradle.java-library-conventions")
}
dependencies {
testImplementation(libs.archunit.junit5)
testImplementation(project(":prometeu-compiler:frontends:prometeu-frontend-pbs"))
testImplementation(project(":prometeu-compiler:prometeu-frontend-registry"))
testImplementation(project(":prometeu-compiler:prometeu-compiler-core"))
testImplementation(project(":prometeu-compiler:prometeu-frontend-api"))
testImplementation(project(":prometeu-compiler:prometeu-build-pipeline"))
testImplementation(project(":prometeu-compiler:prometeu-deps"))
testImplementation(project(":prometeu-infra"))
testImplementation(project(":prometeu-lsp:prometeu-lsp-api"))
testImplementation(project(":prometeu-lsp:prometeu-lsp-v1"))
testImplementation(project(":prometeu-studio"))
testImplementation(project(":prometeu-app"))
}
tasks.named<JacocoReport>("jacocoTestReport") {
enabled = false
}

View File

@ -0,0 +1,63 @@
package p.studio.architecture;
import org.junit.jupiter.api.Test;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
class CommonBackendPbsPrefixScanTest {
private static final String COMPILER_SOURCE_ROOT =
"prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler";
private static final List<String> GUARDED_RELATIVE_ROOTS = List.of(
"backend",
"lifecycle",
"workspaces/stages");
private static final String FORBIDDEN_PBS_PREFIX = "__pbs.";
@Test
void commonLifecycleAndBackendMustNotDiscoverRolesFromPbsNamePrefixes() throws IOException {
final var violations = new ArrayList<String>();
final var repoRoot = locateRepoRoot();
final var compilerRoot = repoRoot.resolve(COMPILER_SOURCE_ROOT);
for (final var relativeRoot : GUARDED_RELATIVE_ROOTS) {
final var root = compilerRoot.resolve(relativeRoot);
if (!Files.exists(root)) {
fail("guarded source root is missing: " + root);
}
try (final var paths = Files.walk(root)) {
paths.filter(path -> path.toString().endsWith(".java")).forEach(path -> {
try {
if (Files.readString(path).contains(FORBIDDEN_PBS_PREFIX)) {
violations.add(repoRoot.relativize(path).toString());
}
} catch (IOException e) {
throw new IllegalStateException("failed to read source file: " + path, e);
}
});
}
}
assertTrue(
violations.isEmpty(),
"common lifecycle/backend code must not inspect __pbs. prefixes: " + violations);
}
private static Path locateRepoRoot() {
var cursor = Path.of(System.getProperty("user.dir")).toAbsolutePath().normalize();
while (cursor != null) {
final var hasDocs = Files.isDirectory(cursor.resolve("docs/specs/compiler"));
final var hasCompiler = Files.isDirectory(cursor.resolve("prometeu-compiler"));
if (hasDocs && hasCompiler) {
return cursor;
}
cursor = cursor.getParent();
}
fail("could not locate repository root from working directory");
throw new IllegalStateException("unreachable");
}
}

View File

@ -0,0 +1,22 @@
package p.studio.architecture;
import com.tngtech.archunit.base.DescribedPredicate;
import com.tngtech.archunit.core.domain.JavaClass;
import java.util.Arrays;
import java.util.Set;
import java.util.stream.Collectors;
final class DescribedPredicates {
private DescribedPredicates() {
}
static DescribedPredicate<JavaClass> notBelongingTo(final Class<?>... allowed) {
final Set<String> allowedNames = Arrays.stream(allowed)
.map(Class::getName)
.collect(Collectors.toUnmodifiableSet());
return DescribedPredicate.describe(
"do not belong to " + allowedNames,
javaClass -> !allowedNames.contains(javaClass.getName()));
}
}

View File

@ -0,0 +1,85 @@
package p.studio.architecture;
import org.junit.jupiter.api.Test;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
class PbsGradleDependencyAllowlistTest {
private static final Pattern PBS_PROJECT_DEP = Pattern.compile(
"(\\w+)\\(\\s*project\\(\\s*\"" + Pattern.quote(PbsModuleOrigins.PBS_GRADLE_PATH) + "\"\\s*\\)");
private static final Map<String, Set<String>> ALLOWED_CONFIGURATIONS = Map.of(
":prometeu-compiler:prometeu-frontend-registry", Set.of("implementation", "api"),
":prometeu-architecture-tests", Set.of("testImplementation"));
@Test
void onlyAllowlistedProjectsMayDependOnThePbsFrontendModule() throws IOException {
final var repoRoot = locateRepoRoot();
final var violations = new ArrayList<String>();
try (final var paths = Files.walk(repoRoot)) {
paths.filter(path -> path.getFileName().toString().equals("build.gradle.kts"))
.filter(path -> !path.toString().contains("/buildSrc/"))
.filter(path -> !path.toString().contains("/build/"))
.forEach(path -> collectViolations(repoRoot, path, violations));
}
assertTrue(violations.isEmpty(), "PBS Gradle project dependency allowlist violated: " + violations);
}
private static void collectViolations(
final Path repoRoot,
final Path buildFile,
final ArrayList<String> violations) {
final var projectPath = toProjectPath(repoRoot, buildFile);
if (PbsModuleOrigins.PBS_GRADLE_PATH.equals(projectPath)) {
return;
}
final String content;
try {
content = Files.readString(buildFile);
} catch (IOException e) {
throw new IllegalStateException("failed to read " + buildFile, e);
}
final var matcher = PBS_PROJECT_DEP.matcher(content);
while (matcher.find()) {
final var configuration = matcher.group(1);
final var allowed = ALLOWED_CONFIGURATIONS.getOrDefault(projectPath, Set.of());
if (!allowed.contains(configuration)) {
violations.add(projectPath + " " + configuration + " " + PbsModuleOrigins.PBS_GRADLE_PATH);
}
}
}
private static String toProjectPath(final Path repoRoot, final Path buildFile) {
final var relative = repoRoot.relativize(buildFile.getParent()).toString().replace('\\', '/');
if (relative.isEmpty() || ".".equals(relative)) {
return ":";
}
return ":" + relative.replace('/', ':');
}
private static Path locateRepoRoot() {
var cursor = Path.of(System.getProperty("user.dir")).toAbsolutePath().normalize();
while (cursor != null) {
final var hasDocs = Files.isDirectory(cursor.resolve("docs/specs/compiler"));
final var hasCompiler = Files.isDirectory(cursor.resolve("prometeu-compiler"));
if (hasDocs && hasCompiler) {
return cursor;
}
cursor = cursor.getParent();
}
fail("could not locate repository root from working directory");
throw new IllegalStateException("unreachable");
}
}

View File

@ -0,0 +1,43 @@
package p.studio.architecture;
import com.tngtech.archunit.core.importer.ImportOption;
import com.tngtech.archunit.junit.AnalyzeClasses;
import com.tngtech.archunit.junit.ArchTest;
import com.tngtech.archunit.lang.ArchRule;
import p.studio.compiler.PBSDefinitions;
import p.studio.compiler.PBSFrontendProvider;
import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses;
import static p.studio.architecture.PbsModuleOrigins.fromFrontendApiModule;
import static p.studio.architecture.PbsModuleOrigins.fromPbsModule;
import static p.studio.architecture.PbsModuleOrigins.fromRegistryModule;
@AnalyzeClasses(packages = "p.studio", importOptions = ImportOption.DoNotIncludeTests.class)
class PbsModuleBoundaryArchTest {
@ArchTest
static final ArchRule productionOutsideAllowlistMustNotDependOnPbsModuleTypes = noClasses()
.that(fromPbsModule().negate())
.and(fromRegistryModule().negate())
.should()
.dependOnClassesThat(fromPbsModule())
.because("DEC-0050: only prometeu-frontend-pbs and prometeu-frontend-registry may depend on PBS module types");
@ArchTest
static final ArchRule registryMustOnlyDependOnPbsProviderFacade = noClasses()
.that(fromRegistryModule())
.should()
.dependOnClassesThat(
fromPbsModule()
.and(DescribedPredicates.notBelongingTo(PBSFrontendProvider.class, PBSDefinitions.class)))
.because("DEC-0050: registry may depend only on PBSFrontendProvider and PBSDefinitions");
@ArchTest
static final ArchRule irBackendModelsMustNotDependOnPbsModuleTypes = noClasses()
.that(fromFrontendApiModule())
.and()
.resideInAPackage("p.studio.compiler.models..")
.should()
.dependOnClassesThat(fromPbsModule())
.because("DEC-0050: public IRBackend contract types must not depend on PBS module types");
}

View File

@ -0,0 +1,36 @@
package p.studio.architecture;
import com.tngtech.archunit.core.importer.ClassFileImporter;
import com.tngtech.archunit.core.importer.ImportOption;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
class PbsModuleClasspathTest {
@Test
void importerMustSeePbsAndRegistryModuleOrigins() {
final var classes = new ClassFileImporter()
.withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_TESTS)
.importPackages("p.studio");
final var pbsSample = classes.stream()
.filter(PbsModuleOrigins::isPbsModule)
.findFirst();
final var registrySample = classes.stream()
.filter(PbsModuleOrigins::isRegistryModule)
.findFirst();
if (pbsSample.isEmpty() || registrySample.isEmpty()) {
final var uris = classes.stream()
.limit(20)
.map(javaClass -> javaClass.getName() + " -> " + PbsModuleOrigins.sourceUri(javaClass))
.toList();
fail("PBS/registry module origins were not visible to ArchUnit. sample URIs: " + uris);
}
assertTrue(pbsSample.isPresent());
assertTrue(registrySample.isPresent());
}
}

View File

@ -0,0 +1,60 @@
package p.studio.architecture;
import com.tngtech.archunit.base.DescribedPredicate;
import com.tngtech.archunit.core.domain.JavaClass;
final class PbsModuleOrigins {
static final String PBS_GRADLE_PATH = ":prometeu-compiler:frontends:prometeu-frontend-pbs";
private PbsModuleOrigins() {
}
static boolean isPbsModule(final JavaClass javaClass) {
return uriContains(javaClass, "/prometeu-frontend-pbs/")
|| uriContains(javaClass, "prometeu-frontend-pbs.jar");
}
static boolean isRegistryModule(final JavaClass javaClass) {
return uriContains(javaClass, "/prometeu-frontend-registry/")
|| uriContains(javaClass, "prometeu-frontend-registry.jar");
}
static boolean isFrontendApiModule(final JavaClass javaClass) {
return uriContains(javaClass, "/prometeu-frontend-api/")
|| uriContains(javaClass, "prometeu-frontend-api.jar");
}
static DescribedPredicate<JavaClass> fromPbsModule() {
return DescribedPredicate.describe(
"originate from the PBS frontend module",
PbsModuleOrigins::isPbsModule);
}
static DescribedPredicate<JavaClass> fromRegistryModule() {
return DescribedPredicate.describe(
"originate from the frontend registry module",
PbsModuleOrigins::isRegistryModule);
}
static DescribedPredicate<JavaClass> fromFrontendApiModule() {
return DescribedPredicate.describe(
"originate from the frontend-api module",
PbsModuleOrigins::isFrontendApiModule);
}
static String sourceUri(final JavaClass javaClass) {
return javaClass.getSource()
.map(source -> normalize(source.getUri().toString()))
.orElse("<no source>");
}
private static boolean uriContains(final JavaClass javaClass, final String fragment) {
return javaClass.getSource()
.map(source -> normalize(source.getUri().toString()).contains(fragment))
.orElse(false);
}
private static String normalize(final String uri) {
return uri.replace('\\', '/');
}
}

View File

@ -1,96 +0,0 @@
package p.studio.compiler.specs;
import org.junit.jupiter.api.Test;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
class CommonBackendArchitectureTest {
private static final String COMPILER_SOURCE_ROOT =
"prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler";
private static final List<String> GUARDED_RELATIVE_ROOTS = List.of(
"backend",
"lifecycle",
"workspaces/stages");
private static final String FORBIDDEN_PBS_PACKAGE = "p.studio.compiler.pbs";
private static final String FORBIDDEN_PBS_PREFIX = "__pbs.";
@Test
void commonBackendMustNotImportPbsFrontendPackages() throws IOException {
final var violations = new ArrayList<String>();
walkGuardedSources(path -> assertNoPbsImport(path, violations));
assertTrue(
violations.isEmpty(),
"common backend must not import PBS frontend packages: " + violations);
}
@Test
void commonLifecycleAndBackendMustNotDiscoverRolesFromPbsNamePrefixes() throws IOException {
final var violations = new ArrayList<String>();
walkGuardedSources(path -> assertNoPbsPrefixMatching(path, violations));
assertTrue(
violations.isEmpty(),
"common lifecycle/backend code must not inspect __pbs. prefixes: " + violations);
}
private static void walkGuardedSources(final java.util.function.Consumer<Path> consumer) throws IOException {
final var compilerRoot = locateRepoRoot().resolve(COMPILER_SOURCE_ROOT);
for (final var relativeRoot : GUARDED_RELATIVE_ROOTS) {
final var root = compilerRoot.resolve(relativeRoot);
if (Files.isRegularFile(root)) {
consumer.accept(root);
continue;
}
try (final var paths = Files.walk(root)) {
paths.filter(path -> path.toString().endsWith(".java")).forEach(consumer);
}
}
}
private static void assertNoPbsImport(
final Path path,
final ArrayList<String> violations) {
try {
final var content = Files.readString(path);
if (content.contains("import " + FORBIDDEN_PBS_PACKAGE)
|| content.contains("import static " + FORBIDDEN_PBS_PACKAGE)) {
violations.add(locateRepoRoot().relativize(path).toString());
}
} catch (IOException e) {
throw new IllegalStateException("failed to read source file: " + path, e);
}
}
private static void assertNoPbsPrefixMatching(
final Path path,
final ArrayList<String> violations) {
try {
final var content = Files.readString(path);
if (content.contains(FORBIDDEN_PBS_PREFIX)) {
violations.add(locateRepoRoot().relativize(path).toString());
}
} catch (IOException e) {
throw new IllegalStateException("failed to read source file: " + path, e);
}
}
private static Path locateRepoRoot() {
var cursor = Path.of(System.getProperty("user.dir")).toAbsolutePath().normalize();
while (cursor != null) {
final var hasDocs = Files.isDirectory(cursor.resolve("docs/specs/compiler"));
final var hasCompiler = Files.isDirectory(cursor.resolve("prometeu-compiler"));
if (hasDocs && hasCompiler) {
return cursor;
}
cursor = cursor.getParent();
}
fail("could not locate repository root from working directory");
throw new IllegalStateException("unreachable");
}
}

View File

@ -11,93 +11,11 @@ import p.studio.compiler.source.diagnostics.DiagnosticSink;
import p.studio.utilities.logs.LogAggregator;
import p.studio.utilities.structures.ReadOnlySet;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
class FrontendProviderBoundaryTest {
@Test
void commonModulesMustNotInstantiatePbsFrontendPhaseServiceDirectly() throws IOException {
final var repoRoot = findRepoRoot(Path.of("").toAbsolutePath().normalize());
final var commonSourceRoots = List.of(
repoRoot.resolve("prometeu-compiler/prometeu-build-pipeline/src/main/java"),
repoRoot.resolve("prometeu-compiler/prometeu-deps/src/main/java"),
repoRoot.resolve("prometeu-studio/src/main/java"),
repoRoot.resolve("prometeu-app/src/main/java"));
for (final var sourceRoot : commonSourceRoots) {
if (!Files.exists(sourceRoot)) {
continue;
}
try (final var files = Files.walk(sourceRoot)) {
final var violations = files
.filter(path -> path.toString().endsWith(".java"))
.filter(path -> contains(path, "new PBSFrontendPhaseService("))
.toList();
if (!violations.isEmpty()) {
fail("common modules must resolve frontend compiler behavior through FrontendProvider: " + violations);
}
}
}
}
@Test
void commonModulesMustNotInstantiatePbsEditorialSupportServiceDirectly() throws IOException {
final var repoRoot = findRepoRoot(Path.of("").toAbsolutePath().normalize());
final var sourceRoots = List.of(
repoRoot.resolve("prometeu-compiler/prometeu-build-pipeline/src/main/java"),
repoRoot.resolve("prometeu-compiler/prometeu-deps/src/main/java"),
repoRoot.resolve("prometeu-lsp/prometeu-lsp-v1/src/main/java"),
repoRoot.resolve("prometeu-studio/src/main/java"),
repoRoot.resolve("prometeu-app/src/main/java"));
for (final var sourceRoot : sourceRoots) {
if (!Files.exists(sourceRoot)) {
continue;
}
try (final var files = Files.walk(sourceRoot)) {
final var violations = files
.filter(path -> path.toString().endsWith(".java"))
.filter(path -> contains(path, "new PbsEditorialSupportService("))
.toList();
if (!violations.isEmpty()) {
fail("common modules must resolve PBS editor behavior through FrontendProvider.languageService(): " + violations);
}
}
}
}
@Test
void commonLspMustNotImportPbsImplementationPackages() throws IOException {
final var repoRoot = findRepoRoot(Path.of("").toAbsolutePath().normalize());
final var sourceRoot = repoRoot.resolve("prometeu-lsp/prometeu-lsp-v1/src/main/java");
final var prohibitedNeedles = List.of(
"p.studio.compiler.PBSFrontendLanguageService",
"p.studio.compiler.services.PBSFrontendPhaseService",
"p.studio.compiler.pbs",
"PbsAst",
"PbsEditorial");
try (final var files = Files.walk(sourceRoot)) {
final var violations = files
.filter(path -> path.toString().endsWith(".java"))
.filter(path -> prohibitedNeedles.stream().anyMatch(needle -> contains(path, needle)))
.toList();
if (!violations.isEmpty()) {
fail("common LSP code must consume generic frontend language-service contracts instead of PBS implementation packages: "
+ violations);
}
}
}
@Test
void compileOnlyFrontendProviderMayOmitLanguageService() {
final FrontendProvider provider = new CompileOnlyFrontendProvider();
@ -110,26 +28,6 @@ class FrontendProviderBoundaryTest {
BuildingIssueSink.empty()));
}
private boolean contains(final Path path, final String needle) {
try {
return Files.readString(path).contains(needle);
} catch (IOException e) {
throw new IllegalStateException("failed to read " + path, e);
}
}
private Path findRepoRoot(final Path start) {
var current = start;
while (current != null) {
if (Files.exists(current.resolve("settings.gradle.kts"))) {
return current;
}
current = current.getParent();
}
fail("unable to locate repository root from " + start);
return start;
}
private static final class CompileOnlyFrontendProvider implements FrontendProvider {
private final FrontendSpec specification = FrontendSpec.builder()
.languageId("compile-only")

View File

@ -43,8 +43,11 @@ class ValidationOwnershipSpecTest {
content.contains("Canonical host-binding identity"),
"spec 19 matrix must include the host-admission split");
assertTrue(
content.contains("MUST NOT import `p.studio.compiler.pbs`"),
"spec 19 must forbid PBS imports in platform validators");
content.contains("MUST NOT depend on types from the PBS frontend module"),
"spec 19 must forbid PBS frontend module types in platform validators");
assertTrue(
content.contains("`p.studio.compiler.pbs` is not the complete boundary"),
"spec 19 must not treat package p.studio.compiler.pbs as the complete PBS boundary");
}
@Test

View File

@ -7,7 +7,6 @@ dependencies {
implementation(project(":prometeu-compiler:prometeu-compiler-core"))
implementation(project(":prometeu-compiler:prometeu-frontend-api"))
implementation(project(":prometeu-compiler:prometeu-build-pipeline"))
implementation(project(":prometeu-compiler:frontends:prometeu-frontend-pbs"))
implementation(project(":prometeu-compiler:prometeu-frontend-registry"))
implementation(project(":prometeu-compiler:prometeu-deps"))
implementation(libs.lsp4j)

View File

@ -14,7 +14,6 @@ dependencies {
implementation(libs.javafx.controls)
implementation(libs.javafx.fxml)
implementation(libs.richtextfx)
testImplementation(project(":prometeu-compiler:frontends:prometeu-frontend-pbs"))
testImplementation(project(":prometeu-packer:prometeu-packer-v1"))
}

View File

@ -22,3 +22,5 @@ include("prometeu-compiler:prometeu-frontend-registry")
include("prometeu-studio")
include("prometeu-app")
include("prometeu-architecture-tests")