pgc-compiler 2.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- compiler/VERSION +1 -0
- compiler/__init__.py +1 -0
- compiler/atoms/__init__.py +54 -0
- compiler/atoms/error_codes.py +160 -0
- compiler/atoms/errors.py +125 -0
- compiler/atoms/fqdn.py +204 -0
- compiler/atoms/invariants.py +106 -0
- compiler/atoms/phase.py +99 -0
- compiler/atoms/pipeline.py +51 -0
- compiler/atoms/snapshot_gate.py +51 -0
- compiler/atoms/sorting.py +125 -0
- compiler/cli.py +637 -0
- compiler/diagnostics/__init__.py +6 -0
- compiler/diagnostics/determination.py +148 -0
- compiler/diagnostics/machine_block_health.py +199 -0
- compiler/governance/__init__.py +6 -0
- compiler/governance_engine/__init__.py +0 -0
- compiler/governance_engine/artifact_kinds.py +221 -0
- compiler/governance_engine/assertions/__init__.py +0 -0
- compiler/governance_engine/assertions/handlers/__init__.py +200 -0
- compiler/governance_engine/assertions/handlers/assert_ac_declaration_well_formed_v0.py +70 -0
- compiler/governance_engine/assertions/handlers/assert_actor_authority_separation_v0.py +17 -0
- compiler/governance_engine/assertions/handlers/assert_artifact_content_hash_declared_v0.py +60 -0
- compiler/governance_engine/assertions/handlers/assert_assert_not_runtime_referenced_v0.py +49 -0
- compiler/governance_engine/assertions/handlers/assert_assert_parity_v0.py +169 -0
- compiler/governance_engine/assertions/handlers/assert_atom_output_purity_v0.py +186 -0
- compiler/governance_engine/assertions/handlers/assert_authority_constituted_v0.py +56 -0
- compiler/governance_engine/assertions/handlers/assert_authority_required_for_execution_v0.py +16 -0
- compiler/governance_engine/assertions/handlers/assert_authority_state_well_formed_v0.py +17 -0
- compiler/governance_engine/assertions/handlers/assert_binding_integrity_v0.py +64 -0
- compiler/governance_engine/assertions/handlers/assert_binding_surface_closed_v0.py +92 -0
- compiler/governance_engine/assertions/handlers/assert_cc_capability_binding_valid_v0.py +85 -0
- compiler/governance_engine/assertions/handlers/assert_cc_inputs_satisfied_v0.py +89 -0
- compiler/governance_engine/assertions/handlers/assert_cc_no_implicit_chaining_v0.py +89 -0
- compiler/governance_engine/assertions/handlers/assert_cc_no_missing_dependencies_v0.py +89 -0
- compiler/governance_engine/assertions/handlers/assert_cc_no_unused_outputs_v0.py +81 -0
- compiler/governance_engine/assertions/handlers/assert_cc_storage_op_conformance_v0.py +74 -0
- compiler/governance_engine/assertions/handlers/assert_compiler_governance_declared_v0.py +65 -0
- compiler/governance_engine/assertions/handlers/assert_compiler_no_execution_v0.py +86 -0
- compiler/governance_engine/assertions/handlers/assert_concern_not_authority_v0.py +51 -0
- compiler/governance_engine/assertions/handlers/assert_conformance_assertion_mode_valid_v0.py +35 -0
- compiler/governance_engine/assertions/handlers/assert_cryptographic_trust_declared_v0.py +51 -0
- compiler/governance_engine/assertions/handlers/assert_cs_isolated_execution_v0.py +35 -0
- compiler/governance_engine/assertions/handlers/assert_cs_surface_closed_v0.py +241 -0
- compiler/governance_engine/assertions/handlers/assert_cs_traceable_v0.py +35 -0
- compiler/governance_engine/assertions/handlers/assert_ct_output_contract_match_v0.py +167 -0
- compiler/governance_engine/assertions/handlers/assert_ct_surface_closed_v0.py +173 -0
- compiler/governance_engine/assertions/handlers/assert_ct_surface_derived_closed_v0.py +108 -0
- compiler/governance_engine/assertions/handlers/assert_ct_test_data_outcome_declared_v0.py +112 -0
- compiler/governance_engine/assertions/handlers/assert_ev_append_only_v0.py +70 -0
- compiler/governance_engine/assertions/handlers/assert_ev_schema_required_v0.py +59 -0
- compiler/governance_engine/assertions/handlers/assert_execution_placement_declared_v0.py +51 -0
- compiler/governance_engine/assertions/handlers/assert_execution_scheduling_declared_v0.py +51 -0
- compiler/governance_engine/assertions/handlers/assert_fqdn_namespace_authorized_v0.py +38 -0
- compiler/governance_engine/assertions/handlers/assert_fqdn_only_references_v0.py +89 -0
- compiler/governance_engine/assertions/handlers/assert_governance_declaration_resolves_v0.py +126 -0
- compiler/governance_engine/assertions/handlers/assert_handler_registry_closed_v0.py +75 -0
- compiler/governance_engine/assertions/handlers/assert_identity_authority_separation_v0.py +19 -0
- compiler/governance_engine/assertions/handlers/assert_identity_fqdn_consistency.py +75 -0
- compiler/governance_engine/assertions/handlers/assert_implementation_admissible_v0.py +66 -0
- compiler/governance_engine/assertions/handlers/assert_in_no_execution_logic_v0.py +82 -0
- compiler/governance_engine/assertions/handlers/assert_in_schema_required_v0.py +74 -0
- compiler/governance_engine/assertions/handlers/assert_in_workflow_binding_v0.py +101 -0
- compiler/governance_engine/assertions/handlers/assert_inspection_capability_read_only_v0.py +114 -0
- compiler/governance_engine/assertions/handlers/assert_no_ambient_authority_v0.py +17 -0
- compiler/governance_engine/assertions/handlers/assert_no_runtime_authorization_v0.py +18 -0
- compiler/governance_engine/assertions/handlers/assert_no_runtime_topology_synthesis_v0.py +18 -0
- compiler/governance_engine/assertions/handlers/assert_no_smart_execution_v0.py +102 -0
- compiler/governance_engine/assertions/handlers/assert_no_undeclared_behavior_surface_v0.py +52 -0
- compiler/governance_engine/assertions/handlers/assert_no_workflow_authorization_logic_v0.py +18 -0
- compiler/governance_engine/assertions/handlers/assert_protocol_surface_closed_v0.py +119 -0
- compiler/governance_engine/assertions/handlers/assert_rb_binding_policy_conformance_v0.py +124 -0
- compiler/governance_engine/assertions/handlers/assert_rb_cs_only_v0.py +68 -0
- compiler/governance_engine/assertions/handlers/assert_rb_no_logic_v0.py +81 -0
- compiler/governance_engine/assertions/handlers/assert_rb_parameters_declared_v0.py +117 -0
- compiler/governance_engine/assertions/handlers/assert_rb_storage_subdomain_owned_v0.py +118 -0
- compiler/governance_engine/assertions/handlers/assert_runtime_invariant_wired_v0.py +163 -0
- compiler/governance_engine/assertions/handlers/assert_schema_conformance_v0.py +64 -0
- compiler/governance_engine/assertions/handlers/assert_schema_description_well_formed_v0.py +97 -0
- compiler/governance_engine/assertions/handlers/assert_security_domain_declared_v0.py +51 -0
- compiler/governance_engine/assertions/handlers/assert_structure_paths_well_formed_v0.py +118 -0
- compiler/governance_engine/assertions/handlers/assert_superseded_not_referenced_v0.py +134 -0
- compiler/governance_engine/assertions/handlers/assert_test_data_match_ct_output_v0.py +134 -0
- compiler/governance_engine/assertions/handlers/assert_topology_acyclic_v0.py +53 -0
- compiler/governance_engine/assertions/handlers/assert_topology_authority_orthogonal_v0.py +110 -0
- compiler/governance_engine/assertions/handlers/assert_topology_capability_reference_unique_v0.py +63 -0
- compiler/governance_engine/assertions/handlers/assert_topology_contract_closed_v0.py +113 -0
- compiler/governance_engine/assertions/handlers/assert_topology_immutable_after_compilation_v0.py +17 -0
- compiler/governance_engine/assertions/handlers/assert_topology_input_reference_declared_v0.py +112 -0
- compiler/governance_engine/assertions/handlers/assert_topology_routing_complete_v0.py +82 -0
- compiler/governance_engine/assertions/handlers/assert_topology_step_declared_v0.py +77 -0
- compiler/governance_engine/assertions/handlers/assert_topology_step_id_unique_v0.py +60 -0
- compiler/governance_engine/assertions/handlers/assert_topology_surface_canonical_v0.py +180 -0
- compiler/governance_engine/assertions/handlers/assert_topology_transport_orthogonal_v0.py +113 -0
- compiler/governance_engine/assertions/handlers/assert_trace_authority_binding_required_v0.py +18 -0
- compiler/governance_engine/assertions/handlers/assert_transport_canonical_normalization_v0.py +83 -0
- compiler/governance_engine/assertions/handlers/assert_transport_no_dynamic_routing_v0.py +93 -0
- compiler/governance_engine/assertions/handlers/assert_transport_no_workflow_semantics_v0.py +106 -0
- compiler/governance_engine/assertions/handlers/assert_transport_operation_identity_independence_v0.py +56 -0
- compiler/governance_engine/assertions/handlers/assert_transport_response_projection_external_v0.py +38 -0
- compiler/governance_engine/assertions/handlers/assert_transport_result_class_protocol_independence_v0.py +48 -0
- compiler/governance_engine/assertions/handlers/assert_transport_target_exists_v0.py +132 -0
- compiler/governance_engine/assertions/handlers/assert_unique_artifact_id_v0.py +58 -0
- compiler/governance_engine/assertions/handlers/assert_vocabulary_symbols_well_formed_v0.py +163 -0
- compiler/governance_engine/assertions/handlers/assert_wf_announcement_distinct_v0.py +85 -0
- compiler/governance_engine/assertions/handlers/assert_wf_cc_only_nodes_v0.py +60 -0
- compiler/governance_engine/assertions/handlers/assert_wf_entry_intent_v0.py +68 -0
- compiler/governance_engine/assertions/handlers/assert_wf_execution_path_valid_v0.py +89 -0
- compiler/governance_engine/assertions/handlers/assert_wf_node_key_binding_unique_v0.py +107 -0
- compiler/governance_engine/platform_root.py +102 -0
- compiler/governance_engine/structure/__init__.py +56 -0
- compiler/governance_engine/structure/discovery/__init__.py +28 -0
- compiler/governance_engine/structure/discovery/artifact_locator.py +210 -0
- compiler/governance_engine/structure/exceptions.py +113 -0
- compiler/governance_engine/structure/loading/__init__.py +88 -0
- compiler/governance_engine/structure/loading/fs_reader.py +225 -0
- compiler/governance_engine/structure/loading/markdown_parser.py +148 -0
- compiler/governance_engine/structure/loading/protocol_loader.py +587 -0
- compiler/governance_engine/structure/loading/test_case_provider.py +125 -0
- compiler/governance_engine/structure/loading/trace_reader.py +98 -0
- compiler/governance_engine/structure/loading/vocabulary_loader.py +174 -0
- compiler/governance_engine/structure/loading/yaml_parser.py +209 -0
- compiler/governance_engine/structure/resolution/__init__.py +22 -0
- compiler/governance_engine/structure/resolution/domain_resolver.py +124 -0
- compiler/governance_engine/structure/resolution/layer_resolver.py +572 -0
- compiler/governance_engine/structure/resolution/path_registry.py +1054 -0
- compiler/graph/__init__.py +38 -0
- compiler/graph/addressing.py +153 -0
- compiler/graph/edge.py +100 -0
- compiler/graph/evidence.py +637 -0
- compiler/graph/graph.py +269 -0
- compiler/graph/hashing.py +89 -0
- compiler/graph/node.py +134 -0
- compiler/graph/query.py +215 -0
- compiler/graph/state.py +237 -0
- compiler/graph/trace.py +97 -0
- compiler/graph/types.py +71 -0
- compiler/projections/__init__.py +207 -0
- compiler/projections/artifact_index.py +225 -0
- compiler/projections/canonical.py +173 -0
- compiler/projections/dispatch.py +532 -0
- compiler/projections/evidence.py +170 -0
- compiler/projections/handlers.py +224 -0
- compiler/projections/ipm.py +388 -0
- compiler/projections/reverse.py +165 -0
- compiler/projections/tokenized.py +135 -0
- compiler/projections/vocabulary.py +108 -0
- compiler/stages/__init__.py +28 -0
- compiler/stages/s1_extract.py +867 -0
- compiler/stages/s2_canonicalize.py +516 -0
- compiler/stages/s3_semantic_addressing.py +66 -0
- compiler/stages/s4_govern.py +754 -0
- compiler/stages/s5_construct.py +537 -0
- compiler/stages/s6_project.py +94 -0
- compiler/stages/s7_materialize.py +1170 -0
- compiler/stages/s8_verify.py +643 -0
- compiler/stages/s9_attest.py +184 -0
- compiler/structure_loader.py +132 -0
- compiler/visualization/__init__.py +12 -0
- compiler/visualization/consumers/__init__.py +28 -0
- compiler/visualization/consumers/evidence_projection.py +162 -0
- compiler/visualization/consumers/evidence_query.py +296 -0
- compiler/visualization/consumers/evidence_reader.py +127 -0
- compiler/visualization/views/__init__.py +70 -0
- compiler/visualization/views/_png_writer.py +45 -0
- compiler/visualization/views/artifact_lineage.py +180 -0
- compiler/visualization/views/family_view.py +164 -0
- compiler/visualization/views/materialization_view.py +150 -0
- compiler/visualization/views/structural_dag.py +158 -0
- compiler/visualization/wf_graph_generator.py +534 -0
- pgc_compiler-2.0.0.dist-info/METADATA +203 -0
- pgc_compiler-2.0.0.dist-info/RECORD +177 -0
- pgc_compiler-2.0.0.dist-info/WHEEL +5 -0
- pgc_compiler-2.0.0.dist-info/entry_points.txt +2 -0
- pgc_compiler-2.0.0.dist-info/licenses/LICENSE +67 -0
- pgc_compiler-2.0.0.dist-info/licenses/NOTICE +11 -0
- pgc_compiler-2.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Evidence query contract — consumer API over deserialized evidence_graph.json.
|
|
3
|
+
|
|
4
|
+
CONTRACT VERSION: v0 (EVIDENCE_QUERY_CONTRACT_VERSION)
|
|
5
|
+
|
|
6
|
+
EvidenceQuery wraps the deserialized graph and provides typed, indexed
|
|
7
|
+
semantic access. All operations return DTOs — never raw JSON dicts.
|
|
8
|
+
|
|
9
|
+
ISOLATION INVARIANT: This module MUST NOT import from compiler internals.
|
|
10
|
+
The evidence_graph.json schema is the only contract.
|
|
11
|
+
|
|
12
|
+
DTO types:
|
|
13
|
+
TraceEventDTO — a single compiler trace event
|
|
14
|
+
EvidenceEdgeDTO — a typed directed edge between two events
|
|
15
|
+
|
|
16
|
+
Query methods:
|
|
17
|
+
by_family(family) — all events for a semantic concern group
|
|
18
|
+
by_stage(stage) — all events from a compilation stage
|
|
19
|
+
by_operation(operation) — all events of a specific operation kind
|
|
20
|
+
event_by_id(event_id) — single event lookup
|
|
21
|
+
downstream(event_id) — direct CAUSALITY children
|
|
22
|
+
upstream(event_id) — direct CAUSALITY parents
|
|
23
|
+
causality_chain(event_id) — all ancestors, root-first (BFS)
|
|
24
|
+
stage_sequence() — STAGE_SEQUENCE edge pairs in order
|
|
25
|
+
materialized_outputs() — output_paths from artifact_written events
|
|
26
|
+
edges_by_kind(kind) — all edges of CAUSALITY or STAGE_SEQUENCE
|
|
27
|
+
families() — all event families present
|
|
28
|
+
stages() — all stages in event order
|
|
29
|
+
|
|
30
|
+
CONTRACT STABILITY POLICY
|
|
31
|
+
--------------------------
|
|
32
|
+
This module is a protocol surface. The following rules apply:
|
|
33
|
+
|
|
34
|
+
FROZEN (breaking changes require a contract version bump):
|
|
35
|
+
- All public method signatures on EvidenceQuery
|
|
36
|
+
- All dataclass fields on TraceEventDTO and EvidenceEdgeDTO
|
|
37
|
+
- Return types of all query methods (list[TraceEventDTO], etc.)
|
|
38
|
+
- Semantics of CAUSALITY and STAGE_SEQUENCE edge traversal
|
|
39
|
+
- Behavior of causality_chain() BFS ordering (root-first)
|
|
40
|
+
- Behavior of stage_sequence() sort order (by source event_id)
|
|
41
|
+
|
|
42
|
+
ADDITIVE (permitted without version bump):
|
|
43
|
+
- New query methods on EvidenceQuery
|
|
44
|
+
- New properties (read-only, non-breaking)
|
|
45
|
+
- New fields on EvidenceQuery with default values only
|
|
46
|
+
|
|
47
|
+
PROHIBITED regardless of version:
|
|
48
|
+
- Importing from compiler/graph/* or any compiler internal
|
|
49
|
+
- Exposing raw dict results (all returns must be DTOs or stdlib types)
|
|
50
|
+
- Adding mutable state to DTOs
|
|
51
|
+
- Returning None from methods that currently return lists
|
|
52
|
+
|
|
53
|
+
DEPRECATION RULE:
|
|
54
|
+
A method may be deprecated (docstring + DeprecationWarning) in one
|
|
55
|
+
contract version and removed in the next. No silent removal.
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
from dataclasses import dataclass, field
|
|
59
|
+
from typing import Any
|
|
60
|
+
|
|
61
|
+
# Contract version — bump when a FROZEN surface changes.
|
|
62
|
+
# Consumers may assert: from visualization.consumers import EVIDENCE_QUERY_CONTRACT_VERSION
|
|
63
|
+
EVIDENCE_QUERY_CONTRACT_VERSION: str = "v0"
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@dataclass
|
|
67
|
+
class TraceEventDTO:
|
|
68
|
+
"""
|
|
69
|
+
DTO for a single compiler trace event.
|
|
70
|
+
|
|
71
|
+
Sourced from the 'events' array in evidence_graph.json.
|
|
72
|
+
Schema-stable: fields match evidence_graph.json event object.
|
|
73
|
+
"""
|
|
74
|
+
event_id: int
|
|
75
|
+
stage: str
|
|
76
|
+
operation: str
|
|
77
|
+
family: str
|
|
78
|
+
subject_fqdn: str
|
|
79
|
+
subject_token: int
|
|
80
|
+
parent_event_id: int
|
|
81
|
+
detail: dict[str, Any] = field(default_factory=dict)
|
|
82
|
+
|
|
83
|
+
@staticmethod
|
|
84
|
+
def from_dict(d: dict[str, Any]) -> "TraceEventDTO":
|
|
85
|
+
return TraceEventDTO(
|
|
86
|
+
event_id=d.get("event_id", -1),
|
|
87
|
+
stage=d.get("stage", ""),
|
|
88
|
+
operation=d.get("operation", ""),
|
|
89
|
+
family=d.get("family", ""),
|
|
90
|
+
subject_fqdn=d.get("subject_fqdn", ""),
|
|
91
|
+
subject_token=d.get("subject_token", -1),
|
|
92
|
+
parent_event_id=d.get("parent_event_id", -1),
|
|
93
|
+
detail=d.get("detail", {}),
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
@dataclass
|
|
98
|
+
class EvidenceEdgeDTO:
|
|
99
|
+
"""
|
|
100
|
+
DTO for a typed directed edge between two trace events.
|
|
101
|
+
|
|
102
|
+
Sourced from the 'edges' array in evidence_graph.json.
|
|
103
|
+
kind is one of: "CAUSALITY", "STAGE_SEQUENCE"
|
|
104
|
+
"""
|
|
105
|
+
source_event_id: int
|
|
106
|
+
target_event_id: int
|
|
107
|
+
kind: str
|
|
108
|
+
|
|
109
|
+
@staticmethod
|
|
110
|
+
def from_dict(d: dict[str, Any]) -> "EvidenceEdgeDTO":
|
|
111
|
+
return EvidenceEdgeDTO(
|
|
112
|
+
source_event_id=d["source_event_id"],
|
|
113
|
+
target_event_id=d["target_event_id"],
|
|
114
|
+
kind=d["kind"],
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
class EvidenceQuery:
|
|
119
|
+
"""
|
|
120
|
+
Consumer API for evidence_graph.json.
|
|
121
|
+
|
|
122
|
+
Provides semantic query operations over the compiler evidence graph.
|
|
123
|
+
All results are typed DTOs. Internal indexes are built once at construction.
|
|
124
|
+
|
|
125
|
+
This is the stable boundary between evidence_graph.json and all consumers.
|
|
126
|
+
Visualization, AI tooling, replay systems, and debuggers all go through here.
|
|
127
|
+
"""
|
|
128
|
+
|
|
129
|
+
def __init__(self, raw: dict[str, Any]) -> None:
|
|
130
|
+
self._structure_id: str = raw.get("structure_id", "")
|
|
131
|
+
self._compiler_version: str = raw.get("compiler_version", "")
|
|
132
|
+
self._evidence_graph_hash: str = raw.get("evidence_graph_hash", "")
|
|
133
|
+
|
|
134
|
+
# Deserialize
|
|
135
|
+
self._events: list[TraceEventDTO] = [
|
|
136
|
+
TraceEventDTO.from_dict(e) for e in raw.get("events", [])
|
|
137
|
+
]
|
|
138
|
+
self._edges: list[EvidenceEdgeDTO] = [
|
|
139
|
+
EvidenceEdgeDTO.from_dict(e) for e in raw.get("edges", [])
|
|
140
|
+
]
|
|
141
|
+
|
|
142
|
+
# Build indexes — O(N) construction, O(1) amortized queries
|
|
143
|
+
self._by_id: dict[int, TraceEventDTO] = {
|
|
144
|
+
e.event_id: e for e in self._events
|
|
145
|
+
}
|
|
146
|
+
self._by_family: dict[str, list[TraceEventDTO]] = {}
|
|
147
|
+
self._by_stage: dict[str, list[TraceEventDTO]] = {}
|
|
148
|
+
self._by_operation: dict[str, list[TraceEventDTO]] = {}
|
|
149
|
+
self._edges_from: dict[int, list[EvidenceEdgeDTO]] = {} # src → edges out
|
|
150
|
+
self._edges_to: dict[int, list[EvidenceEdgeDTO]] = {} # tgt → edges in
|
|
151
|
+
self._stage_order: list[str] = [] # stages in first-seen order
|
|
152
|
+
|
|
153
|
+
_seen_stages: set[str] = set()
|
|
154
|
+
for event in self._events:
|
|
155
|
+
self._by_family.setdefault(event.family, []).append(event)
|
|
156
|
+
self._by_stage.setdefault(event.stage, []).append(event)
|
|
157
|
+
self._by_operation.setdefault(event.operation, []).append(event)
|
|
158
|
+
if event.stage not in _seen_stages:
|
|
159
|
+
self._stage_order.append(event.stage)
|
|
160
|
+
_seen_stages.add(event.stage)
|
|
161
|
+
|
|
162
|
+
for edge in self._edges:
|
|
163
|
+
self._edges_from.setdefault(edge.source_event_id, []).append(edge)
|
|
164
|
+
self._edges_to.setdefault(edge.target_event_id, []).append(edge)
|
|
165
|
+
|
|
166
|
+
# --- Identity ---
|
|
167
|
+
|
|
168
|
+
@property
|
|
169
|
+
def structure_id(self) -> str:
|
|
170
|
+
return self._structure_id
|
|
171
|
+
|
|
172
|
+
@property
|
|
173
|
+
def compiler_version(self) -> str:
|
|
174
|
+
return self._compiler_version
|
|
175
|
+
|
|
176
|
+
@property
|
|
177
|
+
def evidence_graph_hash(self) -> str:
|
|
178
|
+
return self._evidence_graph_hash
|
|
179
|
+
|
|
180
|
+
@property
|
|
181
|
+
def event_count(self) -> int:
|
|
182
|
+
return len(self._events)
|
|
183
|
+
|
|
184
|
+
@property
|
|
185
|
+
def edge_count(self) -> int:
|
|
186
|
+
return len(self._edges)
|
|
187
|
+
|
|
188
|
+
# --- Flat queries ---
|
|
189
|
+
|
|
190
|
+
def by_family(self, family: str) -> list[TraceEventDTO]:
|
|
191
|
+
"""All events for a semantic concern group (e.g. 'DISCOVERY')."""
|
|
192
|
+
return list(self._by_family.get(family, []))
|
|
193
|
+
|
|
194
|
+
def by_stage(self, stage: str) -> list[TraceEventDTO]:
|
|
195
|
+
"""All events emitted by a compilation stage (e.g. 'S1_EXTRACT')."""
|
|
196
|
+
return list(self._by_stage.get(stage, []))
|
|
197
|
+
|
|
198
|
+
def by_operation(self, operation: str) -> list[TraceEventDTO]:
|
|
199
|
+
"""All events of a specific operation kind (e.g. 'artifact_written')."""
|
|
200
|
+
return list(self._by_operation.get(operation, []))
|
|
201
|
+
|
|
202
|
+
def event_by_id(self, event_id: int) -> TraceEventDTO | None:
|
|
203
|
+
"""Single event lookup by event_id. None if not found."""
|
|
204
|
+
return self._by_id.get(event_id)
|
|
205
|
+
|
|
206
|
+
# --- Graph traversal ---
|
|
207
|
+
|
|
208
|
+
def downstream(self, event_id: int) -> list[TraceEventDTO]:
|
|
209
|
+
"""
|
|
210
|
+
Direct CAUSALITY children of this event.
|
|
211
|
+
|
|
212
|
+
Returns events that this event caused/gated.
|
|
213
|
+
"""
|
|
214
|
+
return [
|
|
215
|
+
self._by_id[e.target_event_id]
|
|
216
|
+
for e in self._edges_from.get(event_id, [])
|
|
217
|
+
if e.kind == "CAUSALITY" and e.target_event_id in self._by_id
|
|
218
|
+
]
|
|
219
|
+
|
|
220
|
+
def upstream(self, event_id: int) -> list[TraceEventDTO]:
|
|
221
|
+
"""
|
|
222
|
+
Direct CAUSALITY parents of this event.
|
|
223
|
+
|
|
224
|
+
Returns events that caused/gated this event.
|
|
225
|
+
"""
|
|
226
|
+
return [
|
|
227
|
+
self._by_id[e.source_event_id]
|
|
228
|
+
for e in self._edges_to.get(event_id, [])
|
|
229
|
+
if e.kind == "CAUSALITY" and e.source_event_id in self._by_id
|
|
230
|
+
]
|
|
231
|
+
|
|
232
|
+
def causality_chain(self, event_id: int) -> list[TraceEventDTO]:
|
|
233
|
+
"""
|
|
234
|
+
All ancestors in the causality chain, root-first (BFS).
|
|
235
|
+
|
|
236
|
+
Traverses CAUSALITY edges backward from event_id to all roots.
|
|
237
|
+
Returns [] if event_id not found.
|
|
238
|
+
"""
|
|
239
|
+
result: list[TraceEventDTO] = []
|
|
240
|
+
visited: set[int] = set()
|
|
241
|
+
queue: list[int] = [event_id]
|
|
242
|
+
|
|
243
|
+
while queue:
|
|
244
|
+
current = queue.pop(0)
|
|
245
|
+
if current in visited:
|
|
246
|
+
continue
|
|
247
|
+
visited.add(current)
|
|
248
|
+
event = self._by_id.get(current)
|
|
249
|
+
if event:
|
|
250
|
+
result.append(event)
|
|
251
|
+
for parent in self.upstream(current):
|
|
252
|
+
if parent.event_id not in visited:
|
|
253
|
+
queue.append(parent.event_id)
|
|
254
|
+
|
|
255
|
+
return result
|
|
256
|
+
|
|
257
|
+
def stage_sequence(self) -> list[tuple[TraceEventDTO, TraceEventDTO]]:
|
|
258
|
+
"""
|
|
259
|
+
STAGE_SEQUENCE edge pairs in order (source, target).
|
|
260
|
+
|
|
261
|
+
Each pair is (last event of stage N, first event of stage N+1).
|
|
262
|
+
"""
|
|
263
|
+
pairs = [
|
|
264
|
+
(self._by_id[e.source_event_id], self._by_id[e.target_event_id])
|
|
265
|
+
for e in self._edges
|
|
266
|
+
if e.kind == "STAGE_SEQUENCE"
|
|
267
|
+
and e.source_event_id in self._by_id
|
|
268
|
+
and e.target_event_id in self._by_id
|
|
269
|
+
]
|
|
270
|
+
return sorted(pairs, key=lambda p: p[0].event_id)
|
|
271
|
+
|
|
272
|
+
# --- Derived queries ---
|
|
273
|
+
|
|
274
|
+
def materialized_outputs(self) -> list[str]:
|
|
275
|
+
"""
|
|
276
|
+
Sorted list of output_paths from all artifact_written events.
|
|
277
|
+
|
|
278
|
+
These are the canonical JSON artifacts written to the snapshot.
|
|
279
|
+
"""
|
|
280
|
+
return sorted(
|
|
281
|
+
event.detail["output_path"]
|
|
282
|
+
for event in self.by_operation("artifact_written")
|
|
283
|
+
if "output_path" in event.detail
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
def edges_by_kind(self, kind: str) -> list[EvidenceEdgeDTO]:
|
|
287
|
+
"""All edges of a specific kind: 'CAUSALITY' or 'STAGE_SEQUENCE'."""
|
|
288
|
+
return [e for e in self._edges if e.kind == kind]
|
|
289
|
+
|
|
290
|
+
def families(self) -> list[str]:
|
|
291
|
+
"""All event families present in the graph, sorted."""
|
|
292
|
+
return sorted(self._by_family.keys())
|
|
293
|
+
|
|
294
|
+
def stages(self) -> list[str]:
|
|
295
|
+
"""All stages present in the graph, in first-seen event order."""
|
|
296
|
+
return list(self._stage_order)
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Evidence reader — loads evidence_graph.json and returns an EvidenceQuery.
|
|
3
|
+
|
|
4
|
+
ISOLATION INVARIANT: This module MUST NOT import from compiler internals.
|
|
5
|
+
The evidence_graph.json schema is the only contract.
|
|
6
|
+
|
|
7
|
+
Public API:
|
|
8
|
+
load_evidence_graph(path) -> EvidenceQuery
|
|
9
|
+
Load and deserialize evidence_graph.json from the given path.
|
|
10
|
+
Raises FileNotFoundError if the file is missing.
|
|
11
|
+
Raises ValueError if the file is not valid JSON or fails schema checks.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import json
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
|
|
17
|
+
from .evidence_query import EvidenceQuery
|
|
18
|
+
|
|
19
|
+
_REQUIRED_TOP_LEVEL_KEYS = frozenset({
|
|
20
|
+
"structure_id",
|
|
21
|
+
"compiler_version",
|
|
22
|
+
"schema_version",
|
|
23
|
+
"evidence_graph_hash",
|
|
24
|
+
"event_count",
|
|
25
|
+
"edge_count",
|
|
26
|
+
"events",
|
|
27
|
+
"edges",
|
|
28
|
+
"families",
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
# Schema versions this consumer understands. Declared locally — the
|
|
32
|
+
# evidence_graph.json schema is the only contract (no compiler imports).
|
|
33
|
+
_SUPPORTED_SCHEMA_VERSIONS = frozenset({"v0"})
|
|
34
|
+
|
|
35
|
+
_REQUIRED_EVENT_KEYS = frozenset({
|
|
36
|
+
"event_id",
|
|
37
|
+
"stage",
|
|
38
|
+
"operation",
|
|
39
|
+
"family",
|
|
40
|
+
"subject_fqdn",
|
|
41
|
+
"subject_token",
|
|
42
|
+
"parent_event_id",
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
_REQUIRED_EDGE_KEYS = frozenset({
|
|
46
|
+
"source_event_id",
|
|
47
|
+
"target_event_id",
|
|
48
|
+
"kind",
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def load_evidence_graph(path: Path | str) -> EvidenceQuery:
|
|
53
|
+
"""
|
|
54
|
+
Load evidence_graph.json from path and return a ready EvidenceQuery.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
path: Path to evidence_graph.json (string or Path object).
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
EvidenceQuery over the deserialized graph.
|
|
61
|
+
|
|
62
|
+
Raises:
|
|
63
|
+
FileNotFoundError: File does not exist at path.
|
|
64
|
+
ValueError: File content is invalid JSON or fails structural validation.
|
|
65
|
+
"""
|
|
66
|
+
path = Path(path)
|
|
67
|
+
if not path.exists():
|
|
68
|
+
raise FileNotFoundError(f"evidence_graph.json not found: {path}")
|
|
69
|
+
|
|
70
|
+
try:
|
|
71
|
+
raw = json.loads(path.read_text(encoding="utf-8"))
|
|
72
|
+
except json.JSONDecodeError as exc:
|
|
73
|
+
raise ValueError(f"evidence_graph.json is not valid JSON: {path}\n{exc}") from exc
|
|
74
|
+
|
|
75
|
+
if not isinstance(raw, dict):
|
|
76
|
+
raise ValueError(f"evidence_graph.json root must be a JSON object: {path}")
|
|
77
|
+
|
|
78
|
+
_validate_structure(raw, path)
|
|
79
|
+
return EvidenceQuery(raw)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
# --- Internal validation ---
|
|
83
|
+
|
|
84
|
+
def _validate_structure(raw: dict, path: Path) -> None:
|
|
85
|
+
"""Lightweight structural validation — required keys and array types only."""
|
|
86
|
+
missing = _REQUIRED_TOP_LEVEL_KEYS - raw.keys()
|
|
87
|
+
if missing:
|
|
88
|
+
raise ValueError(
|
|
89
|
+
f"evidence_graph.json missing required keys {sorted(missing)}: {path}"
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
if raw["schema_version"] not in _SUPPORTED_SCHEMA_VERSIONS:
|
|
93
|
+
raise ValueError(
|
|
94
|
+
f"evidence_graph.json schema_version '{raw['schema_version']}' not supported "
|
|
95
|
+
f"(supported: {sorted(_SUPPORTED_SCHEMA_VERSIONS)}): {path}"
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
if not isinstance(raw["events"], list):
|
|
99
|
+
raise ValueError(f"evidence_graph.json 'events' must be an array: {path}")
|
|
100
|
+
if not isinstance(raw["edges"], list):
|
|
101
|
+
raise ValueError(f"evidence_graph.json 'edges' must be an array: {path}")
|
|
102
|
+
if not isinstance(raw["families"], dict):
|
|
103
|
+
raise ValueError(f"evidence_graph.json 'families' must be an object: {path}")
|
|
104
|
+
|
|
105
|
+
for i, event in enumerate(raw["events"]):
|
|
106
|
+
if not isinstance(event, dict):
|
|
107
|
+
raise ValueError(
|
|
108
|
+
f"evidence_graph.json events[{i}] must be an object: {path}"
|
|
109
|
+
)
|
|
110
|
+
missing_ev = _REQUIRED_EVENT_KEYS - event.keys()
|
|
111
|
+
if missing_ev:
|
|
112
|
+
raise ValueError(
|
|
113
|
+
f"evidence_graph.json events[{i}] missing keys "
|
|
114
|
+
f"{sorted(missing_ev)}: {path}"
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
for i, edge in enumerate(raw["edges"]):
|
|
118
|
+
if not isinstance(edge, dict):
|
|
119
|
+
raise ValueError(
|
|
120
|
+
f"evidence_graph.json edges[{i}] must be an object: {path}"
|
|
121
|
+
)
|
|
122
|
+
missing_ed = _REQUIRED_EDGE_KEYS - edge.keys()
|
|
123
|
+
if missing_ed:
|
|
124
|
+
raise ValueError(
|
|
125
|
+
f"evidence_graph.json edges[{i}] missing keys "
|
|
126
|
+
f"{sorted(missing_ed)}: {path}"
|
|
127
|
+
)
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Visualization views — deterministic static renderers over EvidenceQuery.
|
|
3
|
+
|
|
4
|
+
ISOLATION INVARIANT: All modules in this package MUST use only
|
|
5
|
+
visualization.consumers as their data source. No compiler internals.
|
|
6
|
+
|
|
7
|
+
Views:
|
|
8
|
+
structural_dag — compilation topology DAG (DOT format)
|
|
9
|
+
artifact_lineage — causality chain per materialized artifact (DOT format)
|
|
10
|
+
family_view — event family distribution (ASCII / JSON)
|
|
11
|
+
materialization_view — stage-by-stage artifact output (ASCII / JSON)
|
|
12
|
+
|
|
13
|
+
Architecture:
|
|
14
|
+
EvidenceQuery → EvidenceProjection → View DTO → Renderer → output string
|
|
15
|
+
|
|
16
|
+
Renderers produce ONLY deterministic static output (DOT, ASCII, JSON).
|
|
17
|
+
Interactive rendering is out of scope for this layer.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from .structural_dag import (
|
|
21
|
+
StructuralDAGView,
|
|
22
|
+
build_structural_dag_view,
|
|
23
|
+
render_structural_dag,
|
|
24
|
+
write_structural_dag_png,
|
|
25
|
+
)
|
|
26
|
+
from .artifact_lineage import (
|
|
27
|
+
ArtifactLineageView,
|
|
28
|
+
ArtifactLineage,
|
|
29
|
+
build_artifact_lineage_view,
|
|
30
|
+
render_artifact_lineage,
|
|
31
|
+
write_artifact_lineage_png,
|
|
32
|
+
)
|
|
33
|
+
from .family_view import (
|
|
34
|
+
FamilyViewDTO,
|
|
35
|
+
FamilyStageStat,
|
|
36
|
+
build_family_view,
|
|
37
|
+
render_family_view,
|
|
38
|
+
write_family_view_png,
|
|
39
|
+
)
|
|
40
|
+
from .materialization_view import (
|
|
41
|
+
MaterializationViewDTO,
|
|
42
|
+
build_materialization_view,
|
|
43
|
+
render_materialization_view,
|
|
44
|
+
write_materialization_view_png,
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
__all__ = [
|
|
48
|
+
# Structural DAG
|
|
49
|
+
"StructuralDAGView",
|
|
50
|
+
"build_structural_dag_view",
|
|
51
|
+
"render_structural_dag",
|
|
52
|
+
"write_structural_dag_png",
|
|
53
|
+
# Artifact Lineage
|
|
54
|
+
"ArtifactLineageView",
|
|
55
|
+
"ArtifactLineage",
|
|
56
|
+
"build_artifact_lineage_view",
|
|
57
|
+
"render_artifact_lineage",
|
|
58
|
+
"write_artifact_lineage_png",
|
|
59
|
+
# Family View
|
|
60
|
+
"FamilyViewDTO",
|
|
61
|
+
"FamilyStageStat",
|
|
62
|
+
"build_family_view",
|
|
63
|
+
"render_family_view",
|
|
64
|
+
"write_family_view_png",
|
|
65
|
+
# Materialization View
|
|
66
|
+
"MaterializationViewDTO",
|
|
67
|
+
"build_materialization_view",
|
|
68
|
+
"render_materialization_view",
|
|
69
|
+
"write_materialization_view_png",
|
|
70
|
+
]
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Internal PNG writer — shared graphviz rendering utility for evidence views.
|
|
3
|
+
|
|
4
|
+
INTERNAL: Not part of the public visualization.views API.
|
|
5
|
+
Do not import from outside visualization/views/.
|
|
6
|
+
|
|
7
|
+
Converts a DOT string → PNG file using the system `dot` (graphviz) command.
|
|
8
|
+
DOT source is piped via stdin — never written to disk.
|
|
9
|
+
Only the PNG output is persisted.
|
|
10
|
+
|
|
11
|
+
Mirrors the approach in wf_graph_generator._generate_png but:
|
|
12
|
+
- operates on in-memory DOT string (no temp files)
|
|
13
|
+
- returns bool (caller decides how to handle failure)
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import subprocess
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def write_png_from_dot(dot_src: str, output_path: Path) -> bool:
|
|
21
|
+
"""
|
|
22
|
+
Render a DOT string to PNG using graphviz (dot command).
|
|
23
|
+
|
|
24
|
+
DOT source is passed via stdin — never written to disk.
|
|
25
|
+
The PNG is the only persisted artifact.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
dot_src: DOT source as a string.
|
|
29
|
+
output_path: Destination path for the PNG file.
|
|
30
|
+
|
|
31
|
+
Returns:
|
|
32
|
+
True if the PNG was successfully written.
|
|
33
|
+
False if graphviz is unavailable or rendering failed.
|
|
34
|
+
"""
|
|
35
|
+
try:
|
|
36
|
+
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
37
|
+
result = subprocess.run(
|
|
38
|
+
["dot", "-Tpng", "-o", str(output_path)],
|
|
39
|
+
input=dot_src.encode(),
|
|
40
|
+
capture_output=True,
|
|
41
|
+
timeout=30,
|
|
42
|
+
)
|
|
43
|
+
return result.returncode == 0
|
|
44
|
+
except (FileNotFoundError, subprocess.TimeoutExpired):
|
|
45
|
+
return False
|