pgc-compiler 2.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- compiler/VERSION +1 -0
- compiler/__init__.py +1 -0
- compiler/atoms/__init__.py +54 -0
- compiler/atoms/error_codes.py +160 -0
- compiler/atoms/errors.py +125 -0
- compiler/atoms/fqdn.py +204 -0
- compiler/atoms/invariants.py +106 -0
- compiler/atoms/phase.py +99 -0
- compiler/atoms/pipeline.py +51 -0
- compiler/atoms/snapshot_gate.py +51 -0
- compiler/atoms/sorting.py +125 -0
- compiler/cli.py +637 -0
- compiler/diagnostics/__init__.py +6 -0
- compiler/diagnostics/determination.py +148 -0
- compiler/diagnostics/machine_block_health.py +199 -0
- compiler/governance/__init__.py +6 -0
- compiler/governance_engine/__init__.py +0 -0
- compiler/governance_engine/artifact_kinds.py +221 -0
- compiler/governance_engine/assertions/__init__.py +0 -0
- compiler/governance_engine/assertions/handlers/__init__.py +200 -0
- compiler/governance_engine/assertions/handlers/assert_ac_declaration_well_formed_v0.py +70 -0
- compiler/governance_engine/assertions/handlers/assert_actor_authority_separation_v0.py +17 -0
- compiler/governance_engine/assertions/handlers/assert_artifact_content_hash_declared_v0.py +60 -0
- compiler/governance_engine/assertions/handlers/assert_assert_not_runtime_referenced_v0.py +49 -0
- compiler/governance_engine/assertions/handlers/assert_assert_parity_v0.py +169 -0
- compiler/governance_engine/assertions/handlers/assert_atom_output_purity_v0.py +186 -0
- compiler/governance_engine/assertions/handlers/assert_authority_constituted_v0.py +56 -0
- compiler/governance_engine/assertions/handlers/assert_authority_required_for_execution_v0.py +16 -0
- compiler/governance_engine/assertions/handlers/assert_authority_state_well_formed_v0.py +17 -0
- compiler/governance_engine/assertions/handlers/assert_binding_integrity_v0.py +64 -0
- compiler/governance_engine/assertions/handlers/assert_binding_surface_closed_v0.py +92 -0
- compiler/governance_engine/assertions/handlers/assert_cc_capability_binding_valid_v0.py +85 -0
- compiler/governance_engine/assertions/handlers/assert_cc_inputs_satisfied_v0.py +89 -0
- compiler/governance_engine/assertions/handlers/assert_cc_no_implicit_chaining_v0.py +89 -0
- compiler/governance_engine/assertions/handlers/assert_cc_no_missing_dependencies_v0.py +89 -0
- compiler/governance_engine/assertions/handlers/assert_cc_no_unused_outputs_v0.py +81 -0
- compiler/governance_engine/assertions/handlers/assert_cc_storage_op_conformance_v0.py +74 -0
- compiler/governance_engine/assertions/handlers/assert_compiler_governance_declared_v0.py +65 -0
- compiler/governance_engine/assertions/handlers/assert_compiler_no_execution_v0.py +86 -0
- compiler/governance_engine/assertions/handlers/assert_concern_not_authority_v0.py +51 -0
- compiler/governance_engine/assertions/handlers/assert_conformance_assertion_mode_valid_v0.py +35 -0
- compiler/governance_engine/assertions/handlers/assert_cryptographic_trust_declared_v0.py +51 -0
- compiler/governance_engine/assertions/handlers/assert_cs_isolated_execution_v0.py +35 -0
- compiler/governance_engine/assertions/handlers/assert_cs_surface_closed_v0.py +241 -0
- compiler/governance_engine/assertions/handlers/assert_cs_traceable_v0.py +35 -0
- compiler/governance_engine/assertions/handlers/assert_ct_output_contract_match_v0.py +167 -0
- compiler/governance_engine/assertions/handlers/assert_ct_surface_closed_v0.py +173 -0
- compiler/governance_engine/assertions/handlers/assert_ct_surface_derived_closed_v0.py +108 -0
- compiler/governance_engine/assertions/handlers/assert_ct_test_data_outcome_declared_v0.py +112 -0
- compiler/governance_engine/assertions/handlers/assert_ev_append_only_v0.py +70 -0
- compiler/governance_engine/assertions/handlers/assert_ev_schema_required_v0.py +59 -0
- compiler/governance_engine/assertions/handlers/assert_execution_placement_declared_v0.py +51 -0
- compiler/governance_engine/assertions/handlers/assert_execution_scheduling_declared_v0.py +51 -0
- compiler/governance_engine/assertions/handlers/assert_fqdn_namespace_authorized_v0.py +38 -0
- compiler/governance_engine/assertions/handlers/assert_fqdn_only_references_v0.py +89 -0
- compiler/governance_engine/assertions/handlers/assert_governance_declaration_resolves_v0.py +126 -0
- compiler/governance_engine/assertions/handlers/assert_handler_registry_closed_v0.py +75 -0
- compiler/governance_engine/assertions/handlers/assert_identity_authority_separation_v0.py +19 -0
- compiler/governance_engine/assertions/handlers/assert_identity_fqdn_consistency.py +75 -0
- compiler/governance_engine/assertions/handlers/assert_implementation_admissible_v0.py +66 -0
- compiler/governance_engine/assertions/handlers/assert_in_no_execution_logic_v0.py +82 -0
- compiler/governance_engine/assertions/handlers/assert_in_schema_required_v0.py +74 -0
- compiler/governance_engine/assertions/handlers/assert_in_workflow_binding_v0.py +101 -0
- compiler/governance_engine/assertions/handlers/assert_inspection_capability_read_only_v0.py +114 -0
- compiler/governance_engine/assertions/handlers/assert_no_ambient_authority_v0.py +17 -0
- compiler/governance_engine/assertions/handlers/assert_no_runtime_authorization_v0.py +18 -0
- compiler/governance_engine/assertions/handlers/assert_no_runtime_topology_synthesis_v0.py +18 -0
- compiler/governance_engine/assertions/handlers/assert_no_smart_execution_v0.py +102 -0
- compiler/governance_engine/assertions/handlers/assert_no_undeclared_behavior_surface_v0.py +52 -0
- compiler/governance_engine/assertions/handlers/assert_no_workflow_authorization_logic_v0.py +18 -0
- compiler/governance_engine/assertions/handlers/assert_protocol_surface_closed_v0.py +119 -0
- compiler/governance_engine/assertions/handlers/assert_rb_binding_policy_conformance_v0.py +124 -0
- compiler/governance_engine/assertions/handlers/assert_rb_cs_only_v0.py +68 -0
- compiler/governance_engine/assertions/handlers/assert_rb_no_logic_v0.py +81 -0
- compiler/governance_engine/assertions/handlers/assert_rb_parameters_declared_v0.py +117 -0
- compiler/governance_engine/assertions/handlers/assert_rb_storage_subdomain_owned_v0.py +118 -0
- compiler/governance_engine/assertions/handlers/assert_runtime_invariant_wired_v0.py +163 -0
- compiler/governance_engine/assertions/handlers/assert_schema_conformance_v0.py +64 -0
- compiler/governance_engine/assertions/handlers/assert_schema_description_well_formed_v0.py +97 -0
- compiler/governance_engine/assertions/handlers/assert_security_domain_declared_v0.py +51 -0
- compiler/governance_engine/assertions/handlers/assert_structure_paths_well_formed_v0.py +118 -0
- compiler/governance_engine/assertions/handlers/assert_superseded_not_referenced_v0.py +134 -0
- compiler/governance_engine/assertions/handlers/assert_test_data_match_ct_output_v0.py +134 -0
- compiler/governance_engine/assertions/handlers/assert_topology_acyclic_v0.py +53 -0
- compiler/governance_engine/assertions/handlers/assert_topology_authority_orthogonal_v0.py +110 -0
- compiler/governance_engine/assertions/handlers/assert_topology_capability_reference_unique_v0.py +63 -0
- compiler/governance_engine/assertions/handlers/assert_topology_contract_closed_v0.py +113 -0
- compiler/governance_engine/assertions/handlers/assert_topology_immutable_after_compilation_v0.py +17 -0
- compiler/governance_engine/assertions/handlers/assert_topology_input_reference_declared_v0.py +112 -0
- compiler/governance_engine/assertions/handlers/assert_topology_routing_complete_v0.py +82 -0
- compiler/governance_engine/assertions/handlers/assert_topology_step_declared_v0.py +77 -0
- compiler/governance_engine/assertions/handlers/assert_topology_step_id_unique_v0.py +60 -0
- compiler/governance_engine/assertions/handlers/assert_topology_surface_canonical_v0.py +180 -0
- compiler/governance_engine/assertions/handlers/assert_topology_transport_orthogonal_v0.py +113 -0
- compiler/governance_engine/assertions/handlers/assert_trace_authority_binding_required_v0.py +18 -0
- compiler/governance_engine/assertions/handlers/assert_transport_canonical_normalization_v0.py +83 -0
- compiler/governance_engine/assertions/handlers/assert_transport_no_dynamic_routing_v0.py +93 -0
- compiler/governance_engine/assertions/handlers/assert_transport_no_workflow_semantics_v0.py +106 -0
- compiler/governance_engine/assertions/handlers/assert_transport_operation_identity_independence_v0.py +56 -0
- compiler/governance_engine/assertions/handlers/assert_transport_response_projection_external_v0.py +38 -0
- compiler/governance_engine/assertions/handlers/assert_transport_result_class_protocol_independence_v0.py +48 -0
- compiler/governance_engine/assertions/handlers/assert_transport_target_exists_v0.py +132 -0
- compiler/governance_engine/assertions/handlers/assert_unique_artifact_id_v0.py +58 -0
- compiler/governance_engine/assertions/handlers/assert_vocabulary_symbols_well_formed_v0.py +163 -0
- compiler/governance_engine/assertions/handlers/assert_wf_announcement_distinct_v0.py +85 -0
- compiler/governance_engine/assertions/handlers/assert_wf_cc_only_nodes_v0.py +60 -0
- compiler/governance_engine/assertions/handlers/assert_wf_entry_intent_v0.py +68 -0
- compiler/governance_engine/assertions/handlers/assert_wf_execution_path_valid_v0.py +89 -0
- compiler/governance_engine/assertions/handlers/assert_wf_node_key_binding_unique_v0.py +107 -0
- compiler/governance_engine/platform_root.py +102 -0
- compiler/governance_engine/structure/__init__.py +56 -0
- compiler/governance_engine/structure/discovery/__init__.py +28 -0
- compiler/governance_engine/structure/discovery/artifact_locator.py +210 -0
- compiler/governance_engine/structure/exceptions.py +113 -0
- compiler/governance_engine/structure/loading/__init__.py +88 -0
- compiler/governance_engine/structure/loading/fs_reader.py +225 -0
- compiler/governance_engine/structure/loading/markdown_parser.py +148 -0
- compiler/governance_engine/structure/loading/protocol_loader.py +587 -0
- compiler/governance_engine/structure/loading/test_case_provider.py +125 -0
- compiler/governance_engine/structure/loading/trace_reader.py +98 -0
- compiler/governance_engine/structure/loading/vocabulary_loader.py +174 -0
- compiler/governance_engine/structure/loading/yaml_parser.py +209 -0
- compiler/governance_engine/structure/resolution/__init__.py +22 -0
- compiler/governance_engine/structure/resolution/domain_resolver.py +124 -0
- compiler/governance_engine/structure/resolution/layer_resolver.py +572 -0
- compiler/governance_engine/structure/resolution/path_registry.py +1054 -0
- compiler/graph/__init__.py +38 -0
- compiler/graph/addressing.py +153 -0
- compiler/graph/edge.py +100 -0
- compiler/graph/evidence.py +637 -0
- compiler/graph/graph.py +269 -0
- compiler/graph/hashing.py +89 -0
- compiler/graph/node.py +134 -0
- compiler/graph/query.py +215 -0
- compiler/graph/state.py +237 -0
- compiler/graph/trace.py +97 -0
- compiler/graph/types.py +71 -0
- compiler/projections/__init__.py +207 -0
- compiler/projections/artifact_index.py +225 -0
- compiler/projections/canonical.py +173 -0
- compiler/projections/dispatch.py +532 -0
- compiler/projections/evidence.py +170 -0
- compiler/projections/handlers.py +224 -0
- compiler/projections/ipm.py +388 -0
- compiler/projections/reverse.py +165 -0
- compiler/projections/tokenized.py +135 -0
- compiler/projections/vocabulary.py +108 -0
- compiler/stages/__init__.py +28 -0
- compiler/stages/s1_extract.py +867 -0
- compiler/stages/s2_canonicalize.py +516 -0
- compiler/stages/s3_semantic_addressing.py +66 -0
- compiler/stages/s4_govern.py +754 -0
- compiler/stages/s5_construct.py +537 -0
- compiler/stages/s6_project.py +94 -0
- compiler/stages/s7_materialize.py +1170 -0
- compiler/stages/s8_verify.py +643 -0
- compiler/stages/s9_attest.py +184 -0
- compiler/structure_loader.py +132 -0
- compiler/visualization/__init__.py +12 -0
- compiler/visualization/consumers/__init__.py +28 -0
- compiler/visualization/consumers/evidence_projection.py +162 -0
- compiler/visualization/consumers/evidence_query.py +296 -0
- compiler/visualization/consumers/evidence_reader.py +127 -0
- compiler/visualization/views/__init__.py +70 -0
- compiler/visualization/views/_png_writer.py +45 -0
- compiler/visualization/views/artifact_lineage.py +180 -0
- compiler/visualization/views/family_view.py +164 -0
- compiler/visualization/views/materialization_view.py +150 -0
- compiler/visualization/views/structural_dag.py +158 -0
- compiler/visualization/wf_graph_generator.py +534 -0
- pgc_compiler-2.0.0.dist-info/METADATA +203 -0
- pgc_compiler-2.0.0.dist-info/RECORD +177 -0
- pgc_compiler-2.0.0.dist-info/WHEEL +5 -0
- pgc_compiler-2.0.0.dist-info/entry_points.txt +2 -0
- pgc_compiler-2.0.0.dist-info/licenses/LICENSE +67 -0
- pgc_compiler-2.0.0.dist-info/licenses/NOTICE +11 -0
- pgc_compiler-2.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
"""Construction determination records — evidence that a determination was made, including refusals.
|
|
2
|
+
|
|
3
|
+
`3e` §3.1: for any determination, its evidence must be sufficient to establish which closure applied,
|
|
4
|
+
which rules that closure supplied, what each predicate yielded, what the dominant consequence was,
|
|
5
|
+
and that the resulting state is what that consequence permitted.
|
|
6
|
+
|
|
7
|
+
Construction previously produced none. A refused build wrote a diagnostic to stderr and exited 1, so
|
|
8
|
+
the determination *this candidate set is inadmissible under this closure* — the exact subject of
|
|
9
|
+
`3e` §3.3 — left no checkable record anywhere. Admissions were evidenced by their output; refusals
|
|
10
|
+
by nothing, which is EV-3 and EN-12 breached in the direction that matters, since a system recording
|
|
11
|
+
what it permitted and not what it refused has a record of its work proceeding rather than of its
|
|
12
|
+
governance operating.
|
|
13
|
+
|
|
14
|
+
**A record is not usable output.** `4a` GC-6 forbids a refused construction producing usable output,
|
|
15
|
+
and `1c` AI-8 states the boundary exactly: after refusal the governed state is as though the proposal
|
|
16
|
+
had not been made, *"except for the evidence that it was refused."* The realization's own technique
|
|
17
|
+
table lists "a failed build writing nothing" as serving AI-8; that technique overshot the property it
|
|
18
|
+
served, and this is the correction.
|
|
19
|
+
|
|
20
|
+
Records are written outside the compiled output tree. Nothing reads them — they are evidence, and
|
|
21
|
+
`3e` §3.2 makes evidence output only.
|
|
22
|
+
"""
|
|
23
|
+
from __future__ import annotations
|
|
24
|
+
|
|
25
|
+
import json
|
|
26
|
+
import os
|
|
27
|
+
import time
|
|
28
|
+
from pathlib import Path
|
|
29
|
+
from typing import Any
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
RECORD_VERSION = "v0"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _root() -> Path:
|
|
36
|
+
"""Where determination records are written — outside the compiled output tree.
|
|
37
|
+
|
|
38
|
+
`PGC_SNAPSHOT_ROOT` is set per build by the runners; the compiled projections live under
|
|
39
|
+
`<root>/compiled`, so records sit beside them rather than within them. A record inside the output
|
|
40
|
+
would be output, and GC-6 is about what a refused construction leaves behind.
|
|
41
|
+
"""
|
|
42
|
+
root = os.environ.get("PGC_SNAPSHOT_ROOT")
|
|
43
|
+
if not root:
|
|
44
|
+
# No fallback. A relative default would write the record wherever the process happened to be
|
|
45
|
+
# standing — which put it inside the assembled snapshot the first time this ran, where it
|
|
46
|
+
# would have become undeclared content at acceptance. An unlocatable evidence root is a
|
|
47
|
+
# refusal to write, reported, not a guess.
|
|
48
|
+
raise RuntimeError(
|
|
49
|
+
"PGC_SNAPSHOT_ROOT is not set — there is no declared root to write the determination "
|
|
50
|
+
"record to, and a relative default would place evidence by accident."
|
|
51
|
+
)
|
|
52
|
+
return Path(root) / "determinations"
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _closure(state: Any) -> dict[str, Any]:
|
|
56
|
+
"""Which closure applied — `3e` §3.1 point 1."""
|
|
57
|
+
meta = dict(getattr(state, "stage_metadata", {}) or {})
|
|
58
|
+
closure = dict(meta.get("governance_closure") or {})
|
|
59
|
+
return {
|
|
60
|
+
# The closure enumerated. `rules_supplied` and `predicate_results` name every governing
|
|
61
|
+
# element that applied and by what invariant identity — that enumeration is what establishes
|
|
62
|
+
# point 1; the hash below is a compact identifier for the same set, present only where a
|
|
63
|
+
# closure was imported. The platform IS the governance surface and imports none, so it
|
|
64
|
+
# carries no hash and its closure is established by the enumeration alone.
|
|
65
|
+
"authorized_namespaces": list(meta.get("authorized_namespaces", []) or []),
|
|
66
|
+
"imported_governance": closure or None,
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _rules_and_predicates(state: Any) -> tuple[list[str], list[dict[str, Any]]]:
|
|
71
|
+
"""Which rules the closure supplied, and what each predicate yielded — points 2 and 3.
|
|
72
|
+
|
|
73
|
+
`assertion_coverage` is what `s4_govern` already records per derived assertion. It is lifted
|
|
74
|
+
rather than recomputed: a second derivation of one fact is two facts.
|
|
75
|
+
"""
|
|
76
|
+
coverage = list((getattr(state, "stage_metadata", {}) or {}).get("assertion_coverage", []) or [])
|
|
77
|
+
supplied = sorted({c.get("assert_code", "") for c in coverage if c.get("assert_code")})
|
|
78
|
+
yielded = [
|
|
79
|
+
{
|
|
80
|
+
"rule": c.get("assert_code"),
|
|
81
|
+
"invariant": c.get("fqdn_id"),
|
|
82
|
+
"result": c.get("status"),
|
|
83
|
+
"violations": c.get("violations", 0),
|
|
84
|
+
"imported": c.get("imported", False),
|
|
85
|
+
}
|
|
86
|
+
for c in coverage
|
|
87
|
+
]
|
|
88
|
+
return supplied, yielded
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def write(state: Any, structure: str, consequence: str, failed_at: str | None = None) -> Path | None:
|
|
92
|
+
"""Write the determination record for one construction. Returns its path, or None if unwritable.
|
|
93
|
+
|
|
94
|
+
`consequence` is ADMITTED or REFUSED. Both are written, and written the same way: EV-3 requires
|
|
95
|
+
refusals evidenced as fully as admissions, and a record shape that differed between them would
|
|
96
|
+
make the two incomparable — which is how a system ends up able to show what it permitted and not
|
|
97
|
+
what it refused.
|
|
98
|
+
"""
|
|
99
|
+
supplied, yielded = _rules_and_predicates(state)
|
|
100
|
+
errors = list(getattr(state, "errors", []) or [])
|
|
101
|
+
materialized = list(getattr(state, "materialized_paths", []) or [])
|
|
102
|
+
|
|
103
|
+
record = {
|
|
104
|
+
"record_version": RECORD_VERSION,
|
|
105
|
+
"record_type": "construction_determination",
|
|
106
|
+
"consequence": consequence,
|
|
107
|
+
# 1 — which closure applied
|
|
108
|
+
"closure": _closure(state),
|
|
109
|
+
# 2 — which rules that closure supplied
|
|
110
|
+
"rules_supplied": supplied,
|
|
111
|
+
# 3 — what each predicate yielded
|
|
112
|
+
"predicate_results": yielded,
|
|
113
|
+
# what was proposed
|
|
114
|
+
"proposed": {
|
|
115
|
+
"structure": structure,
|
|
116
|
+
"candidates": len(getattr(state, "graph", None).nodes) if getattr(state, "graph", None) else None,
|
|
117
|
+
},
|
|
118
|
+
# 4 — the dominant consequence, and its grounds
|
|
119
|
+
"grounds": [
|
|
120
|
+
{
|
|
121
|
+
"code": getattr(e, "code", None).value if hasattr(getattr(e, "code", None), "value") else str(getattr(e, "code", "")),
|
|
122
|
+
"phase": getattr(e, "phase", None),
|
|
123
|
+
"fqdn_id": getattr(e, "fqdn_id", None),
|
|
124
|
+
"message": getattr(e, "message", ""),
|
|
125
|
+
}
|
|
126
|
+
for e in errors
|
|
127
|
+
],
|
|
128
|
+
# 5 — that the resulting state is what the consequence permitted
|
|
129
|
+
"resulting_state": {
|
|
130
|
+
"failed_at": failed_at,
|
|
131
|
+
"materialized_count": len(materialized),
|
|
132
|
+
"nothing_proceeded": consequence == "REFUSED" and not materialized,
|
|
133
|
+
},
|
|
134
|
+
# Observational content, declared as such (`3e` §5): it may differ between two determinations
|
|
135
|
+
# over the same inputs and no determination may depend on it.
|
|
136
|
+
"observational": {"determined_at_ns": time.time_ns()},
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
try:
|
|
140
|
+
root = _root()
|
|
141
|
+
root.mkdir(parents=True, exist_ok=True)
|
|
142
|
+
path = root / f"{structure}.{consequence.lower()}.json"
|
|
143
|
+
path.write_text(json.dumps(record, indent=2, sort_keys=False) + "\n", encoding="utf-8")
|
|
144
|
+
return path
|
|
145
|
+
except (OSError, RuntimeError):
|
|
146
|
+
# Evidence that cannot be written is a finding, not a reason to fail the determination it
|
|
147
|
+
# records — the determination already happened. Surfaced by the caller, never swallowed.
|
|
148
|
+
return None
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
"""Machine-block health — does the compiler consume the machine-block keys artifacts declare?
|
|
2
|
+
|
|
3
|
+
A heuristic, post-successful-build diagnostic. For each compiled artifact whose kind is NOT
|
|
4
|
+
already closed by schema conformance, it flattens the machine block to leaf keys and checks
|
|
5
|
+
whether each leaf name appears as a string literal in the compiler source. A leaf with no
|
|
6
|
+
detected consumer is a *candidate* — not proof — of an unconsumed declaration.
|
|
7
|
+
|
|
8
|
+
Scope rule (principled, not an arbitrary list): the diagnostic applies only to artifact kinds
|
|
9
|
+
whose machine-block consumption is NOT already closed by `additionalProperties: false` schema
|
|
10
|
+
conformance. For schema-closed kinds an unconsumed key cannot silently survive, so a census
|
|
11
|
+
adds only noise.
|
|
12
|
+
|
|
13
|
+
Disposition (Machine Block §11): a top-level field with no detected compiler consumer is split
|
|
14
|
+
by its kind's disposition. On a DECLARATIVE kind (STRUCTURE, VOCAB, … — inert definitions the
|
|
15
|
+
topology references), such a field is *preserved* declared policy carried into the snapshot for
|
|
16
|
+
downstream/runtime reference; that is a legitimate disposition, not a defect.
|
|
17
|
+
|
|
18
|
+
A field on an EXECUTABLE kind may also be preserved, but only by NAMING its consumer in
|
|
19
|
+
`_DOWNSTREAM_CONSUMERS`. Boundary contracts (TI/TE) are the standing case: the compiler seals
|
|
20
|
+
them and the transport engine or the inspector interprets them, and the compiler cannot import
|
|
21
|
+
either — so the scan structurally cannot see those consumers. Everything else on an EXECUTABLE
|
|
22
|
+
kind is a genuine *candidate unconsumed*.
|
|
23
|
+
|
|
24
|
+
Severity: WARNING only, and only for genuine candidates. It never fails the build, changes
|
|
25
|
+
runtime behavior, or asserts a conformance rule. The authoritative rule is the compiler's actual
|
|
26
|
+
behavior (and, where it applies, schema closure). A candidate means exactly: "this machine-block
|
|
27
|
+
key has no detected compiler consumer" — the source-inspection method cannot prove a key unused.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
from __future__ import annotations
|
|
31
|
+
|
|
32
|
+
import re
|
|
33
|
+
from collections import defaultdict
|
|
34
|
+
from collections.abc import Mapping, Sequence
|
|
35
|
+
from dataclasses import dataclass, field
|
|
36
|
+
from pathlib import Path
|
|
37
|
+
from typing import Any, Iterable
|
|
38
|
+
|
|
39
|
+
from compiler.governance_engine.artifact_kinds import REGISTRY, DECLARATIVE
|
|
40
|
+
|
|
41
|
+
_LITERAL = re.compile(r"""['"]([A-Za-z_][A-Za-z0-9_]*)['"]""")
|
|
42
|
+
|
|
43
|
+
# Keys on an EXECUTABLE kind whose consumer is, BY ARCHITECTURE, outside the compiler.
|
|
44
|
+
#
|
|
45
|
+
# A boundary contract is a declaration the compiler seals and another component interprets. The
|
|
46
|
+
# compiler cannot import that component — purity forbids it — so the source scan structurally
|
|
47
|
+
# cannot see the consumer, and the key reads as unconsumed however live it is. Declaring it here
|
|
48
|
+
# keeps the census honest: a false candidate teaches a reader to ignore the warning, and a
|
|
49
|
+
# genuinely dead key then hides among the ones they have learned to skip.
|
|
50
|
+
#
|
|
51
|
+
# Each entry NAMES its consumer, so the claim is checkable rather than a blanket exemption. A key
|
|
52
|
+
# that no component reads must not be listed — that is the finding the census exists to surface.
|
|
53
|
+
_DOWNSTREAM_CONSUMERS: dict[tuple[str, str], str] = {
|
|
54
|
+
("TE", "evidence_policy"): "consumed by the transport resolver (resolver/resolver.py)",
|
|
55
|
+
("TI", "catalog"): "consumed by snapshot_inspector (inspector/catalog.py)",
|
|
56
|
+
("WF", "subdomain"): "consumed by snapshot_assembler (assembler/indexes.py)",
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
_DECLARATIVE_POLICY = "declarative policy, no compiler consumer"
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@dataclass
|
|
63
|
+
class KindReport:
|
|
64
|
+
consumed: set[str] = field(default_factory=set)
|
|
65
|
+
unconsumed: set[str] = field(default_factory=set)
|
|
66
|
+
preserved: dict[str, str] = field(default_factory=dict) # key → why it is not a candidate
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@dataclass
|
|
70
|
+
class HealthReport:
|
|
71
|
+
by_kind: dict[str, KindReport] = field(default_factory=dict)
|
|
72
|
+
|
|
73
|
+
@property
|
|
74
|
+
def examined(self) -> int:
|
|
75
|
+
return sum(len(k.consumed) + len(k.unconsumed) + len(k.preserved)
|
|
76
|
+
for k in self.by_kind.values())
|
|
77
|
+
|
|
78
|
+
@property
|
|
79
|
+
def candidate_unconsumed(self) -> int:
|
|
80
|
+
return sum(len(k.unconsumed) for k in self.by_kind.values())
|
|
81
|
+
|
|
82
|
+
@property
|
|
83
|
+
def preserved_total(self) -> int:
|
|
84
|
+
return sum(len(k.preserved) for k in self.by_kind.values())
|
|
85
|
+
|
|
86
|
+
@property
|
|
87
|
+
def has_candidates(self) -> bool:
|
|
88
|
+
return self.candidate_unconsumed > 0
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _source_literals(compiler_src: Path) -> set[str]:
|
|
92
|
+
"""Every quoted identifier appearing anywhere in the compiler source (its consumption surface).
|
|
93
|
+
|
|
94
|
+
This module is excluded from its own scan. Its literals name keys in order to CLASSIFY them,
|
|
95
|
+
never to consume them — counting those would let the census answer its own question, reporting
|
|
96
|
+
a key as compiler-consumed on the strength of the table that says it is consumed elsewhere.
|
|
97
|
+
"""
|
|
98
|
+
out: set[str] = set()
|
|
99
|
+
for py in compiler_src.rglob("*.py"):
|
|
100
|
+
if "__pycache__" in py.parts:
|
|
101
|
+
continue
|
|
102
|
+
if py.resolve() == Path(__file__).resolve():
|
|
103
|
+
continue
|
|
104
|
+
try:
|
|
105
|
+
out.update(_LITERAL.findall(py.read_text(encoding="utf-8", errors="ignore")))
|
|
106
|
+
except OSError:
|
|
107
|
+
continue
|
|
108
|
+
return out
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def _declared_fields(fm: Any) -> Iterable[str]:
|
|
112
|
+
"""The top-level declared fields of a machine block.
|
|
113
|
+
|
|
114
|
+
Deliberately top-level only: the health question is whether the compiler consumes the
|
|
115
|
+
*fields an artifact declares*. A field's internal shape (nested data — layer names, paths,
|
|
116
|
+
enum values) is that field's own business, read structurally rather than by literal key
|
|
117
|
+
name, and flattening into it produces heuristic noise, not signal. (frontmatter is a
|
|
118
|
+
MappingProxyType, so accept any Mapping.)
|
|
119
|
+
"""
|
|
120
|
+
if isinstance(fm, Mapping):
|
|
121
|
+
for k in fm.keys():
|
|
122
|
+
yield str(k)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def check(nodes: Iterable[Any], schema_closed_prefixes: set[str], compiler_src: Path) -> HealthReport:
|
|
126
|
+
"""Build the health report over compiled graph nodes."""
|
|
127
|
+
literals = _source_literals(compiler_src)
|
|
128
|
+
# Fail safe: a real compiler source has thousands of literals. If the scan finds far fewer,
|
|
129
|
+
# source is not introspectable (e.g. installed without .py) and every field would falsely
|
|
130
|
+
# read as unconsumed — so report nothing rather than a spurious alarm.
|
|
131
|
+
if len(literals) < 200:
|
|
132
|
+
return HealthReport(by_kind={})
|
|
133
|
+
by_kind: dict[str, KindReport] = defaultdict(KindReport)
|
|
134
|
+
for node in nodes:
|
|
135
|
+
code = getattr(node, "artifact_code", "") or ""
|
|
136
|
+
prefix = code.split("_", 1)[0]
|
|
137
|
+
if not prefix or prefix in schema_closed_prefixes:
|
|
138
|
+
continue # schema-closed kinds are already guaranteed; skip
|
|
139
|
+
# imported artifacts were authored elsewhere — not this build's declaration surface
|
|
140
|
+
if (getattr(node, "metadata", None) or {}).get("imported"):
|
|
141
|
+
continue
|
|
142
|
+
descriptor = REGISTRY.descriptor(prefix)
|
|
143
|
+
is_declarative = descriptor is not None and descriptor.disposition == DECLARATIVE
|
|
144
|
+
fm = getattr(node, "frontmatter", None) or {}
|
|
145
|
+
for leaf in set(_declared_fields(fm)):
|
|
146
|
+
# A DECLARED disposition outranks the heuristic scan: if the key is known to be
|
|
147
|
+
# consumed outside the compiler, an incidental literal match inside it proves nothing.
|
|
148
|
+
if (prefix, leaf) in _DOWNSTREAM_CONSUMERS:
|
|
149
|
+
by_kind[prefix].preserved[leaf] = _DOWNSTREAM_CONSUMERS[(prefix, leaf)]
|
|
150
|
+
elif leaf in literals:
|
|
151
|
+
by_kind[prefix].consumed.add(leaf)
|
|
152
|
+
elif is_declarative:
|
|
153
|
+
# Machine Block §11: declared policy/reference data on a declarative kind, carried
|
|
154
|
+
# into the snapshot but not compiler-consumed — a legitimate *preserved* disposition.
|
|
155
|
+
by_kind[prefix].preserved[leaf] = _DECLARATIVE_POLICY
|
|
156
|
+
else:
|
|
157
|
+
by_kind[prefix].unconsumed.add(leaf)
|
|
158
|
+
return HealthReport(by_kind=dict(by_kind))
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def format_report(report: HealthReport, verbose: bool) -> list[str]:
|
|
162
|
+
"""Render the diagnostic. Empty list = print nothing (clean, non-verbose)."""
|
|
163
|
+
if not report.by_kind:
|
|
164
|
+
return []
|
|
165
|
+
if not verbose:
|
|
166
|
+
if not report.has_candidates:
|
|
167
|
+
return []
|
|
168
|
+
n = report.candidate_unconsumed
|
|
169
|
+
return [f"⚠ Machine-block health: {n} candidate unconsumed key(s). "
|
|
170
|
+
f"Run with --verbose for details."]
|
|
171
|
+
|
|
172
|
+
lines = ["", "Machine-block health", "─" * 20]
|
|
173
|
+
consumed_total = 0
|
|
174
|
+
for kind in sorted(report.by_kind):
|
|
175
|
+
r = report.by_kind[kind]
|
|
176
|
+
consumed_total += len(r.consumed)
|
|
177
|
+
lines.append(f"Kind: {kind}")
|
|
178
|
+
for k in sorted(r.consumed):
|
|
179
|
+
lines.append(f" ✓ {k}")
|
|
180
|
+
for k in sorted(r.preserved):
|
|
181
|
+
lines.append(f" ○ {k} (preserved — {r.preserved[k]})")
|
|
182
|
+
for k in sorted(r.unconsumed):
|
|
183
|
+
lines.append(f" ⚠ {k} (no detected compiler consumer)")
|
|
184
|
+
lines += [
|
|
185
|
+
"",
|
|
186
|
+
"Summary:",
|
|
187
|
+
f" machine-block keys examined: {report.examined}",
|
|
188
|
+
f" consumed: {consumed_total}",
|
|
189
|
+
f" preserved: {report.preserved_total}",
|
|
190
|
+
f" candidate unconsumed: {report.candidate_unconsumed}",
|
|
191
|
+
"",
|
|
192
|
+
"NOTE: census results are heuristic diagnostics. They do not affect compilation",
|
|
193
|
+
" success, runtime behavior, or conformance. 'Preserved' fields either carry",
|
|
194
|
+
" declared policy on a declarative kind (Machine Block §11) or name a consumer",
|
|
195
|
+
" outside the compiler, which the source scan cannot see. A 'candidate' is a key",
|
|
196
|
+
" on an executable kind with no consumer detected or declared anywhere — the",
|
|
197
|
+
" strongest signal this census can give, though still not proof of disuse.",
|
|
198
|
+
]
|
|
199
|
+
return lines
|
|
File without changes
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
"""ArtifactKindRegistry — the single source of truth for artifact-kind metadata.
|
|
2
|
+
|
|
3
|
+
The compiler owns *how to process* artifact kinds; it must not scatter *what kinds exist* across a
|
|
4
|
+
handful of disconnected dictionaries. This module replaces five maps that had drifted apart
|
|
5
|
+
(`_TYPE_TO_KIND`, `_GOVERNANCE_PREFIXES`, the directory `type_map`, `INDEX_SECTION_BY_KIND`, and the
|
|
6
|
+
per-build `artifact_types` whitelist) with one `ArtifactKindDescriptor` per type-prefix.
|
|
7
|
+
|
|
8
|
+
Every consumer asks the registry a question instead of indexing a raw map:
|
|
9
|
+
|
|
10
|
+
registry.descriptor("ENTITY").materialization_directory # "entities"
|
|
11
|
+
registry.node_kind("ENTITY") # "GOVERNANCE"
|
|
12
|
+
registry.canonical_type("GOVERNANCE", "ENTITY_BLOCK_V0") # "ENTITY"
|
|
13
|
+
|
|
14
|
+
Disposition separates the two axes `NodeKind` was conflating:
|
|
15
|
+
* EXECUTABLE — participates in the execution topology (WF/CC/CT/CS/IN/RB/EV/AC/TI/TE)
|
|
16
|
+
* DECLARATIVE — inert definition the topology references (ENTITY/STRUCTURE/SCHEMA/CONSTITUTION/…)
|
|
17
|
+
|
|
18
|
+
`node_kind` is a *string* (the `NodeKind` value), so this governance-layer module never imports the
|
|
19
|
+
compiler's enum (dependency layering: compiler ← governance). The compiler maps the string to its
|
|
20
|
+
`NodeKind` at its own boundary.
|
|
21
|
+
|
|
22
|
+
This is `BuiltInArtifactRegistry` — the substrate bootstrap, exactly as a C compiler ships built-in
|
|
23
|
+
lexical grammar. Step 4 promotes it to a governed protocol artifact: a `CompiledArtifactRegistry`
|
|
24
|
+
loads the same `ArtifactKindDescriptor`s from the protocol surface, and *nothing downstream changes*
|
|
25
|
+
— consumers still ask the registry the same questions. The descriptor fields below ARE that registry's
|
|
26
|
+
schema, revealed by the first entity integration.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
from __future__ import annotations
|
|
30
|
+
|
|
31
|
+
from dataclasses import dataclass
|
|
32
|
+
|
|
33
|
+
EXECUTABLE = "EXECUTABLE"
|
|
34
|
+
DECLARATIVE = "DECLARATIVE"
|
|
35
|
+
|
|
36
|
+
# Governance Ontology (spec fragment 03) — both axes are CLOSED per ontology version.
|
|
37
|
+
# §4 primary Semantic Category: exactly one per kind.
|
|
38
|
+
DEFINITIONAL, NORMATIVE, CONTRACTUAL = "Definitional", "Normative", "Contractual"
|
|
39
|
+
OPERATIONAL, PARTICIPATORY, EVIDENTIAL = "Operational", "Participatory", "Evidential"
|
|
40
|
+
SEMANTIC_CATEGORIES = (DEFINITIONAL, NORMATIVE, CONTRACTUAL, OPERATIONAL, PARTICIPATORY, EVIDENTIAL)
|
|
41
|
+
# §6 provenance: how the element came to exist. Semantic, not physical.
|
|
42
|
+
AUTHORED, DERIVED, PRODUCED = "authored", "derived", "produced"
|
|
43
|
+
PROVENANCES = (AUTHORED, DERIVED, PRODUCED)
|
|
44
|
+
# §7 runtime disposition is a CATEGORY CONTRACT property, never stored per kind — it is derived
|
|
45
|
+
# from the category so the two can never disagree. Distinct from the `disposition` field below,
|
|
46
|
+
# which answers a different question (topology participation).
|
|
47
|
+
_RUNTIME_DISPOSITION = {
|
|
48
|
+
DEFINITIONAL: "not executable",
|
|
49
|
+
NORMATIVE: "not executable",
|
|
50
|
+
CONTRACTUAL: "not independently executable",
|
|
51
|
+
OPERATIONAL: "executable",
|
|
52
|
+
PARTICIPATORY: "participates in execution",
|
|
53
|
+
EVIDENTIAL: "produced by execution",
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@dataclass(frozen=True)
|
|
58
|
+
class ArtifactKindDescriptor:
|
|
59
|
+
"""Everything the pipeline needs to know about one artifact kind. One object, not five maps."""
|
|
60
|
+
prefix: str # the artifact-code type token, e.g. "ENTITY", "CC", "STRUCTURE"
|
|
61
|
+
node_kind: str # NodeKind value (string): the topology classification
|
|
62
|
+
disposition: str # EXECUTABLE | DECLARATIVE — TOPOLOGY PARTICIPATION only.
|
|
63
|
+
# Not ontology §7 runtime disposition (see runtime_disposition).
|
|
64
|
+
materialization_directory: str | None = None # output dir under artifacts/, or None if not materialized here
|
|
65
|
+
index_section: str | None = None # kind_index section for this kind, or None if not indexed
|
|
66
|
+
canonical_keeps_prefix: bool = True # in canonical projection: keep this prefix vs collapse to node_kind
|
|
67
|
+
artifact_kind: str | None = None # canonical in-block discriminator (Kind Vocabulary); None = not yet authorized
|
|
68
|
+
semantic_category: str | None = None # Governance Ontology §4 primary category; None = kind not authorized
|
|
69
|
+
provenance: str | None = None # Governance Ontology §6 provenance; None = kind not authorized
|
|
70
|
+
|
|
71
|
+
@property
|
|
72
|
+
def inspectable_source(self) -> bool:
|
|
73
|
+
return self.index_section is not None
|
|
74
|
+
|
|
75
|
+
@property
|
|
76
|
+
def runtime_disposition(self) -> str | None:
|
|
77
|
+
"""Ontology §7 runtime disposition — derived from the category, never stored.
|
|
78
|
+
|
|
79
|
+
Distinct from `disposition`: a CAPABILITY_CONTRACT participates in the execution
|
|
80
|
+
topology (disposition == EXECUTABLE) yet is "not independently executable" (§7).
|
|
81
|
+
"""
|
|
82
|
+
return _RUNTIME_DISPOSITION.get(self.semantic_category)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _d(prefix, node_kind, disposition, directory=None, index_section=None, keeps_prefix=True, ak=None,
|
|
86
|
+
category=None, provenance=None):
|
|
87
|
+
return ArtifactKindDescriptor(prefix, node_kind, disposition, directory, index_section, keeps_prefix, ak,
|
|
88
|
+
category, provenance)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
# The complete built-in table. Values are the EXACT union of the five legacy maps, so the registry is
|
|
92
|
+
# behavior-identical. EXECUTABLE kinds keep their prefix in canonical (node_kind == prefix); DECLARATIVE
|
|
93
|
+
# kinds that ride NodeKind.GOVERNANCE keep their prefix only when listed (the old _GOVERNANCE_PREFIXES),
|
|
94
|
+
# else they collapse to "GOVERNANCE".
|
|
95
|
+
_DESCRIPTORS: tuple[ArtifactKindDescriptor, ...] = (
|
|
96
|
+
# EXECUTABLE — execution topology
|
|
97
|
+
_d("WF", "WF", EXECUTABLE, "workflows", "workflows", ak="WORKFLOW",
|
|
98
|
+
category=OPERATIONAL, provenance=AUTHORED),
|
|
99
|
+
_d("CC", "CC", EXECUTABLE, "capability_contracts", "capability_contracts", ak="CAPABILITY_CONTRACT",
|
|
100
|
+
category=CONTRACTUAL, provenance=AUTHORED),
|
|
101
|
+
_d("CT", "CT", EXECUTABLE, "capability_transforms", "capability_transforms", ak="CAPABILITY_TRANSFORM",
|
|
102
|
+
category=OPERATIONAL, provenance=AUTHORED),
|
|
103
|
+
_d("CS", "CS", EXECUTABLE, "capability_side_effects", "capability_side_effects", ak="CAPABILITY_SIDE_EFFECT",
|
|
104
|
+
category=OPERATIONAL, provenance=AUTHORED),
|
|
105
|
+
_d("IN", "IN", EXECUTABLE, "intents", "intents", ak="INTENT",
|
|
106
|
+
category=OPERATIONAL, provenance=AUTHORED),
|
|
107
|
+
_d("TI", "TI", EXECUTABLE, "ingress_intents", ak="TRANSPORT_INGRESS",
|
|
108
|
+
category=CONTRACTUAL, provenance=AUTHORED),
|
|
109
|
+
_d("TE", "TE", EXECUTABLE, "transport/egress", ak="TRANSPORT_EGRESS",
|
|
110
|
+
category=CONTRACTUAL, provenance=AUTHORED),
|
|
111
|
+
_d("RB", "RB", EXECUTABLE, "runtime_bindings", "runtime_bindings", ak="RUNTIME_BINDING",
|
|
112
|
+
category=OPERATIONAL, provenance=AUTHORED),
|
|
113
|
+
# EVENT: the *kind* is an authored declaration of an event type (schema + trigger). The
|
|
114
|
+
# produced elements — event occurrences and trace records — belong to the runtime evidence
|
|
115
|
+
# model, not the artifact-kind ontology, so no kind carries provenance `produced`.
|
|
116
|
+
_d("EV", "EV", EXECUTABLE, "events", ak="EVENT",
|
|
117
|
+
category=EVIDENTIAL, provenance=AUTHORED),
|
|
118
|
+
_d("AC", "AC", EXECUTABLE, "actors", ak="ACTOR",
|
|
119
|
+
category=PARTICIPATORY, provenance=AUTHORED),
|
|
120
|
+
# DECLARATIVE — own NodeKind
|
|
121
|
+
_d("ASSERT", "ASSERT", DECLARATIVE, "assertions", ak="ASSERT",
|
|
122
|
+
category=NORMATIVE, provenance=DERIVED),
|
|
123
|
+
_d("TEST_DATA", "TEST_DATA", DECLARATIVE),
|
|
124
|
+
# DECLARATIVE — ride NodeKind.GOVERNANCE, keep their prefix (old _GOVERNANCE_PREFIXES)
|
|
125
|
+
_d("INVARIANT", "GOVERNANCE", DECLARATIVE, "invariants", ak="INVARIANT",
|
|
126
|
+
category=NORMATIVE, provenance=AUTHORED),
|
|
127
|
+
_d("VOCAB", "GOVERNANCE", DECLARATIVE, "vocabulary", ak="VOCABULARY",
|
|
128
|
+
category=DEFINITIONAL, provenance=AUTHORED),
|
|
129
|
+
_d("CONSTITUTION", "GOVERNANCE", DECLARATIVE, "concerns", ak="CONSTITUTION",
|
|
130
|
+
category=NORMATIVE, provenance=AUTHORED),
|
|
131
|
+
_d("SCHEMA", "GOVERNANCE", DECLARATIVE, "schemas"),
|
|
132
|
+
_d("STRUCTURE", "GOVERNANCE", DECLARATIVE, "structures", ak="STRUCTURE",
|
|
133
|
+
category=DEFINITIONAL, provenance=AUTHORED),
|
|
134
|
+
_d("SURFACE", "GOVERNANCE", DECLARATIVE, "surface_contracts", ak="SURFACE_CONTRACT",
|
|
135
|
+
category=CONTRACTUAL, provenance=AUTHORED),
|
|
136
|
+
_d("ENTITY", "GOVERNANCE", DECLARATIVE, "entities", "entities", ak="ENTITY"),
|
|
137
|
+
# DECLARATIVE — ride NodeKind.GOVERNANCE, collapse to "GOVERNANCE" in canonical (not in old prefixes)
|
|
138
|
+
_d("LAYER", "GOVERNANCE", DECLARATIVE, keeps_prefix=False),
|
|
139
|
+
_d("GOVERNANCE", "GOVERNANCE", DECLARATIVE, keeps_prefix=False),
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
class BuiltInArtifactRegistry:
|
|
144
|
+
"""The substrate bootstrap registry. Step 4 swaps in a CompiledArtifactRegistry with the same API."""
|
|
145
|
+
|
|
146
|
+
def __init__(self, descriptors: tuple[ArtifactKindDescriptor, ...] = _DESCRIPTORS):
|
|
147
|
+
self._by_prefix = {d.prefix: d for d in descriptors}
|
|
148
|
+
# Authoritative index: canonical artifact_kind -> descriptor (Machine Block §6).
|
|
149
|
+
self._by_kind = {d.artifact_kind: d for d in descriptors if d.artifact_kind}
|
|
150
|
+
|
|
151
|
+
def descriptor(self, prefix: str) -> ArtifactKindDescriptor | None:
|
|
152
|
+
return self._by_prefix.get(prefix)
|
|
153
|
+
|
|
154
|
+
def descriptor_for_kind(self, artifact_kind: str) -> ArtifactKindDescriptor | None:
|
|
155
|
+
"""Resolve a descriptor by its canonical `artifact_kind` — the authoritative discriminator."""
|
|
156
|
+
return self._by_kind.get(artifact_kind)
|
|
157
|
+
|
|
158
|
+
def node_kind_for_kind(self, artifact_kind: str) -> str | None:
|
|
159
|
+
"""NodeKind (string) for a canonical `artifact_kind`; None if the kind is unknown."""
|
|
160
|
+
d = self._by_kind.get(artifact_kind)
|
|
161
|
+
return d.node_kind if d else None
|
|
162
|
+
|
|
163
|
+
def known(self, prefix: str) -> bool:
|
|
164
|
+
return prefix in self._by_prefix
|
|
165
|
+
|
|
166
|
+
def node_kind(self, prefix: str) -> str | None:
|
|
167
|
+
d = self._by_prefix.get(prefix)
|
|
168
|
+
return d.node_kind if d else None
|
|
169
|
+
|
|
170
|
+
def directory(self, prefix: str) -> str:
|
|
171
|
+
"""Output directory for a type prefix. Raises ValueError (legacy contract) if unmapped."""
|
|
172
|
+
d = self._by_prefix.get(prefix)
|
|
173
|
+
if d is None or d.materialization_directory is None:
|
|
174
|
+
valid = sorted(p for p, x in self._by_prefix.items() if x.materialization_directory)
|
|
175
|
+
raise ValueError(f"Unknown artifact type prefix: {prefix}\nValid prefixes: {', '.join(valid)}")
|
|
176
|
+
return d.materialization_directory
|
|
177
|
+
|
|
178
|
+
def semantic_category(self, artifact_kind: str) -> str | None:
|
|
179
|
+
"""Governance Ontology §4 primary category for a canonical `artifact_kind`."""
|
|
180
|
+
d = self._by_kind.get(artifact_kind)
|
|
181
|
+
return d.semantic_category if d else None
|
|
182
|
+
|
|
183
|
+
def provenance(self, artifact_kind: str) -> str | None:
|
|
184
|
+
"""Governance Ontology §6 provenance for a canonical `artifact_kind`."""
|
|
185
|
+
d = self._by_kind.get(artifact_kind)
|
|
186
|
+
return d.provenance if d else None
|
|
187
|
+
|
|
188
|
+
def runtime_disposition(self, artifact_kind: str) -> str | None:
|
|
189
|
+
"""Ontology §7 runtime disposition for a canonical `artifact_kind`."""
|
|
190
|
+
d = self._by_kind.get(artifact_kind)
|
|
191
|
+
return d.runtime_disposition if d else None
|
|
192
|
+
|
|
193
|
+
def ontology_coverage(self) -> dict[str, tuple[str | None, str | None]]:
|
|
194
|
+
"""Every authorized kind → (category, provenance). Ontology §11 conformance input."""
|
|
195
|
+
return {k: (d.semantic_category, d.provenance) for k, d in sorted(self._by_kind.items())}
|
|
196
|
+
|
|
197
|
+
def index_section(self, prefix: str) -> str | None:
|
|
198
|
+
d = self._by_prefix.get(prefix)
|
|
199
|
+
return d.index_section if d else None
|
|
200
|
+
|
|
201
|
+
def index_sections(self) -> dict[str, str]:
|
|
202
|
+
"""Prefix → kind_index section, for every indexed kind."""
|
|
203
|
+
return {p: d.index_section for p, d in self._by_prefix.items() if d.index_section}
|
|
204
|
+
|
|
205
|
+
def canonical_type(self, node_kind: str, artifact_code: str) -> str:
|
|
206
|
+
"""The artifact_type written into the canonical projection (replaces _resolve_artifact_type).
|
|
207
|
+
|
|
208
|
+
Non-GOVERNANCE node kinds: the node_kind IS the prefix. GOVERNANCE node kinds: keep the code's
|
|
209
|
+
prefix when the descriptor says so (old _GOVERNANCE_PREFIXES), else collapse to "GOVERNANCE".
|
|
210
|
+
"""
|
|
211
|
+
if node_kind != "GOVERNANCE":
|
|
212
|
+
return node_kind
|
|
213
|
+
for d in self._by_prefix.values():
|
|
214
|
+
if d.node_kind == "GOVERNANCE" and d.canonical_keeps_prefix \
|
|
215
|
+
and artifact_code.startswith(d.prefix + "_"):
|
|
216
|
+
return d.prefix
|
|
217
|
+
return node_kind
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
# The process-wide built-in registry. Import this; never re-create raw maps.
|
|
221
|
+
REGISTRY = BuiltInArtifactRegistry()
|
|
File without changes
|