Spaces:
Running
Running
Download core/component_data_guard.py from acecalisto3/SignalMesh: direct link, hf CLI and curl.
- Browser
- Download file 7.64 kB
-
https://huggingface.co/spaces/acecalisto3/SignalMesh/resolve/main/core/component_data_guard.py
- Command line
-
hf download hf://spaces/acecalisto3/SignalMesh/core/component_data_guard.py
-
curl -L -o component_data_guard.py https://huggingface.co/spaces/acecalisto3/SignalMesh/resolve/main/core/component_data_guard.py
7.64 kB
| """Restrict Joomla extension-data migration to component-owned domains. | |
| ``dbmesh_policy_patch`` intentionally made the old catch-all extension phase | |
| conservative, but an empty target table could still be treated as client data | |
| simply because it was not otherwise classified. That is still too broad. | |
| The migration rule is now: | |
| component -> owns client data tables | |
| plugin/module/template/package -> runtime/config/dependency branch only | |
| A physical table that cannot be assigned to an installed source component is | |
| quarantined. Component tables that do not yet exist on the target stay behind | |
| until the target component is installed. The underlying row copier, factory | |
| maps, id remaps and semantic merge rules remain unchanged. | |
| """ | |
| from __future__ import annotations | |
| from . import component_data_domains as domains | |
| from . import dbmesh as d | |
| from . import dbmesh_policy_patch as policy | |
| _INSTALLED = False | |
| _PRIOR_CLASSIFY = policy.classify_table | |
| _PRIOR_PHASE_TABLES = policy.phase_tables | |
| _PRIOR_PLAN = d.plan | |
| _PRIOR_MIGRATE = d.migrate | |
| CORE_CLAIMED = { | |
| "usergroups", "viewlevels", "users", "user_usergroup_map", | |
| "template_styles", "categories", "content", "content_frontpage", | |
| "menu_types", "menu", "modules", "modules_menu", "fields_groups", | |
| "fields", "fields_values", "tags", "contentitem_tag_map", | |
| } | |
| def _owner_shape(owner: dict | None) -> dict: | |
| if not owner: | |
| return {} | |
| return { | |
| "component": owner.get("element") or "", | |
| "component_name": owner.get("name") or owner.get("element") or "", | |
| "component_token": owner.get("token") or "", | |
| "component_version": owner.get("version") or "", | |
| "component_core": bool(owner.get("core")), | |
| } | |
| def classify_table(sconn, dconn, sp: str, dp: str, table: str, *, phase: str = "extensions") -> dict: | |
| """Policy classification with component ownership as the extension gate.""" | |
| if phase != "extensions" or table in CORE_CLAIMED: | |
| return _PRIOR_CLASSIFY(sconn, dconn, sp, dp, table, phase=phase) | |
| stabs, dtabs = d.tables(sconn, sp), d.tables(dconn, dp) | |
| src_rows = d.row_count(sconn, sp + table) if table in stabs else 0 | |
| dst_rows = d.row_count(dconn, dp + table) if table in dtabs else 0 | |
| # Keep the semantic distinction visible even though the lower safety patch | |
| # also places regenerate tables in NEVER_COPY to stop old callers copying | |
| # them. They are rebuildable state, not target factory catalogues. | |
| if table in policy.REGENERATE: | |
| return {"class": "regenerate", "source_rows": src_rows, "target_rows": dst_rows} | |
| if table in policy.FACTORY or table in d.NEVER_COPY: | |
| return _PRIOR_CLASSIFY(sconn, dconn, sp, dp, table, phase=phase) | |
| if table not in stabs: | |
| return _PRIOR_CLASSIFY(sconn, dconn, sp, dp, table, phase=phase) | |
| component = domains.owner(sconn, sp, table) | |
| if component is None: | |
| return { | |
| "class": "quarantine", | |
| "source_rows": src_rows, | |
| "target_rows": dst_rows, | |
| "reason": "table is not owned by an installed Joomla component; plugins/modules/packages are not data owners", | |
| "component": "", | |
| } | |
| result = dict(_PRIOR_CLASSIFY(sconn, dconn, sp, dp, table, phase=phase)) | |
| result.update(_owner_shape(component)) | |
| if result.get("class") == "missing_target": | |
| result["reason"] = ( | |
| f"{component.get('element')} owns this data, but the component table does not exist on the target; " | |
| "install/bridge the target component before migrating its data" | |
| ) | |
| elif result.get("class") == "quarantine": | |
| result["reason"] = result.get("reason") or ( | |
| f"{component.get('element')} owns this table, but the target already contains rows and no safe semantic merge key is known" | |
| ) | |
| return result | |
| def phase_tables(sconn, dconn, sp, dp, phase, exclude=()): | |
| """Reuse the policy phase selector with the stricter classifier installed.""" | |
| return _PRIOR_PHASE_TABLES(sconn, dconn, sp, dp, phase, exclude) | |
| def _component_map(sconn, dconn, sp: str, dp: str) -> dict: | |
| stabs, dtabs = d.tables(sconn, sp), d.tables(dconn, dp) | |
| rows = domains.domains( | |
| sconn, dconn, sp, dp, | |
| source_tables=stabs - CORE_CLAIMED, | |
| target_tables=dtabs - CORE_CLAIMED, | |
| ) | |
| out = [] | |
| for row in rows: | |
| tables = [] | |
| for table in row.get("source_tables") or []: | |
| decision = classify_table(sconn, dconn, sp, dp, table, phase="extensions") | |
| tables.append({"table": table, **decision}) | |
| item = {**row, "tables": tables} | |
| item["rows"] = sum(t.get("source_rows", 0) for t in tables | |
| if t.get("class") not in {"target_factory", "regenerate"}) | |
| item["migratable_tables"] = [t["table"] for t in tables | |
| if t.get("class") in {"client_entity", "semantic_merge"}] | |
| item["blocked_tables"] = [t["table"] for t in tables | |
| if t.get("class") in {"missing_target", "quarantine"}] | |
| out.append(item) | |
| return { | |
| "rule": "components own data; plugin/module/template/package members are runtime dependencies only", | |
| "domains": out, | |
| "components": len(out), | |
| "tables": sum(len(x.get("tables") or []) for x in out), | |
| "rows": sum(x.get("rows", 0) for x in out), | |
| } | |
| def plan(src, dst, resolve, exclude=()): | |
| """Classified plan plus explicit component data domains.""" | |
| result = _PRIOR_PLAN(src, dst, resolve, exclude) | |
| sconn, sp, _ = resolve(src) | |
| dconn, dp, _ = resolve(dst) | |
| try: | |
| result["component_data"] = _component_map(sconn, dconn, sp, dp) | |
| # The prior planner already sees our patched classifier through the | |
| # policy module, so its quarantined bucket includes unowned tables. | |
| result["unowned_tables"] = [ | |
| x for x in result.get("quarantined", []) if not x.get("component") | |
| ] | |
| finally: | |
| sconn.close(); dconn.close() | |
| return result | |
| def migrate(src, dst, phases=d.PHASES, *, live=False, urls=True, resolve, | |
| url_override=None, on_progress=None, on_conflict="reassign", exclude=()): | |
| """Run the existing migrator while exposing component-data semantics.""" | |
| sconn, sp, _ = resolve(src) | |
| dconn, dp, _ = resolve(dst) | |
| try: | |
| component_map = _component_map(sconn, dconn, sp, dp) | |
| finally: | |
| sconn.close(); dconn.close() | |
| def progress(event): | |
| ev = dict(event) | |
| if ev.get("phase") == "extensions": | |
| ev["phase"] = "component data" | |
| if on_progress: | |
| on_progress(ev) | |
| report = _PRIOR_MIGRATE( | |
| src, dst, phases, | |
| live=live, urls=urls, resolve=resolve, url_override=url_override, | |
| on_progress=progress, on_conflict=on_conflict, exclude=exclude, | |
| ) | |
| report["component_data"] = component_map | |
| # Keep the internal phase key for backward compatibility while naming the | |
| # user-facing meaning explicitly. | |
| report["component_data_phase"] = report.get("phases", {}).get("extensions", {}) | |
| return report | |
| def install() -> None: | |
| global _INSTALLED | |
| if _INSTALLED: | |
| return | |
| # dbmesh_policy_patch's plan/migrate resolve these globals at runtime, so | |
| # replacing them here narrows every existing MPort/Studio entry point too. | |
| policy.classify_table = classify_table | |
| policy.phase_tables = phase_tables | |
| d.classify_table = classify_table | |
| d.phase_tables = phase_tables | |
| d.plan = plan | |
| d.migrate = migrate | |
| d.COMPONENT_DATA_DOMAINS = domains | |
| _INSTALLED = True | |