implements PLN-0122

This commit is contained in:
bQUARKz 2026-07-15 13:02:09 +01:00
parent cf14039077
commit f42c83bdca
Signed by: bquarkz
SSH Key Fingerprint: SHA256:Z7dgqoglWwoK6j6u4QC87OveEq74WOhFN+gitsxtkf8
4 changed files with 205 additions and 3 deletions

View File

@ -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-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-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-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-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-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"}]} {"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"}]}

View File

@ -2,7 +2,7 @@
id: PLN-0122 id: PLN-0122
ticket: multi-frontend-serializable-ir ticket: multi-frontend-serializable-ir
title: Prove deterministic IRBackend ordering title: Prove deterministic IRBackend ordering
status: open status: done
created: 2026-07-15 created: 2026-07-15
ref_decisions: [DEC-0044] ref_decisions: [DEC-0044]
tags: [compiler, compiler-general, ir, backend, serialization, multi-frontend] tags: [compiler, compiler-general, ir, backend, serialization, multi-frontend]

View File

@ -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.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 `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.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.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-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. | | 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. |

View File

@ -559,6 +559,208 @@ class IRBackendExecutableContractTest {
assertEquals(1, backend.getCallableSignatures().get(1).moduleId().getIndex()); 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( private static void inspectType(
final String owner, final String owner,
final Type type, final Type type,