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.
- checksums.yaml +7 -0
- data/README.md +65 -0
- data/RELEASE_NOTES.md +137 -0
- data/bin/igc +7 -0
- data/lib/igniter_lang/assembler.rb +717 -0
- data/lib/igniter_lang/classifier.rb +405 -0
- data/lib/igniter_lang/cli.rb +76 -0
- data/lib/igniter_lang/compilation_report.rb +99 -0
- data/lib/igniter_lang/compiler_orchestrator.rb +362 -0
- data/lib/igniter_lang/compiler_profile_contract_validator.rb +286 -0
- data/lib/igniter_lang/compiler_result.rb +77 -0
- data/lib/igniter_lang/diagnostics.rb +125 -0
- data/lib/igniter_lang/fragment_registry_compatibility_adapter.rb +129 -0
- data/lib/igniter_lang/internal_profile_assembly.rb +199 -0
- data/lib/igniter_lang/internal_profile_assembly_source_packet.rb +175 -0
- data/lib/igniter_lang/internal_profile_static_data_carrier.rb +286 -0
- data/lib/igniter_lang/oof_fragment_registry.rb +802 -0
- data/lib/igniter_lang/parser.rb +1736 -0
- data/lib/igniter_lang/runtime_smoke.rb +80 -0
- data/lib/igniter_lang/semanticir_emitter.rb +847 -0
- data/lib/igniter_lang/temporal_access_runtime.rb +437 -0
- data/lib/igniter_lang/temporal_executor.rb +457 -0
- data/lib/igniter_lang/typechecker.rb +821 -0
- data/lib/igniter_lang/version.rb +5 -0
- data/lib/igniter_lang.rb +27 -0
- metadata +72 -0
|
@@ -0,0 +1,802 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# OOF/Fragment Registry internal validator.
|
|
4
|
+
#
|
|
5
|
+
# ISOLATION CONTRACT — this file must not:
|
|
6
|
+
# - be required from lib/igniter_lang.rb
|
|
7
|
+
# - integrate with any compiler pass (parser/classifier/TypeChecker/SemanticIR/assembler)
|
|
8
|
+
# - emit public diagnostics
|
|
9
|
+
# - write to report["diagnostics"] or top-level compilation reports
|
|
10
|
+
# - add CompilerResult fields
|
|
11
|
+
# - expose public API or CLI
|
|
12
|
+
# - call runtime, Ledger/TBackend, Gate 3, cache, signing, or production behavior
|
|
13
|
+
#
|
|
14
|
+
# Authorized by: LANG-R102-A (registry validator)
|
|
15
|
+
# LANG-R110-A (source-envelope helper)
|
|
16
|
+
# LANG-R121-A (profile/pack source acceptance helper slice)
|
|
17
|
+
# Tracks: oof-fragment-registry-implementation-boundary-proof-v0
|
|
18
|
+
# oof-fragment-registry-source-envelope-helper-proof-v0
|
|
19
|
+
#
|
|
20
|
+
# R92 historical note: the shadow proof JSON at
|
|
21
|
+
# experiments/oof_fragment_registry_shadow_proof/out/oof_descriptors.shadow_registry.json
|
|
22
|
+
# placed PINV-*/TINV-* inside oof_descriptors as historical proof evidence.
|
|
23
|
+
# That placement is NON-FORWARD. The forward shape (authorized by LANG-R98-A
|
|
24
|
+
# and LANG-R101-D1) places PINV-*/TINV-* exclusively under
|
|
25
|
+
# support_markers.invariant_support_markers. This validator enforces the
|
|
26
|
+
# forward shape.
|
|
27
|
+
|
|
28
|
+
require "set"
|
|
29
|
+
|
|
30
|
+
module IgniterLang
|
|
31
|
+
class OOFFragmentRegistry
|
|
32
|
+
FORMAT_VERSION = "0.1.0".freeze
|
|
33
|
+
|
|
34
|
+
# Namespace prefixes that must be present in excluded_namespaces.
|
|
35
|
+
REQUIRED_EXCLUDED_PREFIXES = %w[
|
|
36
|
+
compiler_profile_contract.
|
|
37
|
+
compiler_profile_contract_refusal.
|
|
38
|
+
].freeze
|
|
39
|
+
|
|
40
|
+
# Support marker code pattern: only PINV-* and TINV-* are support markers.
|
|
41
|
+
SUPPORT_MARKER_PATTERN = /\A(PINV|TINV)-/.freeze
|
|
42
|
+
|
|
43
|
+
# Fragment row names that are guarded non-fragments (must have classification_kind "not_fragment_class").
|
|
44
|
+
GUARDED_NON_FRAGMENT_NAMES = %w[olap progression].freeze
|
|
45
|
+
|
|
46
|
+
# The special "oof" fragment row that must be non-loadable and capability-free.
|
|
47
|
+
OOF_ROW_NAME = "oof".freeze
|
|
48
|
+
|
|
49
|
+
# Acceptable public_code_stability values for support markers (non-public).
|
|
50
|
+
SUPPORT_MARKER_STABILITY_VALUES = %w[non_public_support_marker proof_only].freeze
|
|
51
|
+
|
|
52
|
+
# -------------------------------------------------------------------------
|
|
53
|
+
# Source-envelope helper — accepted and held/rejected source modes.
|
|
54
|
+
# Authorized by: LANG-R110-A
|
|
55
|
+
# These constants are internal to this helper and are NOT public API.
|
|
56
|
+
# -------------------------------------------------------------------------
|
|
57
|
+
|
|
58
|
+
# Accepted source modes for validate_source_envelope.
|
|
59
|
+
SOURCE_ACCEPTED_MODES = %w[
|
|
60
|
+
proof_fixture
|
|
61
|
+
caller_supplied
|
|
62
|
+
profile_candidate
|
|
63
|
+
pack_descriptor_candidate
|
|
64
|
+
].freeze
|
|
65
|
+
|
|
66
|
+
# Held modes: recognized but not yet authorized for helper processing.
|
|
67
|
+
SOURCE_HELD_MODES = [].freeze
|
|
68
|
+
|
|
69
|
+
# Accepted authority kinds for source envelope.
|
|
70
|
+
SOURCE_ACCEPTED_AUTHORITY_KINDS = %w[proof_only design_accepted].freeze
|
|
71
|
+
|
|
72
|
+
# Accepted (non-canon) canon_status values.
|
|
73
|
+
SOURCE_ACCEPTED_CANON_STATUSES = %w[non_canon accepted_design].freeze
|
|
74
|
+
|
|
75
|
+
# Source-envelope helper diagnostic codes.
|
|
76
|
+
# These are internal helper diagnostics. They are NOT language OOF codes and
|
|
77
|
+
# are NOT central IgniterLang::Diagnostics entries.
|
|
78
|
+
SOURCE_DIAG_WRONG_KIND = "oof_registry.source.validation.wrong_kind".freeze
|
|
79
|
+
SOURCE_DIAG_UNSUPPORTED_FORMAT_VERSION = "oof_registry.source.validation.unsupported_format_version".freeze
|
|
80
|
+
SOURCE_DIAG_UNSUPPORTED_SOURCE_MODE = "oof_registry.source.validation.unsupported_source_mode".freeze
|
|
81
|
+
SOURCE_DIAG_HELD_SOURCE_MODE = "oof_registry.source.validation.held_source_mode".freeze
|
|
82
|
+
SOURCE_DIAG_INVALID_AUTHORITY_KIND = "oof_registry.source.validation.invalid_authority_kind".freeze
|
|
83
|
+
SOURCE_DIAG_CANON_STATUS_FORBIDDEN = "oof_registry.source.validation.canon_status_forbidden".freeze
|
|
84
|
+
SOURCE_DIAG_MISSING_AUTHORITY = "oof_registry.source.validation.missing_authority".freeze
|
|
85
|
+
SOURCE_DIAG_MISSING_AUTHORITY_REF = "oof_registry.source.validation.missing_authority_ref".freeze
|
|
86
|
+
SOURCE_DIAG_MISSING_REGISTRY = "oof_registry.source.validation.missing_registry".freeze
|
|
87
|
+
SOURCE_DIAG_SURFACE_OPEN = "oof_registry.source.validation.surface_open".freeze
|
|
88
|
+
SOURCE_DIAG_MISSING_PACK_REF = "oof_registry.source.validation.missing_pack_ref".freeze
|
|
89
|
+
SOURCE_DIAG_MISSING_PROFILE_REF = "oof_registry.source.validation.missing_profile_ref".freeze
|
|
90
|
+
SOURCE_DIAG_MISSING_SELECTED_PACK_REF = "oof_registry.source.validation.missing_selected_pack_ref".freeze
|
|
91
|
+
SOURCE_DIAG_MISSING_PACK_DESCRIPTOR = "oof_registry.source.validation.missing_pack_descriptor".freeze
|
|
92
|
+
SOURCE_DIAG_ROW_OWNER_MISMATCH = "oof_registry.source.validation.row_owner_mismatch".freeze
|
|
93
|
+
SOURCE_DIAG_DUPLICATE_ROW_OWNERSHIP = "oof_registry.source.validation.duplicate_row_ownership".freeze
|
|
94
|
+
SOURCE_DIAG_DUPLICATE_ALIAS_OWNERSHIP = "oof_registry.source.validation.duplicate_alias_ownership".freeze
|
|
95
|
+
SOURCE_DIAG_EXCLUDED_NAMESPACE_CLAIM = "oof_registry.source.validation.excluded_namespace_claim".freeze
|
|
96
|
+
SOURCE_DIAG_PROFILE_OVERRIDE_FORBIDDEN = "oof_registry.source.validation.profile_override_forbidden".freeze
|
|
97
|
+
SOURCE_DIAG_INVALID_CONFLICT_POLICY = "oof_registry.source.validation.invalid_conflict_policy".freeze
|
|
98
|
+
SOURCE_DIAG_INVALID_PACK_ORDER = "oof_registry.source.validation.invalid_pack_order".freeze
|
|
99
|
+
|
|
100
|
+
# -------------------------------------------------------------------------
|
|
101
|
+
# Internal validator diagnostic codes.
|
|
102
|
+
# These are NOT public language OOF codes and are NOT central IgniterLang::Diagnostics entries.
|
|
103
|
+
DIAG_MISSING_SECTION = "oof_registry.validation.missing_section".freeze
|
|
104
|
+
DIAG_WRONG_KIND = "oof_registry.validation.wrong_kind".freeze
|
|
105
|
+
DIAG_DUPLICATE_CODE = "oof_registry.validation.duplicate_code".freeze
|
|
106
|
+
DIAG_ALIAS_COLLISION = "oof_registry.validation.alias_collision".freeze
|
|
107
|
+
DIAG_ALIAS_MISSING_REPLACEMENT = "oof_registry.validation.alias_missing_replacement".freeze
|
|
108
|
+
DIAG_EXCLUDED_NAMESPACE_COLLISION = "oof_registry.validation.excluded_namespace_collision".freeze
|
|
109
|
+
DIAG_SUPPORT_MARKER_IN_DESCRIPTORS = "oof_registry.validation.support_marker_in_oof_descriptors".freeze
|
|
110
|
+
DIAG_SUPPORT_MARKER_PUBLIC = "oof_registry.validation.support_marker_public".freeze
|
|
111
|
+
DIAG_SUPPORT_MARKER_EMITTED = "oof_registry.validation.support_marker_emitted".freeze
|
|
112
|
+
DIAG_SUPPORT_MARKER_CODE_COLLISION = "oof_registry.validation.support_marker_code_collision".freeze
|
|
113
|
+
DIAG_OOF_PROJECTION_LOADABLE = "oof_registry.validation.oof_projection_loadable".freeze
|
|
114
|
+
DIAG_OOF_PROJECTION_CAPABILITY = "oof_registry.validation.oof_projection_capability".freeze
|
|
115
|
+
DIAG_GUARDED_NON_FRAGMENT = "oof_registry.validation.guarded_non_fragment_violation".freeze
|
|
116
|
+
DIAG_OWNER_BOUNDARY_ABSENT = "oof_registry.validation.owner_boundary_absent".freeze
|
|
117
|
+
|
|
118
|
+
# Validate a registry hash against the R101 forward bucket shape.
|
|
119
|
+
#
|
|
120
|
+
# @param registry [Hash] The registry object to validate (caller-supplied).
|
|
121
|
+
# @param installed_boundaries [Array<String>, nil]
|
|
122
|
+
# When supplied, rows owned by a boundary not in this list are recorded
|
|
123
|
+
# as inactive rows. Inactive rows are NOT silently skipped — they are
|
|
124
|
+
# recorded in the result. Inactive rows do not flip valid: false.
|
|
125
|
+
# When nil, boundary-absence checks are skipped.
|
|
126
|
+
#
|
|
127
|
+
# @return [Hash] Internal validation result.
|
|
128
|
+
# NEVER touches compiler state, reports, or public surfaces.
|
|
129
|
+
def validate(registry, installed_boundaries: nil)
|
|
130
|
+
diags = []
|
|
131
|
+
inactive_rows = []
|
|
132
|
+
|
|
133
|
+
# Step 1 — top-level shape
|
|
134
|
+
shape_diags = check_top_level_shape(registry)
|
|
135
|
+
diags.concat(shape_diags)
|
|
136
|
+
return build_result(false, diags, inactive_rows) unless shape_diags.empty?
|
|
137
|
+
|
|
138
|
+
oof_descriptors = Array(registry["oof_descriptors"])
|
|
139
|
+
fragment_rows = Array(registry["fragment_rows"])
|
|
140
|
+
support_markers_arr = Array(registry.dig("support_markers", "invariant_support_markers"))
|
|
141
|
+
excluded_namespaces = Array(registry["excluded_namespaces"])
|
|
142
|
+
excluded_prefixes = excluded_namespaces.map { |n| n["prefix"] }.compact
|
|
143
|
+
|
|
144
|
+
# Pre-collect all canonical OOF codes + aliases for cross-collision detection
|
|
145
|
+
all_oof_canonical = oof_descriptors.map { |d| d["code"] }.compact.to_set
|
|
146
|
+
all_oof_aliases = oof_descriptors.flat_map { |d| Array(d["aliases"]) }.to_set
|
|
147
|
+
all_oof_code_set = all_oof_canonical | all_oof_aliases
|
|
148
|
+
|
|
149
|
+
# Step 2 — OOF descriptors
|
|
150
|
+
diags.concat(check_oof_descriptors(oof_descriptors, excluded_prefixes, all_oof_canonical))
|
|
151
|
+
|
|
152
|
+
# Step 3 — fragment rows
|
|
153
|
+
diags.concat(check_fragment_rows(fragment_rows))
|
|
154
|
+
|
|
155
|
+
# Step 4 — support markers
|
|
156
|
+
diags.concat(check_support_markers(support_markers_arr, all_oof_code_set))
|
|
157
|
+
|
|
158
|
+
# Step 5 — excluded namespaces
|
|
159
|
+
diags.concat(check_excluded_namespaces(excluded_namespaces))
|
|
160
|
+
|
|
161
|
+
# Step 6 — absent-owner inactive rows (recorded, not silently skipped)
|
|
162
|
+
if installed_boundaries
|
|
163
|
+
boundaries_set = installed_boundaries.to_set
|
|
164
|
+
candidates = [
|
|
165
|
+
*oof_descriptors.map { |row| ["oof_descriptor", row["code"] || row["name"], row] },
|
|
166
|
+
*fragment_rows.map { |row| ["fragment_row", row["name"], row] },
|
|
167
|
+
*support_markers_arr.map { |row| ["support_marker", row["code"] || row["name"], row] }
|
|
168
|
+
]
|
|
169
|
+
candidates.each do |section_kind, row_id, row|
|
|
170
|
+
owner = row["owner_pack_or_boundary"]
|
|
171
|
+
next unless owner
|
|
172
|
+
next if boundaries_set.include?(owner)
|
|
173
|
+
|
|
174
|
+
inactive_rows << {
|
|
175
|
+
"section" => section_kind,
|
|
176
|
+
"row_id" => row_id,
|
|
177
|
+
"owner" => owner,
|
|
178
|
+
"reason" => "owner_pack_or_boundary_absent_from_installed_boundaries"
|
|
179
|
+
}
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
build_result(diags.empty?, diags, inactive_rows)
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
# Validate a source envelope and, if valid, validate its nested registry.
|
|
187
|
+
#
|
|
188
|
+
# This is an internal helper only. It is NOT a public API, NOT a loader,
|
|
189
|
+
# NOT a compiler pass, and NOT a report surface. It is callable only from
|
|
190
|
+
# proof-local harnesses via direct require of this file.
|
|
191
|
+
#
|
|
192
|
+
# Authorized by: LANG-R110-A
|
|
193
|
+
# Design: oof-fragment-registry-source-envelope-helper-boundary-design-v0 (LANG-R109-D1)
|
|
194
|
+
#
|
|
195
|
+
# @param source_envelope [Hash] A source envelope describing where the registry
|
|
196
|
+
# hash comes from. Must have kind, format_version, source_mode, authority, registry.
|
|
197
|
+
# @param installed_boundaries [Array<String>, nil]
|
|
198
|
+
# Forwarded to the nested registry validate call when source envelope passes.
|
|
199
|
+
#
|
|
200
|
+
# @return [Hash] Internal source-envelope validation result.
|
|
201
|
+
# - valid: true only when source-envelope validation AND nested registry validation pass.
|
|
202
|
+
# - source_mode: the source_mode from the envelope (or nil if envelope is malformed).
|
|
203
|
+
# - registry_present: whether the envelope contained a registry hash.
|
|
204
|
+
# - source_diagnostics: internal source-envelope diagnostics only.
|
|
205
|
+
# - registry_validation: the nested registry validation result, or nil if source invalid.
|
|
206
|
+
# - closed_surface_assertions: all false (machine-assertable).
|
|
207
|
+
# NEVER touches compiler state, reports, or public surfaces.
|
|
208
|
+
def validate_source_envelope(source_envelope, installed_boundaries: nil)
|
|
209
|
+
source_diags = []
|
|
210
|
+
|
|
211
|
+
# Step 1 — envelope must be a Hash with correct kind
|
|
212
|
+
unless source_envelope.is_a?(Hash)
|
|
213
|
+
source_diags << source_diag(SOURCE_DIAG_WRONG_KIND,
|
|
214
|
+
"source envelope must be a Hash, got #{source_envelope.class}")
|
|
215
|
+
return build_source_result(false, nil, false, source_diags, nil)
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
if source_envelope["kind"] != "oof_fragment_registry_source"
|
|
219
|
+
source_diags << source_diag(SOURCE_DIAG_WRONG_KIND,
|
|
220
|
+
"source envelope kind must be 'oof_fragment_registry_source', " \
|
|
221
|
+
"got #{source_envelope["kind"].inspect}")
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
# Step 2 — format version
|
|
225
|
+
unless source_envelope["format_version"] == "0.1.0"
|
|
226
|
+
source_diags << source_diag(SOURCE_DIAG_UNSUPPORTED_FORMAT_VERSION,
|
|
227
|
+
"source envelope format_version must be '0.1.0', " \
|
|
228
|
+
"got #{source_envelope["format_version"].inspect}")
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
# Step 3 — source mode
|
|
232
|
+
source_mode = source_envelope["source_mode"]
|
|
233
|
+
if SOURCE_HELD_MODES.include?(source_mode)
|
|
234
|
+
source_diags << source_diag(SOURCE_DIAG_HELD_SOURCE_MODE,
|
|
235
|
+
"source_mode #{source_mode.inspect} is known but held; " \
|
|
236
|
+
"only 'proof_fixture' and 'caller_supplied' are accepted in this helper")
|
|
237
|
+
elsif !SOURCE_ACCEPTED_MODES.include?(source_mode)
|
|
238
|
+
source_diags << source_diag(SOURCE_DIAG_UNSUPPORTED_SOURCE_MODE,
|
|
239
|
+
"source_mode #{source_mode.inspect} is not supported; " \
|
|
240
|
+
"accepted: proof_fixture, caller_supplied")
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
# Step 4 — authority object
|
|
244
|
+
authority = source_envelope["authority"]
|
|
245
|
+
if authority.is_a?(Hash)
|
|
246
|
+
# authority_ref must be present
|
|
247
|
+
if authority["authority_ref"].to_s.strip.empty?
|
|
248
|
+
source_diags << source_diag(SOURCE_DIAG_MISSING_AUTHORITY_REF,
|
|
249
|
+
"authority.authority_ref is required and must be non-empty")
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
# authority_kind must be within proof/design scope
|
|
253
|
+
authority_kind = authority["authority_kind"]
|
|
254
|
+
unless SOURCE_ACCEPTED_AUTHORITY_KINDS.include?(authority_kind)
|
|
255
|
+
source_diags << source_diag(SOURCE_DIAG_INVALID_AUTHORITY_KIND,
|
|
256
|
+
"authority.authority_kind #{authority_kind.inspect} is outside proof/design scope; " \
|
|
257
|
+
"accepted: proof_only, design_accepted")
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
# canon_status must not be canon
|
|
261
|
+
canon_status = authority["canon_status"]
|
|
262
|
+
if canon_status == "canon"
|
|
263
|
+
source_diags << source_diag(SOURCE_DIAG_CANON_STATUS_FORBIDDEN,
|
|
264
|
+
"canon-status source envelopes are forbidden in this helper; " \
|
|
265
|
+
"authority.canon_status must not be 'canon'")
|
|
266
|
+
elsif !SOURCE_ACCEPTED_CANON_STATUSES.include?(canon_status)
|
|
267
|
+
source_diags << source_diag(SOURCE_DIAG_CANON_STATUS_FORBIDDEN,
|
|
268
|
+
"authority.canon_status #{canon_status.inspect} is not an accepted non-canon status; " \
|
|
269
|
+
"accepted: non_canon, accepted_design")
|
|
270
|
+
end
|
|
271
|
+
else
|
|
272
|
+
source_diags << source_diag(SOURCE_DIAG_MISSING_AUTHORITY,
|
|
273
|
+
"source envelope authority object is missing or not a Hash")
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
# Step 5 — nested registry must be present for direct registry sources.
|
|
277
|
+
registry_present = source_envelope["registry"].is_a?(Hash)
|
|
278
|
+
direct_registry_source = %w[proof_fixture caller_supplied].include?(source_mode)
|
|
279
|
+
if direct_registry_source && !registry_present
|
|
280
|
+
source_diags << source_diag(SOURCE_DIAG_MISSING_REGISTRY,
|
|
281
|
+
"source envelope must contain a 'registry' Hash; nested registry is missing or invalid")
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
# Step 6 — closed-surface assertions must all be false
|
|
285
|
+
envelope_assertions = source_envelope.fetch("closed_surface_assertions", nil)
|
|
286
|
+
if envelope_assertions.is_a?(Hash) && !envelope_assertions.values.all?(false)
|
|
287
|
+
open_keys = envelope_assertions.select { |_k, v| v }.keys
|
|
288
|
+
source_diags << source_diag(SOURCE_DIAG_SURFACE_OPEN,
|
|
289
|
+
"source envelope closed_surface_assertions must all be false; " \
|
|
290
|
+
"open assertions: #{open_keys.inspect}")
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
# If source envelope has any diagnostics, do NOT call nested registry validator.
|
|
294
|
+
if source_diags.any?
|
|
295
|
+
return build_source_result(false, source_mode, registry_present, source_diags, nil,
|
|
296
|
+
source_authority_summary(source_envelope))
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
case source_mode
|
|
300
|
+
when "profile_candidate"
|
|
301
|
+
return validate_profile_candidate_source(source_envelope, installed_boundaries: installed_boundaries)
|
|
302
|
+
when "pack_descriptor_candidate"
|
|
303
|
+
return validate_pack_descriptor_candidate_source(source_envelope,
|
|
304
|
+
installed_boundaries: installed_boundaries)
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
# Source envelope passed — call existing nested registry validator.
|
|
308
|
+
registry_result = validate(source_envelope["registry"], installed_boundaries: installed_boundaries)
|
|
309
|
+
source_valid = registry_result.fetch("valid")
|
|
310
|
+
|
|
311
|
+
build_source_result(source_valid, source_mode, true, [], registry_result,
|
|
312
|
+
source_authority_summary(source_envelope))
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
private
|
|
316
|
+
|
|
317
|
+
def validate_pack_descriptor_candidate_source(source_envelope, installed_boundaries:)
|
|
318
|
+
source_diags = pack_descriptor_candidate_diagnostics(source_envelope)
|
|
319
|
+
registry_present = source_envelope["registry"].is_a?(Hash)
|
|
320
|
+
|
|
321
|
+
if source_diags.any?
|
|
322
|
+
return build_source_result(false, "pack_descriptor_candidate", registry_present, source_diags, nil,
|
|
323
|
+
source_authority_summary(source_envelope))
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
registry_validation = registry_present ? validate(source_envelope["registry"], installed_boundaries: installed_boundaries) : nil
|
|
327
|
+
valid = registry_validation.nil? || registry_validation.fetch("valid")
|
|
328
|
+
|
|
329
|
+
build_source_result(valid, "pack_descriptor_candidate", registry_present, [], registry_validation,
|
|
330
|
+
source_authority_summary(source_envelope))
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
def validate_profile_candidate_source(source_envelope, installed_boundaries:)
|
|
334
|
+
source_diags = profile_candidate_schema_diagnostics(source_envelope)
|
|
335
|
+
pack_candidates = Array(source_envelope["pack_descriptor_candidates"])
|
|
336
|
+
pack_candidates.each { |pack| source_diags.concat(pack_descriptor_candidate_diagnostics(pack)) }
|
|
337
|
+
source_diags.concat(profile_authority_diagnostics(source_envelope, pack_candidates))
|
|
338
|
+
source_diags.concat(row_conflict_diagnostics(pack_candidates, source_envelope))
|
|
339
|
+
source_diags.concat(excluded_namespace_claim_diagnostics(pack_candidates))
|
|
340
|
+
|
|
341
|
+
if source_diags.any?
|
|
342
|
+
return build_source_result(false, "profile_candidate", false, source_diags, nil,
|
|
343
|
+
source_authority_summary(source_envelope))
|
|
344
|
+
end
|
|
345
|
+
|
|
346
|
+
derived_registry = derive_registry_from_profile(source_envelope, pack_candidates)
|
|
347
|
+
registry_result = validate(derived_registry, installed_boundaries: installed_boundaries)
|
|
348
|
+
build_source_result(registry_result.fetch("valid"), "profile_candidate", true, [],
|
|
349
|
+
registry_result, source_authority_summary(source_envelope))
|
|
350
|
+
end
|
|
351
|
+
|
|
352
|
+
def pack_descriptor_candidate_diagnostics(pack)
|
|
353
|
+
diags = []
|
|
354
|
+
unless pack.is_a?(Hash)
|
|
355
|
+
return [source_diag(SOURCE_DIAG_MISSING_PACK_DESCRIPTOR,
|
|
356
|
+
"pack_descriptor_candidate entries must be Hash objects")]
|
|
357
|
+
end
|
|
358
|
+
|
|
359
|
+
pack_ref = pack["pack_ref"].to_s
|
|
360
|
+
owner = pack["owner_pack_or_boundary"].to_s
|
|
361
|
+
if pack_ref.strip.empty?
|
|
362
|
+
diags << source_diag(SOURCE_DIAG_MISSING_PACK_REF,
|
|
363
|
+
"pack_descriptor_candidate must include non-empty pack_ref")
|
|
364
|
+
end
|
|
365
|
+
if owner.strip.empty?
|
|
366
|
+
diags << source_diag(DIAG_OWNER_BOUNDARY_ABSENT,
|
|
367
|
+
"pack_descriptor_candidate #{pack_ref.inspect} must include owner_pack_or_boundary")
|
|
368
|
+
end
|
|
369
|
+
unless pack["row_authority_policy"] == "pack_owns_declared_rows"
|
|
370
|
+
diags << source_diag(SOURCE_DIAG_INVALID_CONFLICT_POLICY,
|
|
371
|
+
"pack_descriptor_candidate #{pack_ref.inspect} must set row_authority_policy to " \
|
|
372
|
+
"'pack_owns_declared_rows'")
|
|
373
|
+
end
|
|
374
|
+
|
|
375
|
+
pack_rows(pack).each do |section, row_id, row|
|
|
376
|
+
next if owner.empty?
|
|
377
|
+
next if row["owner_pack_or_boundary"] == owner
|
|
378
|
+
|
|
379
|
+
diags << source_diag(SOURCE_DIAG_ROW_OWNER_MISMATCH,
|
|
380
|
+
"#{section} #{row_id.inspect} owner #{row["owner_pack_or_boundary"].inspect} " \
|
|
381
|
+
"does not match pack owner #{owner.inspect}")
|
|
382
|
+
end
|
|
383
|
+
|
|
384
|
+
diags.concat(excluded_namespace_claim_diagnostics([pack]))
|
|
385
|
+
diags.concat(row_conflict_diagnostics([pack], nil))
|
|
386
|
+
diags
|
|
387
|
+
end
|
|
388
|
+
|
|
389
|
+
def profile_candidate_schema_diagnostics(profile)
|
|
390
|
+
diags = []
|
|
391
|
+
if profile["profile_ref"].to_s.strip.empty?
|
|
392
|
+
diags << source_diag(SOURCE_DIAG_MISSING_PROFILE_REF,
|
|
393
|
+
"profile_candidate must include non-empty profile_ref")
|
|
394
|
+
end
|
|
395
|
+
unless profile["row_authority_policy"] == "pack_descriptor_rows_aggregated_by_profile"
|
|
396
|
+
diags << source_diag(SOURCE_DIAG_INVALID_CONFLICT_POLICY,
|
|
397
|
+
"profile_candidate must set row_authority_policy to " \
|
|
398
|
+
"'pack_descriptor_rows_aggregated_by_profile'")
|
|
399
|
+
end
|
|
400
|
+
unless profile["selected_pack_refs"].is_a?(Array) && profile["selected_pack_refs"].any?
|
|
401
|
+
diags << source_diag(SOURCE_DIAG_MISSING_SELECTED_PACK_REF,
|
|
402
|
+
"profile_candidate must include non-empty selected_pack_refs")
|
|
403
|
+
end
|
|
404
|
+
unless profile["pack_order"].is_a?(Array) && profile["pack_order"] == profile["selected_pack_refs"]
|
|
405
|
+
diags << source_diag(SOURCE_DIAG_INVALID_PACK_ORDER,
|
|
406
|
+
"profile_candidate pack_order must exactly match selected_pack_refs")
|
|
407
|
+
end
|
|
408
|
+
unless conflict_policy_rejects_duplicates?(profile["conflict_policy"])
|
|
409
|
+
diags << source_diag(SOURCE_DIAG_INVALID_CONFLICT_POLICY,
|
|
410
|
+
"profile_candidate conflict_policy must reject duplicate row ownership")
|
|
411
|
+
end
|
|
412
|
+
unless profile["pack_descriptor_candidates"].is_a?(Array)
|
|
413
|
+
diags << source_diag(SOURCE_DIAG_MISSING_PACK_DESCRIPTOR,
|
|
414
|
+
"profile_candidate must include pack_descriptor_candidates")
|
|
415
|
+
end
|
|
416
|
+
if profile.fetch("row_conflict_overrides", {}).is_a?(Hash) &&
|
|
417
|
+
profile.fetch("row_conflict_overrides", {}).any?
|
|
418
|
+
diags << source_diag(SOURCE_DIAG_PROFILE_OVERRIDE_FORBIDDEN,
|
|
419
|
+
"profile_candidate cannot override pack-row ownership conflicts")
|
|
420
|
+
end
|
|
421
|
+
diags
|
|
422
|
+
end
|
|
423
|
+
|
|
424
|
+
def profile_authority_diagnostics(profile, pack_candidates)
|
|
425
|
+
diags = []
|
|
426
|
+
selected = Array(profile["selected_pack_refs"])
|
|
427
|
+
pack_refs = pack_candidates.map { |pack| pack.is_a?(Hash) ? pack["pack_ref"] : nil }.compact
|
|
428
|
+
missing = selected - pack_refs
|
|
429
|
+
missing.each do |pack_ref|
|
|
430
|
+
diags << source_diag(SOURCE_DIAG_MISSING_SELECTED_PACK_REF,
|
|
431
|
+
"profile_candidate selected pack_ref #{pack_ref.inspect} was not supplied")
|
|
432
|
+
end
|
|
433
|
+
diags
|
|
434
|
+
end
|
|
435
|
+
|
|
436
|
+
def row_conflict_diagnostics(pack_candidates, profile)
|
|
437
|
+
diags = []
|
|
438
|
+
seen_rows = {}
|
|
439
|
+
seen_aliases = {}
|
|
440
|
+
|
|
441
|
+
selected_pack_refs = profile ? Array(profile["selected_pack_refs"]) : nil
|
|
442
|
+
selected_packs = pack_candidates.select do |pack|
|
|
443
|
+
pack.is_a?(Hash) && (selected_pack_refs.nil? || selected_pack_refs.include?(pack["pack_ref"]))
|
|
444
|
+
end
|
|
445
|
+
|
|
446
|
+
selected_packs.each do |pack|
|
|
447
|
+
pack_rows(pack).each do |section, row_id, row|
|
|
448
|
+
key = "#{section}:#{row_id}"
|
|
449
|
+
if seen_rows.key?(key)
|
|
450
|
+
diags << source_diag(SOURCE_DIAG_DUPLICATE_ROW_OWNERSHIP,
|
|
451
|
+
"#{key} claimed by #{seen_rows.fetch(key)} and #{pack["pack_ref"]}")
|
|
452
|
+
else
|
|
453
|
+
seen_rows[key] = pack["pack_ref"]
|
|
454
|
+
end
|
|
455
|
+
|
|
456
|
+
next unless section == "oof_descriptor"
|
|
457
|
+
|
|
458
|
+
Array(row["aliases"]).each do |ali|
|
|
459
|
+
if seen_aliases.key?(ali)
|
|
460
|
+
diags << source_diag(SOURCE_DIAG_DUPLICATE_ALIAS_OWNERSHIP,
|
|
461
|
+
"alias #{ali.inspect} claimed by #{seen_aliases.fetch(ali)} and #{pack["pack_ref"]}")
|
|
462
|
+
else
|
|
463
|
+
seen_aliases[ali] = pack["pack_ref"]
|
|
464
|
+
end
|
|
465
|
+
end
|
|
466
|
+
end
|
|
467
|
+
end
|
|
468
|
+
|
|
469
|
+
diags
|
|
470
|
+
end
|
|
471
|
+
|
|
472
|
+
def excluded_namespace_claim_diagnostics(pack_candidates)
|
|
473
|
+
pack_candidates.flat_map do |pack|
|
|
474
|
+
next [] unless pack.is_a?(Hash)
|
|
475
|
+
|
|
476
|
+
Array(pack["owned_oof_descriptors"]).flat_map do |row|
|
|
477
|
+
tokens = [row["code"], *Array(row["aliases"])].compact
|
|
478
|
+
tokens.flat_map do |token|
|
|
479
|
+
REQUIRED_EXCLUDED_PREFIXES.select { |prefix| token.start_with?(prefix) }.map do |prefix|
|
|
480
|
+
source_diag(SOURCE_DIAG_EXCLUDED_NAMESPACE_CLAIM,
|
|
481
|
+
"#{token.inspect} is under excluded namespace #{prefix.inspect}")
|
|
482
|
+
end
|
|
483
|
+
end
|
|
484
|
+
end
|
|
485
|
+
end
|
|
486
|
+
end
|
|
487
|
+
|
|
488
|
+
def derive_registry_from_profile(profile, pack_candidates)
|
|
489
|
+
selected = Array(profile["selected_pack_refs"])
|
|
490
|
+
packs_by_ref = pack_candidates.to_h { |pack| [pack.fetch("pack_ref"), pack] }
|
|
491
|
+
selected_packs = selected.map { |pack_ref| packs_by_ref.fetch(pack_ref) }
|
|
492
|
+
excluded_namespaces = profile["excluded_namespaces"] ||
|
|
493
|
+
profile.dig("registry", "excluded_namespaces") ||
|
|
494
|
+
REQUIRED_EXCLUDED_PREFIXES.map { |prefix| { "prefix" => prefix } }
|
|
495
|
+
|
|
496
|
+
{
|
|
497
|
+
"kind" => "oof_fragment_registry",
|
|
498
|
+
"format_version" => FORMAT_VERSION,
|
|
499
|
+
"source_authority" => source_authority_summary(profile).merge(
|
|
500
|
+
"profile_ref" => profile["profile_ref"],
|
|
501
|
+
"profile_authority_scope" => "selected_pack_set_order_conflict_policy",
|
|
502
|
+
"pack_row_authority_scope" => "row_identity_ownership"
|
|
503
|
+
),
|
|
504
|
+
"historical_source_refs" => Array(profile["historical_source_refs"]),
|
|
505
|
+
"migration_policy" => "derived_after_profile_pack_source_acceptance",
|
|
506
|
+
"forward_shape_authority" => "LANG-R121-A plus LANG-R122-I1",
|
|
507
|
+
"oof_descriptors" => selected_packs.flat_map { |pack| annotate_source_rows(pack, "owned_oof_descriptors") },
|
|
508
|
+
"fragment_rows" => selected_packs.flat_map { |pack| annotate_source_rows(pack, "owned_fragment_rows") },
|
|
509
|
+
"support_markers" => {
|
|
510
|
+
"invariant_support_markers" => selected_packs.flat_map { |pack| annotate_source_rows(pack, "owned_support_markers") }
|
|
511
|
+
},
|
|
512
|
+
"excluded_namespaces" => excluded_namespaces
|
|
513
|
+
}
|
|
514
|
+
end
|
|
515
|
+
|
|
516
|
+
def annotate_source_rows(pack, key)
|
|
517
|
+
Array(pack[key]).map do |row|
|
|
518
|
+
row.merge(
|
|
519
|
+
"row_authority" => {
|
|
520
|
+
"pack_ref" => pack["pack_ref"],
|
|
521
|
+
"authority_kind" => pack.dig("authority", "authority_kind"),
|
|
522
|
+
"canon_status" => pack.dig("authority", "canon_status")
|
|
523
|
+
}
|
|
524
|
+
)
|
|
525
|
+
end
|
|
526
|
+
end
|
|
527
|
+
|
|
528
|
+
def pack_rows(pack)
|
|
529
|
+
[
|
|
530
|
+
*Array(pack["owned_oof_descriptors"]).map { |row| ["oof_descriptor", row["code"], row] },
|
|
531
|
+
*Array(pack["owned_fragment_rows"]).map { |row| ["fragment_row", row["name"], row] },
|
|
532
|
+
*Array(pack["owned_support_markers"]).map { |row| ["support_marker", row["code"], row] }
|
|
533
|
+
].select { |_section, row_id, row| row_id && row.is_a?(Hash) }
|
|
534
|
+
end
|
|
535
|
+
|
|
536
|
+
def conflict_policy_rejects_duplicates?(policy)
|
|
537
|
+
return policy == "reject_duplicate_row_ownership" if policy.is_a?(String)
|
|
538
|
+
return false unless policy.is_a?(Hash)
|
|
539
|
+
|
|
540
|
+
%w[
|
|
541
|
+
duplicate_oof_descriptor
|
|
542
|
+
duplicate_fragment_row
|
|
543
|
+
duplicate_support_marker
|
|
544
|
+
duplicate_alias_owner
|
|
545
|
+
missing_selected_pack_ref
|
|
546
|
+
excluded_namespace
|
|
547
|
+
].all? { |key| policy[key] == "reject" }
|
|
548
|
+
end
|
|
549
|
+
|
|
550
|
+
def source_authority_summary(source_envelope)
|
|
551
|
+
authority = source_envelope.is_a?(Hash) ? source_envelope["authority"] : nil
|
|
552
|
+
return {} unless authority.is_a?(Hash)
|
|
553
|
+
|
|
554
|
+
{
|
|
555
|
+
"authority_ref" => authority["authority_ref"],
|
|
556
|
+
"authority_kind" => authority["authority_kind"],
|
|
557
|
+
"canon_status" => authority["canon_status"]
|
|
558
|
+
}
|
|
559
|
+
end
|
|
560
|
+
|
|
561
|
+
def check_top_level_shape(registry)
|
|
562
|
+
diags = []
|
|
563
|
+
|
|
564
|
+
unless registry.is_a?(Hash)
|
|
565
|
+
diags << diag(DIAG_WRONG_KIND, "registry must be a Hash, got #{registry.class}")
|
|
566
|
+
return diags
|
|
567
|
+
end
|
|
568
|
+
|
|
569
|
+
if registry["kind"] != "oof_fragment_registry"
|
|
570
|
+
diags << diag(DIAG_WRONG_KIND,
|
|
571
|
+
"registry.kind must be 'oof_fragment_registry', got #{registry["kind"].inspect}")
|
|
572
|
+
end
|
|
573
|
+
|
|
574
|
+
%w[oof_descriptors fragment_rows support_markers excluded_namespaces].each do |section|
|
|
575
|
+
unless registry.key?(section)
|
|
576
|
+
diags << diag(DIAG_MISSING_SECTION, "registry missing required section: #{section}")
|
|
577
|
+
end
|
|
578
|
+
end
|
|
579
|
+
|
|
580
|
+
if registry.key?("support_markers")
|
|
581
|
+
sm = registry["support_markers"]
|
|
582
|
+
unless sm.is_a?(Hash) && sm.key?("invariant_support_markers") &&
|
|
583
|
+
sm["invariant_support_markers"].is_a?(Array)
|
|
584
|
+
diags << diag(DIAG_MISSING_SECTION,
|
|
585
|
+
"registry.support_markers.invariant_support_markers must be an Array")
|
|
586
|
+
end
|
|
587
|
+
end
|
|
588
|
+
|
|
589
|
+
diags
|
|
590
|
+
end
|
|
591
|
+
|
|
592
|
+
def check_oof_descriptors(descriptors, excluded_prefixes, all_canonical_codes)
|
|
593
|
+
diags = []
|
|
594
|
+
seen_codes = Set.new
|
|
595
|
+
seen_aliases = {} # alias → canonical code that owns it
|
|
596
|
+
|
|
597
|
+
descriptors.each do |desc|
|
|
598
|
+
code = desc["code"]
|
|
599
|
+
next unless code
|
|
600
|
+
|
|
601
|
+
# Support marker codes must not appear in oof_descriptors
|
|
602
|
+
if code.match?(SUPPORT_MARKER_PATTERN)
|
|
603
|
+
diags << diag(DIAG_SUPPORT_MARKER_IN_DESCRIPTORS,
|
|
604
|
+
"#{code.inspect} matches support marker pattern (PINV-*/TINV-*) and must not " \
|
|
605
|
+
"appear in oof_descriptors; place under support_markers.invariant_support_markers")
|
|
606
|
+
next
|
|
607
|
+
end
|
|
608
|
+
|
|
609
|
+
# Duplicate canonical code
|
|
610
|
+
if seen_codes.include?(code)
|
|
611
|
+
diags << diag(DIAG_DUPLICATE_CODE, "duplicate OOF descriptor code: #{code.inspect}")
|
|
612
|
+
else
|
|
613
|
+
seen_codes.add(code)
|
|
614
|
+
end
|
|
615
|
+
|
|
616
|
+
# Excluded namespace prefix
|
|
617
|
+
excluded_prefixes.each do |prefix|
|
|
618
|
+
if code.start_with?(prefix)
|
|
619
|
+
diags << diag(DIAG_EXCLUDED_NAMESPACE_COLLISION,
|
|
620
|
+
"OOF descriptor code #{code.inspect} is in excluded namespace #{prefix.inspect}")
|
|
621
|
+
end
|
|
622
|
+
end
|
|
623
|
+
|
|
624
|
+
# Alias checks.
|
|
625
|
+
# Rule: the same alias must not be claimed by two different descriptors.
|
|
626
|
+
# Note: an alias may match a canonical descriptor code when that descriptor
|
|
627
|
+
# is a compatibility alias (deprecated_by pointing back to this descriptor).
|
|
628
|
+
# This is the standard backward-compatibility pattern and is allowed.
|
|
629
|
+
Array(desc["aliases"]).each do |ali|
|
|
630
|
+
if seen_aliases.key?(ali)
|
|
631
|
+
diags << diag(DIAG_ALIAS_COLLISION,
|
|
632
|
+
"alias #{ali.inspect} claimed by both #{code.inspect} and #{seen_aliases[ali].inspect}")
|
|
633
|
+
else
|
|
634
|
+
seen_aliases[ali] = code
|
|
635
|
+
end
|
|
636
|
+
end
|
|
637
|
+
|
|
638
|
+
# Deprecated descriptors must name replacement
|
|
639
|
+
if desc["deprecated"]
|
|
640
|
+
rc = desc["replacement_code"]
|
|
641
|
+
db = desc["deprecated_by"]
|
|
642
|
+
if rc.nil? && db.nil?
|
|
643
|
+
diags << diag(DIAG_ALIAS_MISSING_REPLACEMENT,
|
|
644
|
+
"deprecated descriptor #{code.inspect} must set replacement_code or deprecated_by")
|
|
645
|
+
elsif rc && !all_canonical_codes.include?(rc)
|
|
646
|
+
diags << diag(DIAG_ALIAS_MISSING_REPLACEMENT,
|
|
647
|
+
"descriptor #{code.inspect} replacement_code #{rc.inspect} not found among canonical codes")
|
|
648
|
+
end
|
|
649
|
+
end
|
|
650
|
+
end
|
|
651
|
+
|
|
652
|
+
diags
|
|
653
|
+
end
|
|
654
|
+
|
|
655
|
+
def check_fragment_rows(rows)
|
|
656
|
+
diags = []
|
|
657
|
+
|
|
658
|
+
rows.each do |row|
|
|
659
|
+
name = row["name"]
|
|
660
|
+
next unless name
|
|
661
|
+
|
|
662
|
+
case name
|
|
663
|
+
when OOF_ROW_NAME
|
|
664
|
+
if row["loadable"] == true
|
|
665
|
+
diags << diag(DIAG_OOF_PROJECTION_LOADABLE,
|
|
666
|
+
"fragment row 'oof' must not be loadable " \
|
|
667
|
+
"(status-primary/status-only projection; blocked, non-loadable, capability-free)")
|
|
668
|
+
end
|
|
669
|
+
if row["capability"] == true
|
|
670
|
+
diags << diag(DIAG_OOF_PROJECTION_CAPABILITY,
|
|
671
|
+
"fragment row 'oof' must not have capability: true (capability-free by design)")
|
|
672
|
+
end
|
|
673
|
+
|
|
674
|
+
when *GUARDED_NON_FRAGMENT_NAMES
|
|
675
|
+
ck = row["classification_kind"]
|
|
676
|
+
unless ck == "not_fragment_class"
|
|
677
|
+
diags << diag(DIAG_GUARDED_NON_FRAGMENT,
|
|
678
|
+
"fragment row #{name.inspect} is a guarded non-fragment; " \
|
|
679
|
+
"classification_kind must be 'not_fragment_class', got #{ck.inspect}")
|
|
680
|
+
end
|
|
681
|
+
if row["loadable"] == true
|
|
682
|
+
diags << diag(DIAG_GUARDED_NON_FRAGMENT,
|
|
683
|
+
"guarded non-fragment row #{name.inspect} must not be loadable")
|
|
684
|
+
end
|
|
685
|
+
end
|
|
686
|
+
end
|
|
687
|
+
|
|
688
|
+
diags
|
|
689
|
+
end
|
|
690
|
+
|
|
691
|
+
def check_support_markers(markers, oof_code_set)
|
|
692
|
+
diags = []
|
|
693
|
+
|
|
694
|
+
markers.each do |marker|
|
|
695
|
+
code = marker["code"]
|
|
696
|
+
next unless code
|
|
697
|
+
|
|
698
|
+
# Code must not collide with any OOF descriptor code or alias
|
|
699
|
+
if oof_code_set.include?(code)
|
|
700
|
+
diags << diag(DIAG_SUPPORT_MARKER_CODE_COLLISION,
|
|
701
|
+
"support marker code #{code.inspect} collides with OOF descriptor code or alias; " \
|
|
702
|
+
"support marker codes must be distinct from public OOF codes")
|
|
703
|
+
end
|
|
704
|
+
|
|
705
|
+
# Must be non-public
|
|
706
|
+
stability = marker["public_code_stability"]
|
|
707
|
+
unless SUPPORT_MARKER_STABILITY_VALUES.include?(stability)
|
|
708
|
+
diags << diag(DIAG_SUPPORT_MARKER_PUBLIC,
|
|
709
|
+
"support marker #{code.inspect} has public_code_stability #{stability.inspect}; " \
|
|
710
|
+
"must be non_public_support_marker or proof_only (support markers are non-public)")
|
|
711
|
+
end
|
|
712
|
+
|
|
713
|
+
# Must not be emitted (no blocking_oof status_class, no emitted lifecycle_state)
|
|
714
|
+
if marker["lifecycle_state"] == "emitted" || marker["status_class"] == "blocking_oof"
|
|
715
|
+
diags << diag(DIAG_SUPPORT_MARKER_EMITTED,
|
|
716
|
+
"support marker #{code.inspect} must not be emitted as a public diagnostic; " \
|
|
717
|
+
"lifecycle_state/status_class must not indicate emission")
|
|
718
|
+
end
|
|
719
|
+
end
|
|
720
|
+
|
|
721
|
+
diags
|
|
722
|
+
end
|
|
723
|
+
|
|
724
|
+
def check_excluded_namespaces(namespaces)
|
|
725
|
+
diags = []
|
|
726
|
+
present = namespaces.map { |n| n["prefix"] }.compact.to_set
|
|
727
|
+
|
|
728
|
+
REQUIRED_EXCLUDED_PREFIXES.each do |required|
|
|
729
|
+
unless present.include?(required)
|
|
730
|
+
diags << diag(DIAG_MISSING_SECTION,
|
|
731
|
+
"excluded_namespaces must include required prefix #{required.inspect}; " \
|
|
732
|
+
"(compiler_profile_contract.* and compiler_profile_contract_refusal.* " \
|
|
733
|
+
"are always excluded from OOF namespace)")
|
|
734
|
+
end
|
|
735
|
+
end
|
|
736
|
+
|
|
737
|
+
diags
|
|
738
|
+
end
|
|
739
|
+
|
|
740
|
+
def source_diag(code, message)
|
|
741
|
+
{ "code" => code, "message" => message }
|
|
742
|
+
end
|
|
743
|
+
|
|
744
|
+
def build_source_result(valid, source_mode, registry_present, source_diags, registry_validation,
|
|
745
|
+
source_authority = {})
|
|
746
|
+
{
|
|
747
|
+
"kind" => "oof_fragment_registry_source_validation",
|
|
748
|
+
"format_version" => "0.1.0",
|
|
749
|
+
"valid" => valid,
|
|
750
|
+
"source_mode" => source_mode,
|
|
751
|
+
"registry_present" => registry_present,
|
|
752
|
+
"source_authority" => source_authority,
|
|
753
|
+
"source_diagnostics" => source_diags,
|
|
754
|
+
"registry_validation" => registry_validation,
|
|
755
|
+
"closed_surface_assertions" => {
|
|
756
|
+
"static_data_file" => false,
|
|
757
|
+
"lib_igniter_lang_rb_require" => false,
|
|
758
|
+
"compiler_pass_integration" => false,
|
|
759
|
+
"public_api_cli" => false,
|
|
760
|
+
"top_level_report_diagnostics" => false,
|
|
761
|
+
"compiler_result_field" => false,
|
|
762
|
+
"loader_report" => false,
|
|
763
|
+
"compatibility_report" => false,
|
|
764
|
+
"runtime_behavior" => false,
|
|
765
|
+
"igapp_mutation" => false,
|
|
766
|
+
"specs_canon_proposals" => false
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
end
|
|
770
|
+
|
|
771
|
+
def diag(code, message)
|
|
772
|
+
{ "code" => code, "message" => message }
|
|
773
|
+
end
|
|
774
|
+
|
|
775
|
+
def build_result(valid, diags, inactive_rows)
|
|
776
|
+
{
|
|
777
|
+
"kind" => "oof_fragment_registry_validation",
|
|
778
|
+
"format_version" => FORMAT_VERSION,
|
|
779
|
+
"valid" => valid,
|
|
780
|
+
"registry_service_present" => true,
|
|
781
|
+
"checked_sections" => %w[
|
|
782
|
+
oof_descriptors
|
|
783
|
+
fragment_rows
|
|
784
|
+
support_markers.invariant_support_markers
|
|
785
|
+
excluded_namespaces
|
|
786
|
+
],
|
|
787
|
+
"diagnostics" => diags,
|
|
788
|
+
"inactive_rows" => inactive_rows,
|
|
789
|
+
"closed_surface_assertions" => {
|
|
790
|
+
"compiler_integration" => false,
|
|
791
|
+
"public_api_cli" => false,
|
|
792
|
+
"top_level_report_diagnostics" => false,
|
|
793
|
+
"compiler_result_field" => false,
|
|
794
|
+
"loader_report" => false,
|
|
795
|
+
"compatibility_report" => false,
|
|
796
|
+
"runtime_behavior" => false,
|
|
797
|
+
"igapp_mutation" => false
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
end
|
|
801
|
+
end
|
|
802
|
+
end
|