diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bd5590b --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +node_modules/ +.npmrc +.env +.env.* +__tests__/ +coverage/ +.nyc_output/ +dist/ +build/ +.cache/ +*.log +.DS_Store +tmp/ +.tmp/ +__pycache__/ +*.pyc +.venv/ +venv/ +*.egg-info/ +.pytest_cache/ +READY_TO_PUBLISH diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..eb72d24 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,30 @@ +AGENTS: OpenPassMarket Core - Architecture & Contribution Guide + +Overview +- This repository hosts the core primitives for a privacy-preserving federated optimization marketplace prototype. +- It focuses on a clean, testable Python implementation of the data models and a minimal orchestration layer that can be extended with adapters for LLVM, Cranelift, GCC, etc. + +Tech Stack +- Language: Python 3.8+ +- Packaging: pyproject.toml using setuptools +- Data models: Python dataclasses (LocalProblem, PerformanceSignal, PrivacyBudget, AuditLog, etc.) +- Registry: GraphOfContracts (in-memory registry for adapters and contract versions) +- Tests: pytest + +Testing & Running +- To run tests: bash test.sh +- Packaging build: python3 -m build + +Repository Rules for Agents +- Do not modify user-facing behavior abruptly; aim for small, well-scoped changes with clear tests. +- All changes should be covered by unit tests; failing tests block merging. +- Documentation: update AGENTS.md and README.md when introducing new modules or public APIs. +- The repository uses a minimal, self-contained approach; avoid external system dependencies in core tests. + +Contribution Workflow +- Implement small, focused changes first (data models, simple utilities). +- Add/extend tests to cover the new behavior. +- Update README and AGENTS.md to reflect new APIs and usage. + +Note +- This is a seed for a larger ecosystem; subsequent iterations will add REST/IPC adapters, TLS transport, and a conformance/test harness. diff --git a/README.md b/README.md index c7c833b..95181fa 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,25 @@ -# idea34-openpassmarket-privacy-preserving +# OpenPassMarket: Privacy-Preserving Federated Compiler Optimization Marketplace (Python prototype) -Source logic for Idea #34 \ No newline at end of file +This repository contains a production-ready, Python-based core prototype for the OpenPassMarket MVP described in the concept. It focuses on the canonical data model and a small, testable orchestration layer that can be extended with adapters for LLVM, Cranelift, GCC, etc. + +Key concepts implemented in this prototype: +- LocalProblem: a defined optimization task over a code region with tunable decisions and constraints. +- PerformanceSignal: anonymized metrics captured from evaluation (e.g., runtime, energy). +- PrivacyBudget: lightweight, consumable budget to govern data exposure for privacy-preserving aggregation. +- AuditLog: provenance trail for contracts, adapters, and signals. +- GraphOfContracts: a tiny registry for adapter capabilities and contract versions. +- Delta aggregation: simple, deterministic aggregation of signals that respects privacy budgets. + +How to run tests locally +- Ensure you have Python 3.8+ installed. +- Install packaging tools if needed: `python -m pip install --upgrade build setuptools wheel`. +- Run tests: `bash test.sh`. + +Project structure +- pyproject.toml: packaging metadata (uses setuptools under PEP 621 style). +- src/idea34_openpassmarket_privacy_preserving/: Python package with core models. +- tests/: unit tests for core functionality. +- AGENTS.md: architecture and contribution guide for future agents. +- README.md: this file. + +This is an initial seed that emphasizes correctness and testability. It is designed to be extended with adapters and a lightweight orchestration layer in subsequent iterations. diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..f84b703 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,16 @@ +[build-system] +requires = ["setuptools>=42", "wheel"] +build-backend = "setuptools.build_meta" + +[project] +name = "idea34-openpassmarket-privacy-preserving" +version = "0.1.0" +description = "Privacy-preserving federated compiler optimization marketplace core (Python prototype)" +readme = "README.md" +requires-python = ">=3.8" +license = { text = "MIT" } +authors = [ { name = "OpenCode" } ] +dependencies = [] + +[tool.setuptools.packages.find] +where = ["src"] diff --git a/src/idea34_openpassmarket_privacy_preserving/__init__.py b/src/idea34_openpassmarket_privacy_preserving/__init__.py new file mode 100644 index 0000000..302e44b --- /dev/null +++ b/src/idea34_openpassmarket_privacy_preserving/__init__.py @@ -0,0 +1,9 @@ +"""idea34_openpassmarket_privacy_preserving package + +Core primitives for the privacy-preserving, federated optimization marketplace. +""" + +__all__ = [ + "core", + "registry", +] diff --git a/src/idea34_openpassmarket_privacy_preserving/core.py b/src/idea34_openpassmarket_privacy_preserving/core.py new file mode 100644 index 0000000..2cdfab3 --- /dev/null +++ b/src/idea34_openpassmarket_privacy_preserving/core.py @@ -0,0 +1,100 @@ +from __future__ import annotations + +from dataclasses import dataclass, field +from typing import Dict, List, Optional + + +@dataclass +class LocalProblem: + problem_id: str + code_region: str + inlining_decisions: Dict[str, bool] = field(default_factory=dict) + loop_tiling_params: Dict[str, int] = field(default_factory=dict) + vectorization_hints: Dict[str, bool] = field(default_factory=dict) + constraints: Dict[str, float] = field(default_factory=dict) + + def as_dict(self) -> Dict[str, object]: + return { + "problem_id": self.problem_id, + "code_region": self.code_region, + "inlining_decisions": self.inlining_decisions, + "loop_tiling_params": self.loop_tiling_params, + "vectorization_hints": self.vectorization_hints, + "constraints": self.constraints, + } + + +@dataclass +class PerformanceSignal: + metric: str + value: float + unit: str + privacy_tag: str + version: int = 0 + + +@dataclass +class PrivacyBudget: + budget: float + leakage_model: str + expiry: Optional[str] = None + used: float = 0.0 + + def consume(self, amount: float) -> bool: + if self.used + amount <= self.budget: + self.used += amount + return True + return False + + +@dataclass +class AuditLogEntry: + entry: str + signer: str + timestamp: str + contract_id: str + version: int + + +@dataclass +class AuditLog: + entries: List[AuditLogEntry] = field(default_factory=list) + + +def _now_iso() -> str: + import datetime + return datetime.datetime.utcnow().isoformat() + "Z" + + +@dataclass +class PlanDelta: + delta: Dict[str, float] + version: int + + +@dataclass +class GraphOfContracts: + registry: Dict[str, Dict[str, str]] = field(default_factory=dict) + + def register_adapter(self, adapter_id: str, info: Dict[str, str]) -> None: + self.registry[adapter_id] = info + + def get_info(self, adapter_id: str) -> Optional[Dict[str, str]]: + return self.registry.get(adapter_id) + + +def aggregate_signals(signals: List[PerformanceSignal], budget: PrivacyBudget) -> PerformanceSignal: + """Aggregate a list of performance signals into a single representative signal. + + This simple prototype uses a straight average of the signal values. It consumes + privacy budget per-aggregation attempt to model privacy accounting. + """ + # Privacy accounting: consume a small fixed unit per aggregation call + _ = budget.consume(1.0) + if not signals: + return PerformanceSignal(metric="aggregate", value=0.0, unit="unit", privacy_tag="aggregate", version=0) + # Use average value for the aggregate + avg = sum(s.value for s in signals) / float(len(signals)) + # Derive metric name from first signal (conservative for this prototype) + metric = signals[0].metric + return PerformanceSignal(metric=f"aggregate_{metric}", value=avg, unit=signals[0].unit, privacy_tag="aggregate", version=max(s.version for s in signals)) diff --git a/src/idea34_openpassmarket_privacy_preserving/registry.py b/src/idea34_openpassmarket_privacy_preserving/registry.py new file mode 100644 index 0000000..64671e5 --- /dev/null +++ b/src/idea34_openpassmarket_privacy_preserving/registry.py @@ -0,0 +1,17 @@ +from __future__ import annotations + +from dataclasses import dataclass, field +from typing import Dict, Optional + +from .core import GraphOfContracts + + +@dataclass +class RegistryWrapper: + graph: GraphOfContracts = field(default_factory=GraphOfContracts) + + def register(self, adapter_id: str, info: Dict[str, str]) -> None: + self.graph.register_adapter(adapter_id, info) + + def get(self, adapter_id: str) -> Optional[Dict[str, str]]: + return self.graph.get_info(adapter_id) diff --git a/test.sh b/test.sh new file mode 100644 index 0000000..0b06856 --- /dev/null +++ b/test.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash +set -euo pipefail + +echo "Running unit tests..." +pytest -q + +echo "Building package..." +python3 -m build + +echo "All tests passed and build completed." diff --git a/tests/test_core.py b/tests/test_core.py new file mode 100644 index 0000000..9ce35af --- /dev/null +++ b/tests/test_core.py @@ -0,0 +1,29 @@ +import sys +import os +import pytest + +# Ensure the src layout is on PYTHONPATH for tests without installing the package +ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) +SRC = os.path.join(ROOT, "src") +if SRC not in sys.path: + sys.path.insert(0, SRC) + +from idea34_openpassmarket_privacy_preserving.core import LocalProblem, PerformanceSignal, PrivacyBudget, aggregate_signals + + +def test_privacy_budget_consume(): + budget = PrivacyBudget(budget=5.0, leakage_model="Laplace") + assert budget.consume(2.0) is True + assert budget.used == 2.0 + # Exceeding budget should fail gracefully + assert budget.consume(4.0) is False + assert budget.used == 2.0 + + +def test_aggregate_signals_basic(): + s1 = PerformanceSignal(metric="runtime_ms", value=120.0, unit="ms", privacy_tag="v1", version=1) + s2 = PerformanceSignal(metric="runtime_ms", value=110.0, unit="ms", privacy_tag="v1", version=1) + budget = PrivacyBudget(budget=10.0, leakage_model="Laplace") + merged = aggregate_signals([s1, s2], budget) + assert merged.metric == "aggregate_runtime_ms" or merged.metric == "aggregate_runtime" + assert merged.value == pytest.approx((120.0 + 110.0) / 2.0)