{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://pif-spec.github.io/pif/v0.2/preflight-assertion.schema.json",
  "title": "PIF PreflightAssertion v0.2",
  "description": "The output of a preflight assessment against a WorkflowDescription. The audit-defensible artifact. PIF (Preflight Interchange Format) v0.2. Cumulative validation: accepts v0.1 documents (pif_version: \"0.1\") with the v0.1 required set, and v0.2 documents (pif_version: \"0.2\") with the v0.2 tightened required set (adds out_of_scope and clarifying_questions per the spec.md 'honesty by architecture' principle, closing a v0.1 schema-vs-prose drift identified during eval).",
  "type": "object",
  "required": [
    "pif_version",
    "assertion_id",
    "workflow_ref",
    "produced_by",
    "produced_at",
    "risk_classification",
    "applicable_requirements",
    "missing_controls",
    "assumptions_made",
    "verification_steps",
    "status"
  ],
  "allOf": [
    {
      "if": {
        "properties": { "pif_version": { "const": "0.2" } },
        "required": ["pif_version"]
      },
      "then": {
        "required": ["out_of_scope", "clarifying_questions"]
      }
    }
  ],
  "properties": {
    "pif_version": {
      "type": "string",
      "enum": ["0.1", "0.2"],
      "description": "Version of the PIF spec this document conforms to. v0.2 schemas accept v0.1 documents for cumulative validation; v0.2 producers SHOULD emit \"0.2\"."
    },
    "pif_corpus_version": {
      "type": "string",
      "minLength": 1,
      "maxLength": 200,
      "description": "OPTIONAL but RECOMMENDED. Opaque identifier for the corpus snapshot the producer validated this assertion against (e.g., 'preclari-eu-gmp-2026-w20'). New in v0.2. Producers emitting any regulatory_domains or missing_controls token outside the v0.2.0 recommended-token list SHOULD populate this so consumers can reproduce 'unrecognised token' advisories deterministically. Consumers MUST NOT interpret the value; it is an audit-trail field. Empty string is rejected — a semantically empty audit-trail string is worse than omitting the field."
    },
    "assertion_id": {
      "type": "string",
      "pattern": "^[a-zA-Z0-9_:.-]{4,128}$",
      "description": "Stable identifier for this assertion."
    },
    "workflow_ref": {
      "type": "string",
      "description": "The workflow_id of the WorkflowDescription this assertion was produced against."
    },
    "workflow_snapshot": {
      "$ref": "workflow-description.schema.json",
      "description": "Optional embedded snapshot of the WorkflowDescription at the time of assessment."
    },
    "produced_by": {
      "type": "object",
      "required": ["tool", "version"],
      "properties": {
        "tool": { "type": "string" },
        "version": { "type": "string" },
        "methodology": { "type": "string" },
        "tier": {
          "type": "string",
          "enum": ["requested_access", "builder", "practice", "team", "private"]
        },
        "workspace_id": { "type": "string" },
        "provider": { "type": "string" },
        "model_used": { "type": "string" },
        "model_assignments": {
          "type": "object",
          "additionalProperties": { "type": "string" }
        },
        "entitlements": {
          "type": "object",
          "properties": {
            "jurisdictions": { "type": "array", "items": { "type": "string" } },
            "gxp_domains": { "type": "array", "items": { "type": "string" } },
            "premium_features": { "type": "array", "items": { "type": "string" } }
          }
        }
      }
    },
    "produced_at": {
      "type": "string",
      "format": "date-time"
    },
    "corpus_snapshot": {
      "type": "object",
      "description": "Per-snapshot detail (id, hash, date, source_count). Complements the envelope-level pif_corpus_version added in v0.2.",
      "properties": {
        "snapshot_id": { "type": "string" },
        "snapshot_hash": {
          "type": "string",
          "pattern": "^sha256:[a-f0-9]{64}$"
        },
        "snapshot_date": { "type": "string", "format": "date-time" },
        "source_count": { "type": "integer", "minimum": 0 }
      }
    },
    "risk_classification": {
      "type": "object",
      "required": ["level", "rationale"],
      "properties": {
        "level": { "type": "string", "enum": ["low", "medium", "high", "critical"] },
        "rationale": { "type": "string", "minLength": 20 },
        "drivers": { "type": "array", "items": { "type": "string" } }
      }
    },
    "gxp_domains_identified": {
      "type": "array",
      "items": {
        "type": "string",
        "enum": [
          "GMP", "GDP", "GCP", "GLP", "GVP",
          "CSV", "data_integrity", "quality_systems",
          "regulatory_affairs", "pharmacovigilance", "labeling"
        ]
      },
      "uniqueItems": true,
      "deprecated": true,
      "description": "DEPRECATED in v0.2: use regulatory_domains_identified instead. Retained for cumulative validation of v0.1 documents. v0.2 producers MUST NOT emit; consumers MUST accept from v0.1 documents and treat values as equivalent to gxp:-prefixed regulatory_domains_identified entries."
    },
    "regulatory_domains_identified": {
      "type": "array",
      "items": { "$ref": "#/$defs/RegulatoryDomainCurie" },
      "uniqueItems": true,
      "description": "Regulatory framework / domain CURIEs the preflight identified as relevant. May differ from the workflow's regulatory_domains_self_declared. Format <namespace>:<token> per the closed namespace list plus open token pattern; see §Vocabulary in spec.md. Consumers preparing canonical forms for signing MUST lexicographically sort the array."
    },
    "applicable_requirements": {
      "type": "array",
      "items": { "$ref": "#/$defs/RequirementProjection" },
      "description": "Requirements identified as applicable to this workflow, with applicability basis and source provenance."
    },
    "missing_controls": {
      "type": "array",
      "items": {
        "type": "object",
        "required": ["control", "rationale"],
        "properties": {
          "control": { "$ref": "#/$defs/ControlIdentifier" },
          "rationale": { "type": "string" },
          "criticality": {
            "type": "string",
            "enum": ["required", "recommended", "advisable"]
          },
          "related_requirements": {
            "type": "array",
            "items": { "type": "string" }
          }
        }
      }
    },
    "assumptions_made": {
      "type": "array",
      "items": {
        "type": "object",
        "required": ["assumption", "impact_if_wrong"],
        "properties": {
          "assumption": { "type": "string" },
          "impact_if_wrong": { "type": "string" },
          "basis": { "type": "string" }
        }
      },
      "description": "Inferences the preflight made when WorkflowDescription fields were missing or ambiguous. Honesty-by-architecture."
    },
    "clarifying_questions": {
      "type": "array",
      "items": {
        "type": "object",
        "required": ["question", "would_change"],
        "properties": {
          "question": { "type": "string" },
          "would_change": { "type": "string" }
        }
      },
      "description": "Questions that, if answered, would materially change the output."
    },
    "out_of_scope": {
      "type": "array",
      "items": {
        "type": "object",
        "required": ["topic", "reason"],
        "properties": {
          "topic": { "type": "string" },
          "reason": { "type": "string" }
        }
      },
      "description": "Topics, jurisdictions, or considerations that were explicitly NOT addressed. Required for v0.2 documents (spec.md §honesty by architecture; closed v0.1 schema-vs-prose drift)."
    },
    "superseded_or_excluded": {
      "type": "array",
      "items": {
        "type": "object",
        "required": ["source", "reason"],
        "properties": {
          "source": { "$ref": "#/$defs/SourceReference" },
          "reason": { "type": "string" }
        }
      }
    },
    "cross_jurisdiction_conflicts": {
      "type": "array",
      "items": {
        "type": "object",
        "required": ["topic", "jurisdictions_involved", "nature_of_conflict"],
        "properties": {
          "topic": { "type": "string" },
          "jurisdictions_involved": { "type": "array", "items": { "type": "string" } },
          "nature_of_conflict": {
            "type": "string",
            "enum": ["divergent_requirements", "differing_thresholds", "scope_disagreement", "procedural_difference"]
          },
          "summary": { "type": "string" },
          "related_requirements": { "type": "array", "items": { "type": "string" } }
        }
      }
    },
    "verification_steps": {
      "type": "array",
      "items": {
        "type": "object",
        "required": ["step", "type"],
        "properties": {
          "step": { "type": "string" },
          "type": {
            "type": "string",
            "enum": ["source_check", "requirement_lookup", "expert_review", "internal_policy_check", "external_validation"]
          },
          "applies_to": { "type": "string" }
        }
      }
    },
    "reasoning_trace": {
      "type": "object",
      "properties": {
        "decision_points": {
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "decision": { "type": "string" },
              "alternatives_considered": { "type": "array", "items": { "type": "string" } },
              "basis_for_choice": { "type": "string" },
              "confidence": {
                "type": "string",
                "enum": ["high", "medium", "low"]
              }
            }
          }
        },
        "uncertainty_sources": {
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "source": { "type": "string" },
              "type": {
                "type": "string",
                "enum": ["model_uncertainty", "corpus_gap", "ambiguous_input", "contested_interpretation"]
              }
            }
          }
        }
      }
    },
    "recommendation": { "type": "string" },
    "reviewer_actions": {
      "type": "array",
      "items": {
        "type": "object",
        "required": ["reviewer", "action", "timestamp"],
        "properties": {
          "reviewer": { "type": "string" },
          "action": {
            "type": "string",
            "enum": ["approved", "overridden", "flagged", "annotated", "rejected"]
          },
          "timestamp": { "type": "string", "format": "date-time" },
          "applies_to": { "type": "string" },
          "note": { "type": "string" },
          "override_value": {}
        }
      }
    },
    "status": {
      "type": "string",
      "enum": ["draft", "in_review", "approved", "contested", "superseded"]
    },
    "supersedes": { "type": "string" },
    "superseded_by": { "type": "string" },
    "signature": {
      "type": "object",
      "properties": {
        "algorithm": {
          "type": "string",
          "enum": ["Ed25519", "RSA-PSS-SHA256", "ECDSA-P256-SHA256"]
        },
        "public_key_id": { "type": "string" },
        "signature_value": { "type": "string" },
        "canonicalization": {
          "type": "string",
          "enum": ["JCS-RFC8785"]
        }
      }
    },
    "notice": { "type": "string" },
    "extensions": {
      "type": "object",
      "description": "Implementation-specific extensions. Field names should be prefixed with the implementing tool (e.g., 'preclari:custom_field')."
    }
  },
  "additionalProperties": false,
  "$defs": {
    "RegulatoryDomainCurie": {
      "type": "string",
      "maxLength": 128,
      "description": "A semi-closed CURIE in the format <namespace>:<token>, OR the reserved literal 'none_claimed'. Namespace MUST be lowercase. Tokens are case-preserving and case-sensitive. No whitespace; no multiple colons (use . or _ for hierarchy); US-ASCII only. 'ext' namespace is reserved for vendor-internal frameworks.",
      "oneOf": [
        { "const": "none_claimed" },
        {
          "type": "string",
          "pattern": "^(gxp|ich|iso|iec|imdrf|nist|eu_mdr|eu_ivdr|eu_ai_act|eu_eba|eu_ecb|eu_dora|eu_csrd|eu_cs3d|eu_eudr|eu_battery_regulation|eu_green_claims|eu_nis2|eu_cra|eu_data_act|eu_dsa|eu_dma|eu_mica|eu_gdpr|eu_sfdr|us_fda_21cfr_803|us_fda_21cfr_820|us_fda_21cfr_11|us_fda_samd|us_hhs_hipaa|us_frb_sr_11_7|uk_mhra|jp_pmda|ch_swissmedic|ca_health_canada|au_tga|mdcg|gamp5|iso27001|iso_42001|iso_23894|iso_14971|iec_62304|nist_sp800_53|ext):[a-zA-Z0-9_.-]+$"
        }
      ]
    },
    "ControlIdentifier": {
      "type": "string",
      "maxLength": 128,
      "description": "A control identifier. Accepts either: (a) v0.1 legacy free-text shape (e.g., 'audit_trail', 'human_approval_gate', '21CFR_part_11', 'audit-trail') — DEPRECATED in v0.2, scheduled for removal in v0.3; or (b) v0.2 CURIE shape <namespace>:<token> (e.g., 'iso27001:A.8.1.1', 'gamp5:appendix_m4'). The free-text branch accepts the permissive shape v0.1 had no schema-level constraint on, narrowed only to exclude colons (so the two branches are mutually exclusive). v0.2 producers SHOULD emit CURIE form. The semantic distinction from regulatory_domains tokens: control identifiers name individual controls within a framework; regulatory_domains tokens name frameworks/scopes the workflow operates under.",
      "pattern": "^(([A-Za-z0-9_][A-Za-z0-9_.\\- ]*)|((gxp|ich|iso|iec|imdrf|nist|eu_mdr|eu_ivdr|eu_ai_act|eu_eba|eu_ecb|eu_dora|eu_csrd|eu_cs3d|eu_eudr|eu_battery_regulation|eu_green_claims|eu_nis2|eu_cra|eu_data_act|eu_dsa|eu_dma|eu_mica|eu_gdpr|eu_sfdr|us_fda_21cfr_803|us_fda_21cfr_820|us_fda_21cfr_11|us_fda_samd|us_hhs_hipaa|us_frb_sr_11_7|uk_mhra|jp_pmda|ch_swissmedic|ca_health_canada|au_tga|mdcg|gamp5|iso27001|iso_42001|iso_23894|iso_14971|iec_62304|nist_sp800_53|ext):[a-zA-Z0-9_.-]+))$"
    },
    "SourceReference": {
      "type": "object",
      "required": ["url"],
      "properties": {
        "url": { "type": "string", "format": "uri" },
        "canonical_document_id": { "type": "string" },
        "title": { "type": "string" },
        "issuing_authority": { "type": "string" },
        "jurisdiction": { "type": "string" },
        "document_type": {
          "type": "string",
          "enum": ["guidance", "regulation", "directive", "guideline", "annex", "qa", "reflection_paper", "inspection_guide", "other"]
        },
        "section_anchor": { "type": "string" },
        "effective_date": { "type": "string", "format": "date" },
        "superseded_date": { "type": "string", "format": "date" },
        "retrieved_at": { "type": "string", "format": "date-time" },
        "content_hash": {
          "type": "string",
          "pattern": "^sha256:[a-f0-9]{64}$"
        },
        "excerpt": { "type": "string", "maxLength": 1000 }
      }
    },
    "RequirementProjection": {
      "type": "object",
      "required": ["requirement_id", "requirement_text", "source", "applicability_basis", "confidence"],
      "properties": {
        "requirement_id": { "type": "string" },
        "requirement_text": { "type": "string" },
        "source": { "$ref": "#/$defs/SourceReference" },
        "applicability_basis": {
          "type": "string",
          "minLength": 20
        },
        "confidence": {
          "type": "string",
          "enum": ["high", "medium", "low", "contested"]
        },
        "conditional_applicability": {
          "type": "boolean",
          "description": "OPTIONAL. New in v0.2 (from eval n=1 finding #2). When true, signals that this requirement's applicability is CONDITIONAL on one or more assumptions in assumptions_made resolving in a specific direction. When false or absent, the requirement is unconditionally applicable given the workflow's declared attributes. This boolean decouples assumption-driven retrieval from precision torpedoing: a preflight that surfaces a requirement on a hedged assumption can flag the requirement as conditional without weakening its visibility. Consumers SHOULD treat conditional_applicability=true requirements as belonging to a 'verify the assumption first' branch rather than the directly-applicable branch."
        },
        "jurisdictional_scope": {
          "type": "array",
          "items": { "type": "string" }
        },
        "interpretation_notes": { "type": "string" },
        "related_requirements": {
          "type": "array",
          "items": { "type": "string" }
        }
      }
    }
  }
}
