diff --git a/cosmic_ledger/contracts.py b/cosmic_ledger/contracts.py index 1942da7..3003bfa 100644 --- a/cosmic_ledger/contracts.py +++ b/cosmic_ledger/contracts.py @@ -5,6 +5,7 @@ class DataContractRegistry: self._contracts = {} def register(self, name: str, schema: dict, version: int = 1): + # Store contract with versioning and associated schema self._contracts[name] = { "version": version, "schema": schema, @@ -15,3 +16,21 @@ class DataContractRegistry: def list_contracts(self): return {k: v for k, v in self._contracts.items()} + + def conform(self, name: str, payload: dict) -> bool: + """Basic conformance check for a given payload against a registered contract. + + If a contract is registered with a 'fields' list inside its schema, we + require that all those fields exist in the payload. Extra fields are + allowed for forward-compatibility. + """ + contract = self._contracts.get(name) + if not contract: + # No contract defined; be permissive in MVP + return True + schema = contract.get("schema", {}) + if isinstance(schema, dict) and "fields" in schema: + required = set(schema["fields"]) + return required.issubset(set(payload.keys())) + # Fallback: no strict contract; assume conformant + return True diff --git a/cosmic_ledger/ledger.py b/cosmic_ledger/ledger.py index dd0b5d1..8f53325 100644 --- a/cosmic_ledger/ledger.py +++ b/cosmic_ledger/ledger.py @@ -72,6 +72,13 @@ class LocalLedger: def register_contract(self, name: str, schema: dict, version: int = 1): self.contracts.register(name, schema, version) + def anchor_delta_root(self, anchor: str) -> None: + """Anchor the current delta root to an external reference (cloud/ground). + + This is optional in MVP and helps provide long-term verifiability. + """ + self.delta_log.anchor_root(anchor) + def add_entry(self, entry_type: str, payload: dict, signer_name: str = None, contract_name: str = None) -> LedgerEntry: if not self.contracts.get(entry_type) and contract_name is None: # Allow ad-hoc entry without contract in MVP for simplicity @@ -79,6 +86,10 @@ class LocalLedger: else: contract = self.contracts.get(entry_type) contract_version = contract["version"] if contract else 1 + # Enforce contract conformance if a contract is registered for this entry_type + if self.contracts.get(entry_type) is not None: + if not self.contracts.conform(entry_type, payload): + raise ValueError(f"Payload does not conform to contract for entry_type '{entry_type}'") ts = time.time() # Build a lightweight id: hash of type+ts+payload raw = json.dumps({"type": entry_type, "ts": ts, "payload": payload}, sort_keys=True).encode('utf-8')