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,99 @@
1
+ """
2
+ Phase result model for pipeline execution.
3
+
4
+ Design:
5
+ - Explicit status (SUCCESS/PARTIAL/FAILED)
6
+ - Typed outputs (phase-specific data)
7
+ - Error collection (all errors, not just first)
8
+ - Metrics (timing, counts for reporting)
9
+ """
10
+
11
+ from dataclasses import dataclass, field
12
+ from enum import Enum
13
+ from typing import Any
14
+
15
+ from compiler.atoms.errors import CompilerError
16
+
17
+
18
+ class PhaseStatus(Enum):
19
+ """Phase execution status."""
20
+
21
+ SUCCESS = "SUCCESS" # No errors, all items processed
22
+ PARTIAL = "PARTIAL" # Some errors, some items processed
23
+ FAILED = "FAILED" # Critical error, phase could not complete
24
+
25
+
26
+ @dataclass
27
+ class PhaseMetrics:
28
+ """
29
+ Metrics for phase execution.
30
+
31
+ Used for reporting and performance analysis.
32
+ """
33
+
34
+ duration_ms: int = 0
35
+ items_processed: int = 0
36
+ items_failed: int = 0
37
+ items_skipped: int = 0
38
+
39
+ def __post_init__(self) -> None:
40
+ """Calculate total."""
41
+ self.total = self.items_processed + self.items_failed + self.items_skipped
42
+
43
+
44
+ @dataclass(frozen=True)
45
+ class PhaseResult:
46
+ """
47
+ Result from phase execution.
48
+
49
+ All phases return this type for consistency.
50
+
51
+ IMMUTABILITY GUARANTEE:
52
+ - frozen=True prevents mutation after creation
53
+ - No phase side-effects
54
+ - No hidden mutation across pipeline
55
+ - Build once with all errors, don't mutate
56
+
57
+ Fields:
58
+ status: Execution status
59
+ outputs: Phase-specific output data (artifacts, metadata, etc.)
60
+ errors: Tuple of errors encountered (immutable)
61
+ metrics: Performance metrics
62
+
63
+ Examples:
64
+ >>> result = PhaseResult(
65
+ ... status=PhaseStatus.SUCCESS,
66
+ ... outputs={"artifacts": [...]},
67
+ ... errors=(),
68
+ ... metrics=PhaseMetrics(duration_ms=50, items_processed=10)
69
+ ... )
70
+ """
71
+
72
+ status: PhaseStatus
73
+ outputs: dict[str, Any]
74
+ errors: tuple[CompilerError, ...] = field(default_factory=tuple)
75
+ metrics: PhaseMetrics = field(default_factory=PhaseMetrics)
76
+
77
+ @property
78
+ def success(self) -> bool:
79
+ """Check if phase succeeded (no errors)."""
80
+ return self.status == PhaseStatus.SUCCESS and len(self.errors) == 0
81
+
82
+ @property
83
+ def failed(self) -> bool:
84
+ """Check if phase failed (critical error or all items failed)."""
85
+ return self.status == PhaseStatus.FAILED
86
+
87
+ def to_dict(self) -> dict[str, Any]:
88
+ """Convert to dictionary for JSON output."""
89
+ return {
90
+ "status": self.status.value,
91
+ "outputs": self.outputs,
92
+ "errors": [e.to_dict() for e in self.errors],
93
+ "metrics": {
94
+ "duration_ms": self.metrics.duration_ms,
95
+ "items_processed": self.metrics.items_processed,
96
+ "items_failed": self.metrics.items_failed,
97
+ "items_skipped": self.metrics.items_skipped,
98
+ },
99
+ }
@@ -0,0 +1,51 @@
1
+ """
2
+ pipeline.py — Compiler pipeline metadata management.
3
+
4
+ Defines which fields are transient compiler-operational state and must never
5
+ cross the materialization boundary into compiled artifacts or snapshots.
6
+
7
+ Design principle:
8
+ Compiled artifacts must contain ONLY governable semantic state.
9
+ They must never contain:
10
+ - local filesystem state (source_path, loader_origin)
11
+ - compiler operational state (parse_cache, temp_resolution)
12
+ - author workstation state (filesystem hints, debug fields)
13
+ - build-environment leakage (absolute paths, host identifiers)
14
+
15
+ Transient fields serve the build pipeline internally (e.g., discover → parse)
16
+ and are stripped at the materialization boundary — the formal threshold between
17
+ compiler-operational state and protocol-semantic state.
18
+
19
+ As new pipeline-internal fields are introduced, register them here.
20
+ Never use ad hoc .pop() at call sites.
21
+ """
22
+
23
+ # Fields that are compiler-internal pipeline metadata.
24
+ # Present on artifact dicts during build; must not appear in materialized output.
25
+ _TRANSIENT_PIPELINE_FIELDS: frozenset[str] = frozenset(
26
+ {
27
+ "source_path", # Absolute filesystem path set by discover; used by parse to load content
28
+ # Future candidates (register here as needed):
29
+ # "parse_cache",
30
+ # "loader_origin",
31
+ # "compiler_debug",
32
+ # "temp_resolution",
33
+ # "filesystem_hint",
34
+ }
35
+ )
36
+
37
+
38
+ def strip_transient_pipeline_fields(artifact: dict) -> dict:
39
+ """
40
+ Return a copy of artifact with all transient compiler pipeline fields removed.
41
+
42
+ Call this once, immediately before serializing an artifact to disk.
43
+ The returned dict is a shallow copy — nested structures are not copied.
44
+
45
+ Args:
46
+ artifact: Artifact dict as it exists in the compiler pipeline.
47
+
48
+ Returns:
49
+ New dict with transient fields stripped. Original is not mutated.
50
+ """
51
+ return {k: v for k, v in artifact.items() if k not in _TRANSIENT_PIPELINE_FIELDS}
@@ -0,0 +1,51 @@
1
+ """
2
+ Snapshot admission control gate.
3
+
4
+ All CLI subcommands that consume a compiled workspace snapshot
5
+ MUST call assert_snapshot_valid() before reading from it.
6
+
7
+ The admissibility chain:
8
+ Invalid Protocol → No Snapshot → Invalid Snapshot → No Build → Invalid Build → No Index
9
+ """
10
+
11
+ import json
12
+ import sys
13
+ from pathlib import Path
14
+
15
+
16
+ def assert_snapshot_valid(workspace: Path) -> None:
17
+ """
18
+ Hard gate: workspace snapshot must be VALID before any consumer subcommand proceeds.
19
+
20
+ Reads snapshot_status.json from the workspace root. Exits 1 with a diagnostic
21
+ message if the file is missing, unreadable, or reports a non-VALID status.
22
+
23
+ Args:
24
+ workspace: Absolute path to pgs_workspace root.
25
+
26
+ Raises:
27
+ SystemExit(1): If snapshot is absent or not VALID.
28
+ """
29
+ snapshot_status_file = workspace / "snapshot_status.json"
30
+
31
+ if not snapshot_status_file.exists():
32
+ print(
33
+ "ERROR: snapshot_status.json not found — snapshot has not been built.\n"
34
+ " Run 'compiler build --workspace <path>' first.",
35
+ file=sys.stderr,
36
+ )
37
+ sys.exit(1)
38
+
39
+ try:
40
+ snapshot_status = json.loads(snapshot_status_file.read_text())
41
+ except Exception as exc:
42
+ print(f"ERROR: Failed to read snapshot_status.json: {exc}", file=sys.stderr)
43
+ sys.exit(1)
44
+
45
+ if snapshot_status.get("status") != "VALID":
46
+ print(
47
+ f"ERROR: Snapshot is not VALID (status: {snapshot_status.get('status', 'UNKNOWN')}).\n"
48
+ " Fix all compile errors and rebuild the snapshot before proceeding.",
49
+ file=sys.stderr,
50
+ )
51
+ sys.exit(1)
@@ -0,0 +1,125 @@
1
+ """
2
+ Deterministic sorting utilities.
3
+
4
+ Design:
5
+ - Sort by FQDN (primary identity)
6
+ - Stable ordering (same input → same order)
7
+ - No implicit ordering (dict iteration, set ordering)
8
+ - Explicit sort at phase boundaries
9
+ """
10
+
11
+ from typing import Any, Callable, TypeVar
12
+
13
+ T = TypeVar("T")
14
+
15
+
16
+ def sort_artifacts_by_fqdn(artifacts: list[dict[str, Any]]) -> list[dict[str, Any]]:
17
+ """
18
+ Sort artifacts by FQDN (deterministic ordering).
19
+
20
+ CRITICAL: Use this at phase boundaries to prevent nondeterminism.
21
+
22
+ Args:
23
+ artifacts: List of artifact dicts (must have "fqdn_id" field)
24
+
25
+ Returns:
26
+ Sorted list (by fqdn_id lexicographic order)
27
+
28
+ Examples:
29
+ >>> artifacts = [
30
+ ... {"fqdn_id": "pkg_b::CT_FOO_V0", ...},
31
+ ... {"fqdn_id": "pkg_a::CT_BAR_V0", ...},
32
+ ... ]
33
+ >>> sorted_artifacts = sort_artifacts_by_fqdn(artifacts)
34
+ >>> [a["fqdn_id"] for a in sorted_artifacts]
35
+ ['pkg_a::CT_BAR_V0', 'pkg_b::CT_FOO_V0']
36
+ """
37
+ return sorted(artifacts, key=lambda a: a["fqdn_id"])
38
+
39
+
40
+ def sort_by_fqdn(items: list[T], key_func: Callable[[T], str]) -> list[T]:
41
+ """
42
+ Sort generic items by FQDN key (deterministic ordering).
43
+
44
+ More flexible version - works with any type.
45
+
46
+ Args:
47
+ items: List of items to sort
48
+ key_func: Function to extract FQDN string from item
49
+
50
+ Returns:
51
+ Sorted list (by FQDN lexicographic order)
52
+
53
+ Examples:
54
+ >>> from dataclasses import dataclass
55
+ >>> @dataclass
56
+ ... class Artifact:
57
+ ... fqdn: str
58
+ ... data: dict
59
+ >>> artifacts = [
60
+ ... Artifact("pkg_b::CT_FOO_V0", {}),
61
+ ... Artifact("pkg_a::CT_BAR_V0", {}),
62
+ ... ]
63
+ >>> sorted_artifacts = sort_by_fqdn(artifacts, lambda a: a.fqdn)
64
+ >>> [a.fqdn for a in sorted_artifacts]
65
+ ['pkg_a::CT_BAR_V0', 'pkg_b::CT_FOO_V0']
66
+ """
67
+ return sorted(items, key=key_func)
68
+
69
+
70
+ def sort_dict_keys(d: dict[str, Any]) -> dict[str, Any]:
71
+ """
72
+ Sort dictionary by keys (deterministic ordering).
73
+
74
+ Use for JSON output to ensure byte-identical outputs.
75
+
76
+ Args:
77
+ d: Dictionary to sort
78
+
79
+ Returns:
80
+ New dict with keys in lexicographic order
81
+
82
+ Examples:
83
+ >>> d = {"z": 1, "a": 2, "m": 3}
84
+ >>> sorted_d = sort_dict_keys(d)
85
+ >>> list(sorted_d.keys())
86
+ ['a', 'm', 'z']
87
+ """
88
+ return dict(sorted(d.items()))
89
+
90
+
91
+ def ensure_deterministic_output(obj: Any) -> Any:
92
+ """
93
+ Recursively sort all collections for deterministic output.
94
+
95
+ Use before JSON serialization to guarantee byte-identical outputs.
96
+
97
+ Args:
98
+ obj: Object to make deterministic (dict, list, primitive)
99
+
100
+ Returns:
101
+ Deterministic version (sorted dicts, sorted lists where applicable)
102
+
103
+ Examples:
104
+ >>> obj = {
105
+ ... "artifacts": [
106
+ ... {"fqdn_id": "b::CT", "name": "foo"},
107
+ ... {"fqdn_id": "a::CT", "name": "bar"},
108
+ ... ],
109
+ ... "z_field": "last",
110
+ ... "a_field": "first",
111
+ ... }
112
+ >>> deterministic = ensure_deterministic_output(obj)
113
+ >>> list(deterministic.keys())
114
+ ['a_field', 'artifacts', 'z_field']
115
+ """
116
+ if isinstance(obj, dict):
117
+ # Sort dict keys and recursively process values
118
+ return {k: ensure_deterministic_output(v) for k, v in sorted(obj.items())}
119
+ elif isinstance(obj, list):
120
+ # Recursively process list items
121
+ # Don't sort lists (order may be meaningful)
122
+ return [ensure_deterministic_output(item) for item in obj]
123
+ else:
124
+ # Primitives (str, int, bool, None) pass through
125
+ return obj