diff --git a/discussion/index.ndjson b/discussion/index.ndjson index eda588d1..55bb71a0 100644 --- a/discussion/index.ndjson +++ b/discussion/index.ndjson @@ -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"}]} diff --git a/discussion/lessons/DSC-0064-multi-frontend-architectural-tests/LSN-0066-archunit-guards-java-multi-frontend-boundaries.md b/discussion/lessons/DSC-0064-multi-frontend-architectural-tests/LSN-0066-archunit-guards-java-multi-frontend-boundaries.md new file mode 100644 index 00000000..aed5b5ed --- /dev/null +++ b/discussion/lessons/DSC-0064-multi-frontend-architectural-tests/LSN-0066-archunit-guards-java-multi-frontend-boundaries.md @@ -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` diff --git a/discussion/workflow/agendas/AGD-0067-multi-frontend-architectural-tests.md b/discussion/workflow/agendas/AGD-0067-multi-frontend-architectural-tests.md deleted file mode 100644 index 6e47d875..00000000 --- a/discussion/workflow/agendas/AGD-0067-multi-frontend-architectural-tests.md +++ /dev/null @@ -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. - diff --git a/docs/specs/compiler/19. Verification and Safety Checks Specification.md b/docs/specs/compiler/19. Verification and Safety Checks Specification.md index 19d60c7a..364a8c55 100644 --- a/docs/specs/compiler/19. Verification and Safety Checks Specification.md +++ b/docs/specs/compiler/19. Verification and Safety Checks Specification.md @@ -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. diff --git a/docs/specs/compiler/20. IRBackend to IRVM Lowering Specification.md b/docs/specs/compiler/20. IRBackend to IRVM Lowering Specification.md index 27595efe..dbc652e8 100644 --- a/docs/specs/compiler/20. IRBackend to IRVM Lowering Specification.md +++ b/docs/specs/compiler/20. IRBackend to IRVM Lowering Specification.md @@ -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 diff --git a/docs/specs/compiler/22. Backend Spec-to-Test Conformance Matrix.md b/docs/specs/compiler/22. Backend Spec-to-Test Conformance Matrix.md index cb9580e2..9dca081e 100644 --- a/docs/specs/compiler/22. Backend Spec-to-Test Conformance Matrix.md +++ b/docs/specs/compiler/22. Backend Spec-to-Test Conformance Matrix.md @@ -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. | diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 8324a30f..097bd713 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -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" } diff --git a/prometeu-app/build.gradle.kts b/prometeu-app/build.gradle.kts index bbfcdfce..8338bc9a 100644 --- a/prometeu-app/build.gradle.kts +++ b/prometeu-app/build.gradle.kts @@ -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")) diff --git a/prometeu-architecture-tests/build.gradle.kts b/prometeu-architecture-tests/build.gradle.kts new file mode 100644 index 00000000..e0eee078 --- /dev/null +++ b/prometeu-architecture-tests/build.gradle.kts @@ -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("jacocoTestReport") { + enabled = false +} diff --git a/prometeu-architecture-tests/src/test/java/p/studio/architecture/CommonBackendPbsPrefixScanTest.java b/prometeu-architecture-tests/src/test/java/p/studio/architecture/CommonBackendPbsPrefixScanTest.java new file mode 100644 index 00000000..45c5ca3a --- /dev/null +++ b/prometeu-architecture-tests/src/test/java/p/studio/architecture/CommonBackendPbsPrefixScanTest.java @@ -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 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(); + 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"); + } +} diff --git a/prometeu-architecture-tests/src/test/java/p/studio/architecture/DescribedPredicates.java b/prometeu-architecture-tests/src/test/java/p/studio/architecture/DescribedPredicates.java new file mode 100644 index 00000000..04899ca3 --- /dev/null +++ b/prometeu-architecture-tests/src/test/java/p/studio/architecture/DescribedPredicates.java @@ -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 notBelongingTo(final Class... allowed) { + final Set allowedNames = Arrays.stream(allowed) + .map(Class::getName) + .collect(Collectors.toUnmodifiableSet()); + return DescribedPredicate.describe( + "do not belong to " + allowedNames, + javaClass -> !allowedNames.contains(javaClass.getName())); + } +} diff --git a/prometeu-architecture-tests/src/test/java/p/studio/architecture/PbsGradleDependencyAllowlistTest.java b/prometeu-architecture-tests/src/test/java/p/studio/architecture/PbsGradleDependencyAllowlistTest.java new file mode 100644 index 00000000..afc5300b --- /dev/null +++ b/prometeu-architecture-tests/src/test/java/p/studio/architecture/PbsGradleDependencyAllowlistTest.java @@ -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> 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(); + + 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 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"); + } +} diff --git a/prometeu-architecture-tests/src/test/java/p/studio/architecture/PbsModuleBoundaryArchTest.java b/prometeu-architecture-tests/src/test/java/p/studio/architecture/PbsModuleBoundaryArchTest.java new file mode 100644 index 00000000..69841525 --- /dev/null +++ b/prometeu-architecture-tests/src/test/java/p/studio/architecture/PbsModuleBoundaryArchTest.java @@ -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"); +} diff --git a/prometeu-architecture-tests/src/test/java/p/studio/architecture/PbsModuleClasspathTest.java b/prometeu-architecture-tests/src/test/java/p/studio/architecture/PbsModuleClasspathTest.java new file mode 100644 index 00000000..8fa1cea5 --- /dev/null +++ b/prometeu-architecture-tests/src/test/java/p/studio/architecture/PbsModuleClasspathTest.java @@ -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()); + } +} diff --git a/prometeu-architecture-tests/src/test/java/p/studio/architecture/PbsModuleOrigins.java b/prometeu-architecture-tests/src/test/java/p/studio/architecture/PbsModuleOrigins.java new file mode 100644 index 00000000..4b8b9139 --- /dev/null +++ b/prometeu-architecture-tests/src/test/java/p/studio/architecture/PbsModuleOrigins.java @@ -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 fromPbsModule() { + return DescribedPredicate.describe( + "originate from the PBS frontend module", + PbsModuleOrigins::isPbsModule); + } + + static DescribedPredicate fromRegistryModule() { + return DescribedPredicate.describe( + "originate from the frontend registry module", + PbsModuleOrigins::isRegistryModule); + } + + static DescribedPredicate 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(""); + } + + 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('\\', '/'); + } +} diff --git a/prometeu-compiler/prometeu-build-pipeline/src/test/java/p/studio/compiler/specs/CommonBackendArchitectureTest.java b/prometeu-compiler/prometeu-build-pipeline/src/test/java/p/studio/compiler/specs/CommonBackendArchitectureTest.java deleted file mode 100644 index d460268a..00000000 --- a/prometeu-compiler/prometeu-build-pipeline/src/test/java/p/studio/compiler/specs/CommonBackendArchitectureTest.java +++ /dev/null @@ -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 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(); - 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(); - 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 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 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 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"); - } -} diff --git a/prometeu-compiler/prometeu-build-pipeline/src/test/java/p/studio/compiler/specs/FrontendProviderBoundaryTest.java b/prometeu-compiler/prometeu-build-pipeline/src/test/java/p/studio/compiler/specs/FrontendProviderBoundaryTest.java index 03428814..c9eacb9e 100644 --- a/prometeu-compiler/prometeu-build-pipeline/src/test/java/p/studio/compiler/specs/FrontendProviderBoundaryTest.java +++ b/prometeu-compiler/prometeu-build-pipeline/src/test/java/p/studio/compiler/specs/FrontendProviderBoundaryTest.java @@ -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") diff --git a/prometeu-compiler/prometeu-build-pipeline/src/test/java/p/studio/compiler/specs/ValidationOwnershipSpecTest.java b/prometeu-compiler/prometeu-build-pipeline/src/test/java/p/studio/compiler/specs/ValidationOwnershipSpecTest.java index cdddb143..4b8b6f2d 100644 --- a/prometeu-compiler/prometeu-build-pipeline/src/test/java/p/studio/compiler/specs/ValidationOwnershipSpecTest.java +++ b/prometeu-compiler/prometeu-build-pipeline/src/test/java/p/studio/compiler/specs/ValidationOwnershipSpecTest.java @@ -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 diff --git a/prometeu-lsp/prometeu-lsp-v1/build.gradle.kts b/prometeu-lsp/prometeu-lsp-v1/build.gradle.kts index c2b12b6f..7785f0eb 100644 --- a/prometeu-lsp/prometeu-lsp-v1/build.gradle.kts +++ b/prometeu-lsp/prometeu-lsp-v1/build.gradle.kts @@ -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) diff --git a/prometeu-studio/build.gradle.kts b/prometeu-studio/build.gradle.kts index 4993f398..93d2ea90 100644 --- a/prometeu-studio/build.gradle.kts +++ b/prometeu-studio/build.gradle.kts @@ -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")) } diff --git a/settings.gradle.kts b/settings.gradle.kts index a3801c38..98adda19 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -22,3 +22,5 @@ include("prometeu-compiler:prometeu-frontend-registry") include("prometeu-studio") include("prometeu-app") + +include("prometeu-architecture-tests")