"""Injection-only PostgreSQL implementation of the existing owner journal seam. Binding and continuity callbacks are supplied by a trusted owner integration, never constructed from an HTTP payload. Neither is an authorization decision. No server environment switch, automatic schema creation or recovery admission. """ from __future__ import annotations from contextlib import contextmanager from dataclasses import asdict, dataclass import hashlib import json import re from typing import Any, Callable, Protocol from uuid import UUID from group_provenance import GroupProvenance, GroupProvenanceUnavailable @dataclass(frozen=True) class GroupJournalBinding: tenant_scope: str directory: str case_digest: str source_digest: str principal: str epoch: str def __post_init__(self): for value in (self.tenant_scope, self.directory, self.principal): if not isinstance(value, str) or not 1 <= len(value) <= 256 or any(ord(c) < 32 for c in value): raise ValueError("explicit owner journal binding required") for value in (self.case_digest, self.source_digest): if not isinstance(value, str) or not re.fullmatch(r"sha256:[0-9a-f]{64}", value): raise ValueError("exact owner source and case digests required") if not isinstance(self.epoch, str) or str(UUID(self.epoch)) != self.epoch: raise ValueError("explicit canonical journal epoch required") @property def digest(self): return hashlib.sha256(json.dumps(asdict(self), sort_keys=True, separators=(",", ":")).encode()).hexdigest() def journal_genesis(epoch: str, directory: str) -> str: """Deterministic initial marker for explicit owner migration/admission only.""" return hashlib.sha256(json.dumps({"schema": "nk-group-journal-genesis.v1", "epoch": epoch, "directory": directory}, sort_keys=True, separators=(",", ":")).encode()).hexdigest() class JournalContinuity(Protocol): """Existing independently retained owner checkpoint, outside restored SQL state. Implementations compare/advance exact epoch+directory+head+commit digest atomically and durably. An in-memory implementation is a fixture only. Missing/stale or ambiguous checkpoints must refuse; no callback grants mutation authority. """ def check(self, epoch: str, directory: str, head: int, digest: str) -> bool: ... def advance(self, epoch: str, directory: str, previous: int, previous_digest: str, current: int, current_digest: str) -> bool: ... class PostgresGroupProvenance: def __init__(self, *, connect: Callable, binding: GroupJournalBinding, continuity: JournalContinuity, capacity: int = 256): if not callable(connect) or not isinstance(binding, GroupJournalBinding) or not 1 <= capacity <= 4096: raise ValueError("explicit owner connector, binding and bounded capacity required") if not callable(getattr(continuity, "check", None)) or not callable(getattr(continuity, "advance", None)): raise ValueError("independent owner continuity callbacks required") self.connect, self.binding, self.continuity, self.capacity = connect, binding, continuity, capacity self._poisoned = False def _continuity(self, method, *args): try: if getattr(self.continuity, method)(self.binding.epoch, self.binding.directory, *args) is not True: raise GroupProvenanceUnavailable() except Exception: raise GroupProvenanceUnavailable() from None @contextmanager def _transaction(self): if self._poisoned: raise GroupProvenanceUnavailable() connection = None tx = None try: connection = self.connect() with connection: with connection.cursor() as cursor: cursor.execute("SET LOCAL statement_timeout = '5s'") cursor.execute("SET LOCAL idle_in_transaction_session_timeout = '15s'") cursor.execute("SELECT epoch::text, directory, head, head_digest, quarantined FROM identity_provisioner.group_journal_meta WHERE singleton = true FOR UPDATE") meta = cursor.fetchone() if not meta or meta[0] != self.binding.epoch or meta[1] != self.binding.directory or meta[4] is not False or type(meta[2]) is not int or meta[2] < 0 or not isinstance(meta[3], str) or not re.fullmatch(r"[0-9a-f]{64}", meta[3]): raise GroupProvenanceUnavailable() self._continuity("check", meta[2], meta[3]) tx = {"cursor": cursor, "head": meta[2], "digest": meta[3], "dirty": False} yield tx if tx["dirty"]: cursor.execute("UPDATE identity_provisioner.group_journal_meta SET head = head + 1, head_digest = %s WHERE singleton = true AND epoch = %s AND directory = %s AND head = %s AND head_digest = %s AND quarantined = false", (tx["next_digest"], self.binding.epoch, self.binding.directory, tx["head"], tx["digest"])) if cursor.rowcount != 1: raise GroupProvenanceUnavailable() # SQL commit precedes the independent checkpoint acknowledgment. # A lost acknowledgment refuses before the next directory effect. if tx and tx["dirty"]: self._continuity("advance", tx["head"], tx["digest"], tx["head"] + 1, tx["next_digest"]) except ValueError: raise except GroupProvenanceUnavailable: # Caller mismatches are domain refusals. Storage/continuity failures # poison this object; durable pending records are never rewritten # automatically by an exception handler after ambiguous commit. self._poisoned = True raise except Exception: self._poisoned = True raise GroupProvenanceUnavailable() from None finally: if connection is not None: try: connection.close() except Exception: self._poisoned = True raise GroupProvenanceUnavailable() from None def check(self): with self._transaction(): pass def _read(self, cursor, key, request_binding): cursor.execute("SELECT binding, version, body, context_json FROM identity_provisioner.group_receipts WHERE context_digest = %s AND key = %s", (self.binding.digest, key)) row = cursor.fetchone() if row is None: return None if row[0] != request_binding: raise ValueError("ensure receipt request binding mismatch") if row[3] != json.dumps(asdict(self.binding), sort_keys=True): raise GroupProvenanceUnavailable() receipt = GroupProvenance._load(row[2], key, request_binding) if type(row[1]) is not int or row[1] < 1 or type(receipt.get("_pg_version")) is not int or receipt.get("_pg_version") != row[1] or receipt.get("_pg_context") != self.binding.digest: raise GroupProvenanceUnavailable() return receipt @staticmethod def _inputs(key, binding): if not isinstance(key, str) or not 1 <= len(key) <= 256 or not isinstance(binding, str) or not re.fullmatch(r"[0-9a-f]{64}", binding): raise ValueError("bounded exact journal request binding required") def _active(self, cursor): cursor.execute("SELECT 1 FROM identity_provisioner.group_receipts WHERE active = true LIMIT 1") if cursor.fetchone(): raise GroupProvenanceUnavailable() def _transition(self, tx, receipt, operation): # Bind the exact transition, not merely a counter. Two restored/forked # copies that commit different bodies at the same next head cannot both # match an independently retained checkpoint after reopening. transition = {"schema": "nk-group-journal-transition.v1", "epoch": self.binding.epoch, "directory": self.binding.directory, "prior_head": tx["head"], "prior_digest": tx["digest"], "head": tx["head"] + 1, "operation": operation, "context": asdict(self.binding), "key": receipt["key"], "request_binding": receipt["binding"], "version": receipt["_pg_version"], "body": receipt} tx["next_digest"] = hashlib.sha256(json.dumps(transition, sort_keys=True, separators=(",", ":")).encode()).hexdigest() tx["dirty"] = True def reserve(self, key, binding): self._inputs(key, binding) with self._transaction() as tx: cursor = tx["cursor"] receipt = self._read(cursor, key, binding) if receipt is not None: return receipt, False self._active(cursor) cursor.execute("SELECT COUNT(*) FROM identity_provisioner.group_receipts") if cursor.fetchone()[0] >= self.capacity: raise GroupProvenanceUnavailable() receipt = {"key": key, "binding": binding, "state": "pending", "intent": None, "groups": {}, "created_groups": [], "created_memberships": [], "_pg_version": 1, "_pg_context": self.binding.digest} cursor.execute("INSERT INTO identity_provisioner.group_receipts (context_digest, context_json, key, binding, version, state, active, body) VALUES (%s, %s, %s, %s, 1, 'pending', true, %s)", (self.binding.digest, json.dumps(asdict(self.binding), sort_keys=True), key, binding, json.dumps(receipt, sort_keys=True))) if cursor.rowcount != 1: raise GroupProvenanceUnavailable() self._transition(tx, receipt, "reserve") return receipt, True def get(self, key, binding): self._inputs(key, binding) with self._transaction() as tx: receipt = self._read(tx["cursor"], key, binding) if receipt is None: raise ValueError("unknown ensure receipt or request binding mismatch") return receipt def _write(self, tx, receipt, operation="save"): self._inputs(receipt.get("key"), receipt.get("binding")) if receipt.get("_pg_context") != self.binding.digest or type(receipt.get("_pg_version")) is not int or receipt["_pg_version"] < 1: raise GroupProvenanceUnavailable() encoded = json.dumps(receipt, sort_keys=True) GroupProvenance._load(encoded, receipt["key"], receipt["binding"]) updated = json.loads(encoded) updated["_pg_version"] += 1 active = True if receipt["state"] in {"pending", "revoking", "uncertain"} else None tx["cursor"].execute("UPDATE identity_provisioner.group_receipts SET state = %s, active = %s, body = %s, version = version + 1 WHERE context_digest = %s AND key = %s AND binding = %s AND version = %s", (receipt["state"], active, json.dumps(updated, sort_keys=True), self.binding.digest, receipt["key"], receipt["binding"], receipt["_pg_version"])) if tx["cursor"].rowcount != 1: raise GroupProvenanceUnavailable() self._transition(tx, updated, operation) return updated def save(self, receipt): with self._transaction() as tx: updated = self._write(tx, receipt) # The provisioner keeps references to nested group pre/poststate across # intent persistence. Replace only this backend's acknowledged version; # _write serializes without changing the caller's mutation body. receipt["_pg_version"] = updated["_pg_version"] def claim_revoke(self, key, binding, delete_if_empty): self._inputs(key, binding) if not isinstance(delete_if_empty, bool): raise ValueError("delete_if_empty must be boolean") with self._transaction() as tx: receipt = self._read(tx["cursor"], key, binding) if receipt is None: raise ValueError("unknown ensure receipt or request binding mismatch") if receipt["state"] == "revoked": if receipt["delete_if_empty"] != delete_if_empty: raise ValueError("completed rollback options cannot change") return receipt if receipt["state"] != "ensured": raise GroupProvenanceUnavailable() self._active(tx["cursor"]) receipt.update(state="revoking", delete_if_empty=delete_if_empty) updated = self._write(tx, receipt, "claim_revoke") return updated def psycopg_connector(*, connection_info: Callable[[], dict[str, Any]]): """Owner-provided renewable lease values stay in memory; never env/argv/logs. No connection happens here. Lazy import keeps disabled group endpoints free of a runtime dependency. The admitted owner supplies TLS/host/db/role facts. """ if not callable(connection_info): raise ValueError("owner connection-info callback required") def connect(): try: import psycopg values = dict(connection_info()) return psycopg.connect(**values, connect_timeout=5) except Exception: raise GroupProvenanceUnavailable() from None return connect