"""Immutable generic project, node, edge, and context contracts.""" from __future__ import annotations from collections.abc import Mapping from dataclasses import asdict, dataclass from pathlib import Path from typing import Protocol, runtime_checkable @dataclass(frozen=True) class Limits: max_source_bytes: int = 1_000_000 max_nodes: int = 10_000 max_query_chars: int = 500 max_results: int = 100 max_traversal_depth: int = 8 max_context_tokens: int = 32_000 max_tool_output_chars: int = 200_000 max_changesets: int = 1_000 max_changeset_operations: int = 100 max_changeset_bytes: int = 1_000_000 max_render_views: int = 100 max_template_bytes: int = 1_000_000 max_render_bytes: int = 1_000_000 @dataclass(frozen=True) class ProposalWriter: writer_id: str families: tuple[str, ...] operations: tuple[str, ...] @dataclass(frozen=True) class RenderView: view_id: str renderer: str template_path: Path output_path: Path title: str families: tuple[str, ...] @dataclass(frozen=True) class RenderConfig: template_root: Path preview_root: Path views: tuple[RenderView, ...] @dataclass(frozen=True) class ContextProfile: profile_id: str families: tuple[str, ...] statuses: tuple[str, ...] required_nodes: tuple[str, ...] token_budget: int dependency_depth: int @dataclass(frozen=True) class ProjectDescriptor: schema_version: int project_id: str title: str adapter: str root: Path descriptor_path: Path descriptor_hash: str content_roots: tuple[Path, ...] authority_files: tuple[Path, ...] cache_root: Path index_path: Path changeset_root: Path proposal_writers: tuple[ProposalWriter, ...] render: RenderConfig | None allowed_relations: tuple[str, ...] profiles: tuple[ContextProfile, ...] limits: Limits @dataclass(frozen=True) class Node: node_id: str title: str family: str authority: str status: str tags: tuple[str, ...] summary: str content: str source_path: str source_anchor: str | None content_hash: str def as_dict(self, *, include_content: bool = True) -> dict[str, object]: result = asdict(self) if not include_content: result.pop("content") return result @dataclass(frozen=True) class Edge: source_id: str relation: str target_id: str def as_dict(self) -> dict[str, str]: return asdict(self) @dataclass(frozen=True) class LogicNode: """One function-scoped control-flow node kept outside the primary graph.""" logic_id: str kind: str label: str source_anchor: str | None def as_dict(self) -> dict[str, object]: return asdict(self) @dataclass(frozen=True) class LogicEdge: """One directed control-flow transition with an explicit branch label.""" source_id: str relation: str target_id: str label: str | None = None ordinal: int = 0 def as_dict(self) -> dict[str, object]: return asdict(self) @dataclass(frozen=True) class LogicProjection: """A lazy control-flow projection owned by one primary graph symbol.""" owner_node_id: str source_id: str nodes: tuple[LogicNode, ...] edges: tuple[LogicEdge, ...] def as_dict(self) -> dict[str, object]: return { "owner_node_id": self.owner_node_id, "source_id": self.source_id, "nodes": [node.as_dict() for node in self.nodes], "edges": [edge.as_dict() for edge in self.edges], } @dataclass(frozen=True) class ProjectSnapshot: descriptor: ProjectDescriptor nodes: tuple[Node, ...] edges: tuple[Edge, ...] source_hash: str revision: str @dataclass(frozen=True) class ProjectState: """Cheap canonical identity used to prove a derived snapshot is current.""" source_hash: str revision: str class ProjectService(Protocol): """Minimum immutable project boundary required by derived read services.""" descriptor: ProjectDescriptor def load(self) -> ProjectSnapshot: ... def canonical_source_paths(self) -> tuple[Path, ...]: ... def validate_proposal( self, base: ProjectSnapshot, projected: ProjectSnapshot, operations: tuple[Mapping[str, object], ...], ) -> None: ... @runtime_checkable class BuildReportingProject(ProjectService, Protocol): """Optional project boundary exposing extraction metrics for builds.""" def build_report(self) -> dict[str, object]: ... @runtime_checkable class IncrementalStateProject(ProjectService, Protocol): """Optional project boundary for manifest-only stale-state checks.""" def incremental_state(self) -> ProjectState | None: ... @runtime_checkable class LogicProject(ProjectService, Protocol): """Optional project boundary exposing logic from its most recent validated load.""" def logic_projections(self) -> tuple[LogicProjection, ...]: ... @dataclass(frozen=True) class ContextEntry: node_id: str reason: str estimated_tokens: int source_path: str content_hash: str text: str def as_dict(self) -> dict[str, object]: return asdict(self)