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
compiler/atoms/phase.py
ADDED
|
@@ -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
|