From 7e43a776c45d314370dad5338ff9a9917182437f Mon Sep 17 00:00:00 2001 From: desp0042 Date: Thu, 30 Jul 2026 08:05:44 +0200 Subject: [PATCH 1/3] feat(amr): resolve Reflux through provider protocol --- python/pops/amr/__init__.py | 2 + python/pops/amr/_resolution.py | 10 ++- python/pops/amr/providers.py | 85 +++++++++++++++++++++ python/pops/codegen/_amr_plan_validation.py | 5 +- python/pops/layouts/__init__.py | 20 ++++- python/pops/lib/amr/__init__.py | 53 +++++++++++++ python/pops/runtime/_runtime_authorities.py | 6 +- 7 files changed, 171 insertions(+), 10 deletions(-) diff --git a/python/pops/amr/__init__.py b/python/pops/amr/__init__.py index 1db610322..5cea94341 100644 --- a/python/pops/amr/__init__.py +++ b/python/pops/amr/__init__.py @@ -36,6 +36,7 @@ AMRProviderLoweringContext, amr_provider_binding_identity, ClusteringProvider, + RefluxProvider, ResolvedAMRProviderBinding, TaggerProvider, ) @@ -66,6 +67,7 @@ "PatchLayout", "PreparedHierarchyNativeLowering", "PreparedHierarchyNativeProvider", + "RefluxProvider", "ResolvedAMRProviderBinding", "Tag", "TaggerProvider", diff --git a/python/pops/amr/_resolution.py b/python/pops/amr/_resolution.py index 060a63912..62d6a027b 100644 --- a/python/pops/amr/_resolution.py +++ b/python/pops/amr/_resolution.py @@ -625,6 +625,7 @@ def resolve_amr_authorities( load_balance: Any, tagger: Any, clustering: Any, + reflux: Any, context: AMRResolutionContext, ) -> ResolvedAMRAuthorities: """Resolve every adaptive-layout concern exactly once from its owning declaration.""" @@ -643,7 +644,7 @@ def resolve_amr_authorities( raise TypeError("AMR %s authority must implement %s()" % (slot, method)) if type(context) is not AMRResolutionContext: raise TypeError("AMR resolution requires an AMRResolutionContext") - providers = (tagger, clustering) + providers = (tagger, clustering, reflux) for value in providers: for method in ("inspect", "resolve_references", "lower_amr_provider"): if not callable(getattr(value, method, None)): @@ -684,10 +685,11 @@ def resolve_amr_authorities( if lowered.role in provider_bindings: raise ValueError("AMR provider roles must be unique") provider_bindings[lowered.role] = lowered.data - if set(provider_bindings) != {"clustering", "tagger"}: - raise ValueError("AMR resolution requires exact clustering and tagger provider roles") + if set(provider_bindings) != {"clustering", "tagger", "reflux"}: + raise ValueError( + "AMR resolution requires exact clustering, tagger and reflux provider roles") provider_bindings = { - role: provider_bindings[role] for role in ("clustering", "tagger") + role: provider_bindings[role] for role in ("clustering", "tagger", "reflux") } resolved_hierarchy = _hierarchy( hierarchy, diff --git a/python/pops/amr/providers.py b/python/pops/amr/providers.py index c704b9132..223cf4d14 100644 --- a/python/pops/amr/providers.py +++ b/python/pops/amr/providers.py @@ -423,6 +423,64 @@ def runtime_binding_data(self) -> dict[str, Any]: canonical_identity = runtime_binding_data +@dataclass(frozen=True, slots=True) +class RefluxProvider: + """Bind one external local Reflux table to the conservative AMR transition.""" + + component: Any + __pops_ir_immutable__ = True + + def __post_init__(self) -> None: + from pops import interfaces + + _external_component( + self.component, + interface=interfaces.Reflux, + where="RefluxProvider.component", + ) + + def resolve_references(self, resolver: Any) -> RefluxProvider: + if not callable(resolver): + raise TypeError("RefluxProvider.resolve_references requires a callable resolver") + return self + + def require_component_inputs(self, components: Any) -> None: + _require_component(self.component, components, where="RefluxProvider") + + def lower_amr_provider( + self, context: AMRProviderLoweringContext, + ) -> ResolvedAMRProviderBinding: + """Authenticate the component, hierarchy layout and Program clock.""" + if type(context) is not AMRProviderLoweringContext: + raise TypeError("RefluxProvider requires an AMRProviderLoweringContext") + self.require_component_inputs(context.components) + data = { + **self.runtime_binding_data(), + "layout_identity": context.layout_identity, + "clock_identity": context.clock_identity, + } + data["provider_identity"] = amr_provider_binding_identity("reflux", data) + return ResolvedAMRProviderBinding("reflux", data) + + def runtime_binding_data(self) -> dict[str, Any]: + from pops import interfaces + + data = { + "schema_version": 1, + "provider_type": "external_amr_reflux", + "runtime_installation": { + "schema_version": 1, + "protocol": "external_component", + }, + **_component_binding(self.component, interfaces.Reflux), + } + data["provider_identity"] = make_identity("amr-reflux-provider", data).token + return data + + inspect = runtime_binding_data + canonical_identity = runtime_binding_data + + @dataclass(frozen=True, slots=True) class _AMRRuntimeInterfaceProtocol: """Native-interface-owned validation and installation route.""" @@ -598,6 +656,26 @@ def validate_installed_capability( "external AMR Tagger lacks its exact graph/capability/clock contract") +@dataclass(frozen=True, slots=True) +class _RefluxRuntimeInterfaceProtocol(_AMRRuntimeInterfaceProtocol): + """The local Reflux callback is qualified by the accepted Program clock.""" + + def validate_resolved_capability( + self, binding: Mapping[str, Any], resolved_tagging_identity: str | None, + ) -> None: + del resolved_tagging_identity + if not isinstance(binding.get("clock_identity"), str) \ + or not binding["clock_identity"]: + raise ValueError("AMR Reflux lacks its exact Program clock authority") + + def validate_installed_capability( + self, binding: Mapping[str, Any], installed: Any, + resolved_tagging_identity: str | None, + ) -> None: + del installed + self.validate_resolved_capability(binding, resolved_tagging_identity) + + def _runtime_interface_key(value: Any) -> tuple[Any, ...]: if not isinstance(value, Mapping): raise TypeError("AMR provider binding has no native-interface protocol") @@ -632,6 +710,12 @@ def _runtime_interface_protocols() -> dict[tuple[Any, ...], _AMRRuntimeInterface builtin_provider_id="pops.lib.amr::symbolic_tagger", component_installer="_install_amr_tagger_component", ), + _RefluxRuntimeInterfaceProtocol( + role="reflux", + native_interface=interfaces.Reflux.to_data(), + builtin_provider_id="pops.lib.amr::flux_register_reflux", + component_installer="_install_amr_reflux_component", + ), ) return {_runtime_interface_key(row.native_interface): row for row in protocols} @@ -933,6 +1017,7 @@ def prepare_amr_provider_installation( "amr_provider_binding_identity", "ClusteringProvider", "PreparedAMRProviderNativeConfig", + "RefluxProvider", "ResolvedAMRProviderBinding", "TaggerProvider", "validate_amr_provider_binding", diff --git a/python/pops/codegen/_amr_plan_validation.py b/python/pops/codegen/_amr_plan_validation.py index 63d9a8311..29fe06fd2 100644 --- a/python/pops/codegen/_amr_plan_validation.py +++ b/python/pops/codegen/_amr_plan_validation.py @@ -81,8 +81,9 @@ def validate_amr_authorities(plan: Any) -> None: or plan.bootstrap_plan.initial_identity != plan.initial_condition_plan.identity: raise ValueError("ResolvedSimulationPlan bootstrap does not authenticate AMR authorities") providers = plan.amr_providers - if tuple(providers) != ("clustering", "tagger"): - raise ValueError("AMR plan requires exact clustering and tagger provider bindings") + if tuple(providers) != ("clustering", "tagger", "reflux"): + raise ValueError( + "AMR plan requires exact clustering, tagger and reflux provider bindings") # Component inputs deliberately admit both source authorities and already-compiled # artifacts. Their representations differ, but both expose the same authenticated # projection protocol. Index that projection instead of reaching through the source-only diff --git a/python/pops/layouts/__init__.py b/python/pops/layouts/__init__.py index 6f8354777..b137a558e 100644 --- a/python/pops/layouts/__init__.py +++ b/python/pops/layouts/__init__.py @@ -396,6 +396,7 @@ def __init__( load_balance: Any = None, tagger: Any = None, clustering: Any = None, + reflux: Any = None, ) -> None: # Structural snapshots consume ``options()``. Keeping authorities private prevents the # generic snapshotter from recursively treating Schedule implementation helpers as public @@ -407,15 +408,22 @@ def __init__( self._transfer = transfer self._execution = execution self._patch_layout = PatchLayout() if patch_layout is None else patch_layout - if load_balance is None or tagger is None or clustering is None: - from pops.lib.amr import BergerRigoutsos, SpaceFillingCurve, SymbolicTagger + if load_balance is None or tagger is None or clustering is None or reflux is None: + from pops.lib.amr import ( + BergerRigoutsos, + FluxRegisterReflux, + SpaceFillingCurve, + SymbolicTagger, + ) load_balance = SpaceFillingCurve() if load_balance is None else load_balance tagger = SymbolicTagger() if tagger is None else tagger clustering = BergerRigoutsos() if clustering is None else clustering + reflux = FluxRegisterReflux() if reflux is None else reflux self._load_balance = load_balance self._tagger = tagger self._clustering = clustering + self._reflux = reflux @property def grid(self) -> Any: @@ -457,6 +465,10 @@ def tagger(self) -> Any: def clustering(self) -> Any: return self._clustering + @property + def reflux(self) -> Any: + return self._reflux + def _validate_authorities(self) -> None: authorities = { "hierarchy": self.hierarchy, "tagging": self.tagging, @@ -468,6 +480,7 @@ def _validate_authorities(self) -> None: _load_balance_data(self.load_balance) _provider_data(self.tagger, "tagger") _provider_data(self.clustering, "clustering") + _provider_data(self.reflux, "reflux") for method in ("validate", "capabilities", "requirements", "options", "to_dict"): if not callable(getattr(self.grid, method, None)): raise TypeError("AMR.grid must implement %s()" % method) @@ -516,6 +529,7 @@ def options(self) -> dict[str, Any]: "load_balance": _load_balance_data(self.load_balance), "tagger": self.tagger.inspect(), "clustering": self.clustering.inspect(), + "reflux": self.reflux.inspect(), } def _summary(self) -> str: @@ -567,6 +581,7 @@ def resolved(value: Any) -> Any: load_balance=self.load_balance, tagger=self.tagger.resolve_references(resolved), clustering=self.clustering.resolve_references(resolved), + reflux=self.reflux.resolve_references(resolved), ) def resolve_amr_authorities(self, context: Any) -> Any: @@ -583,6 +598,7 @@ def resolve_amr_authorities(self, context: Any) -> Any: load_balance=self.load_balance, tagger=self.tagger, clustering=self.clustering, + reflux=self.reflux, context=context, ) diff --git a/python/pops/lib/amr/__init__.py b/python/pops/lib/amr/__init__.py index e59531afa..4fc244028 100644 --- a/python/pops/lib/amr/__init__.py +++ b/python/pops/lib/amr/__init__.py @@ -373,6 +373,58 @@ def runtime_binding_data(self) -> dict[str, Any]: canonical_identity = runtime_binding_data +@dataclass(frozen=True, slots=True) +class FluxRegisterReflux: + """Builtin conservative flux-register correction through the Reflux provider protocol.""" + + __pops_ir_immutable__: ClassVar[bool] = True + + def resolve_references(self, resolver: Any) -> FluxRegisterReflux: + if not callable(resolver): + raise TypeError("FluxRegisterReflux.resolve_references requires a callable resolver") + return self + + def require_component_inputs(self, components: Any) -> None: + del components + + def lower_amr_provider(self, context: Any) -> Any: + from pops.amr.providers import ( + AMRProviderLoweringContext, + ResolvedAMRProviderBinding, + amr_provider_binding_identity, + ) + + if type(context) is not AMRProviderLoweringContext: + raise TypeError("FluxRegisterReflux requires an AMRProviderLoweringContext") + self.require_component_inputs(context.components) + data = { + **self.runtime_binding_data(), + "layout_identity": context.layout_identity, + "clock_identity": context.clock_identity, + } + data["provider_identity"] = amr_provider_binding_identity("reflux", data) + return ResolvedAMRProviderBinding("reflux", data) + + def runtime_binding_data(self) -> dict[str, Any]: + from pops import interfaces + + data = { + "schema_version": 1, + "provider_type": "builtin_amr_reflux", + "runtime_installation": { + "schema_version": 1, + "protocol": "builtin", + }, + "provider_id": "pops.lib.amr::flux_register_reflux", + "native_interface": interfaces.Reflux.to_data(), + } + data["provider_identity"] = make_identity("amr-reflux-provider", data).token + return data + + inspect = runtime_binding_data + canonical_identity = runtime_binding_data + + @dataclass(frozen=True, slots=True) class BergerRigoutsos: """Builtin clustering provider with intrinsic validated algorithm controls.""" @@ -459,6 +511,7 @@ def runtime_binding_data(self) -> dict[str, Any]: "DivergencePreservingFace", "EllipticRecompute", "FaceTransfer", + "FluxRegisterReflux", "LinearTimeInterpolation", "Knapsack", "NodeTransfer", diff --git a/python/pops/runtime/_runtime_authorities.py b/python/pops/runtime/_runtime_authorities.py index aff8a712d..7d51102f1 100644 --- a/python/pops/runtime/_runtime_authorities.py +++ b/python/pops/runtime/_runtime_authorities.py @@ -511,8 +511,10 @@ def _install_amr_provider_authorities(engine: Any, install_plan: Any) -> None: """Install every AMR provider through its authority-carried runtime protocol.""" providers = install_plan.amr_providers - if not isinstance(providers, Mapping) or tuple(providers) != ("clustering", "tagger"): - raise ValueError("adaptive runtime requires exact clustering and tagger providers") + if not isinstance(providers, Mapping) \ + or tuple(providers) != ("clustering", "tagger", "reflux"): + raise ValueError( + "adaptive runtime requires exact clustering, tagger and reflux providers") native = getattr(engine, "_s", None) from pops.amr.providers import prepare_amr_provider_installation from pops.runtime._component_execution_context import component_execution_data From 4e7cc59ddd08e0dfd42000888aa81a93e322913f Mon Sep 17 00:00:00 2001 From: desp0042 Date: Thu, 30 Jul 2026 08:05:50 +0200 Subject: [PATCH 2/3] tests(amr): prove public Reflux provider installation --- ...prepared_reflux_runtime_execution_fence.py | 7 ++- .../unit/amr/test_external_amr_providers.py | 56 +++++++++++++++---- .../unit/amr/test_public_amr_resolution.py | 13 +++++ 3 files changed, 64 insertions(+), 12 deletions(-) diff --git a/tests/python/architecture/test_prepared_reflux_runtime_execution_fence.py b/tests/python/architecture/test_prepared_reflux_runtime_execution_fence.py index 9616dbb72..88b47e4f5 100644 --- a/tests/python/architecture/test_prepared_reflux_runtime_execution_fence.py +++ b/tests/python/architecture/test_prepared_reflux_runtime_execution_fence.py @@ -26,6 +26,7 @@ AMR_SYSTEM = ROOT / "src" / "runtime" / "amr" / "amr_system.cpp" AMR_BINDING = ROOT / "python" / "bindings" / "core" / "init" / "init_amr.cpp" RUNTIME_AUTHORITIES = ROOT / "python" / "pops" / "runtime" / "_runtime_authorities.py" +AMR_PROVIDER_PROTOCOLS = ROOT / "python" / "pops" / "amr" / "providers.py" def _between(text: str, begin: str, end: str) -> str: @@ -116,10 +117,11 @@ def test_runtime_installation_reprepares_transitions_and_routes_logical_time() - ) -def test_internal_install_seam_exists_without_claiming_public_resolution() -> None: +def test_reflux_uses_the_public_normalized_amr_provider_resolution() -> None: system = AMR_SYSTEM.read_text() binding = AMR_BINDING.read_text() authorities = RUNTIME_AUTHORITIES.read_text() + protocols = AMR_PROVIDER_PROTOCOLS.read_text() assert "install_amr_reflux_component(" in system assert "runtime->install_external_reflux(amr_reflux_component_);" in system assert "if (amr_reflux_component_)" not in _between( @@ -128,5 +130,6 @@ def test_internal_install_seam_exists_without_claiming_public_resolution() -> No "if (!boundary_plans_.empty())", ) assert '"_install_amr_reflux_component"' in binding + assert 'component_installer="_install_amr_reflux_component"' in protocols assert '"_install_amr_reflux_component"' not in authorities - assert 'tuple(providers) != ("clustering", "tagger")' in authorities + assert 'tuple(providers) != ("clustering", "tagger", "reflux")' in authorities diff --git a/tests/python/unit/amr/test_external_amr_providers.py b/tests/python/unit/amr/test_external_amr_providers.py index de1364fd5..8df244749 100644 --- a/tests/python/unit/amr/test_external_amr_providers.py +++ b/tests/python/unit/amr/test_external_amr_providers.py @@ -12,7 +12,7 @@ import pytest from pops import interfaces -from pops.amr import ClusteringProvider, TaggerProvider +from pops.amr import ClusteringProvider, RefluxProvider, TaggerProvider from pops.external import build_source_package_manifest, load from pops.layouts import AMR from pops.model import ComponentManifest @@ -141,13 +141,14 @@ def test_external_tagger_native_backend_accepts_an_exact_gpu_target(tmp_path): TaggerProvider(mismatched) -def _layout(authored, *, tagger, clustering): +def _layout(authored, *, tagger, clustering, reflux=None): return AMR( grid=authored.grid, hierarchy=authored.hierarchy, tagging=authored.tagging, tagger=tagger, clustering=clustering, + reflux=authored.reflux if reflux is None else reflux, regrid=authored.regrid, transfer=authored.transfer, execution=authored.execution, @@ -160,21 +161,25 @@ def test_external_amr_providers_survive_resolution_with_exact_components(tmp_pat tmp_path, name="tagger", interface=interfaces.Tagger) clustering_component = _component( tmp_path, name="clustering", interface=interfaces.Clustering) + reflux_component = _component( + tmp_path, name="reflux", interface=interfaces.Reflux) layout = _layout( target.layout, tagger=TaggerProvider(tagger_component), clustering=ClusteringProvider(clustering_component), + reflux=RefluxProvider(reflux_component), ) resolved = pops.resolve( pops.validate(target.authoring.case), layout=layout, - components=(tagger_component, clustering_component), + components=(tagger_component, clustering_component, reflux_component), ) - assert tuple(resolved.amr_providers) == ("clustering", "tagger") + assert tuple(resolved.amr_providers) == ("clustering", "tagger", "reflux") tagger = resolved.amr_providers["tagger"] clustering = resolved.amr_providers["clustering"] + reflux = resolved.amr_providers["reflux"] assert tagger["provider_type"] == "external_amr_tagger" assert tagger["component_id"] == tagger_component.component_manifest.component_id assert tagger["tagging_graph_identity"] == resolved.bootstrap_plan.tagging.qualified_id @@ -184,6 +189,10 @@ def test_external_amr_providers_survive_resolution_with_exact_components(tmp_pat assert clustering["provider_type"] == "external_amr_clustering" assert clustering["component_id"] == clustering_component.component_manifest.component_id assert clustering["native_interface"] == interfaces.Clustering.to_data() + assert reflux["provider_type"] == "external_amr_reflux" + assert reflux["component_id"] == reflux_component.component_manifest.component_id + assert reflux["native_interface"] == interfaces.Reflux.to_data() + assert reflux["clock_identity"] == target.authoring.program.clock.qualified_id from pops.identity.semantic import semantic_value assert resolved.resolved_hierarchy.plan.clustering.options.to_data() == { @@ -465,18 +474,21 @@ def binding(slot, interface, component_id, manifest): "interface_version": interface.version, "layout_identity": layout_identity, } + if slot in {"tagger", "reflux"}: + row["clock_identity"] = clock_identity if slot == "tagger": row.update({ - "clock_identity": clock_identity, "tagging_graph_identity": graph_identity, "tagging_capability": normalized_capability, }) row["provider_identity"] = amr_provider_binding_identity(slot, row) return row - tagger_handle, clustering_handle = object(), object() - tagger_id, clustering_id = "test::tagger", "test::clustering" - tagger_manifest, clustering_manifest = "manifest::tagger", "manifest::clustering" + tagger_handle, clustering_handle, reflux_handle = object(), object(), object() + tagger_id, clustering_id, reflux_id = "test::tagger", "test::clustering", "test::reflux" + tagger_manifest = "manifest::tagger" + clustering_manifest = "manifest::clustering" + reflux_manifest = "manifest::reflux" installed = { tagger_id: SimpleNamespace( component_manifest=SimpleNamespace(token=tagger_manifest), @@ -490,6 +502,12 @@ def binding(slot, interface, component_id, manifest): native_handle=clustering_handle, runtime_contract=SimpleNamespace(capabilities=()), ), + reflux_id: SimpleNamespace( + component_manifest=SimpleNamespace(token=reflux_manifest), + interface=interfaces.Reflux, + native_handle=reflux_handle, + runtime_contract=SimpleNamespace(capabilities=()), + ), } execution = ExecutionContext( backend=proven_serial_manifest( @@ -506,6 +524,8 @@ def binding(slot, interface, component_id, manifest): clustering_id, clustering_manifest), "tagger": binding( "tagger", interfaces.Tagger, tagger_id, tagger_manifest), + "reflux": binding( + "reflux", interfaces.Reflux, reflux_id, reflux_manifest), }, components=installed, execution_context=execution, @@ -528,6 +548,9 @@ def _install_amr_clustering_component(self, *args): def _install_amr_tagger_component(self, *args): self.calls.append(("tagger", args)) + def _install_amr_reflux_component(self, *args): + self.calls.append(("reflux", args)) + def _discard_amr_provider_components(self): self.calls.clear() self.discarded = True @@ -535,10 +558,11 @@ def _discard_amr_provider_components(self): native = Native() engine = SimpleNamespace(_s=native) _install_amr_provider_authorities(engine, plan) - assert [name for name, _ in native.calls] == ["clustering", "tagger"] + assert [name for name, _ in native.calls] == ["clustering", "tagger", "reflux"] assert native.calls[0][1][0] is clustering_handle assert native.calls[1][1][0] is tagger_handle - assert tuple(engine._amr_provider_authorities) == ("clustering", "tagger") + assert native.calls[2][1][0] is reflux_handle + assert tuple(engine._amr_provider_authorities) == ("clustering", "tagger", "reflux") missing = SimpleNamespace(**vars(plan)) missing.components = {clustering_id: installed[clustering_id]} @@ -547,3 +571,15 @@ def _discard_amr_provider_components(self): _install_amr_provider_authorities(SimpleNamespace(_s=untouched), missing) assert untouched.calls == [] assert not untouched.discarded + + missing_reflux = SimpleNamespace(**vars(plan)) + missing_reflux.components = { + clustering_id: installed[clustering_id], + tagger_id: installed[tagger_id], + } + untouched_reflux = Native() + with pytest.raises(ValueError, match="AMR reflux provider.*not installed"): + _install_amr_provider_authorities( + SimpleNamespace(_s=untouched_reflux), missing_reflux) + assert untouched_reflux.calls == [] + assert not untouched_reflux.discarded diff --git a/tests/python/unit/amr/test_public_amr_resolution.py b/tests/python/unit/amr/test_public_amr_resolution.py index 8d599742b..39fabc5ab 100644 --- a/tests/python/unit/amr/test_public_amr_resolution.py +++ b/tests/python/unit/amr/test_public_amr_resolution.py @@ -590,6 +590,19 @@ def set_temporal_relations(self, numerators, denominators, policies): "memory_spaces": list(tagging_abi["memory_spaces"]), }, }, + "reflux": { + "schema_version": 1, + "provider_type": "builtin_amr_reflux", + "runtime_installation": { + "schema_version": 1, + "protocol": "builtin", + }, + "provider_id": "pops.lib.amr::flux_register_reflux", + "provider_identity": "test::reflux-provider", + "native_interface": interfaces.Reflux.to_data(), + "layout_identity": layout_identity, + "clock_identity": "test::clock", + }, }, ) for role, binding in install_plan.amr_providers.items(): From bd8ee62e30eb9d26f174c7d9baf3d5c531613ece Mon Sep 17 00:00:00 2001 From: desp0042 Date: Thu, 30 Jul 2026 08:05:56 +0200 Subject: [PATCH 3/3] docs(amr): publish Reflux provider selection --- CHANGELOG.md | 3 +++ ...TION_TECHNIQUE_FINALE_POPS_ARCHITECTURE.md | 21 +++++++++++-------- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 89338adb0..3aa6fe1fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,9 @@ Format: [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versioning ### Changed +- External AMR `Reflux` components now use the normalized public provider route from + `AMR(..., reflux=...)` through resolve, compiled provenance and transactional native + installation; the builtin flux-register kernel follows the same reported contract. - AMR checkpoint capability reports now distinguish same-rank bit-identical replay from non-bit-identical rank-count rematerialization with Dense persisted histories. The M3 gate executes the persisted two-rank to one-rank restart proof. The explicit `RegridOnRestart()` diff --git a/docs/design/SPECIFICATION_TECHNIQUE_FINALE_POPS_ARCHITECTURE.md b/docs/design/SPECIFICATION_TECHNIQUE_FINALE_POPS_ARCHITECTURE.md index 935aac84b..1ce2877ed 100644 --- a/docs/design/SPECIFICATION_TECHNIQUE_FINALE_POPS_ARCHITECTURE.md +++ b/docs/design/SPECIFICATION_TECHNIQUE_FINALE_POPS_ARCHITECTURE.md @@ -606,22 +606,24 @@ Les builtins de `pops.lib.amr` et les composants externes implémentent le même provider. Un composant externe est sélectionné sans callback Python : ```python -from pops.amr import ClusteringProvider, TaggerProvider +from pops.amr import ClusteringProvider, RefluxProvider, TaggerProvider layout = AMR( ..., tagger=TaggerProvider(component=my_tagger), clustering=ClusteringProvider(component=my_clustering), + reflux=RefluxProvider(component=my_reflux), ) resolved = pops.resolve( pops.validate(case), layout=layout, - components=(my_tagger, my_clustering), + components=(my_tagger, my_clustering, my_reflux), ) ``` -Les deux valeurs doivent référencer un exact `pops.external.ExternalComponent` portant -respectivement l'interface générée `Tagger` ou `Clustering`. Le même objet exact doit être fourni à +Les trois valeurs doivent référencer un exact `pops.external.ExternalComponent` portant +respectivement l'interface générée `Tagger`, `Clustering` ou `Reflux`. Le même objet exact doit +être fourni à `resolve(components=...)`; son identité de manifest, son interface et sa version traversent `resolve -> compile -> bind`. Le manifest doit déclarer une classification déterministe `bitwise` ou `reproducible`, car chaque rang doit produire la même hiérarchie. Un `Tagger` déclare en plus une @@ -1426,11 +1428,12 @@ d'échec entre rangs, puis applique seul périodicité, masque de couverture, r publication transactionnelle. La présence et le contrat exact du provider sont également comparés entre rangs avant toute exécution. -Cette tranche ne publie pas encore la sélection `Reflux` dans la résolution normalisée des providers -AMR : le seam d'installation demeure interne et les configurations publiques continuent donc -d'utiliser le kernel builtin. La qualification initiale de l'adaptateur reste limitée à la cible 2D, -`float64`, CPU avec stockage hôte. Le chemin n'est pas encore prouvé par compilation native, exécution -MPI avec un composant externe, mesure de conservation ni backend GPU. +La sélection `AMR(..., reflux=RefluxProvider(component))` traverse désormais la même résolution +normalisée, identité de provider, artifact et transaction d'installation que `Tagger` et +`Clustering`. Sans sélection explicite, `FluxRegisterReflux` décrit le kernel builtin par le même +protocole et apparaît dans le même rapport de providers. La qualification initiale de l'adaptateur +reste limitée à la cible 2D, `float64`, CPU avec stockage hôte. Le chemin n'est pas encore prouvé par +exécution MPI avec un composant externe, mesure de conservation ni backend GPU. Les champs sémantiques inconnus, capacités sans preuve, collisions d'identité et entry points manquants sont refusés. Un vieux manifest n'est pas « réparé » silencieusement.