{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://github.com/Imbad0202/academic-research-skills/shared/contracts/passport/constraint_violation.schema.json",
  "title": "Material Passport Constraint Violation Entry",
  "description": "One entry in Material Passport Schema 9 constraint_violations[] aggregate. Per uncited claim that violates a manifest negative constraint, emitted by claim_ref_alignment_audit_agent §4 step 5 stream (d). Rides in its own aggregate because constraint violations on uncited claims are real HIGH-WARN gate-refuse blockers, but claim_audit_result.ref_slug is required and uncited_assertion is LOW-WARN advisory only — routing through uncited_assertions[] would silently downgrade a HIGH-WARN signal; routing through claim_audit_result would require a sentinel ref_slug. Drives the [HIGH-WARN-CONSTRAINT-VIOLATION-UNCITED ({violated_constraint_id})] formatter annotation; gate-refuse at the formatter hard gate alongside the other four HIGH-WARN classes. Cross-field invariants CV-INV-1..CV-INV-4 are lint-enforced in scripts/check_claim_audit_consistency.py. See docs/design/2026-05-15-issue-103-claim-alignment-audit-spec.md §3.5.",

  "type": "object",
  "additionalProperties": false,

  "required": [
    "finding_id",
    "claim_text",
    "section_path",
    "violated_constraint_id",
    "scoped_manifest_id",
    "judge_verdict",
    "rationale",
    "judge_model",
    "judge_run_at",
    "rule_version"
  ],

  "properties": {
    "finding_id": {
      "type": "string",
      "pattern": "^CV-[0-9]{3,}$",
      "description": "Per-passport unique finding identifier for constraint_violations[]. CV-INV-1 enforces uniqueness."
    },
    "claim_text": { "type": "string", "minLength": 1, "maxLength": 2000 },
    "section_path": {
      "type": "string",
      "minLength": 1,
      "description": "Hierarchical path from document root to the section containing the offending sentence."
    },
    "violated_constraint_id": {
      "type": "string",
      "pattern": "^(NC-C[0-9]{3,}-[0-9]+|MNC-[0-9]+)$",
      "description": "Identifier of the violated constraint. NC-C{n}-{m} = claim-level (per INV-17 canonical parse rule); MNC-{m} = manifest-level. CV-INV-2 enforces cross-array resolvability against the active manifest."
    },
    "scoped_manifest_id": {
      "type": "string",
      "pattern": "^M-[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z-[0-9a-f]{4}$",
      "description": "Points to the claim_intent_manifest.manifest_id owning the violated constraint. Required (no MANIFEST-MISSING sentinel admitted — constraints require an active manifest to exist)."
    },
    "manifest_claim_id": {
      "type": ["string", "null"],
      "pattern": "^C-[0-9]{3,}$",
      "description": "For NC-C{n}-{m} (claim-level constraint), the parent claim_id from the manifest. For MNC-{m} (global), null. CV-INV-3 enforces polarity match with the constraint id prefix."
    },
    "judge_verdict": {
      "const": "VIOLATED",
      "description": "Frozen literal: only VIOLATED outcomes from the §4 negative-constraint judge prompt emit an entry here. NOT_VIOLATED outcomes produce no entry."
    },
    "rationale": { "type": "string", "minLength": 1, "maxLength": 2000 },
    "judge_model": { "type": "string", "minLength": 1 },
    "judge_run_at": { "type": "string", "format": "date-time" },
    "rule_version": {
      "const": "D4-a-v1",
      "description": "Frozen literal for the v3.8.0 release. Future rule revisions bump the constant and require re-lint."
    },
    "upstream_owner_agent": {
      "enum": ["synthesis_agent", "draft_writer_agent", "report_compiler_agent", null]
    }
  }
}
