diff --git a/discussion/index.ndjson b/discussion/index.ndjson index b0c93015..ec8c5d2a 100644 --- a/discussion/index.ndjson +++ b/discussion/index.ndjson @@ -6,7 +6,7 @@ {"type":"discussion","id":"DSC-0061","status":"open","ticket":"multi-frontend-sdk-canonical-definition","title":"Auditoria e centralizacao gradual da definicao canonica do SDK","created_at":"2026-07-15","updated_at":"2026-07-15","tags":["compiler","compiler-general","sdk","stdlib","hostcalls","intrinsics","multi-frontend"],"agendas":[{"id":"AGD-0064","file":"AGD-0064-multi-frontend-sdk-canonical-definition.md","status":"open","created_at":"2026-07-15","updated_at":"2026-07-15"}],"decisions":[],"plans":[],"lessons":[]} {"type":"discussion","id":"DSC-0060","status":"open","ticket":"multi-frontend-validation-boundaries","title":"Separar validacoes de linguagem e validacoes de plataforma","created_at":"2026-07-15","updated_at":"2026-07-15","tags":["compiler","compiler-general","compiler-pbs","backend","validation","multi-frontend"],"agendas":[{"id":"AGD-0063","file":"AGD-0063-multi-frontend-validation-boundaries.md","status":"open","created_at":"2026-07-15","updated_at":"2026-07-15"}],"decisions":[],"plans":[],"lessons":[]} {"type":"discussion","id":"DSC-0059","status":"open","ticket":"multi-frontend-common-lifecycle","title":"Extrair lifecycle comum das responsabilidades do frontend PBS","created_at":"2026-07-15","updated_at":"2026-07-15","tags":["compiler","compiler-general","compiler-pbs","lifecycle","backend","multi-frontend"],"agendas":[{"id":"AGD-0062","file":"AGD-0062-multi-frontend-common-lifecycle.md","status":"open","created_at":"2026-07-15","updated_at":"2026-07-15"}],"decisions":[],"plans":[],"lessons":[]} -{"type":"discussion","id":"DSC-0058","status":"in_progress","ticket":"multi-frontend-serializable-ir","title":"Manter a IR comum serializavel por design","created_at":"2026-07-15","updated_at":"2026-07-15","tags":["compiler","compiler-general","ir","backend","serialization","multi-frontend"],"agendas":[{"id":"AGD-0061","file":"AGD-0061-multi-frontend-serializable-ir.md","status":"accepted","created_at":"2026-07-15","updated_at":"2026-07-15"}],"decisions":[{"id":"DEC-0044","file":"DEC-0044-serializable-common-ir-boundary.md","status":"accepted","created_at":"2026-07-15","updated_at":"2026-07-15","ref_agenda":"AGD-0061"}],"plans":[{"id":"PLN-0119","file":"PLN-0119-document-irbackend-serializability-invariants.md","status":"done","created_at":"2026-07-15","updated_at":"2026-07-15","ref_decisions":["DEC-0044"]},{"id":"PLN-0120","file":"PLN-0120-audit-public-irbackend-contract-shape.md","status":"done","created_at":"2026-07-15","updated_at":"2026-07-15","ref_decisions":["DEC-0044"]},{"id":"PLN-0121","file":"PLN-0121-add-serializable-irbackend-reflection-guardrails.md","status":"done","created_at":"2026-07-15","updated_at":"2026-07-15","ref_decisions":["DEC-0044"]},{"id":"PLN-0122","file":"PLN-0122-prove-deterministic-irbackend-ordering.md","status":"open","created_at":"2026-07-15","updated_at":"2026-07-15","ref_decisions":["DEC-0044"]},{"id":"PLN-0123","file":"PLN-0123-fix-concrete-irbackend-serialization-leaks.md","status":"open","created_at":"2026-07-15","updated_at":"2026-07-15","ref_decisions":["DEC-0044"]}],"lessons":[]} +{"type":"discussion","id":"DSC-0058","status":"in_progress","ticket":"multi-frontend-serializable-ir","title":"Manter a IR comum serializavel por design","created_at":"2026-07-15","updated_at":"2026-07-15","tags":["compiler","compiler-general","ir","backend","serialization","multi-frontend"],"agendas":[{"id":"AGD-0061","file":"AGD-0061-multi-frontend-serializable-ir.md","status":"accepted","created_at":"2026-07-15","updated_at":"2026-07-15"}],"decisions":[{"id":"DEC-0044","file":"DEC-0044-serializable-common-ir-boundary.md","status":"accepted","created_at":"2026-07-15","updated_at":"2026-07-15","ref_agenda":"AGD-0061"}],"plans":[{"id":"PLN-0119","file":"PLN-0119-document-irbackend-serializability-invariants.md","status":"done","created_at":"2026-07-15","updated_at":"2026-07-15","ref_decisions":["DEC-0044"]},{"id":"PLN-0120","file":"PLN-0120-audit-public-irbackend-contract-shape.md","status":"done","created_at":"2026-07-15","updated_at":"2026-07-15","ref_decisions":["DEC-0044"]},{"id":"PLN-0121","file":"PLN-0121-add-serializable-irbackend-reflection-guardrails.md","status":"done","created_at":"2026-07-15","updated_at":"2026-07-15","ref_decisions":["DEC-0044"]},{"id":"PLN-0122","file":"PLN-0122-prove-deterministic-irbackend-ordering.md","status":"done","created_at":"2026-07-15","updated_at":"2026-07-15","ref_decisions":["DEC-0044"]},{"id":"PLN-0123","file":"PLN-0123-fix-concrete-irbackend-serialization-leaks.md","status":"open","created_at":"2026-07-15","updated_at":"2026-07-15","ref_decisions":["DEC-0044"]}],"lessons":[]} {"type":"discussion","id":"DSC-0057","status":"done","ticket":"multi-frontend-frontend-backend-contract","title":"Estabilizar contrato entre frontend e backend comum","created_at":"2026-07-15","updated_at":"2026-07-15","tags":["compiler","compiler-general","compiler-pbs","ir","backend","multi-frontend"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0059","file":"discussion/lessons/DSC-0057-multi-frontend-frontend-backend-contract/LSN-0059-common-irbackend-handoff-and-backend-guardrails.md","status":"done","created_at":"2026-07-15","updated_at":"2026-07-15"}]} {"type":"discussion","id":"DSC-0056","status":"done","ticket":"multi-frontend-remove-pbs-branches","title":"Generalizar o contrato LSP/editorial para frontends","created_at":"2026-07-15","updated_at":"2026-07-15","tags":["compiler","compiler-general","compiler-pbs","studio","frontend","coupling","multi-frontend"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0058","file":"discussion/lessons/DSC-0056-multi-frontend-remove-pbs-branches/LSN-0058-generic-frontend-editorial-contract-for-lsp.md","status":"done","created_at":"2026-07-15","updated_at":"2026-07-15"}]} {"type":"discussion","id":"DSC-0055","status":"done","ticket":"multi-frontend-compiler-vs-language-services","title":"Separar compilacao de servicos editoriais de frontend","created_at":"2026-07-15","updated_at":"2026-07-15","tags":["compiler","compiler-general","studio","lsp","editor","frontend","multi-frontend"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0056","file":"discussion/lessons/DSC-0055-multi-frontend-compiler-vs-language-services/LSN-0056-compile-first-frontends-with-optional-editorial-capabilities.md","status":"done","created_at":"2026-07-15","updated_at":"2026-07-15"}]} diff --git a/discussion/workflow/plans/PLN-0122-prove-deterministic-irbackend-ordering.md b/discussion/workflow/plans/PLN-0122-prove-deterministic-irbackend-ordering.md index 0d227ca3..5d67af60 100644 --- a/discussion/workflow/plans/PLN-0122-prove-deterministic-irbackend-ordering.md +++ b/discussion/workflow/plans/PLN-0122-prove-deterministic-irbackend-ordering.md @@ -2,7 +2,7 @@ id: PLN-0122 ticket: multi-frontend-serializable-ir title: Prove deterministic IRBackend ordering -status: open +status: done created: 2026-07-15 ref_decisions: [DEC-0044] tags: [compiler, compiler-general, ir, backend, serialization, multi-frontend] 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 495b273d..85fa813e 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 @@ -43,7 +43,7 @@ to concrete positive/negative test evidence and current status. | 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.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`; planned deterministic ordering tests from `PLN-0122` | N/A | partial | Public shape guardrails exist; deterministic ordering evidence is still planned before this row can move to pass. | +| 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. | Planned: public contract audit from `PLN-0120`; targeted leak fixes from `PLN-0123` when needed | N/A | missing | Existing `FileId`, `ModuleId`, `CallableId`, and `IntrinsicId` are expected allowed values when table scope is deterministic. | | 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. | diff --git a/prometeu-compiler/prometeu-frontend-api/src/test/java/p/studio/compiler/models/IRBackendExecutableContractTest.java b/prometeu-compiler/prometeu-frontend-api/src/test/java/p/studio/compiler/models/IRBackendExecutableContractTest.java index 435c587c..d6ed249e 100644 --- a/prometeu-compiler/prometeu-frontend-api/src/test/java/p/studio/compiler/models/IRBackendExecutableContractTest.java +++ b/prometeu-compiler/prometeu-frontend-api/src/test/java/p/studio/compiler/models/IRBackendExecutableContractTest.java @@ -559,6 +559,208 @@ class IRBackendExecutableContractTest { assertEquals(1, backend.getCallableSignatures().get(1).moduleId().getIndex()); } + @Test + void aggregatorMustEmitDeterministicOrderedPublicContractShape() { + final var first = emitDeterministicAggregateFixture(); + final var second = emitDeterministicAggregateFixture(); + + assertEquals( + List.of("alpha", "beta"), + first.getFunctions().asList().stream().map(IRFunction::name).toList()); + assertEquals( + List.of("frameWrapper", "initWrapper"), + first.getSyntheticFunctions().asList().stream().map(IRSyntheticFunction::callableName).toList()); + assertEquals( + List.of("state", "timer"), + first.getGlobals().asList().stream().map(IRGlobal::name).toList()); + assertEquals( + List.of("frame", "init"), + first.getExecutableFunctions().asList().stream().map(IRBackendExecutableFunction::callableName).toList()); + assertEquals( + List.of("app:alpha", "app:beta"), + first.getModulePool().asList().stream() + .map(module -> module.project() + ":" + String.join(".", module.pathSegments().asList())) + .toList()); + assertEquals( + List.of("frame", "helper", "init"), + first.getCallableSignatures().asList().stream().map(CallableSignatureRef::callableName).toList()); + assertEquals( + List.of("core.draw", "core.color.pack"), + first.getIntrinsicPool().asList().stream().map(IntrinsicReference::canonicalName).toList()); + assertEquals( + List.of("draw", "play"), + first.getReservedMetadata().hostMethodBindings().asList().stream() + .map(IRReservedMetadata.HostMethodBinding::abiMethod) + .toList()); + assertEquals( + List.of("Sprite", "Sound"), + first.getReservedMetadata().builtinTypeSurfaces().asList().stream() + .map(IRReservedMetadata.BuiltinTypeSurface::sourceTypeName) + .toList()); + assertEquals( + List.of("WHITE", "BLACK"), + first.getReservedMetadata().builtinConstSurfaces().asList().stream() + .map(IRReservedMetadata.BuiltinConstSurface::sourceConstName) + .toList()); + assertEquals( + List.of("gfx.draw", "audio.play", "input.read"), + first.getReservedMetadata().requiredCapabilities().asList()); + + assertEquals(first.toString(), second.toString()); + assertEquals( + first.getReservedMetadata().requiredCapabilities().asList(), + second.getReservedMetadata().requiredCapabilities().asList()); + assertEquals( + first.getExecutableFunctions().get(0).moduleId(), + second.getExecutableFunctions().get(0).moduleId()); + assertEquals( + first.getExecutableFunctions().get(1).callableId(), + second.getExecutableFunctions().get(1).callableId()); + } + + private static IRBackend emitDeterministicAggregateFixture() { + final var aggregator = IRBackend.aggregator(); + aggregator.merge(new IRBackendFile( + new FileId(10), + ReadOnlyList.from(new IRFunction(new FileId(10), "alpha", 0, false, new Span(new FileId(10), 0, 5))), + ReadOnlyList.from(new IRSyntheticFunction( + new ModuleId(0), + "frameWrapper", + IRSyntheticCallableKind.PUBLISHED_FRAME_WRAPPER, + new IRSyntheticOrigin(new FileId(10), "frame", new Span(new FileId(10), 0, 5)))), + ReadOnlyList.from(new IRGlobal( + new FileId(10), + new ModuleId(0), + "state", + "int", + new Span(new FileId(10), 1, 2))), + ReadOnlyList.from(new IRBackendExecutableFunction( + new FileId(10), + new ModuleId(0), + "frame", + new CallableId(0), + 0, + 10, + 0, + 0, + 0, + 1, + ReadOnlyList.from(new IRBackendExecutableFunction.Instruction( + IRBackendExecutableFunction.InstructionKind.CALL_INTRINSIC, + new ModuleId(0), + "", + null, + null, + new IRBackendExecutableFunction.IntrinsicCallMetadata("core.draw", 1, new IntrinsicId(0)), + Span.none())), + Span.none())), + new IRReservedMetadata( + ReadOnlyList.from(new IRReservedMetadata.HostMethodBinding( + "Graphics", + "draw", + "gfx", + "draw", + 1, + 0, + null, + true, + "gfx.draw", + Span.none())), + ReadOnlyList.from(new IRReservedMetadata.BuiltinTypeSurface( + "Sprite", + "core.Sprite", + 1, + ReadOnlyList.from(new IRReservedMetadata.BuiltinFieldSurface( + "x", + "int", + 0, + Span.none())), + ReadOnlyList.empty(), + Span.none())), + ReadOnlyList.from(new IRReservedMetadata.BuiltinConstSurface( + "WHITE", + "color", + "core.color.white", + 1, + Span.none())), + ReadOnlyList.from("gfx.draw", "audio.play")), + ReadOnlyList.from(new ModuleReference("app", ReadOnlyList.from("alpha"))), + ReadOnlyList.from( + new CallableSignatureRef(new ModuleId(0), "frame", 0, "() -> unit"), + new CallableSignatureRef(new ModuleId(0), "helper", 0, "() -> unit")), + ReadOnlyList.from(new IntrinsicReference("core.draw", 1)))); + aggregator.merge(new IRBackendFile( + new FileId(20), + ReadOnlyList.from(new IRFunction(new FileId(20), "beta", 1, true, new Span(new FileId(20), 0, 7))), + ReadOnlyList.from(new IRSyntheticFunction( + new ModuleId(0), + "initWrapper", + IRSyntheticCallableKind.PROJECT_INIT, + new IRSyntheticOrigin(new FileId(20), "init", new Span(new FileId(20), 0, 7)))), + ReadOnlyList.from(new IRGlobal( + new FileId(20), + new ModuleId(0), + "timer", + "int", + new Span(new FileId(20), 1, 3))), + ReadOnlyList.from(new IRBackendExecutableFunction( + new FileId(20), + new ModuleId(0), + "init", + new CallableId(0), + 0, + 8, + 0, + 0, + 0, + 1, + ReadOnlyList.from(new IRBackendExecutableFunction.Instruction( + IRBackendExecutableFunction.InstructionKind.CALL_INTRINSIC, + new ModuleId(0), + "", + null, + null, + new IRBackendExecutableFunction.IntrinsicCallMetadata("core.color.pack", 1, new IntrinsicId(0)), + Span.none())), + Span.none())), + new IRReservedMetadata( + ReadOnlyList.from(new IRReservedMetadata.HostMethodBinding( + "Audio", + "play", + "audio", + "play", + 1, + 0, + null, + true, + "audio.play", + Span.none())), + ReadOnlyList.from(new IRReservedMetadata.BuiltinTypeSurface( + "Sound", + "core.Sound", + 1, + ReadOnlyList.empty(), + ReadOnlyList.from(new IRReservedMetadata.IntrinsicSurface( + "volume", + "core.sound.volume", + 1, + 1, + 1, + Span.none())), + Span.none())), + ReadOnlyList.from(new IRReservedMetadata.BuiltinConstSurface( + "BLACK", + "color", + "core.color.black", + 1, + Span.none())), + ReadOnlyList.from("gfx.draw", "input.read")), + ReadOnlyList.from(new ModuleReference("app", ReadOnlyList.from("beta"))), + ReadOnlyList.from(new CallableSignatureRef(new ModuleId(0), "init", 0, "() -> unit")), + ReadOnlyList.from(new IntrinsicReference("core.color.pack", 1)))); + return aggregator.emit(); + } + private static void inspectType( final String owner, final Type type,