igniter_lang 0.1.0.alpha.1

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.
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "diagnostics"
4
+
5
+ module IgniterLang
6
+ module CompilerResult
7
+ module_function
8
+
9
+ def ok(format_version:, semantic_ir:, source_path:, report:, igapp_path:, contracts:, runtime_smoke:)
10
+ {
11
+ "kind" => "compiler_result",
12
+ "format_version" => format_version,
13
+ "status" => "ok",
14
+ "program_id" => semantic_ir.fetch("program_id"),
15
+ "source_path" => source_path.to_s,
16
+ "source_hash" => report.fetch("source_hash"),
17
+ "grammar_version" => report.fetch("grammar_version"),
18
+ "stages" => stages(report, assemble: "ok"),
19
+ "igapp_path" => igapp_path.to_s,
20
+ "compilation_report_ref" => report.fetch("program_id"),
21
+ "semantic_ir_ref" => report.fetch("semantic_ir_ref"),
22
+ "contracts" => contracts,
23
+ "diagnostics" => [],
24
+ "warnings" => Diagnostics.warnings(report.fetch("diagnostics", [])),
25
+ "runtime_smoke" => runtime_smoke,
26
+ "report" => report
27
+ }
28
+ end
29
+
30
+ def refusal(format_version:, status:, report:, source_path:, report_path:)
31
+ diagnostics = report.fetch("diagnostics", [])
32
+ {
33
+ "kind" => "compiler_result",
34
+ "format_version" => format_version,
35
+ "status" => status,
36
+ "program_id" => report.fetch("semantic_ir_ref", nil),
37
+ "source_path" => source_path.to_s,
38
+ "source_hash" => report.fetch("source_hash", nil),
39
+ "grammar_version" => report.fetch("grammar_version", nil),
40
+ "stages" => stages(report, assemble: "skipped"),
41
+ "igapp_path" => nil,
42
+ "contracts" => [],
43
+ "compilation_report_path" => report_path.to_s,
44
+ "diagnostics" => Diagnostics.errors(diagnostics),
45
+ "warnings" => Diagnostics.warnings(diagnostics),
46
+ "report" => report
47
+ }
48
+ end
49
+
50
+ def strict_terminal(format_version:, status:, report:, source_path:, diagnostics:)
51
+ {
52
+ "kind" => "compiler_result",
53
+ "format_version" => format_version,
54
+ "status" => status,
55
+ "program_id" => report.fetch("semantic_ir_ref", nil),
56
+ "source_path" => source_path.to_s,
57
+ "source_hash" => report.fetch("source_hash", nil),
58
+ "grammar_version" => report.fetch("grammar_version", nil),
59
+ "stages" => stages(report, assemble: "skipped"),
60
+ "igapp_path" => nil,
61
+ "contracts" => [],
62
+ "compilation_report_path" => nil,
63
+ "diagnostics" => diagnostics,
64
+ "warnings" => Diagnostics.warnings(report.fetch("diagnostics", [])),
65
+ "report" => report
66
+ }
67
+ end
68
+
69
+ def public_result(result)
70
+ result.reject { |key, _value| key == "report" }
71
+ end
72
+
73
+ def stages(report, assemble:)
74
+ report.fetch("stages").merge("assemble" => assemble)
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,125 @@
1
+ # frozen_string_literal: true
2
+
3
+ module IgniterLang
4
+ module Diagnostics
5
+ CATEGORIES = {
6
+ parse_error: "parser_error",
7
+ parse_warning: "parser_warning",
8
+ classified: "classifier_oof",
9
+ typechecked: "typechecker_oof",
10
+ assembler: "assembler_refusal",
11
+ runtime_smoke: "runtime_smoke_failure"
12
+ }.freeze
13
+
14
+ module_function
15
+
16
+ def enrich(entries, category:, contract: nil)
17
+ Array(entries).map do |entry|
18
+ normalized = stringify_keys(entry)
19
+ diagnostic_contract = normalized.key?("contract") ? normalized.fetch("contract") : contract
20
+ node = normalized.fetch("node", nil)
21
+ path = normalized.fetch("path", nil) || path_for(diagnostic_contract, node, normalized)
22
+ span = span_for(normalized)
23
+
24
+ normalized.merge(
25
+ "category" => normalized.fetch("category", category),
26
+ "rule" => normalized.fetch("rule", "UNKNOWN"),
27
+ "severity" => normalized.fetch("severity", "error"),
28
+ "message" => normalized.fetch("message", "compiler diagnostic"),
29
+ "contract" => diagnostic_contract,
30
+ "node" => node,
31
+ "path" => path,
32
+ "span" => span
33
+ ).reject { |key, _value| key == "line" || key == "col" }
34
+ end
35
+ end
36
+
37
+ def from_parse_errors(errors)
38
+ Array(errors).flat_map do |entry|
39
+ severity = stringify_keys(entry).fetch("severity", "error")
40
+ category = severity == "warning" ? CATEGORIES.fetch(:parse_warning) : CATEGORIES.fetch(:parse_error)
41
+ enrich([entry], category: category)
42
+ end
43
+ end
44
+
45
+ def from_classified(diagnostics, contract: nil)
46
+ enrich(diagnostics, category: CATEGORIES.fetch(:classified), contract: contract)
47
+ end
48
+
49
+ def from_typechecked(diagnostics, contract: nil)
50
+ enrich(diagnostics, category: CATEGORIES.fetch(:typechecked), contract: contract)
51
+ end
52
+
53
+ def from_assembler_refusal(refusal)
54
+ enrich(
55
+ [
56
+ {
57
+ "rule" => "ASSEMBLER-REFUSAL",
58
+ "severity" => "error",
59
+ "message" => refusal.respond_to?(:message) ? refusal.message : refusal.to_s
60
+ }
61
+ ],
62
+ category: CATEGORIES.fetch(:assembler)
63
+ )
64
+ end
65
+
66
+ def from_runtime_smoke(smoke)
67
+ return [] if smoke.fetch("trusted", false)
68
+
69
+ enrich(
70
+ [
71
+ {
72
+ "rule" => "OOF-RUNTIME-SMOKE",
73
+ "severity" => "error",
74
+ "message" => "RuntimeMachine load/evaluate smoke failed",
75
+ "details" => smoke
76
+ }
77
+ ],
78
+ category: CATEGORIES.fetch(:runtime_smoke)
79
+ )
80
+ end
81
+
82
+ def warnings(entries)
83
+ Array(entries).select { |entry| entry.fetch("severity", nil) == "warning" }
84
+ end
85
+
86
+ def errors(entries)
87
+ Array(entries).reject { |entry| entry.fetch("severity", nil) == "warning" }
88
+ end
89
+
90
+ def stringify_keys(value)
91
+ case value
92
+ when Hash
93
+ value.each_with_object({}) { |(key, entry), out| out[key.to_s] = stringify_keys(entry) }
94
+ when Array
95
+ value.map { |entry| stringify_keys(entry) }
96
+ else
97
+ value
98
+ end
99
+ end
100
+
101
+ def path_for(contract, node, entry)
102
+ return nil unless contract || node
103
+
104
+ parts = []
105
+ parts << "contract:#{contract}" if contract
106
+ parts << "#{node_kind(entry)}:#{node}" if node
107
+ parts.join("/")
108
+ end
109
+
110
+ def node_kind(entry)
111
+ entry.fetch("node_kind", nil) || entry.fetch("kind", nil) || "node"
112
+ end
113
+
114
+ def span_for(entry)
115
+ span = entry.fetch("span", nil)
116
+ return span if span.is_a?(Hash)
117
+
118
+ line = entry.fetch("line", nil)
119
+ col = entry.fetch("col", nil) || entry.fetch("column", nil)
120
+ return nil unless line && col
121
+
122
+ { "line" => line, "col" => col }
123
+ end
124
+ end
125
+ end
@@ -0,0 +1,129 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Fragment Registry Compatibility Adapter — internal direct-require-only helper.
4
+ #
5
+ # ISOLATION CONTRACT — this file must not:
6
+ # - be required from lib/igniter_lang.rb (direct-require-only; no root require)
7
+ # - be wired into the live classifier (classifier_wiring_authorized: false)
8
+ # - participate in live classifier dispatch (held_live_dispatch: true)
9
+ # - return results that become a ClassifiedProgram field, compiler input,
10
+ # report output, CLI/API output, or artifact metadata
11
+ # - touch parser, TypeChecker, SemanticIR, assembler, report, .igapp,
12
+ # runtime, Spark, or production behavior
13
+ #
14
+ # Authorized by: S3-R147-C1-A
15
+ # Track: fragment-registry-compatibility-adapter-helper-implementation-proof-v0
16
+ #
17
+ # Selection rules (R146 proof selection order, pinned exact):
18
+ # if oof present → oof
19
+ # elsif temporal → temporal
20
+ # elsif escape → escape
21
+ # elsif stream → escape (stream maps to escape)
22
+ # elsif epistemic → epistemic
23
+ # else → core
24
+ #
25
+ # API (exact R146 C1 shape; no field-name or result-structure change without
26
+ # a separate explicit delta review):
27
+ # IgniterLang::FragmentRegistryCompatibilityAdapter.project(input_hash) -> result_hash
28
+
29
+ module IgniterLang
30
+ class FragmentRegistryCompatibilityAdapter
31
+ FORMAT_VERSION = "0.1.0".freeze
32
+ KIND_INPUT = "fragment_registry_compatibility_adapter_helper_input".freeze
33
+ KIND_RESULT = "fragment_registry_compatibility_adapter_helper_result".freeze
34
+
35
+ # R146 proof selection rules in priority order.
36
+ # Must match the proof helper_result_shape.json rules_in_order exactly.
37
+ SELECTION_RULES = [
38
+ { presence: "oof", selected: "oof" },
39
+ { presence: "temporal", selected: "temporal" },
40
+ { presence: "escape", selected: "escape" },
41
+ { presence: "stream", selected: "escape" }, # stream → escape (not a direct fragment)
42
+ { presence: "epistemic", selected: "epistemic" }
43
+ ].freeze
44
+
45
+ DEFAULT_SELECTED = "core".freeze
46
+
47
+ # Project fragment registry compatibility from an internal input hash.
48
+ #
49
+ # Input hash shape (kind: fragment_registry_compatibility_adapter_helper_input):
50
+ # contracts[] — array of { contract_ref, declaration_fragment_presence,
51
+ # current_selected_fragment }
52
+ # guarded_non_fragments[] — array of { name, classification_kind, selected_fragment }
53
+ # oof_projection_policy — { primary_semantics, blocked, loadable, capability }
54
+ # classifier_wiring_authorized — false (must remain false)
55
+ # source_r144 — { matrix_digest, ... }
56
+ #
57
+ # @param input_hash [Hash] Internal helper input conforming to R146 C1 shape.
58
+ # @return [Hash] Internal result conforming to R146 C1 result shape.
59
+ # NEVER touches classifier state, reports, public surfaces, or artifacts.
60
+ def self.project(input_hash)
61
+ contracts = Array(input_hash["contracts"])
62
+ guarded_non_fragments = Array(input_hash["guarded_non_fragments"])
63
+ oof_projection_policy = input_hash.fetch("oof_projection_policy", {})
64
+
65
+ r144_source_digest = input_hash.dig("source_r144", "matrix_digest")
66
+ r144_source_status = input_hash.dig("source_r144", "source_status") || "PASS"
67
+
68
+ rows = contracts.map do |contract|
69
+ ref = contract["contract_ref"]
70
+ presence = Array(contract["declaration_fragment_presence"])
71
+ current = contract["current_selected_fragment"]
72
+ selected = select_fragment(presence)
73
+ parity = selected == current ? "PASS" : "MISMATCH"
74
+
75
+ {
76
+ "contract_ref" => ref,
77
+ "declaration_fragment_presence" => presence,
78
+ "current_selected_fragment" => current,
79
+ "selected_fragment" => selected,
80
+ "parity" => parity
81
+ }
82
+ end
83
+
84
+ mismatches = rows.select { |r| r["parity"] == "MISMATCH" }
85
+ r144_preserved = mismatches.empty?
86
+
87
+ {
88
+ "kind" => KIND_RESULT,
89
+ "format_version" => FORMAT_VERSION,
90
+ "selected_fragment_projection" => {
91
+ "rows" => rows,
92
+ "mismatches" => mismatches,
93
+ "rules_in_order" => rules_in_order_description
94
+ },
95
+ "guarded_non_fragments" => guarded_non_fragments,
96
+ "oof_projection_policy" => oof_projection_policy,
97
+ "r144_parity" => {
98
+ "preserved" => r144_preserved,
99
+ "source_digest" => r144_source_digest,
100
+ "source_status" => r144_source_status
101
+ },
102
+ "held_live_dispatch" => true,
103
+ "classifier_wiring_authorized" => false
104
+ }
105
+ end
106
+
107
+ # -------------------------------------------------------------------------
108
+ # Private class-level helpers
109
+ # -------------------------------------------------------------------------
110
+
111
+ # Select the fragment name for the given list of declared fragment presences,
112
+ # applying the R146 proof selection rules in priority order.
113
+ def self.select_fragment(presence_list)
114
+ SELECTION_RULES.each do |rule|
115
+ return rule[:selected] if presence_list.include?(rule[:presence])
116
+ end
117
+ DEFAULT_SELECTED
118
+ end
119
+ private_class_method :select_fragment
120
+
121
+ # Produce the canonical rules_in_order array for inclusion in the result shape.
122
+ def self.rules_in_order_description
123
+ SELECTION_RULES.map do |rule|
124
+ { "if" => "#{rule[:presence]} present", "selected" => rule[:selected] }
125
+ end + [{ "otherwise" => DEFAULT_SELECTED }]
126
+ end
127
+ private_class_method :rules_in_order_description
128
+ end
129
+ end
@@ -0,0 +1,199 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Internal profile assembly boundary.
4
+ #
5
+ # ISOLATION CONTRACT — this file must not:
6
+ # - be required from lib/igniter_lang.rb
7
+ # - integrate with parser/classifier/TypeChecker/SemanticIR/assembler/orchestrator
8
+ # - expose public API or CLI
9
+ # - read paths, manifests, loader reports, CompatibilityReports, or runtime state
10
+ # - write .igapp, .ilk, reports, sidecars, goldens, diagnostics, or CompilerResult fields
11
+ #
12
+ # Authorized by: LANG-R132-A
13
+ # Track: internal-profile-assembly-boundary-implementation-v0
14
+
15
+ require "digest"
16
+ require "json"
17
+
18
+ module IgniterLang
19
+ class InternalProfileAssembly
20
+ KIND = "internal_profile_assembly_result"
21
+ FORMAT_VERSION = "0.1.0"
22
+
23
+ SOURCE_PACKET_KIND = "compiler_profile_oof_registry_source_input"
24
+ IMPLEMENTATION_CANDIDATE = "implementation_candidate"
25
+ FINALIZED_INTERNAL = "finalized_internal"
26
+
27
+ FINALIZED_INTERNAL_MEANING =
28
+ "internal assembly state only; not PROP-036 finalization, not compiler_profile_id, " \
29
+ "and not manifest/profile identity"
30
+
31
+ CLOSED_SURFACE_ASSERTIONS = {
32
+ "root_require" => false,
33
+ "compiler_pipeline_usage" => false,
34
+ "public_api_cli" => false,
35
+ "loader_report" => false,
36
+ "compatibility_report" => false,
37
+ "igapp_mutation" => false,
38
+ "manifest_mutation" => false,
39
+ "prop036_mutation" => false,
40
+ "prop038_mutation" => false,
41
+ "runtime_behavior" => false,
42
+ "production_behavior" => false,
43
+ "spark_surface" => false
44
+ }.freeze
45
+
46
+ DIAG_INVALID_SOURCE_PACKET = "internal_profile_assembly.invalid_source_packet"
47
+ DIAG_INVALID_LIFECYCLE_STATE = "internal_profile_assembly.invalid_lifecycle_state"
48
+ DIAG_PACKET_MAPPING_FAILED = "internal_profile_assembly.packet_mapping_failed"
49
+ DIAG_PACKET_VALIDATION_FAILED = "internal_profile_assembly.packet_validation_failed"
50
+
51
+ def self.assemble(source_packet:, registry_validator: IgniterLang::OOFFragmentRegistry.new)
52
+ new(source_packet: source_packet, registry_validator: registry_validator).assemble
53
+ end
54
+
55
+ def initialize(source_packet:, registry_validator:)
56
+ @source_packet = source_packet
57
+ @registry_validator = registry_validator
58
+ end
59
+
60
+ def assemble
61
+ diagnostics = []
62
+ packet_hash = nil
63
+ helper_envelopes = nil
64
+ validation = nil
65
+ input_lifecycle_state = lifecycle_state_of(@source_packet)
66
+
67
+ unless source_packet_compatible?(@source_packet)
68
+ diagnostics << diag(DIAG_INVALID_SOURCE_PACKET,
69
+ "source_packet must support lifecycle_state, to_h, to_helper_envelopes, and validate_with")
70
+ return build_result(false, input_lifecycle_state, diagnostics, packet_hash, helper_envelopes, validation)
71
+ end
72
+
73
+ if input_lifecycle_state != IMPLEMENTATION_CANDIDATE
74
+ diagnostics << diag(DIAG_INVALID_LIFECYCLE_STATE,
75
+ "source_packet lifecycle_state must be #{IMPLEMENTATION_CANDIDATE.inspect}, " \
76
+ "got #{input_lifecycle_state.inspect}")
77
+ end
78
+
79
+ begin
80
+ packet_hash = @source_packet.to_h
81
+ helper_envelopes = @source_packet.to_helper_envelopes
82
+ rescue StandardError => e
83
+ diagnostics << diag(DIAG_PACKET_MAPPING_FAILED, "#{e.class}: #{e.message}")
84
+ return build_result(false, input_lifecycle_state, diagnostics, packet_hash, helper_envelopes, validation)
85
+ end
86
+
87
+ unless packet_hash.is_a?(Hash) && packet_hash["kind"] == SOURCE_PACKET_KIND
88
+ diagnostics << diag(DIAG_INVALID_SOURCE_PACKET,
89
+ "source_packet.to_h must return kind #{SOURCE_PACKET_KIND.inspect}")
90
+ end
91
+
92
+ begin
93
+ validation = @source_packet.validate_with(registry_validator: @registry_validator)
94
+ rescue StandardError => e
95
+ diagnostics << diag(DIAG_PACKET_VALIDATION_FAILED, "#{e.class}: #{e.message}")
96
+ return build_result(false, input_lifecycle_state, diagnostics, packet_hash, helper_envelopes, validation)
97
+ end
98
+
99
+ diagnostics.concat(internal_diagnostics_from(validation))
100
+ valid = diagnostics.empty? && validation.fetch("valid", false)
101
+
102
+ build_result(valid, input_lifecycle_state, diagnostics, packet_hash, helper_envelopes, validation)
103
+ end
104
+
105
+ private
106
+
107
+ def source_packet_compatible?(packet)
108
+ %i[
109
+ lifecycle_state
110
+ to_h
111
+ to_helper_envelopes
112
+ validate_with
113
+ ].all? { |method_name| packet.respond_to?(method_name) }
114
+ end
115
+
116
+ def lifecycle_state_of(packet)
117
+ return nil unless packet.respond_to?(:lifecycle_state)
118
+
119
+ packet.lifecycle_state
120
+ end
121
+
122
+ def build_result(valid, input_lifecycle_state, diagnostics, packet_hash, helper_envelopes, validation)
123
+ {
124
+ "kind" => KIND,
125
+ "format_version" => FORMAT_VERSION,
126
+ "valid" => valid,
127
+ "lifecycle_state" => valid ? FINALIZED_INTERNAL : non_final_lifecycle(input_lifecycle_state),
128
+ "input_lifecycle_state" => input_lifecycle_state,
129
+ "packet_kind" => packet_hash.is_a?(Hash) ? packet_hash["kind"] : nil,
130
+ "packet_digest" => packet_hash ? digest(packet_hash) : nil,
131
+ "helper_envelopes_digest" => helper_envelopes ? digest(helper_envelopes) : nil,
132
+ "profile_validation" => validation.is_a?(Hash) ? validation["profile_validation"] : nil,
133
+ "pack_descriptor_validations" => validation.is_a?(Hash) ? Array(validation["pack_descriptor_validations"]) : [],
134
+ "diagnostics" => diagnostics,
135
+ "finalized_internal_meaning" => FINALIZED_INTERNAL_MEANING,
136
+ "closed_surface_assertions" => deep_copy(CLOSED_SURFACE_ASSERTIONS)
137
+ }
138
+ end
139
+
140
+ def internal_diagnostics_from(validation)
141
+ return [diag(DIAG_PACKET_VALIDATION_FAILED, "source_packet.validate_with must return a Hash")] unless validation.is_a?(Hash)
142
+ return [] if validation.fetch("valid", false)
143
+
144
+ diagnostics = []
145
+ profile_diags = Array(validation.dig("profile_validation", "source_diagnostics"))
146
+ diagnostics.concat(profile_diags.map { |source_diag| source_validation_diag("profile_validation", source_diag) })
147
+
148
+ Array(validation["pack_descriptor_validations"]).each_with_index do |pack_validation, index|
149
+ Array(pack_validation["source_diagnostics"]).each do |source_diag|
150
+ diagnostics << source_validation_diag("pack_descriptor_validations[#{index}]", source_diag)
151
+ end
152
+ end
153
+
154
+ diagnostics << diag(DIAG_PACKET_VALIDATION_FAILED, "source packet validation failed") if diagnostics.empty?
155
+ diagnostics
156
+ end
157
+
158
+ def non_final_lifecycle(input_lifecycle_state)
159
+ return IMPLEMENTATION_CANDIDATE if input_lifecycle_state == IMPLEMENTATION_CANDIDATE
160
+
161
+ "invalid"
162
+ end
163
+
164
+ def source_validation_diag(path, source_diag)
165
+ {
166
+ "code" => DIAG_PACKET_VALIDATION_FAILED,
167
+ "path" => path,
168
+ "source_code" => source_diag.fetch("code", nil),
169
+ "message" => source_diag.fetch("message", "source packet validation failed")
170
+ }
171
+ end
172
+
173
+ def diag(code, message)
174
+ {
175
+ "code" => code,
176
+ "message" => message
177
+ }
178
+ end
179
+
180
+ def digest(value)
181
+ Digest::SHA256.hexdigest(JSON.generate(canonicalize(value)))[0, 24]
182
+ end
183
+
184
+ def canonicalize(value)
185
+ case value
186
+ when Hash
187
+ value.keys.sort.to_h { |key| [key, canonicalize(value[key])] }
188
+ when Array
189
+ value.map { |inner| canonicalize(inner) }
190
+ else
191
+ value
192
+ end
193
+ end
194
+
195
+ def deep_copy(value)
196
+ Marshal.load(Marshal.dump(value))
197
+ end
198
+ end
199
+ end