{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://quailpages.com/schema/site-building/qp.site.json",
  "title": "Quail Pages Site Contract",
  "description": "Describes the best current factual understanding of the complete site to be built: organization, mandate, audiences, persuasion, information architecture and page decisions, materials, imagery/media strategy, requirements, governance, and provenance. It should contain enough grounded site knowledge for a downstream builder to build the complete site rather than merely propose it, while leaving actual SiteDocument and PageDocument authoring to the canonical Quail Pages authoring schema. It normally works alongside a separate Quail Pages Style Guide.",
  "$comment": "Authoritative guidance: This contract describes site knowledge and intent, not authored QP documents and not the brand. Unknown, inferred, assumed, and conflicting knowledge remain explicit. The page plan may include pages inferred or recommended from ordinary site needs when supported by the mandate, audiences, journeys, trust/contact needs, content, or operational/privacy requirements. Page-plan entries record what should exist and why; they MUST NOT become a shadow PageDocument schema. The downstream builder uses qp.schema.json to author actual SiteDocument and PageDocument structures. Do not invent business facts, legal claims, or data practices. Brand rules belong in the Style Guide. Downstream builders should build the complete planned site, not treat the page plan as a suggestion for a representative subset. Imagery/media is build knowledge, not an assumed later population step. Record authorized or possible media sources, useful discovered assets/references, page/content media needs, and unresolved gaps. For ports/migrations, preserve source-site media knowledge so Site/Create does not need to recrawl merely to identify available imagery. The downstream builder may expand selections within established sourcing authority, but material planned media must not be silently omitted. The absence of supplied media does not imply that a site should be text-only. When imagery materially supports the site, Discovery must establish an authorized sourcing strategy—supplied assets, existing-site assets, stock, generated media, screenshots, or another approved source—and carry that strategy into this contract. Candidate assets are recommendations, not mandatory placement decisions. Exact selection, composition, placement, optimization, and final context-sensitive alt text remain Site/Create responsibilities.",
  "type": "object",
  "additionalProperties": false,
  "properties": {
    "version": {
      "const": "0.4",
      "description": "Identifies the V0.4 site contract vocabulary when a producer chooses to record a version."
    },
    "organization": {
      "$ref": "#/$defs/organization"
    },
    "mandate": {
      "$ref": "#/$defs/mandate"
    },
    "audiences": {
      "type": "array",
      "description": "People the site may need to serve, kept separate from what the site is intended to accomplish.",
      "items": {
        "$ref": "#/$defs/audience"
      }
    },
    "persuasion": {
      "$ref": "#/$defs/persuasion"
    },
    "structure": {
      "$ref": "#/$defs/structure"
    },
    "materials": {
      "$ref": "#/$defs/materials"
    },
    "imageryMedia": {
      "$ref": "#/$defs/imageryMedia",
      "description": "Sourcing authority, discovered media inventory/references, planned media needs, and unresolved gaps for downstream execution."
    },
    "requirements": {
      "$ref": "#/$defs/requirements"
    },
    "styleGuide": {
      "$ref": "#/$defs/styleGuide"
    },
    "creativeConstraints": {
      "$ref": "#/$defs/creativeConstraints"
    },
    "governance": {
      "$ref": "#/$defs/governance"
    },
    "epistemics": {
      "$ref": "#/$defs/epistemics"
    }
  },
  "$defs": {
    "nonEmptyString": {
      "type": "string",
      "minLength": 1
    },
    "stringList": {
      "type": "array",
      "items": {
        "$ref": "#/$defs/nonEmptyString"
      },
      "uniqueItems": true
    },
    "id": {
      "type": "string",
      "minLength": 1,
      "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$"
    },
    "organization": {
      "type": "object",
      "description": "Known facts about the organization or person the site represents, including boundaries that prevent attractive but false positioning.",
      "additionalProperties": false,
      "properties": {
        "name": {
          "$ref": "#/$defs/nonEmptyString"
        },
        "legalName": {
          "$ref": "#/$defs/nonEmptyString"
        },
        "description": {
          "$ref": "#/$defs/nonEmptyString"
        },
        "history": {
          "$ref": "#/$defs/nonEmptyString"
        },
        "operatingModel": {
          "$ref": "#/$defs/nonEmptyString"
        },
        "locations": {
          "$ref": "#/$defs/stringList"
        },
        "relationships": {
          "$ref": "#/$defs/stringList"
        },
        "doesNotDo": {
          "$ref": "#/$defs/stringList",
          "description": "Explicit negative truth: services, roles, promises, or identities that must not be attributed to the organization."
        }
      }
    },
    "mandate": {
      "type": "object",
      "description": "Why the site exists or needs to change and what evidence would indicate useful progress.",
      "additionalProperties": false,
      "properties": {
        "currentSituation": {
          "$ref": "#/$defs/nonEmptyString"
        },
        "problem": {
          "$ref": "#/$defs/nonEmptyString"
        },
        "desiredChange": {
          "$ref": "#/$defs/nonEmptyString"
        },
        "successSignals": {
          "$ref": "#/$defs/stringList"
        },
        "constraints": {
          "$ref": "#/$defs/stringList"
        },
        "nonGoals": {
          "$ref": "#/$defs/stringList"
        }
      }
    },
    "audience": {
      "type": "object",
      "description": "One audience and the real situation in which that audience encounters the site.",
      "additionalProperties": false,
      "properties": {
        "id": {
          "$ref": "#/$defs/id"
        },
        "name": {
          "$ref": "#/$defs/nonEmptyString"
        },
        "priority": {
          "enum": [
            "primary",
            "secondary",
            "supporting"
          ]
        },
        "description": {
          "$ref": "#/$defs/nonEmptyString"
        },
        "visitSituations": {
          "$ref": "#/$defs/stringList"
        },
        "needs": {
          "$ref": "#/$defs/stringList"
        },
        "barriers": {
          "$ref": "#/$defs/stringList"
        },
        "desiredActions": {
          "$ref": "#/$defs/stringList"
        },
        "currentBeliefs": {
          "$ref": "#/$defs/stringList"
        },
        "desiredBeliefs": {
          "$ref": "#/$defs/stringList"
        }
      }
    },
    "persuasion": {
      "type": "object",
      "description": "Actions, beliefs, objections, claims, and evidence that explain what the site must communicate without collapsing truth into presentation.",
      "additionalProperties": false,
      "properties": {
        "desiredActions": {
          "$ref": "#/$defs/stringList"
        },
        "currentBeliefs": {
          "$ref": "#/$defs/stringList"
        },
        "desiredBeliefs": {
          "$ref": "#/$defs/stringList"
        },
        "objections": {
          "$ref": "#/$defs/stringList"
        },
        "alternatives": {
          "$ref": "#/$defs/stringList"
        },
        "claims": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/claim"
          }
        },
        "evidence": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/evidence"
          }
        }
      }
    },
    "claim": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "id": {
          "$ref": "#/$defs/id"
        },
        "statement": {
          "$ref": "#/$defs/nonEmptyString"
        },
        "importance": {
          "enum": [
            "high",
            "medium",
            "low"
          ]
        },
        "support": {
          "enum": [
            "supported",
            "partially-supported",
            "unsupported",
            "disputed"
          ]
        },
        "evidenceIds": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/id"
          },
          "uniqueItems": true
        }
      }
    },
    "evidence": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "id": {
          "$ref": "#/$defs/id"
        },
        "description": {
          "$ref": "#/$defs/nonEmptyString"
        },
        "sourceIds": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/id"
          },
          "uniqueItems": true
        }
      }
    },
    "structure": {
      "type": "object",
      "description": "Known and planned information architecture and content relationships. It records existing state plus the reasoned page-set decisions the site should actually fulfill, distinguishing explicit requirements from inferred or recommended completeness decisions without redefining the Quail Pages PageDocument authoring schema.",
      "additionalProperties": false,
      "properties": {
        "existingPages": {
          "$ref": "#/$defs/stringList"
        },
        "knownContent": {
          "$ref": "#/$defs/stringList"
        },
        "requiredContent": {
          "$ref": "#/$defs/stringList"
        },
        "navigationConstraints": {
          "$ref": "#/$defs/stringList"
        },
        "relationships": {
          "$ref": "#/$defs/stringList"
        },
        "pagePlan": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/pageDecision"
          },
          "description": "Reasoned decisions about the pages the site should contain. These entries describe what should exist and why; they are site knowledge, not alternate PageDocument definitions. The downstream builder authors actual pages through the canonical QP authoring schema."
        }
      }
    },
    "materials": {
      "type": "object",
      "description": "Available content and media inputs, their condition, permissions, and gaps. Possessing an asset does not by itself make it evidence for a claim. Use imageryMedia for build-oriented sourcing authority, useful media inventory/references, page/media needs, and downstream handoff context.",
      "additionalProperties": false,
      "properties": {
        "content": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/material"
          }
        },
        "media": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/material"
          },
          "description": "Generic available media inputs recorded as materials. Build-oriented media sourcing authority, selective inventory, intended uses, handoff disposition, and page/content media needs belong in imageryMedia. Avoid duplicating the same detailed asset knowledge in both places."
        },
        "gaps": {
          "$ref": "#/$defs/stringList"
        }
      }
    },
    "material": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "id": {
          "$ref": "#/$defs/id"
        },
        "description": {
          "$ref": "#/$defs/nonEmptyString"
        },
        "location": {
          "$ref": "#/$defs/nonEmptyString"
        },
        "condition": {
          "$ref": "#/$defs/nonEmptyString"
        },
        "permission": {
          "enum": [
            "cleared",
            "restricted",
            "unknown"
          ]
        },
        "notes": {
          "$ref": "#/$defs/nonEmptyString"
        }
      }
    },
    "requirements": {
      "type": "object",
      "description": "Operational, legal, accessibility, privacy, integration, and delivery constraints the eventual work must honor.",
      "additionalProperties": false,
      "properties": {
        "functional": {
          "$ref": "#/$defs/stringList"
        },
        "accessibility": {
          "$ref": "#/$defs/stringList"
        },
        "legal": {
          "$ref": "#/$defs/stringList"
        },
        "privacy": {
          "$ref": "#/$defs/stringList",
          "description": "Known privacy/data-handling requirements and practices relevant to the site, including forms, analytics, cookies, accounts, commerce, integrations, or other data collection when established. Preserve unknowns; do not invent legal or operational claims."
        },
        "integrations": {
          "$ref": "#/$defs/stringList"
        },
        "delivery": {
          "$ref": "#/$defs/stringList"
        }
      }
    },
    "governance": {
      "type": "object",
      "description": "Who may decide, approve, maintain, and exercise creative judgment, including limits on that authority.",
      "additionalProperties": false,
      "properties": {
        "decisionMakers": {
          "$ref": "#/$defs/stringList"
        },
        "approvers": {
          "$ref": "#/$defs/stringList"
        },
        "maintainers": {
          "$ref": "#/$defs/stringList"
        },
        "creativeAuthority": {
          "$ref": "#/$defs/nonEmptyString"
        },
        "reviewConstraints": {
          "$ref": "#/$defs/stringList"
        }
      }
    },
    "epistemics": {
      "type": "object",
      "description": "Provenance and uncertainty for the knowledge above. Annotations use JSON Pointers so domain values remain readable while their status and sources remain explicit.",
      "additionalProperties": false,
      "properties": {
        "sources": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/source"
          }
        },
        "annotations": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/annotation"
          }
        },
        "assumptions": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/knowledgeIssue"
          }
        },
        "conflicts": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/knowledgeIssue"
          }
        },
        "openQuestions": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/knowledgeIssue"
          }
        }
      }
    },
    "source": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "id": {
          "$ref": "#/$defs/id"
        },
        "label": {
          "$ref": "#/$defs/nonEmptyString"
        },
        "location": {
          "$ref": "#/$defs/nonEmptyString"
        },
        "kind": {
          "enum": [
            "user",
            "document",
            "website",
            "dataset",
            "observation",
            "other"
          ]
        },
        "authority": {
          "$ref": "#/$defs/nonEmptyString"
        },
        "notes": {
          "$ref": "#/$defs/nonEmptyString"
        }
      }
    },
    "annotation": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "path": {
          "type": "string",
          "pattern": "^(|/.*)$",
          "description": "JSON Pointer to the knowledge value being qualified."
        },
        "status": {
          "enum": [
            "known",
            "inferred",
            "assumed",
            "conflicting"
          ]
        },
        "sourceIds": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/id"
          },
          "uniqueItems": true
        },
        "note": {
          "$ref": "#/$defs/nonEmptyString"
        }
      }
    },
    "knowledgeIssue": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "path": {
          "type": "string",
          "pattern": "^(|/.*)$"
        },
        "statement": {
          "$ref": "#/$defs/nonEmptyString"
        },
        "impact": {
          "enum": [
            "high",
            "medium",
            "low"
          ]
        },
        "sourceIds": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/id"
          },
          "uniqueItems": true
        }
      }
    },
    "styleGuide": {
      "type": "object",
      "description": "Relationship to the separate Quail Pages Style Guide that governs brand identity and visual language for this site. A full site build normally consumes both contracts; this object identifies the intended style-guide input without duplicating it.",
      "additionalProperties": false,
      "properties": {
        "reference": {
          "$ref": "#/$defs/nonEmptyString",
          "description": "URI, path, identifier, or other resolvable reference to the applicable style guide."
        },
        "status": {
          "enum": [
            "available",
            "needed",
            "not-applicable",
            "unknown"
          ]
        },
        "notes": {
          "$ref": "#/$defs/stringList"
        }
      }
    },
    "creativeConstraints": {
      "type": "object",
      "description": "Site-specific creative constraints or exceptions that materially affect this site without redefining the underlying brand. Reusable brand rules belong in the Style Guide.",
      "additionalProperties": false,
      "properties": {
        "requirements": {
          "$ref": "#/$defs/stringList"
        },
        "exceptions": {
          "$ref": "#/$defs/stringList"
        },
        "avoid": {
          "$ref": "#/$defs/stringList"
        },
        "notes": {
          "$ref": "#/$defs/stringList"
        }
      }
    },
    "pageDecision": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "name": {
          "$ref": "#/$defs/nonEmptyString",
          "description": "Human-readable identity of the page or destination being considered."
        },
        "origin": {
          "enum": [
            "explicit",
            "existing-preserved",
            "inferred",
            "recommended"
          ],
          "description": "Why this page is in the plan; origin records provenance, not priority."
        },
        "purpose": {
          "$ref": "#/$defs/nonEmptyString",
          "description": "What job this page should perform for the organization or its audiences."
        },
        "rationale": {
          "$ref": "#/$defs/nonEmptyString",
          "description": "Why this page belongs in the site based on the known mandate, audiences, journeys, content, trust/contact needs, or operational requirements."
        },
        "readiness": {
          "enum": [
            "ready",
            "build-with-known-gaps",
            "blocked"
          ],
          "description": "Whether current grounded site knowledge is sufficient for the downstream builder to author the page without inventing consequential facts. Material media needs should be represented through imageryMedia; readiness must not assume an undocumented later imagery pass. As guidance, a page should not normally be considered fully ready when imagery is materially important to its purpose or brand expression while corresponding media needs remain source-needed, permission-needed, or blocked; exceptions may exist and should remain explicit rather than becoming rigid schema validation."
        },
        "notes": {
          "$ref": "#/$defs/stringList"
        }
      }
    },
    "imageryMedia": {
      "type": "object",
      "additionalProperties": false,
      "description": "Build-oriented imagery/media knowledge. It carries enough sourcing authority, discovered asset context, and intended usage for Site/Create to execute without repeating discovery; it is not a PageDocument schema. Inventory is intentionally selective: preserve material, reusable, or source-defining media and enough context to avoid repeat discovery, not every decorative asset. A need can be ready with an authorized source strategy even when Discovery does not choose the final asset.",
      "properties": {
        "strategy": {
          "$ref": "#/$defs/nonEmptyString"
        },
        "sources": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/mediaSource"
          }
        },
        "inventory": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/mediaAsset"
          }
        },
        "needs": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/mediaNeed"
          }
        },
        "gaps": {
          "$ref": "#/$defs/stringList"
        }
      }
    },
    "mediaSource": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "id": {
          "$ref": "#/$defs/id"
        },
        "kind": {
          "enum": [
            "supplied",
            "existing-site",
            "screenshot-capture",
            "stock",
            "generated",
            "other"
          ]
        },
        "location": {
          "$ref": "#/$defs/nonEmptyString"
        },
        "permission": {
          "enum": [
            "cleared",
            "restricted",
            "unknown"
          ]
        },
        "authority": {
          "$ref": "#/$defs/nonEmptyString",
          "description": "What downstream work is authorized to do with media from this source."
        },
        "notes": {
          "$ref": "#/$defs/stringList"
        }
      }
    },
    "mediaAsset": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "id": {
          "$ref": "#/$defs/id"
        },
        "sourceId": {
          "$ref": "#/$defs/id"
        },
        "reference": {
          "$ref": "#/$defs/nonEmptyString",
          "description": "Durable path, URL, uploaded-file reference, or other resolvable asset reference."
        },
        "description": {
          "$ref": "#/$defs/nonEmptyString"
        },
        "subjects": {
          "$ref": "#/$defs/stringList"
        },
        "suggestedUses": {
          "$ref": "#/$defs/stringList"
        },
        "condition": {
          "$ref": "#/$defs/nonEmptyString"
        },
        "permission": {
          "enum": [
            "cleared",
            "restricted",
            "unknown"
          ]
        },
        "filename": {
          "$ref": "#/$defs/nonEmptyString",
          "description": "Preserved or normalized filename when useful for downstream handling."
        },
        "altContext": {
          "$ref": "#/$defs/nonEmptyString",
          "description": "Grounded factual context about what the asset depicts or communicates, preserved for downstream accessibility authoring. This is not final alt text: Site/Create authors final alt based on the asset's actual placement and communicative purpose."
        },
        "notes": {
          "$ref": "#/$defs/stringList"
        },
        "handoff": {
          "$ref": "#/$defs/mediaHandoff",
          "description": "Expected or authorized downstream treatment of this discovered asset. This carries execution intent without prescribing page composition."
        }
      }
    },
    "mediaNeed": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "target": {
          "$ref": "#/$defs/nonEmptyString",
          "description": "Planned page, destination, section purpose, or site-wide context needing media."
        },
        "purpose": {
          "$ref": "#/$defs/nonEmptyString"
        },
        "preferredSourceIds": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/id"
          },
          "uniqueItems": true,
          "description": "Authorized/preferred sources Site/Create may use to fulfill this need. A ready need may name a source without naming a specific candidate asset when Discovery has provided enough authority and context for Site/Create to make the final selection."
        },
        "candidateAssetIds": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/id"
          },
          "uniqueItems": true,
          "description": "Discovered assets that appear relevant to this need. These are candidates, not mandates; Site/Create may choose among them or select another asset from an authorized source."
        },
        "status": {
          "enum": [
            "ready",
            "source-needed",
            "acquisition-needed",
            "permission-needed",
            "blocked"
          ],
          "description": "ready means Site/Create has sufficient grounded context and sourcing authority to fulfill this media need and is expected to account for it in the authored site. A ready need must not be silently omitted merely because placement is creative; intentional non-use should be an explicit build decision. Other statuses identify the remaining sourcing, acquisition, permission, or blocking work."
        },
        "notes": {
          "$ref": "#/$defs/stringList"
        }
      }
    },
    "mediaHandoff": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "mode": {
          "enum": [
            "copy",
            "reference",
            "acquire",
            "capture",
            "regenerate"
          ],
          "description": "How Site/Create is expected or authorized to turn the discovered reference into build media. regenerate is valid only when the applicable source authority permits generated media."
        },
        "preferredPath": {
          "$ref": "#/$defs/nonEmptyString",
          "description": "Optional preferred site-relative destination or naming direction for the build asset; Site/Create may normalize it when execution requires."
        },
        "preserveOriginal": {
          "type": "boolean",
          "description": "Whether the discovered/original asset should be retained alongside any optimized or transformed build derivative."
        },
        "notes": {
          "$ref": "#/$defs/stringList"
        }
      }
    }
  }
}
