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/VERSION
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
14
|
compiler/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""compiler — Protocol Intermediate Representation compiler for PGS."""
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Atoms: Pure functions with no side effects.
|
|
3
|
+
|
|
4
|
+
Design:
|
|
5
|
+
- Single responsibility
|
|
6
|
+
- Explicit inputs/outputs
|
|
7
|
+
- No I/O, no state
|
|
8
|
+
- Independently testable
|
|
9
|
+
- Reusable across phases
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from compiler.atoms.error_codes import ErrorCode, ERROR_SUGGESTIONS
|
|
13
|
+
from compiler.atoms.errors import CompilerError, Severity
|
|
14
|
+
from compiler.atoms.fqdn import FQDN, parse_fqdn, build_fqdn, to_fqdn
|
|
15
|
+
from compiler.atoms.invariants import require, require_not_none, require_exists
|
|
16
|
+
from compiler.atoms.phase import PhaseResult, PhaseStatus, PhaseMetrics
|
|
17
|
+
from compiler.atoms.pipeline import strip_transient_pipeline_fields
|
|
18
|
+
from compiler.atoms.sorting import (
|
|
19
|
+
ensure_deterministic_output,
|
|
20
|
+
sort_artifacts_by_fqdn,
|
|
21
|
+
sort_by_fqdn,
|
|
22
|
+
sort_dict_keys,
|
|
23
|
+
)
|
|
24
|
+
from compiler.atoms.snapshot_gate import assert_snapshot_valid
|
|
25
|
+
|
|
26
|
+
__all__ = [
|
|
27
|
+
# Error model
|
|
28
|
+
"CompilerError",
|
|
29
|
+
"ErrorCode",
|
|
30
|
+
"ERROR_SUGGESTIONS",
|
|
31
|
+
"Severity",
|
|
32
|
+
# FQDN identity
|
|
33
|
+
"FQDN",
|
|
34
|
+
"parse_fqdn",
|
|
35
|
+
"build_fqdn",
|
|
36
|
+
"to_fqdn",
|
|
37
|
+
# Phase results
|
|
38
|
+
"PhaseResult",
|
|
39
|
+
"PhaseStatus",
|
|
40
|
+
"PhaseMetrics",
|
|
41
|
+
# Invariant enforcement
|
|
42
|
+
"require",
|
|
43
|
+
"require_not_none",
|
|
44
|
+
"require_exists",
|
|
45
|
+
# Pipeline metadata
|
|
46
|
+
"strip_transient_pipeline_fields",
|
|
47
|
+
# Deterministic ordering
|
|
48
|
+
"ensure_deterministic_output",
|
|
49
|
+
"sort_artifacts_by_fqdn",
|
|
50
|
+
"sort_by_fqdn",
|
|
51
|
+
"sort_dict_keys",
|
|
52
|
+
# Snapshot admission control
|
|
53
|
+
"assert_snapshot_valid",
|
|
54
|
+
]
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Centralized error code registry.
|
|
3
|
+
|
|
4
|
+
All error codes in one place to prevent:
|
|
5
|
+
- String drift
|
|
6
|
+
- Inconsistent reporting
|
|
7
|
+
- Duplicate codes
|
|
8
|
+
- Missing documentation
|
|
9
|
+
|
|
10
|
+
Add new codes here, never inline in phase code.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from enum import Enum
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class ErrorCode(Enum):
|
|
17
|
+
"""
|
|
18
|
+
Error codes for the PGS compiler.
|
|
19
|
+
|
|
20
|
+
Organized by stage:
|
|
21
|
+
- E0xx: Discovery errors (S1)
|
|
22
|
+
- E1xx: Parse errors (S1)
|
|
23
|
+
- E2xx: Validation errors (S2–S4)
|
|
24
|
+
- E3xx: Materialization errors (S7)
|
|
25
|
+
- E4xx: Verification errors (S8)
|
|
26
|
+
- E7xx: ASSERT errors (S4)
|
|
27
|
+
- E8xx: Conformance errors (S7)
|
|
28
|
+
- E9xx: Internal/system errors
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
# ==================
|
|
32
|
+
# Discovery (E0xx)
|
|
33
|
+
# ==================
|
|
34
|
+
E001_NO_ARTIFACTS = "E001_NO_ARTIFACTS"
|
|
35
|
+
"""No artifacts found in search path."""
|
|
36
|
+
|
|
37
|
+
E002_DUPLICATE_FQDN = "E002_DUPLICATE_FQDN"
|
|
38
|
+
"""Duplicate FQDN detected (collision)."""
|
|
39
|
+
|
|
40
|
+
E003_INVALID_FILENAME = "E003_INVALID_FILENAME"
|
|
41
|
+
"""Invalid filename pattern."""
|
|
42
|
+
|
|
43
|
+
E004_CROSS_REPO_REF = "E004_CROSS_REPO_REF"
|
|
44
|
+
"""Cross-repo reference detected (source_path outside input_dir)."""
|
|
45
|
+
|
|
46
|
+
E005_DEPRECATED_ARTIFACT = "E005_DEPRECATED_ARTIFACT"
|
|
47
|
+
"""Artifact marked status=deprecated; skipped during extraction."""
|
|
48
|
+
|
|
49
|
+
# ==================
|
|
50
|
+
# Parse (E1xx)
|
|
51
|
+
# ==================
|
|
52
|
+
E101_INVALID_YAML = "E101_INVALID_YAML"
|
|
53
|
+
"""Invalid YAML frontmatter."""
|
|
54
|
+
|
|
55
|
+
E102_MISSING_FIELD = "E102_MISSING_FIELD"
|
|
56
|
+
"""Missing required field."""
|
|
57
|
+
|
|
58
|
+
E103_TYPE_MISMATCH = "E103_TYPE_MISMATCH"
|
|
59
|
+
"""Field type mismatch."""
|
|
60
|
+
|
|
61
|
+
E104_INVALID_FQDN = "E104_INVALID_FQDN"
|
|
62
|
+
"""Invalid FQDN format."""
|
|
63
|
+
|
|
64
|
+
# ==================
|
|
65
|
+
# Validation (E2xx)
|
|
66
|
+
# ==================
|
|
67
|
+
E201_MISSING_REFERENCE = "E201_MISSING_REFERENCE"
|
|
68
|
+
"""Reference to non-existent artifact (FQDN not found)."""
|
|
69
|
+
|
|
70
|
+
E202_CIRCULAR_DEPENDENCY = "E202_CIRCULAR_DEPENDENCY"
|
|
71
|
+
"""Circular dependency detected (A→B→A)."""
|
|
72
|
+
|
|
73
|
+
E203_SCHEMA_INVALID = "E203_SCHEMA_INVALID"
|
|
74
|
+
"""Schema validation failed (Pydantic error)."""
|
|
75
|
+
|
|
76
|
+
E204_INVALID_RB_BINDING = "E204_INVALID_RB_BINDING"
|
|
77
|
+
"""RB references invalid CS artifact."""
|
|
78
|
+
|
|
79
|
+
E205_CT_VALIDATION_FAILED = "E205_CT_VALIDATION_FAILED"
|
|
80
|
+
"""CT-IR validation failed."""
|
|
81
|
+
|
|
82
|
+
# ==================
|
|
83
|
+
# Materialization (E3xx)
|
|
84
|
+
# ==================
|
|
85
|
+
E301_WRITE_FAILED = "E301_WRITE_FAILED"
|
|
86
|
+
"""Failed to write output file."""
|
|
87
|
+
|
|
88
|
+
E302_JSON_SERIALIZE_FAILED = "E302_JSON_SERIALIZE_FAILED"
|
|
89
|
+
"""JSON serialization failed."""
|
|
90
|
+
|
|
91
|
+
# ==================
|
|
92
|
+
# Verification (E4xx)
|
|
93
|
+
# ==================
|
|
94
|
+
E401_MISSING_OUTPUT = "E401_MISSING_OUTPUT"
|
|
95
|
+
"""Expected output file not found."""
|
|
96
|
+
|
|
97
|
+
E402_UNDECLARED_OUTPUT = "E402_UNDECLARED_OUTPUT"
|
|
98
|
+
"""Output file not declared in expected outputs."""
|
|
99
|
+
|
|
100
|
+
E403_OUTPUT_MISMATCH = "E403_OUTPUT_MISMATCH"
|
|
101
|
+
"""Output doesn't match expected schema."""
|
|
102
|
+
|
|
103
|
+
# ==================
|
|
104
|
+
# ASSERT Phase (E701-E750)
|
|
105
|
+
# ==================
|
|
106
|
+
E701_ASSERTION_FAILURE = "E701_ASSERTION_FAILURE"
|
|
107
|
+
"""Assertion violations detected."""
|
|
108
|
+
|
|
109
|
+
E702_UNKNOWN_ASSERT = "E702_UNKNOWN_ASSERT"
|
|
110
|
+
"""No executor for ASSERT artifact."""
|
|
111
|
+
|
|
112
|
+
E703_MALFORMED_ASSERT = "E703_MALFORMED_ASSERT"
|
|
113
|
+
"""ASSERT artifact schema violation."""
|
|
114
|
+
|
|
115
|
+
# ==================
|
|
116
|
+
# Conformance (E8xx)
|
|
117
|
+
# ==================
|
|
118
|
+
E801_TEST_DATA_MISSING = "E801_TEST_DATA_MISSING"
|
|
119
|
+
"""Test data missing for CT."""
|
|
120
|
+
|
|
121
|
+
E802_CONFORMANCE_FAILURE = "E802_CONFORMANCE_FAILURE"
|
|
122
|
+
"""CT conformance test failed."""
|
|
123
|
+
|
|
124
|
+
E803_TEST_DATA_INVALID = "E803_TEST_DATA_INVALID"
|
|
125
|
+
"""TEST_DATA does not match CT contract."""
|
|
126
|
+
|
|
127
|
+
# ==================
|
|
128
|
+
# Internal (E9xx)
|
|
129
|
+
# ==================
|
|
130
|
+
E901_INTERNAL_ERROR = "E901_INTERNAL_ERROR"
|
|
131
|
+
"""Internal compiler error (bug)."""
|
|
132
|
+
|
|
133
|
+
E902_CONFIG_ERROR = "E902_CONFIG_ERROR"
|
|
134
|
+
"""Configuration error."""
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
# Error suggestions (actionable fixes)
|
|
138
|
+
ERROR_SUGGESTIONS: dict[ErrorCode, str] = {
|
|
139
|
+
ErrorCode.E001_NO_ARTIFACTS: "Check input directory path and artifact_types config",
|
|
140
|
+
ErrorCode.E002_DUPLICATE_FQDN: "Rename artifact or change namespace to make FQDN unique",
|
|
141
|
+
ErrorCode.E003_INVALID_FILENAME: "Ensure filename matches pattern: {TYPE}_{NAME}_V{N}.md",
|
|
142
|
+
ErrorCode.E004_CROSS_REPO_REF: "Move artifact into input_dir or remove reference",
|
|
143
|
+
ErrorCode.E005_DEPRECATED_ARTIFACT: "Remove deprecated artifact or update its status field",
|
|
144
|
+
ErrorCode.E101_INVALID_YAML: "Check YAML syntax, ensure proper indentation",
|
|
145
|
+
ErrorCode.E102_MISSING_FIELD: "Add required field to artifact frontmatter",
|
|
146
|
+
ErrorCode.E104_INVALID_FQDN: "FQDN format: {namespace}::{artifact_code}",
|
|
147
|
+
ErrorCode.E201_MISSING_REFERENCE: "Add referenced artifact or remove reference",
|
|
148
|
+
ErrorCode.E202_CIRCULAR_DEPENDENCY: "Break dependency cycle between artifacts",
|
|
149
|
+
ErrorCode.E204_INVALID_RB_BINDING: "Ensure RB references valid CS artifact",
|
|
150
|
+
ErrorCode.E205_CT_VALIDATION_FAILED: "Check CT atom_stream and purity constraints",
|
|
151
|
+
ErrorCode.E301_WRITE_FAILED: "Check output directory permissions and disk space",
|
|
152
|
+
ErrorCode.E401_MISSING_OUTPUT: "Check materialization phase completed successfully",
|
|
153
|
+
ErrorCode.E402_UNDECLARED_OUTPUT: "Remove stale file or add to expected outputs",
|
|
154
|
+
ErrorCode.E701_ASSERTION_FAILURE: "Fix detected violations in protocol artifacts",
|
|
155
|
+
ErrorCode.E702_UNKNOWN_ASSERT: "Ensure an executor is registered for this ASSERT artifact",
|
|
156
|
+
ErrorCode.E703_MALFORMED_ASSERT: "Check ASSERT artifact against CONSTITUTION_ASSERT_V0",
|
|
157
|
+
ErrorCode.E801_TEST_DATA_MISSING: "Create TEST_DATA artifact for the CT",
|
|
158
|
+
ErrorCode.E802_CONFORMANCE_FAILURE: "Debug CT implementation to match expected behavior",
|
|
159
|
+
ErrorCode.E803_TEST_DATA_INVALID: "Fix TEST_DATA bindings to match CT inputs contract",
|
|
160
|
+
}
|
compiler/atoms/errors.py
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Structured error model for the PGS compiler.
|
|
3
|
+
|
|
4
|
+
Design:
|
|
5
|
+
- Machine-readable + human-friendly
|
|
6
|
+
- Phase-tagged (S1–S8)
|
|
7
|
+
- Context-rich (FQDN, artifact_code, source_path)
|
|
8
|
+
- Actionable messages via ERROR_SUGGESTIONS
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from dataclasses import dataclass, field
|
|
12
|
+
from enum import Enum
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import Any
|
|
15
|
+
|
|
16
|
+
from compiler.atoms.error_codes import ErrorCode, ERROR_SUGGESTIONS
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class Severity(Enum):
|
|
20
|
+
"""Error severity levels."""
|
|
21
|
+
|
|
22
|
+
ERROR = "error"
|
|
23
|
+
WARNING = "warning"
|
|
24
|
+
INFO = "info"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass
|
|
28
|
+
class CompilerError(Exception):
|
|
29
|
+
"""
|
|
30
|
+
Structured error for compiler failures.
|
|
31
|
+
|
|
32
|
+
Fields:
|
|
33
|
+
code: Error code (e.g., E201_MISSING_REFERENCE)
|
|
34
|
+
message: Human-readable description
|
|
35
|
+
phase: Stage where error occurred (e.g., S1_EXTRACT)
|
|
36
|
+
fqdn_id: Fully qualified domain name (primary identity)
|
|
37
|
+
artifact_code: Short code for display/logging (not unique)
|
|
38
|
+
source_path: Path to source file if applicable
|
|
39
|
+
context: Additional debug information
|
|
40
|
+
severity: Error severity level
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
code: ErrorCode
|
|
44
|
+
message: str
|
|
45
|
+
phase: str
|
|
46
|
+
fqdn_id: str | None = None
|
|
47
|
+
artifact_code: str | None = None
|
|
48
|
+
source_path: Path | None = None
|
|
49
|
+
context: dict[str, Any] = field(default_factory=dict)
|
|
50
|
+
severity: Severity = Severity.ERROR
|
|
51
|
+
|
|
52
|
+
def __str__(self) -> str:
|
|
53
|
+
"""String representation delegates to format()."""
|
|
54
|
+
return self.format(verbose=False)
|
|
55
|
+
|
|
56
|
+
def format(self, verbose: bool = False) -> str:
|
|
57
|
+
"""
|
|
58
|
+
Format error for terminal output.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
verbose: Include full context and suggestions
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
Formatted error string
|
|
65
|
+
"""
|
|
66
|
+
# Color codes (basic ANSI)
|
|
67
|
+
color = {
|
|
68
|
+
Severity.ERROR: "\033[91m", # Red
|
|
69
|
+
Severity.WARNING: "\033[93m", # Yellow
|
|
70
|
+
Severity.INFO: "\033[94m", # Blue
|
|
71
|
+
}
|
|
72
|
+
reset = "\033[0m"
|
|
73
|
+
|
|
74
|
+
severity_color = color.get(self.severity, "")
|
|
75
|
+
severity_str = self.severity.value.upper()
|
|
76
|
+
|
|
77
|
+
# Basic format: [ERROR] E201: Message
|
|
78
|
+
basic = f"{severity_color}[{severity_str}]{reset} {self.code.value}: {self.message}"
|
|
79
|
+
|
|
80
|
+
if not verbose:
|
|
81
|
+
return basic
|
|
82
|
+
|
|
83
|
+
# Verbose format includes location, context
|
|
84
|
+
parts = [basic]
|
|
85
|
+
|
|
86
|
+
if self.source_path:
|
|
87
|
+
parts.append(f" File: {self.source_path}")
|
|
88
|
+
|
|
89
|
+
if self.fqdn_id:
|
|
90
|
+
parts.append(f" FQDN: {self.fqdn_id}")
|
|
91
|
+
|
|
92
|
+
if self.artifact_code:
|
|
93
|
+
parts.append(f" Code: {self.artifact_code}")
|
|
94
|
+
|
|
95
|
+
if self.phase:
|
|
96
|
+
parts.append(f" Phase: {self.phase}")
|
|
97
|
+
|
|
98
|
+
if self.context:
|
|
99
|
+
parts.append(" Context:")
|
|
100
|
+
for key, value in self.context.items():
|
|
101
|
+
parts.append(f" {key}: {value}")
|
|
102
|
+
|
|
103
|
+
# Add suggestion if available
|
|
104
|
+
suggestion = self._get_suggestion()
|
|
105
|
+
if suggestion:
|
|
106
|
+
parts.append(f" 💡 Suggestion: {suggestion}")
|
|
107
|
+
|
|
108
|
+
return "\n".join(parts)
|
|
109
|
+
|
|
110
|
+
def _get_suggestion(self) -> str | None:
|
|
111
|
+
"""Get actionable suggestion for fixing this error."""
|
|
112
|
+
return ERROR_SUGGESTIONS.get(self.code)
|
|
113
|
+
|
|
114
|
+
def to_dict(self) -> dict[str, Any]:
|
|
115
|
+
"""Convert to dictionary for JSON output."""
|
|
116
|
+
return {
|
|
117
|
+
"code": self.code.value,
|
|
118
|
+
"message": self.message,
|
|
119
|
+
"phase": self.phase,
|
|
120
|
+
"fqdn_id": self.fqdn_id,
|
|
121
|
+
"artifact_code": self.artifact_code,
|
|
122
|
+
"source_path": str(self.source_path) if self.source_path else None,
|
|
123
|
+
"context": self.context,
|
|
124
|
+
"severity": self.severity.value,
|
|
125
|
+
}
|
compiler/atoms/fqdn.py
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
"""
|
|
2
|
+
FQDN (Fully Qualified Domain Name) utilities.
|
|
3
|
+
|
|
4
|
+
Patch 1 Applied: FQDN is PRIMARY identity, never use artifact_code for uniqueness.
|
|
5
|
+
|
|
6
|
+
FQDN Format: {namespace}::{artifact_code}
|
|
7
|
+
Example: pgs_governance::CT_SCAN_ARTIFACTS_V0
|
|
8
|
+
|
|
9
|
+
Design:
|
|
10
|
+
- FQDN is PRIMARY identity (all references use FQDN)
|
|
11
|
+
- artifact_code is display/logging ONLY (not guaranteed unique)
|
|
12
|
+
- Namespace prevents collision across layers (e.g., pgs_governance, compiler)
|
|
13
|
+
- Parse/build functions for deterministic construction
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import re
|
|
17
|
+
from dataclasses import dataclass
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass(frozen=True)
|
|
22
|
+
class FQDN:
|
|
23
|
+
"""
|
|
24
|
+
Fully Qualified Domain Name for artifact identity.
|
|
25
|
+
|
|
26
|
+
CRITICAL: This is the PRIMARY identity.
|
|
27
|
+
artifact_code alone is NOT sufficient (may collide).
|
|
28
|
+
|
|
29
|
+
IMMUTABILITY GUARANTEE:
|
|
30
|
+
- frozen=True prevents mutation
|
|
31
|
+
- Always use str(fqdn) or fqdn.fqdn for identity
|
|
32
|
+
- NEVER access .namespace or .artifact_code separately outside atoms layer
|
|
33
|
+
- Prevents accidental reversion to partial identity
|
|
34
|
+
|
|
35
|
+
Fields:
|
|
36
|
+
namespace: Package/layer namespace (e.g., "pgs_governance") [INTERNAL]
|
|
37
|
+
artifact_code: Short code (e.g., "CT_SCAN_V0") [INTERNAL]
|
|
38
|
+
fqdn: Full identifier (namespace::artifact_code) [PUBLIC]
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
namespace: str
|
|
42
|
+
artifact_code: str
|
|
43
|
+
|
|
44
|
+
@property
|
|
45
|
+
def fqdn(self) -> str:
|
|
46
|
+
"""
|
|
47
|
+
Get full FQDN string.
|
|
48
|
+
|
|
49
|
+
This is the ONLY public interface for identity.
|
|
50
|
+
Use this, not namespace or artifact_code separately.
|
|
51
|
+
"""
|
|
52
|
+
return f"{self.namespace}::{self.artifact_code}"
|
|
53
|
+
|
|
54
|
+
def __str__(self) -> str:
|
|
55
|
+
"""
|
|
56
|
+
String representation is the FQDN.
|
|
57
|
+
|
|
58
|
+
Always use this for display/logging/comparison.
|
|
59
|
+
"""
|
|
60
|
+
return self.fqdn
|
|
61
|
+
|
|
62
|
+
def __hash__(self) -> int:
|
|
63
|
+
"""Hash by FQDN (for use in dicts/sets)."""
|
|
64
|
+
return hash(self.fqdn)
|
|
65
|
+
|
|
66
|
+
def __repr__(self) -> str:
|
|
67
|
+
"""Repr is also the FQDN (not internal components)."""
|
|
68
|
+
return f"FQDN('{self.fqdn}')"
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def parse_fqdn(fqdn_str: str) -> FQDN:
|
|
72
|
+
"""
|
|
73
|
+
Parse FQDN string into components.
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
fqdn_str: FQDN string (namespace::artifact_code)
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
FQDN object
|
|
80
|
+
|
|
81
|
+
Raises:
|
|
82
|
+
ValueError: If FQDN format invalid
|
|
83
|
+
|
|
84
|
+
Examples:
|
|
85
|
+
>>> parse_fqdn("pgs_governance::CT_SCAN_V0")
|
|
86
|
+
FQDN(namespace='pgs_governance', artifact_code='CT_SCAN_V0')
|
|
87
|
+
"""
|
|
88
|
+
if "::" not in fqdn_str:
|
|
89
|
+
raise ValueError(
|
|
90
|
+
f"Invalid FQDN format: '{fqdn_str}'\n"
|
|
91
|
+
f"Expected: {{namespace}}::{{artifact_code}}\n"
|
|
92
|
+
f"Example: pgs_governance::CT_SCAN_V0"
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
parts = fqdn_str.split("::", 1)
|
|
96
|
+
if len(parts) != 2:
|
|
97
|
+
raise ValueError(f"Invalid FQDN: '{fqdn_str}' (expected exactly one '::')")
|
|
98
|
+
|
|
99
|
+
namespace, artifact_code = parts
|
|
100
|
+
|
|
101
|
+
if not namespace:
|
|
102
|
+
raise ValueError(f"Invalid FQDN: '{fqdn_str}' (empty namespace)")
|
|
103
|
+
|
|
104
|
+
if not artifact_code:
|
|
105
|
+
raise ValueError(f"Invalid FQDN: '{fqdn_str}' (empty artifact_code)")
|
|
106
|
+
|
|
107
|
+
return FQDN(namespace=namespace, artifact_code=artifact_code)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def build_fqdn(source_path: Path, layer_root: Path, artifact_code: str) -> FQDN:
|
|
111
|
+
"""
|
|
112
|
+
Build FQDN from source path and layer root.
|
|
113
|
+
|
|
114
|
+
Derives namespace from directory structure:
|
|
115
|
+
- Namespace = layer_root.name (e.g., pgs_governance, compiler)
|
|
116
|
+
|
|
117
|
+
Args:
|
|
118
|
+
source_path: Path to artifact source file
|
|
119
|
+
layer_root: Root directory of layer
|
|
120
|
+
artifact_code: Short code (e.g., CT_SCAN_V0)
|
|
121
|
+
|
|
122
|
+
Returns:
|
|
123
|
+
FQDN object
|
|
124
|
+
|
|
125
|
+
Examples:
|
|
126
|
+
>>> build_fqdn(
|
|
127
|
+
... Path("/pgs/pgs_governance/registry/CT_FOO.md"),
|
|
128
|
+
... Path("/pgs/pgs_governance"),
|
|
129
|
+
... "CT_FOO_V0"
|
|
130
|
+
... )
|
|
131
|
+
FQDN(namespace='pgs_governance', artifact_code='CT_FOO_V0')
|
|
132
|
+
"""
|
|
133
|
+
# Namespace is derived from layer root directory name
|
|
134
|
+
# This ensures uniqueness across different layers/repos
|
|
135
|
+
namespace = layer_root.name
|
|
136
|
+
|
|
137
|
+
return FQDN(namespace=namespace, artifact_code=artifact_code)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def extract_artifact_code(filename: str) -> str:
|
|
141
|
+
"""
|
|
142
|
+
Extract artifact code from filename.
|
|
143
|
+
|
|
144
|
+
Args:
|
|
145
|
+
filename: Artifact filename (e.g., "CT_SCAN_V0.md")
|
|
146
|
+
|
|
147
|
+
Returns:
|
|
148
|
+
Artifact code without extension
|
|
149
|
+
|
|
150
|
+
Examples:
|
|
151
|
+
>>> extract_artifact_code("CT_SCAN_ARTIFACTS_V0.md")
|
|
152
|
+
'CT_SCAN_ARTIFACTS_V0'
|
|
153
|
+
"""
|
|
154
|
+
# Remove extension
|
|
155
|
+
if filename.endswith(".md"):
|
|
156
|
+
return filename[:-3]
|
|
157
|
+
return filename
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def validate_artifact_code(code: str) -> bool:
|
|
161
|
+
"""
|
|
162
|
+
Validate artifact code format.
|
|
163
|
+
|
|
164
|
+
Expected pattern: {TYPE}_{NAME}_V{VERSION}
|
|
165
|
+
Examples: CT_SCAN_V0, WF_BUILD_V1
|
|
166
|
+
|
|
167
|
+
Args:
|
|
168
|
+
code: Artifact code to validate
|
|
169
|
+
|
|
170
|
+
Returns:
|
|
171
|
+
True if valid, False otherwise
|
|
172
|
+
"""
|
|
173
|
+
# Pattern: TYPE_NAME_VN (where TYPE is 2-20 uppercase letters)
|
|
174
|
+
pattern = r"^[A-Z]{2,20}_[A-Z0-9_]+_V\d+$"
|
|
175
|
+
return bool(re.match(pattern, code))
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def to_fqdn(ref: str, namespace: str) -> FQDN:
|
|
179
|
+
"""
|
|
180
|
+
Normalize reference to FQDN.
|
|
181
|
+
|
|
182
|
+
CRITICAL: All references MUST be FQDN.
|
|
183
|
+
This helper converts partial references (artifact_code only) to full FQDN.
|
|
184
|
+
|
|
185
|
+
Args:
|
|
186
|
+
ref: Reference string (either "namespace::artifact_code" or "artifact_code")
|
|
187
|
+
namespace: Default namespace if ref is partial
|
|
188
|
+
|
|
189
|
+
Returns:
|
|
190
|
+
FQDN object
|
|
191
|
+
|
|
192
|
+
Examples:
|
|
193
|
+
>>> to_fqdn("pgs_governance::CT_SCAN_V0", "fallback")
|
|
194
|
+
FQDN('pgs_governance::CT_SCAN_V0')
|
|
195
|
+
|
|
196
|
+
>>> to_fqdn("CT_SCAN_V0", "pgs_governance")
|
|
197
|
+
FQDN('pgs_governance::CT_SCAN_V0')
|
|
198
|
+
"""
|
|
199
|
+
if "::" in ref:
|
|
200
|
+
# Already FQDN format
|
|
201
|
+
return parse_fqdn(ref)
|
|
202
|
+
else:
|
|
203
|
+
# Partial reference (artifact_code only)
|
|
204
|
+
return FQDN(namespace=namespace, artifact_code=ref)
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Lightweight invariant enforcement.
|
|
3
|
+
|
|
4
|
+
NOT the PGS ASSERT framework - just simple fail-fast helpers.
|
|
5
|
+
|
|
6
|
+
Design:
|
|
7
|
+
- Simple condition checking
|
|
8
|
+
- Raises CompilerError on violation
|
|
9
|
+
- No protocol semantics
|
|
10
|
+
- No registry hooks
|
|
11
|
+
- Just: if not condition → error
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from .errors import CompilerError
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def require(condition: bool, error: CompilerError) -> None:
|
|
18
|
+
"""
|
|
19
|
+
Require condition to be true, raise error if false.
|
|
20
|
+
|
|
21
|
+
Lightweight assertion for fail-fast enforcement.
|
|
22
|
+
NOT PGS ASSERT framework - no protocol semantics.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
condition: Boolean condition that must be true
|
|
26
|
+
error: Error to raise if condition is false
|
|
27
|
+
|
|
28
|
+
Raises:
|
|
29
|
+
CompilerError: If condition is false
|
|
30
|
+
|
|
31
|
+
Examples:
|
|
32
|
+
>>> require(
|
|
33
|
+
... fqdn_id in artifacts,
|
|
34
|
+
... CompilerError(
|
|
35
|
+
... code=ErrorCode.E201_MISSING_REFERENCE,
|
|
36
|
+
... message=f"Reference {fqdn_id} not found",
|
|
37
|
+
... phase="VALIDATE"
|
|
38
|
+
... )
|
|
39
|
+
... )
|
|
40
|
+
"""
|
|
41
|
+
if not condition:
|
|
42
|
+
raise error
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def require_not_none(value: any, error: CompilerError) -> any:
|
|
46
|
+
"""
|
|
47
|
+
Require value is not None, raise error if None.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
value: Value to check
|
|
51
|
+
error: Error to raise if value is None
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
value (if not None)
|
|
55
|
+
|
|
56
|
+
Raises:
|
|
57
|
+
CompilerError: If value is None
|
|
58
|
+
|
|
59
|
+
Examples:
|
|
60
|
+
>>> config = require_not_none(
|
|
61
|
+
... config_dict.get("artifact_types"),
|
|
62
|
+
... CompilerError(
|
|
63
|
+
... code=ErrorCode.E902_CONFIG_ERROR,
|
|
64
|
+
... message="Missing required config: artifact_types",
|
|
65
|
+
... phase="CONFIG"
|
|
66
|
+
... )
|
|
67
|
+
... )
|
|
68
|
+
"""
|
|
69
|
+
if value is None:
|
|
70
|
+
raise error
|
|
71
|
+
return value
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def require_exists(path: "Path", error: CompilerError) -> "Path":
|
|
75
|
+
"""
|
|
76
|
+
Require path exists, raise error if not.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
path: Path to check
|
|
80
|
+
error: Error to raise if path doesn't exist
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
path (if exists)
|
|
84
|
+
|
|
85
|
+
Raises:
|
|
86
|
+
CompilerError: If path doesn't exist
|
|
87
|
+
|
|
88
|
+
Examples:
|
|
89
|
+
>>> input_dir = require_exists(
|
|
90
|
+
... Path(args.input_dir),
|
|
91
|
+
... CompilerError(
|
|
92
|
+
... code=ErrorCode.E001_NO_ARTIFACTS,
|
|
93
|
+
... message=f"Input directory not found: {args.input_dir}",
|
|
94
|
+
... phase="DISCOVERY"
|
|
95
|
+
... )
|
|
96
|
+
... )
|
|
97
|
+
"""
|
|
98
|
+
from pathlib import Path
|
|
99
|
+
|
|
100
|
+
if not isinstance(path, Path):
|
|
101
|
+
path = Path(path)
|
|
102
|
+
|
|
103
|
+
if not path.exists():
|
|
104
|
+
raise error
|
|
105
|
+
|
|
106
|
+
return path
|