implements PLN-0129 #23

Merged
bquarkz merged 1 commits from dev/multi-frontend-architectural-tests into master 2026-09-19 02:40:54 +00:00
21 changed files with 500 additions and 290 deletions
Showing only changes of commit e809ce63d3 - Show all commits

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-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-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-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"}]} {"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; - extracting PBS linking;
- or reopening lifecycle-assembly ownership. - 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. 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. 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, 2. PBS parser cursors, parse contexts, parse nodes, or token kinds,
3. PBS semantic validator internals, 3. PBS semantic validator internals,
4. PBS editorial or language-service objects, 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 ## 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.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.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.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. | | 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.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.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.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.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.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.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.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.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.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. | | 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" jacoco = "0.8.12"
lombok = "1.18.32" lombok = "1.18.32"
junit-jupiter = "5.12.1" junit-jupiter = "5.12.1"
archunit = "1.4.2"
slf4j = "2.0.7" slf4j = "2.0.7"
lsp4j = "1.0.0" 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" } jacoco-core = { group = "org.jacoco", name = "org.jacoco.core", version.ref = "jacoco" }
lombok = { group = "org.projectlombok", name = "lombok", version.ref = "lombok" } lombok = { group = "org.projectlombok", name = "lombok", version.ref = "lombok" }
junit-jupiter = { group = "org.junit.jupiter", name = "junit-jupiter", version.ref = "junit-jupiter" } 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-api = { group = "org.slf4j", name = "slf4j-api", version.ref = "slf4j" }
slf4j-simple = { group = "org.slf4j", name = "slf4j-simple", 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" } 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-infra"))
implementation(project(":prometeu-lsp:prometeu-lsp-api")) implementation(project(":prometeu-lsp:prometeu-lsp-api"))
implementation(project(":prometeu-lsp:prometeu-lsp-v1")) 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-compiler:prometeu-frontend-registry"))
implementation(project(":prometeu-packer:prometeu-packer-api")) implementation(project(":prometeu-packer:prometeu-packer-api"))
implementation(project(":prometeu-packer:prometeu-packer-v1")) 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.logs.LogAggregator;
import p.studio.utilities.structures.ReadOnlySet; 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.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
class FrontendProviderBoundaryTest { 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 @Test
void compileOnlyFrontendProviderMayOmitLanguageService() { void compileOnlyFrontendProviderMayOmitLanguageService() {
final FrontendProvider provider = new CompileOnlyFrontendProvider(); final FrontendProvider provider = new CompileOnlyFrontendProvider();
@ -110,26 +28,6 @@ class FrontendProviderBoundaryTest {
BuildingIssueSink.empty())); 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 static final class CompileOnlyFrontendProvider implements FrontendProvider {
private final FrontendSpec specification = FrontendSpec.builder() private final FrontendSpec specification = FrontendSpec.builder()
.languageId("compile-only") .languageId("compile-only")

View File

@ -43,8 +43,11 @@ class ValidationOwnershipSpecTest {
content.contains("Canonical host-binding identity"), content.contains("Canonical host-binding identity"),
"spec 19 matrix must include the host-admission split"); "spec 19 matrix must include the host-admission split");
assertTrue( assertTrue(
content.contains("MUST NOT import `p.studio.compiler.pbs`"), content.contains("MUST NOT depend on types from the PBS frontend module"),
"spec 19 must forbid PBS imports in platform validators"); "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 @Test

View File

@ -7,7 +7,6 @@ dependencies {
implementation(project(":prometeu-compiler:prometeu-compiler-core")) implementation(project(":prometeu-compiler:prometeu-compiler-core"))
implementation(project(":prometeu-compiler:prometeu-frontend-api")) implementation(project(":prometeu-compiler:prometeu-frontend-api"))
implementation(project(":prometeu-compiler:prometeu-build-pipeline")) 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-frontend-registry"))
implementation(project(":prometeu-compiler:prometeu-deps")) implementation(project(":prometeu-compiler:prometeu-deps"))
implementation(libs.lsp4j) implementation(libs.lsp4j)

View File

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

View File

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