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,175 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Internal profile-assembly source packet.
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-R128-A
13
+ # Track: internal-profile-assembly-source-packet-implementation-v0
14
+ #
15
+ # Lifecycle wording:
16
+ # implementation_candidate = internal implementation boundary state
17
+ # finalized_internal = internal assembly state only; not PROP-036
18
+ # finalization, not compiler_profile_id, and not
19
+ # manifest/profile identity.
20
+
21
+ module IgniterLang
22
+ class InternalProfileAssemblySourcePacket
23
+ KIND = "compiler_profile_oof_registry_source_input"
24
+ FORMAT_VERSION = "0.1.0"
25
+ HELPER_KIND = "oof_fragment_registry_source"
26
+ VALIDATION_TARGET = "oof_fragment_registry_source_envelope_helper"
27
+
28
+ IMPLEMENTATION_CANDIDATE = "implementation_candidate"
29
+ FINALIZED_INTERNAL = "finalized_internal"
30
+ LIFECYCLE_STATES = [IMPLEMENTATION_CANDIDATE, FINALIZED_INTERNAL].freeze
31
+
32
+ DEFAULT_CLOSED_SURFACE_ASSERTIONS = {
33
+ "compiler_integration" => false,
34
+ "public_api_cli" => false,
35
+ "loader_report" => false,
36
+ "compatibility_report" => false,
37
+ "igapp_mutation" => false,
38
+ "prop036_manifest_change" => false,
39
+ "prop038_validator_report_change" => false,
40
+ "runtime_behavior" => false,
41
+ "production_behavior" => false,
42
+ "spark_surface" => false
43
+ }.freeze
44
+
45
+ def self.build(authority:, profile_candidate:, pack_descriptor_candidates:,
46
+ lifecycle_state: IMPLEMENTATION_CANDIDATE, closed_surface_assertions: {},
47
+ excluded_namespaces: nil)
48
+ new(
49
+ authority: authority,
50
+ profile_candidate: profile_candidate,
51
+ pack_descriptor_candidates: pack_descriptor_candidates,
52
+ lifecycle_state: lifecycle_state,
53
+ closed_surface_assertions: DEFAULT_CLOSED_SURFACE_ASSERTIONS.merge(
54
+ stringify_keys(closed_surface_assertions)
55
+ ),
56
+ excluded_namespaces: excluded_namespaces
57
+ )
58
+ end
59
+
60
+ def initialize(authority:, profile_candidate:, pack_descriptor_candidates:,
61
+ lifecycle_state:, closed_surface_assertions:, excluded_namespaces:)
62
+ unless LIFECYCLE_STATES.include?(lifecycle_state)
63
+ raise ArgumentError, "unsupported lifecycle_state #{lifecycle_state.inspect}"
64
+ end
65
+
66
+ @authority = deep_copy(authority)
67
+ @profile_candidate = deep_copy(profile_candidate)
68
+ @pack_descriptor_candidates = deep_copy(pack_descriptor_candidates)
69
+ @lifecycle_state = lifecycle_state
70
+ @closed_surface_assertions = deep_copy(closed_surface_assertions)
71
+ @excluded_namespaces = deep_copy(excluded_namespaces)
72
+ end
73
+
74
+ attr_reader :lifecycle_state
75
+
76
+ def to_h
77
+ packet = {
78
+ "kind" => KIND,
79
+ "format_version" => FORMAT_VERSION,
80
+ "lifecycle_state" => lifecycle_state,
81
+ "authority" => deep_copy(@authority),
82
+ "profile_candidate" => deep_copy(@profile_candidate),
83
+ "pack_descriptor_candidates" => deep_copy(@pack_descriptor_candidates),
84
+ "validation_target" => VALIDATION_TARGET,
85
+ "closed_surface_assertions" => deep_copy(@closed_surface_assertions)
86
+ }
87
+ packet["excluded_namespaces"] = deep_copy(@excluded_namespaces) if @excluded_namespaces
88
+ packet
89
+ end
90
+
91
+ def to_helper_envelopes
92
+ packet = to_h
93
+ common = {
94
+ "kind" => HELPER_KIND,
95
+ "format_version" => FORMAT_VERSION,
96
+ "authority" => packet.fetch("authority"),
97
+ "closed_surface_assertions" => packet.fetch("closed_surface_assertions")
98
+ }
99
+ profile = packet.fetch("profile_candidate")
100
+ pack_candidates = packet.fetch("pack_descriptor_candidates")
101
+
102
+ profile_envelope = common.merge(
103
+ "source_mode" => "profile_candidate",
104
+ "profile_ref" => profile.fetch("profile_ref"),
105
+ "profile_contract_ref" => profile["profile_contract_ref"],
106
+ "row_authority_policy" => profile.fetch("row_authority_policy"),
107
+ "selected_pack_refs" => profile.fetch("selected_pack_refs"),
108
+ "pack_order" => profile.fetch("pack_order"),
109
+ "conflict_policy" => profile.fetch("conflict_policy"),
110
+ "pack_descriptor_candidates" => pack_candidates
111
+ )
112
+ profile_envelope["excluded_namespaces"] = packet.fetch("excluded_namespaces") if packet.key?("excluded_namespaces")
113
+
114
+ pack_envelopes = pack_candidates.map do |pack|
115
+ common.merge(
116
+ "source_mode" => "pack_descriptor_candidate",
117
+ "pack_ref" => pack.fetch("pack_ref"),
118
+ "slot_name" => pack.fetch("slot_name"),
119
+ "owner_pack_or_boundary" => pack.fetch("owner_pack_or_boundary"),
120
+ "row_authority_policy" => pack.fetch("row_authority_policy"),
121
+ "owned_oof_descriptors" => pack.fetch("owned_oof_descriptors"),
122
+ "owned_fragment_rows" => pack.fetch("owned_fragment_rows"),
123
+ "owned_support_markers" => pack.fetch("owned_support_markers")
124
+ )
125
+ end
126
+
127
+ {
128
+ "kind" => "mapped_oof_fragment_registry_source_envelopes",
129
+ "format_version" => FORMAT_VERSION,
130
+ "source_input_kind" => KIND,
131
+ "profile_envelope" => profile_envelope,
132
+ "pack_descriptor_envelopes" => pack_envelopes
133
+ }
134
+ end
135
+
136
+ def validate_with(registry_validator:)
137
+ helper_envelopes = to_helper_envelopes
138
+ profile_validation = registry_validator.validate_source_envelope(
139
+ helper_envelopes.fetch("profile_envelope")
140
+ )
141
+ pack_validations = helper_envelopes.fetch("pack_descriptor_envelopes").map do |envelope|
142
+ registry_validator.validate_source_envelope(envelope)
143
+ end
144
+ valid = profile_validation.fetch("valid") &&
145
+ pack_validations.all? { |result| result.fetch("valid") }
146
+ result_state = valid ? FINALIZED_INTERNAL : lifecycle_state
147
+
148
+ {
149
+ "kind" => "internal_profile_assembly_source_packet_validation",
150
+ "format_version" => FORMAT_VERSION,
151
+ "valid" => valid,
152
+ "lifecycle_state" => lifecycle_state,
153
+ "result_lifecycle_state" => result_state,
154
+ "finalized_internal_meaning" =>
155
+ "internal assembly state only; not PROP-036 finalization, not compiler_profile_id, " \
156
+ "and not manifest/profile identity",
157
+ "source_packet" => to_h,
158
+ "helper_envelopes" => helper_envelopes,
159
+ "profile_validation" => profile_validation,
160
+ "pack_descriptor_validations" => pack_validations,
161
+ "closed_surface_assertions" => deep_copy(@closed_surface_assertions)
162
+ }
163
+ end
164
+
165
+ def self.stringify_keys(hash)
166
+ hash.to_h.transform_keys(&:to_s)
167
+ end
168
+
169
+ def deep_copy(value)
170
+ Marshal.load(Marshal.dump(value))
171
+ end
172
+
173
+ private :deep_copy
174
+ end
175
+ end
@@ -0,0 +1,286 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "digest"
4
+ require "json"
5
+ require_relative "internal_profile_assembly_source_packet"
6
+
7
+ module IgniterLang
8
+ class InternalProfileStaticDataCarrier
9
+ KIND = "internal_profile_static_data_carrier"
10
+ FORMAT_VERSION = "0.1.0"
11
+ STATIC_DATA_STATUSES = ["proof_local_only", "internal_test_seam_only"].freeze
12
+
13
+ DIAG_INVALID_SHAPE = "internal_profile_static_data_carrier.invalid_shape"
14
+ DIAG_UNSUPPORTED_KIND = "internal_profile_static_data_carrier.unsupported_kind"
15
+ DIAG_UNSUPPORTED_FORMAT_VERSION =
16
+ "internal_profile_static_data_carrier.unsupported_format_version"
17
+ DIAG_UNSUPPORTED_STATIC_DATA_STATUS =
18
+ "internal_profile_static_data_carrier.unsupported_static_data_status"
19
+ DIAG_INVALID_AUTHORITY = "internal_profile_static_data_carrier.invalid_authority"
20
+ DIAG_MISSING_PROFILE_CANDIDATE =
21
+ "internal_profile_static_data_carrier.missing_profile_candidate"
22
+ DIAG_MISSING_PACK_DESCRIPTOR_CANDIDATES =
23
+ "internal_profile_static_data_carrier.missing_pack_descriptor_candidates"
24
+ DIAG_SURFACE_OPEN = "internal_profile_static_data_carrier.surface_open"
25
+ DIAG_FORBIDDEN_FIELD = "internal_profile_static_data_carrier.forbidden_field"
26
+ DIAG_PACKET_BUILD_FAILED = "internal_profile_static_data_carrier.packet_build_failed"
27
+
28
+ ACCEPTED_AUTHORITY_KINDS = ["proof_only", "design_accepted"].freeze
29
+ ACCEPTED_CANON_STATUSES = ["non_canon", "accepted_design"].freeze
30
+
31
+ FORBIDDEN_FIELDS = %w[
32
+ compiler_profile_id
33
+ compiler_profile_id_source
34
+ compiler_profile_source
35
+ profile_source
36
+ default_profile
37
+ named_profile
38
+ profile_discovery
39
+ igapp_path
40
+ compilation_report_path
41
+ loader_report
42
+ compatibility_report
43
+ compiler_result
44
+ manifest
45
+ sidecar
46
+ artifact_hash
47
+ runtime_ready
48
+ production_ready
49
+ spark_ready
50
+ demo_ready
51
+ public
52
+ discovery
53
+ loader
54
+ report
55
+ artifact
56
+ runtime
57
+ spark
58
+ production
59
+ demo
60
+ ].freeze
61
+
62
+ def self.build(static_data:)
63
+ new(static_data)
64
+ end
65
+
66
+ def initialize(static_data)
67
+ @raw_static_data = static_data
68
+ @static_data = normalize_hash(static_data)
69
+ @packet_build_diagnostics = []
70
+ @diagnostics = validate_static_data
71
+ end
72
+
73
+ def to_h
74
+ {
75
+ "kind" => KIND,
76
+ "format_version" => FORMAT_VERSION,
77
+ "valid" => valid_shape?,
78
+ "static_data_status" => static_data_status_output,
79
+ "authority" => authority_output,
80
+ "profile_candidate" => deep_copy(@static_data["profile_candidate"]),
81
+ "pack_descriptor_candidates" => deep_copy(pack_descriptor_candidates),
82
+ "excluded_namespaces" => deep_copy(excluded_namespaces),
83
+ "diagnostics" => diagnostics,
84
+ "static_data_digest" => static_data_digest,
85
+ "closed_surface_assertions" => closed_surface_assertions_output
86
+ }
87
+ end
88
+
89
+ def valid_shape?
90
+ diagnostics.empty?
91
+ end
92
+
93
+ def diagnostics
94
+ deep_copy(@diagnostics + @packet_build_diagnostics)
95
+ end
96
+
97
+ def to_source_packet
98
+ return nil unless valid_shape?
99
+
100
+ IgniterLang::InternalProfileAssemblySourcePacket.build(
101
+ authority: deep_copy(@static_data.fetch("authority")),
102
+ profile_candidate: deep_copy(@static_data.fetch("profile_candidate")),
103
+ pack_descriptor_candidates: deep_copy(@static_data.fetch("pack_descriptor_candidates")),
104
+ lifecycle_state: IgniterLang::InternalProfileAssemblySourcePacket::IMPLEMENTATION_CANDIDATE,
105
+ closed_surface_assertions: deep_copy(closed_surface_assertions),
106
+ excluded_namespaces: deep_copy(excluded_namespaces)
107
+ )
108
+ rescue StandardError => e
109
+ record_packet_build_failure(e)
110
+ nil
111
+ end
112
+
113
+ def static_data_digest
114
+ Digest::SHA256.hexdigest(JSON.generate(canonicalize(carrier_material)))[0, 24]
115
+ end
116
+
117
+ private
118
+
119
+ def validate_static_data
120
+ diags = []
121
+
122
+ unless @raw_static_data.is_a?(Hash)
123
+ diags << diag(DIAG_INVALID_SHAPE, "static_data must be a Hash")
124
+ return diags
125
+ end
126
+
127
+ unless @static_data["kind"] == KIND
128
+ diags << diag(DIAG_UNSUPPORTED_KIND, "static_data kind is not supported")
129
+ end
130
+
131
+ unless @static_data["format_version"] == FORMAT_VERSION
132
+ diags << diag(DIAG_UNSUPPORTED_FORMAT_VERSION, "static_data format_version is not supported")
133
+ end
134
+
135
+ unless STATIC_DATA_STATUSES.include?(@static_data["static_data_status"])
136
+ diags << diag(DIAG_UNSUPPORTED_STATIC_DATA_STATUS, "static_data status is not supported")
137
+ end
138
+
139
+ diags << diag(DIAG_INVALID_AUTHORITY, "authority is outside internal proof/design scope") unless valid_authority?
140
+
141
+ unless @static_data["profile_candidate"].is_a?(Hash)
142
+ diags << diag(DIAG_MISSING_PROFILE_CANDIDATE, "profile_candidate must be present")
143
+ end
144
+
145
+ unless @static_data["pack_descriptor_candidates"].is_a?(Array) &&
146
+ @static_data["pack_descriptor_candidates"].any?
147
+ diags << diag(DIAG_MISSING_PACK_DESCRIPTOR_CANDIDATES,
148
+ "pack_descriptor_candidates must be a non-empty Array")
149
+ end
150
+
151
+ unless closed_surface_assertions.is_a?(Hash)
152
+ diags << diag(DIAG_INVALID_SHAPE, "closed_surface_assertions must be a Hash when present")
153
+ end
154
+
155
+ if closed_surface_assertions.is_a?(Hash) &&
156
+ closed_surface_assertions.values.any? { |value| value == true }
157
+ diags << diag(DIAG_SURFACE_OPEN, "closed_surface_assertions must remain closed")
158
+ end
159
+
160
+ forbidden_field_count.times do
161
+ diags << diag(DIAG_FORBIDDEN_FIELD, "forbidden field is not allowed in internal carrier input")
162
+ end
163
+
164
+ diags
165
+ end
166
+
167
+ def valid_authority?
168
+ authority = @static_data["authority"]
169
+ return false unless authority.is_a?(Hash)
170
+
171
+ ACCEPTED_AUTHORITY_KINDS.include?(authority["authority_kind"]) &&
172
+ ACCEPTED_CANON_STATUSES.include?(authority["canon_status"]) &&
173
+ authority["authority_ref"].to_s.strip != ""
174
+ end
175
+
176
+ def forbidden_field_count
177
+ count_forbidden_keys(@static_data)
178
+ end
179
+
180
+ def count_forbidden_keys(value)
181
+ case value
182
+ when Hash
183
+ value.sum do |key, nested|
184
+ (FORBIDDEN_FIELDS.include?(key.to_s) ? 1 : 0) + count_forbidden_keys(nested)
185
+ end
186
+ when Array
187
+ value.sum { |nested| count_forbidden_keys(nested) }
188
+ else
189
+ 0
190
+ end
191
+ end
192
+
193
+ def record_packet_build_failure(error)
194
+ return if @packet_build_diagnostics.any? do |existing|
195
+ existing["code"] == DIAG_PACKET_BUILD_FAILED
196
+ end
197
+
198
+ @packet_build_diagnostics << diag(DIAG_PACKET_BUILD_FAILED, "#{error.class}: packet build failed")
199
+ end
200
+
201
+ def carrier_material
202
+ {
203
+ "kind" => @static_data["kind"],
204
+ "format_version" => @static_data["format_version"],
205
+ "static_data_status" => @static_data["static_data_status"],
206
+ "authority" => @static_data["authority"],
207
+ "profile_candidate" => @static_data["profile_candidate"],
208
+ "pack_descriptor_candidates" => pack_descriptor_candidates,
209
+ "excluded_namespaces" => excluded_namespaces,
210
+ "closed_surface_assertions" => closed_surface_assertions
211
+ }
212
+ end
213
+
214
+ def static_data_status_output
215
+ status = @static_data["static_data_status"]
216
+ STATIC_DATA_STATUSES.include?(status) ? status : nil
217
+ end
218
+
219
+ def authority_output
220
+ return deep_copy(@static_data["authority"]) if valid_authority?
221
+
222
+ { "valid" => false }
223
+ end
224
+
225
+ def closed_surface_assertions_output
226
+ return {} unless closed_surface_assertions.is_a?(Hash)
227
+ return {} unless closed_surface_assertions.values.all?(false)
228
+
229
+ deep_copy(closed_surface_assertions)
230
+ end
231
+
232
+ def pack_descriptor_candidates
233
+ value = @static_data["pack_descriptor_candidates"]
234
+ value.is_a?(Array) ? value : []
235
+ end
236
+
237
+ def excluded_namespaces
238
+ value = @static_data["excluded_namespaces"]
239
+ value.is_a?(Array) ? value : []
240
+ end
241
+
242
+ def closed_surface_assertions
243
+ value = @static_data["closed_surface_assertions"]
244
+ value.nil? ? {} : value
245
+ end
246
+
247
+ def normalize_hash(value)
248
+ return recursive_stringify(value) if value.is_a?(Hash)
249
+
250
+ {}
251
+ end
252
+
253
+ def recursive_stringify(value)
254
+ case value
255
+ when Hash
256
+ value.to_h { |key, nested| [key.to_s, recursive_stringify(nested)] }
257
+ when Array
258
+ value.map { |nested| recursive_stringify(nested) }
259
+ else
260
+ value
261
+ end
262
+ end
263
+
264
+ def canonicalize(value)
265
+ case value
266
+ when Hash
267
+ value.keys.sort.to_h { |key| [key, canonicalize(value[key])] }
268
+ when Array
269
+ value.map { |inner| canonicalize(inner) }
270
+ else
271
+ value
272
+ end
273
+ end
274
+
275
+ def deep_copy(value)
276
+ Marshal.load(Marshal.dump(value))
277
+ end
278
+
279
+ def diag(code, message)
280
+ {
281
+ "code" => code,
282
+ "message" => message
283
+ }
284
+ end
285
+ end
286
+ end