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.
Files changed (177) hide show
  1. compiler/VERSION +1 -0
  2. compiler/__init__.py +1 -0
  3. compiler/atoms/__init__.py +54 -0
  4. compiler/atoms/error_codes.py +160 -0
  5. compiler/atoms/errors.py +125 -0
  6. compiler/atoms/fqdn.py +204 -0
  7. compiler/atoms/invariants.py +106 -0
  8. compiler/atoms/phase.py +99 -0
  9. compiler/atoms/pipeline.py +51 -0
  10. compiler/atoms/snapshot_gate.py +51 -0
  11. compiler/atoms/sorting.py +125 -0
  12. compiler/cli.py +637 -0
  13. compiler/diagnostics/__init__.py +6 -0
  14. compiler/diagnostics/determination.py +148 -0
  15. compiler/diagnostics/machine_block_health.py +199 -0
  16. compiler/governance/__init__.py +6 -0
  17. compiler/governance_engine/__init__.py +0 -0
  18. compiler/governance_engine/artifact_kinds.py +221 -0
  19. compiler/governance_engine/assertions/__init__.py +0 -0
  20. compiler/governance_engine/assertions/handlers/__init__.py +200 -0
  21. compiler/governance_engine/assertions/handlers/assert_ac_declaration_well_formed_v0.py +70 -0
  22. compiler/governance_engine/assertions/handlers/assert_actor_authority_separation_v0.py +17 -0
  23. compiler/governance_engine/assertions/handlers/assert_artifact_content_hash_declared_v0.py +60 -0
  24. compiler/governance_engine/assertions/handlers/assert_assert_not_runtime_referenced_v0.py +49 -0
  25. compiler/governance_engine/assertions/handlers/assert_assert_parity_v0.py +169 -0
  26. compiler/governance_engine/assertions/handlers/assert_atom_output_purity_v0.py +186 -0
  27. compiler/governance_engine/assertions/handlers/assert_authority_constituted_v0.py +56 -0
  28. compiler/governance_engine/assertions/handlers/assert_authority_required_for_execution_v0.py +16 -0
  29. compiler/governance_engine/assertions/handlers/assert_authority_state_well_formed_v0.py +17 -0
  30. compiler/governance_engine/assertions/handlers/assert_binding_integrity_v0.py +64 -0
  31. compiler/governance_engine/assertions/handlers/assert_binding_surface_closed_v0.py +92 -0
  32. compiler/governance_engine/assertions/handlers/assert_cc_capability_binding_valid_v0.py +85 -0
  33. compiler/governance_engine/assertions/handlers/assert_cc_inputs_satisfied_v0.py +89 -0
  34. compiler/governance_engine/assertions/handlers/assert_cc_no_implicit_chaining_v0.py +89 -0
  35. compiler/governance_engine/assertions/handlers/assert_cc_no_missing_dependencies_v0.py +89 -0
  36. compiler/governance_engine/assertions/handlers/assert_cc_no_unused_outputs_v0.py +81 -0
  37. compiler/governance_engine/assertions/handlers/assert_cc_storage_op_conformance_v0.py +74 -0
  38. compiler/governance_engine/assertions/handlers/assert_compiler_governance_declared_v0.py +65 -0
  39. compiler/governance_engine/assertions/handlers/assert_compiler_no_execution_v0.py +86 -0
  40. compiler/governance_engine/assertions/handlers/assert_concern_not_authority_v0.py +51 -0
  41. compiler/governance_engine/assertions/handlers/assert_conformance_assertion_mode_valid_v0.py +35 -0
  42. compiler/governance_engine/assertions/handlers/assert_cryptographic_trust_declared_v0.py +51 -0
  43. compiler/governance_engine/assertions/handlers/assert_cs_isolated_execution_v0.py +35 -0
  44. compiler/governance_engine/assertions/handlers/assert_cs_surface_closed_v0.py +241 -0
  45. compiler/governance_engine/assertions/handlers/assert_cs_traceable_v0.py +35 -0
  46. compiler/governance_engine/assertions/handlers/assert_ct_output_contract_match_v0.py +167 -0
  47. compiler/governance_engine/assertions/handlers/assert_ct_surface_closed_v0.py +173 -0
  48. compiler/governance_engine/assertions/handlers/assert_ct_surface_derived_closed_v0.py +108 -0
  49. compiler/governance_engine/assertions/handlers/assert_ct_test_data_outcome_declared_v0.py +112 -0
  50. compiler/governance_engine/assertions/handlers/assert_ev_append_only_v0.py +70 -0
  51. compiler/governance_engine/assertions/handlers/assert_ev_schema_required_v0.py +59 -0
  52. compiler/governance_engine/assertions/handlers/assert_execution_placement_declared_v0.py +51 -0
  53. compiler/governance_engine/assertions/handlers/assert_execution_scheduling_declared_v0.py +51 -0
  54. compiler/governance_engine/assertions/handlers/assert_fqdn_namespace_authorized_v0.py +38 -0
  55. compiler/governance_engine/assertions/handlers/assert_fqdn_only_references_v0.py +89 -0
  56. compiler/governance_engine/assertions/handlers/assert_governance_declaration_resolves_v0.py +126 -0
  57. compiler/governance_engine/assertions/handlers/assert_handler_registry_closed_v0.py +75 -0
  58. compiler/governance_engine/assertions/handlers/assert_identity_authority_separation_v0.py +19 -0
  59. compiler/governance_engine/assertions/handlers/assert_identity_fqdn_consistency.py +75 -0
  60. compiler/governance_engine/assertions/handlers/assert_implementation_admissible_v0.py +66 -0
  61. compiler/governance_engine/assertions/handlers/assert_in_no_execution_logic_v0.py +82 -0
  62. compiler/governance_engine/assertions/handlers/assert_in_schema_required_v0.py +74 -0
  63. compiler/governance_engine/assertions/handlers/assert_in_workflow_binding_v0.py +101 -0
  64. compiler/governance_engine/assertions/handlers/assert_inspection_capability_read_only_v0.py +114 -0
  65. compiler/governance_engine/assertions/handlers/assert_no_ambient_authority_v0.py +17 -0
  66. compiler/governance_engine/assertions/handlers/assert_no_runtime_authorization_v0.py +18 -0
  67. compiler/governance_engine/assertions/handlers/assert_no_runtime_topology_synthesis_v0.py +18 -0
  68. compiler/governance_engine/assertions/handlers/assert_no_smart_execution_v0.py +102 -0
  69. compiler/governance_engine/assertions/handlers/assert_no_undeclared_behavior_surface_v0.py +52 -0
  70. compiler/governance_engine/assertions/handlers/assert_no_workflow_authorization_logic_v0.py +18 -0
  71. compiler/governance_engine/assertions/handlers/assert_protocol_surface_closed_v0.py +119 -0
  72. compiler/governance_engine/assertions/handlers/assert_rb_binding_policy_conformance_v0.py +124 -0
  73. compiler/governance_engine/assertions/handlers/assert_rb_cs_only_v0.py +68 -0
  74. compiler/governance_engine/assertions/handlers/assert_rb_no_logic_v0.py +81 -0
  75. compiler/governance_engine/assertions/handlers/assert_rb_parameters_declared_v0.py +117 -0
  76. compiler/governance_engine/assertions/handlers/assert_rb_storage_subdomain_owned_v0.py +118 -0
  77. compiler/governance_engine/assertions/handlers/assert_runtime_invariant_wired_v0.py +163 -0
  78. compiler/governance_engine/assertions/handlers/assert_schema_conformance_v0.py +64 -0
  79. compiler/governance_engine/assertions/handlers/assert_schema_description_well_formed_v0.py +97 -0
  80. compiler/governance_engine/assertions/handlers/assert_security_domain_declared_v0.py +51 -0
  81. compiler/governance_engine/assertions/handlers/assert_structure_paths_well_formed_v0.py +118 -0
  82. compiler/governance_engine/assertions/handlers/assert_superseded_not_referenced_v0.py +134 -0
  83. compiler/governance_engine/assertions/handlers/assert_test_data_match_ct_output_v0.py +134 -0
  84. compiler/governance_engine/assertions/handlers/assert_topology_acyclic_v0.py +53 -0
  85. compiler/governance_engine/assertions/handlers/assert_topology_authority_orthogonal_v0.py +110 -0
  86. compiler/governance_engine/assertions/handlers/assert_topology_capability_reference_unique_v0.py +63 -0
  87. compiler/governance_engine/assertions/handlers/assert_topology_contract_closed_v0.py +113 -0
  88. compiler/governance_engine/assertions/handlers/assert_topology_immutable_after_compilation_v0.py +17 -0
  89. compiler/governance_engine/assertions/handlers/assert_topology_input_reference_declared_v0.py +112 -0
  90. compiler/governance_engine/assertions/handlers/assert_topology_routing_complete_v0.py +82 -0
  91. compiler/governance_engine/assertions/handlers/assert_topology_step_declared_v0.py +77 -0
  92. compiler/governance_engine/assertions/handlers/assert_topology_step_id_unique_v0.py +60 -0
  93. compiler/governance_engine/assertions/handlers/assert_topology_surface_canonical_v0.py +180 -0
  94. compiler/governance_engine/assertions/handlers/assert_topology_transport_orthogonal_v0.py +113 -0
  95. compiler/governance_engine/assertions/handlers/assert_trace_authority_binding_required_v0.py +18 -0
  96. compiler/governance_engine/assertions/handlers/assert_transport_canonical_normalization_v0.py +83 -0
  97. compiler/governance_engine/assertions/handlers/assert_transport_no_dynamic_routing_v0.py +93 -0
  98. compiler/governance_engine/assertions/handlers/assert_transport_no_workflow_semantics_v0.py +106 -0
  99. compiler/governance_engine/assertions/handlers/assert_transport_operation_identity_independence_v0.py +56 -0
  100. compiler/governance_engine/assertions/handlers/assert_transport_response_projection_external_v0.py +38 -0
  101. compiler/governance_engine/assertions/handlers/assert_transport_result_class_protocol_independence_v0.py +48 -0
  102. compiler/governance_engine/assertions/handlers/assert_transport_target_exists_v0.py +132 -0
  103. compiler/governance_engine/assertions/handlers/assert_unique_artifact_id_v0.py +58 -0
  104. compiler/governance_engine/assertions/handlers/assert_vocabulary_symbols_well_formed_v0.py +163 -0
  105. compiler/governance_engine/assertions/handlers/assert_wf_announcement_distinct_v0.py +85 -0
  106. compiler/governance_engine/assertions/handlers/assert_wf_cc_only_nodes_v0.py +60 -0
  107. compiler/governance_engine/assertions/handlers/assert_wf_entry_intent_v0.py +68 -0
  108. compiler/governance_engine/assertions/handlers/assert_wf_execution_path_valid_v0.py +89 -0
  109. compiler/governance_engine/assertions/handlers/assert_wf_node_key_binding_unique_v0.py +107 -0
  110. compiler/governance_engine/platform_root.py +102 -0
  111. compiler/governance_engine/structure/__init__.py +56 -0
  112. compiler/governance_engine/structure/discovery/__init__.py +28 -0
  113. compiler/governance_engine/structure/discovery/artifact_locator.py +210 -0
  114. compiler/governance_engine/structure/exceptions.py +113 -0
  115. compiler/governance_engine/structure/loading/__init__.py +88 -0
  116. compiler/governance_engine/structure/loading/fs_reader.py +225 -0
  117. compiler/governance_engine/structure/loading/markdown_parser.py +148 -0
  118. compiler/governance_engine/structure/loading/protocol_loader.py +587 -0
  119. compiler/governance_engine/structure/loading/test_case_provider.py +125 -0
  120. compiler/governance_engine/structure/loading/trace_reader.py +98 -0
  121. compiler/governance_engine/structure/loading/vocabulary_loader.py +174 -0
  122. compiler/governance_engine/structure/loading/yaml_parser.py +209 -0
  123. compiler/governance_engine/structure/resolution/__init__.py +22 -0
  124. compiler/governance_engine/structure/resolution/domain_resolver.py +124 -0
  125. compiler/governance_engine/structure/resolution/layer_resolver.py +572 -0
  126. compiler/governance_engine/structure/resolution/path_registry.py +1054 -0
  127. compiler/graph/__init__.py +38 -0
  128. compiler/graph/addressing.py +153 -0
  129. compiler/graph/edge.py +100 -0
  130. compiler/graph/evidence.py +637 -0
  131. compiler/graph/graph.py +269 -0
  132. compiler/graph/hashing.py +89 -0
  133. compiler/graph/node.py +134 -0
  134. compiler/graph/query.py +215 -0
  135. compiler/graph/state.py +237 -0
  136. compiler/graph/trace.py +97 -0
  137. compiler/graph/types.py +71 -0
  138. compiler/projections/__init__.py +207 -0
  139. compiler/projections/artifact_index.py +225 -0
  140. compiler/projections/canonical.py +173 -0
  141. compiler/projections/dispatch.py +532 -0
  142. compiler/projections/evidence.py +170 -0
  143. compiler/projections/handlers.py +224 -0
  144. compiler/projections/ipm.py +388 -0
  145. compiler/projections/reverse.py +165 -0
  146. compiler/projections/tokenized.py +135 -0
  147. compiler/projections/vocabulary.py +108 -0
  148. compiler/stages/__init__.py +28 -0
  149. compiler/stages/s1_extract.py +867 -0
  150. compiler/stages/s2_canonicalize.py +516 -0
  151. compiler/stages/s3_semantic_addressing.py +66 -0
  152. compiler/stages/s4_govern.py +754 -0
  153. compiler/stages/s5_construct.py +537 -0
  154. compiler/stages/s6_project.py +94 -0
  155. compiler/stages/s7_materialize.py +1170 -0
  156. compiler/stages/s8_verify.py +643 -0
  157. compiler/stages/s9_attest.py +184 -0
  158. compiler/structure_loader.py +132 -0
  159. compiler/visualization/__init__.py +12 -0
  160. compiler/visualization/consumers/__init__.py +28 -0
  161. compiler/visualization/consumers/evidence_projection.py +162 -0
  162. compiler/visualization/consumers/evidence_query.py +296 -0
  163. compiler/visualization/consumers/evidence_reader.py +127 -0
  164. compiler/visualization/views/__init__.py +70 -0
  165. compiler/visualization/views/_png_writer.py +45 -0
  166. compiler/visualization/views/artifact_lineage.py +180 -0
  167. compiler/visualization/views/family_view.py +164 -0
  168. compiler/visualization/views/materialization_view.py +150 -0
  169. compiler/visualization/views/structural_dag.py +158 -0
  170. compiler/visualization/wf_graph_generator.py +534 -0
  171. pgc_compiler-2.0.0.dist-info/METADATA +203 -0
  172. pgc_compiler-2.0.0.dist-info/RECORD +177 -0
  173. pgc_compiler-2.0.0.dist-info/WHEEL +5 -0
  174. pgc_compiler-2.0.0.dist-info/entry_points.txt +2 -0
  175. pgc_compiler-2.0.0.dist-info/licenses/LICENSE +67 -0
  176. pgc_compiler-2.0.0.dist-info/licenses/NOTICE +11 -0
  177. 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