diff --git a/README.md b/README.md index 6ee4d50..0381e52 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,8 @@ Minimal DSL seeds for interoperability (low surface area) - PolicyBlock { safety, exposure_rules } - GoC registry entry schema (adapter_id, supported_domains, contract_version) +- Enhancements (seeded DSL primitives): We extended the DSL seeds to include PrivacyBudget, AuditLog, and PolicyBlock seeds to bootstrap governance and privacy features in tests and adapters. See dsl_seed.py for concrete dataclass definitions and helper functions (PrivacyBudgetSeed, AuditLogSeed, PolicyBlockSeed, and extended seed_end_to_catopt_full). + How to run and contribute - Run tests and packaging checks: bash test.sh - Build the package for distribution: python3 -m build diff --git a/src/cosmosmesh_privacy_preserving_federated/dsl_seed.py b/src/cosmosmesh_privacy_preserving_federated/dsl_seed.py index bcb06ee..d6bb524 100644 --- a/src/cosmosmesh_privacy_preserving_federated/dsl_seed.py +++ b/src/cosmosmesh_privacy_preserving_federated/dsl_seed.py @@ -18,6 +18,9 @@ from .catopt_bridge import LocalProblem as CP_LocalProblem from .catopt_bridge import SharedVariables as CP_SharedVariables from .catopt_bridge import PlanDelta as CP_PlanDelta from .catopt_bridge import DualVariables as CP_DualVariables +from .catopt_bridge import PrivacyBudget as CP_PrivacyBudget +from .catopt_bridge import AuditLog as CP_AuditLog +from .catopt_bridge import PolicyBlock as CP_PolicyBlock @dataclass @@ -76,6 +79,33 @@ class PlanDeltaSeed: return CP_PlanDelta(delta=self.delta, timestamp=self.timestamp, author=self.author, contract_id=self.contract_id, signature=self.signature) +@dataclass +class PrivacyBudgetSeed: + budget: float | None = None + spent: float = 0.0 + version: str | None = None + + def to_catopt(self) -> CP_PrivacyBudget: + return CP_PrivacyBudget(budget=self.budget, spent=self.spent, version=self.version) + + +@dataclass +class AuditLogSeed: + entries: List[str] + + def to_catopt(self) -> CP_AuditLog: + return CP_AuditLog(entries=self.entries) + + +@dataclass +class PolicyBlockSeed: + name: str + rules: Dict[str, Any] | None = None + + def to_catopt(self) -> CP_PolicyBlock: + return CP_PolicyBlock(name=self.name, rules=self.rules or {}) + + def seed_end_to_catopt( lp_seed: LocalProblemSeed, sv_seed: SharedVariablesSeed, @@ -98,10 +128,40 @@ def seed_end_to_catopt( return _to_catopt(lp, sv, delta) # type: ignore[arg-type] +def seed_end_to_catopt_full( + lp_seed: LocalProblemSeed, + sv_seed: SharedVariablesSeed, + dv_seed: DualVariablesSeed, + delta_seed: PlanDeltaSeed, + pb_seed: PrivacyBudgetSeed | None = None, + al_seed: AuditLogSeed | None = None, + pb_block_seed: PolicyBlockSeed | None = None, +) -> Dict[str, Any]: + """Extended seed-to-IR mapping including optional governance blocks. + + This helper composes the core CatOpt-like IR and any provided governance bits + into a single dictionary structure. It is intended for testing/interoperability + seeds without altering the existing core path. + """ + base = seed_end_to_catopt(lp_seed, sv_seed, dv_seed, delta_seed) + # Extend with optional governance surfaces if seeds are provided + if pb_seed is not None: + base["PrivacyBudget"] = pb_seed.to_catopt() + if al_seed is not None: + base["AuditLog"] = al_seed.to_catopt() + if pb_block_seed is not None: + base["PolicyBlock"] = pb_block_seed.to_catopt() + return base + + __all__ = [ "LocalProblemSeed", "SharedVariablesSeed", "DualVariablesSeed", "PlanDeltaSeed", "seed_end_to_catopt", + "PrivacyBudgetSeed", + "AuditLogSeed", + "PolicyBlockSeed", + "seed_end_to_catopt_full", ]