Initial SFERA platform baseline
This commit is contained in:
@@ -0,0 +1,35 @@
|
||||
from sir.delta import SirDelta
|
||||
from sir.diagnostics import Diagnostic, DiagnosticSeverity
|
||||
from sir.edges import SemanticEdge
|
||||
from sir.enums import EdgeKind, NodeKind
|
||||
from sir.hashing import compute_snapshot_hash
|
||||
from sir.lineage import make_lineage_id, make_semantic_id, stable_hash
|
||||
from sir.nodes import SemanticNode
|
||||
from sir.references import ReferenceKind, UnresolvedReference
|
||||
from sir.serialization import snapshot_from_json, snapshot_to_json
|
||||
from sir.snapshot import SirSnapshot, SnapshotMetadata
|
||||
from sir.source_ref import SourceRef
|
||||
from sir.validation import SnapshotValidationError, validate_snapshot
|
||||
|
||||
__all__ = [
|
||||
"Diagnostic",
|
||||
"DiagnosticSeverity",
|
||||
"EdgeKind",
|
||||
"NodeKind",
|
||||
"ReferenceKind",
|
||||
"SemanticEdge",
|
||||
"SemanticNode",
|
||||
"SirDelta",
|
||||
"SirSnapshot",
|
||||
"SnapshotMetadata",
|
||||
"SnapshotValidationError",
|
||||
"SourceRef",
|
||||
"UnresolvedReference",
|
||||
"compute_snapshot_hash",
|
||||
"make_lineage_id",
|
||||
"make_semantic_id",
|
||||
"snapshot_from_json",
|
||||
"snapshot_to_json",
|
||||
"stable_hash",
|
||||
"validate_snapshot",
|
||||
]
|
||||
@@ -0,0 +1,15 @@
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from sir.edges import SemanticEdge
|
||||
from sir.nodes import SemanticNode
|
||||
|
||||
|
||||
class SirDelta(BaseModel):
|
||||
delta_id: str
|
||||
snapshot_from: str
|
||||
snapshot_to: str
|
||||
added_nodes: list[SemanticNode] = Field(default_factory=list)
|
||||
updated_nodes: list[SemanticNode] = Field(default_factory=list)
|
||||
removed_nodes: list[str] = Field(default_factory=list)
|
||||
added_edges: list[SemanticEdge] = Field(default_factory=list)
|
||||
removed_edges: list[str] = Field(default_factory=list)
|
||||
@@ -0,0 +1,21 @@
|
||||
from enum import Enum
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from sir.source_ref import SourceRef
|
||||
|
||||
|
||||
class DiagnosticSeverity(str, Enum):
|
||||
INFO = "INFO"
|
||||
WARNING = "WARNING"
|
||||
ERROR = "ERROR"
|
||||
|
||||
|
||||
class Diagnostic(BaseModel):
|
||||
diagnostic_id: str
|
||||
code: str
|
||||
severity: DiagnosticSeverity
|
||||
message: str
|
||||
source_ref: SourceRef | None = None
|
||||
related_lineages: list[str] = Field(default_factory=list)
|
||||
attributes: dict = Field(default_factory=dict)
|
||||
@@ -0,0 +1,13 @@
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from sir.enums import EdgeKind
|
||||
from sir.source_ref import SourceRef
|
||||
|
||||
|
||||
class SemanticEdge(BaseModel):
|
||||
edge_id: str
|
||||
kind: EdgeKind
|
||||
source_lineage: str
|
||||
target_lineage: str
|
||||
source_ref: SourceRef | None = None
|
||||
attributes: dict = Field(default_factory=dict)
|
||||
@@ -0,0 +1,59 @@
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class NodeKind(str, Enum):
|
||||
PROJECT = "PROJECT"
|
||||
MODULE = "MODULE"
|
||||
PROCEDURE = "PROCEDURE"
|
||||
FUNCTION = "FUNCTION"
|
||||
QUERY = "QUERY"
|
||||
TABLE = "TABLE"
|
||||
REGISTER = "REGISTER"
|
||||
CATALOG = "CATALOG"
|
||||
DOCUMENT = "DOCUMENT"
|
||||
CONSTANT = "CONSTANT"
|
||||
DOCUMENT_JOURNAL = "DOCUMENT_JOURNAL"
|
||||
ENUM = "ENUM"
|
||||
REPORT = "REPORT"
|
||||
DATA_PROCESSOR = "DATA_PROCESSOR"
|
||||
CHART_OF_CHARACTERISTIC_TYPES = "CHART_OF_CHARACTERISTIC_TYPES"
|
||||
CHART_OF_ACCOUNTS = "CHART_OF_ACCOUNTS"
|
||||
CHART_OF_CALCULATION_TYPES = "CHART_OF_CALCULATION_TYPES"
|
||||
COMMON_MODULE = "COMMON_MODULE"
|
||||
EXCHANGE_PLAN = "EXCHANGE_PLAN"
|
||||
EXTERNAL_DATA_SOURCE = "EXTERNAL_DATA_SOURCE"
|
||||
SCHEDULED_JOB = "SCHEDULED_JOB"
|
||||
BUSINESS_PROCESS = "BUSINESS_PROCESS"
|
||||
TASK = "TASK"
|
||||
SUBSYSTEM = "SUBSYSTEM"
|
||||
HTTP_SERVICE = "HTTP_SERVICE"
|
||||
XDTO_PACKAGE = "XDTO_PACKAGE"
|
||||
EXTENSION = "EXTENSION"
|
||||
LAYOUT = "LAYOUT"
|
||||
MOVEMENT = "MOVEMENT"
|
||||
INTEGRATION_ENDPOINT = "INTEGRATION_ENDPOINT"
|
||||
FORM = "FORM"
|
||||
COMMAND = "COMMAND"
|
||||
ROLE = "ROLE"
|
||||
ATTRIBUTE = "ATTRIBUTE"
|
||||
TABULAR_SECTION = "TABULAR_SECTION"
|
||||
FORM_ELEMENT = "FORM_ELEMENT"
|
||||
|
||||
|
||||
class EdgeKind(str, Enum):
|
||||
CONTAINS = "CONTAINS"
|
||||
DECLARES = "DECLARES"
|
||||
CALLS = "CALLS"
|
||||
OWNS_QUERY = "OWNS_QUERY"
|
||||
READS_TABLE = "READS_TABLE"
|
||||
WRITES = "WRITES"
|
||||
HAS_FORM = "HAS_FORM"
|
||||
HAS_COMMAND = "HAS_COMMAND"
|
||||
HAS_ROLE = "HAS_ROLE"
|
||||
HAS_ATTRIBUTE = "HAS_ATTRIBUTE"
|
||||
HAS_TABULAR_SECTION = "HAS_TABULAR_SECTION"
|
||||
HAS_ELEMENT = "HAS_ELEMENT"
|
||||
GRANTS_ACCESS = "GRANTS_ACCESS"
|
||||
RUNS = "RUNS"
|
||||
USES_INTEGRATION = "USES_INTEGRATION"
|
||||
HANDLES = "HANDLES"
|
||||
@@ -0,0 +1,13 @@
|
||||
import hashlib
|
||||
|
||||
import orjson
|
||||
|
||||
from sir.snapshot import SirSnapshot
|
||||
|
||||
|
||||
def compute_snapshot_hash(snapshot: SirSnapshot) -> str:
|
||||
payload = snapshot.model_dump(mode="json")
|
||||
payload["snapshot_hash"] = None
|
||||
payload["created_at"] = None
|
||||
raw = orjson.dumps(payload, option=orjson.OPT_SORT_KEYS)
|
||||
return hashlib.sha256(raw).hexdigest()
|
||||
@@ -0,0 +1,13 @@
|
||||
import hashlib
|
||||
|
||||
|
||||
def stable_hash(value: str) -> str:
|
||||
return hashlib.sha1(value.encode("utf-8")).hexdigest()[:16]
|
||||
|
||||
|
||||
def make_lineage_id(kind: str, stable_key: str) -> str:
|
||||
return f"lineage.{kind.lower()}.{stable_hash(stable_key)}"
|
||||
|
||||
|
||||
def make_semantic_id(kind: str, qualified_name: str) -> str:
|
||||
return f"{kind.lower()}.{stable_hash(qualified_name)}"
|
||||
@@ -0,0 +1,14 @@
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from sir.enums import NodeKind
|
||||
from sir.source_ref import SourceRef
|
||||
|
||||
|
||||
class SemanticNode(BaseModel):
|
||||
semantic_id: str
|
||||
lineage_id: str
|
||||
kind: NodeKind
|
||||
name: str
|
||||
qualified_name: str
|
||||
source_ref: SourceRef
|
||||
attributes: dict = Field(default_factory=dict)
|
||||
@@ -0,0 +1,22 @@
|
||||
from enum import Enum
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from sir.source_ref import SourceRef
|
||||
|
||||
|
||||
class ReferenceKind(str, Enum):
|
||||
CALL = "CALL"
|
||||
TABLE = "TABLE"
|
||||
ATTRIBUTE = "ATTRIBUTE"
|
||||
FORM_ELEMENT = "FORM_ELEMENT"
|
||||
COMMAND = "COMMAND"
|
||||
|
||||
|
||||
class UnresolvedReference(BaseModel):
|
||||
reference_id: str
|
||||
kind: ReferenceKind
|
||||
source_lineage: str
|
||||
target_name: str
|
||||
source_ref: SourceRef
|
||||
attributes: dict = Field(default_factory=dict)
|
||||
@@ -0,0 +1,11 @@
|
||||
import orjson
|
||||
|
||||
from sir.snapshot import SirSnapshot
|
||||
|
||||
|
||||
def snapshot_to_json(snapshot: SirSnapshot) -> bytes:
|
||||
return orjson.dumps(snapshot.model_dump(mode="json"), option=orjson.OPT_INDENT_2)
|
||||
|
||||
|
||||
def snapshot_from_json(data: bytes) -> SirSnapshot:
|
||||
return SirSnapshot.model_validate_json(data)
|
||||
@@ -0,0 +1,32 @@
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from sir.diagnostics import Diagnostic
|
||||
from sir.edges import SemanticEdge
|
||||
from sir.nodes import SemanticNode
|
||||
from sir.references import UnresolvedReference
|
||||
|
||||
|
||||
class SnapshotMetadata(BaseModel):
|
||||
platform_version: str | None = None
|
||||
compatibility_mode: str | None = None
|
||||
source_kind: str | None = None
|
||||
source_root: str | None = None
|
||||
created_by: str | None = None
|
||||
task_id: str | None = None
|
||||
session_id: str | None = None
|
||||
|
||||
|
||||
class SirSnapshot(BaseModel):
|
||||
sir_schema_version: str = "0.1"
|
||||
snapshot_id: str
|
||||
project_id: str
|
||||
revision: str | None = None
|
||||
snapshot_hash: str | None = None
|
||||
metadata: SnapshotMetadata = Field(default_factory=SnapshotMetadata)
|
||||
created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
|
||||
nodes: list[SemanticNode] = Field(default_factory=list)
|
||||
edges: list[SemanticEdge] = Field(default_factory=list)
|
||||
diagnostics: list[Diagnostic] = Field(default_factory=list)
|
||||
unresolved_references: list[UnresolvedReference] = Field(default_factory=list)
|
||||
@@ -0,0 +1,10 @@
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class SourceRef(BaseModel):
|
||||
source_path: str
|
||||
line_start: int | None = None
|
||||
line_end: int | None = None
|
||||
column_start: int | None = None
|
||||
column_end: int | None = None
|
||||
source_hash: str | None = None
|
||||
@@ -0,0 +1,30 @@
|
||||
from sir.snapshot import SirSnapshot
|
||||
|
||||
|
||||
class SnapshotValidationError(Exception):
|
||||
"""Raised when a SIR snapshot violates graph consistency rules."""
|
||||
|
||||
|
||||
def validate_snapshot(snapshot: SirSnapshot) -> None:
|
||||
semantic_ids = [node.semantic_id for node in snapshot.nodes]
|
||||
lineage_ids = [node.lineage_id for node in snapshot.nodes]
|
||||
|
||||
if len(semantic_ids) != len(set(semantic_ids)):
|
||||
raise SnapshotValidationError("Duplicate semantic_id detected")
|
||||
|
||||
if len(lineage_ids) != len(set(lineage_ids)):
|
||||
raise SnapshotValidationError("Duplicate lineage_id detected")
|
||||
|
||||
known_lineages = set(lineage_ids)
|
||||
|
||||
for edge in snapshot.edges:
|
||||
if edge.source_lineage not in known_lineages:
|
||||
raise SnapshotValidationError(f"Dangling edge source: {edge.source_lineage}")
|
||||
if edge.target_lineage not in known_lineages:
|
||||
raise SnapshotValidationError(f"Dangling edge target: {edge.target_lineage}")
|
||||
|
||||
for reference in snapshot.unresolved_references:
|
||||
if reference.source_lineage not in known_lineages:
|
||||
raise SnapshotValidationError(
|
||||
f"Unresolved reference with unknown source: {reference.source_lineage}"
|
||||
)
|
||||
Reference in New Issue
Block a user