{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://gtmjosh.com/schema/context-layer-entry.schema.json",
  "title": "CRM AI context layer entry",
  "description": "One entry in a CRM AI context layer. A `field` entry explains what a CRM field means and how far to trust it. A `rule` entry carries a business rule the agent must apply. Both share the governance envelope so an entry can be reviewed, versioned, retired, and traced back to the failure that caused it. Reference: https://gtmjosh.com/guides/context-layer-rule-schema",

  "type": "object",
  "required": ["id", "kind", "title", "appliesTo", "status"],
  "unevaluatedProperties": false,

  "properties": {
    "id": {
      "type": "string",
      "pattern": "^[A-Za-z][A-Za-z0-9_-]*$",
      "description": "Stable handle. Never reused, never renumbered. Referenced by dependsOn, run logs, and golden questions."
    },
    "kind": {
      "enum": ["field", "rule"],
      "description": "`field` describes a CRM field. `rule` carries an instruction."
    },
    "title": {
      "type": "string",
      "minLength": 12,
      "maxLength": 160,
      "description": "The retrieval key. In a phased design the agent decides whether to fetch the body from this line alone, so state the condition under which the entry matters rather than the category it belongs to. 'Opportunity exclusions' is a folder name. 'Exclude system-generated and test opportunities from all pipeline and win-rate reporting' is a trigger."
    },
    "category": {
      "enum": [
        "definition",
        "interpretation",
        "authority",
        "freshness",
        "exclusion",
        "attribution",
        "temporal",
        "currency",
        "write_policy",
        "output_shape",
        "anti_pattern",
        "cross_object_dependency",
        "security"
      ],
      "description": "What job the entry does. `exclusion`, `anti_pattern`, and `security` entries must always load — an agent that does not know an exclusion exists cannot decide to fetch it."
    },
    "appliesTo": {
      "type": "array",
      "minItems": 1,
      "items": { "type": "string" },
      "description": "Objects this entry governs, as API names. Always an array: one rule commonly spans several objects, and encoding that in prose leaves it invisible to the retrieval layer. Use [\"*\"] for entries that apply to every question."
    },
    "dependsOn": {
      "type": "array",
      "items": { "type": "string" },
      "description": "Entry ids to load automatically alongside this one. A definition that assumes an exclusion has been applied declares that here rather than hoping both get retrieved."
    },
    "alwaysLoad": {
      "type": "boolean",
      "default": false,
      "description": "Ship this entry with every question regardless of scope. Correct for exclusions, hygiene, and safety rules; unnecessary for interpretation, which is safe to fetch on demand."
    },

    "status": {
      "enum": ["draft", "approved", "active", "deprecated"],
      "description": "Lifecycle state. Only `active` entries reach a production agent."
    },
    "version": {
      "type": "integer",
      "minimum": 1,
      "description": "Increment on any change to meaning or text, so a regression is attributable to a revision rather than to the entry in general."
    },
    "priority": {
      "type": "integer",
      "description": "Conflict resolution. Higher wins. Entries will contradict each other; decide the outcome deliberately instead of inheriting sort order."
    },
    "effectiveFrom": { "type": "string", "format": "date" },
    "expiresOn": {
      "type": "string",
      "format": "date",
      "description": "For entries that are only correct inside a window, such as a fiscal-period definition."
    },
    "reviewAfter": {
      "type": "string",
      "format": "date",
      "description": "Freshness SLA for the entry itself, separate from the freshness of the CRM field it describes."
    },
    "replacedBy": {
      "type": "string",
      "description": "Entry id that supersedes this one. Set alongside status: deprecated."
    },

    "owner": { "type": "string", "description": "Team or person accountable for the entry." },
    "evidence": {
      "type": "string",
      "description": "Why this is true: a Finance definition, an automation that force-sets the field, a policy document, an operating agreement. Makes review possible by someone who was not in the room."
    },
    "originFailure": {
      "type": "string",
      "description": "The logged wrong answer that caused this entry to exist. Most entries past the first handful should have one."
    },
    "testIds": {
      "type": "array",
      "items": { "type": "string" },
      "description": "Golden questions that fail without this entry and pass with it."
    },
    "lastReviewed": { "type": "string", "format": "date" },
    "updated": { "type": "string", "format": "date" },
    "notes": { "type": "string" }
  },

  "oneOf": [
    {
      "title": "Field entry",
      "properties": {
        "kind": { "const": "field" },
        "property": { "type": "string", "description": "Field or property API name." },
        "object": { "type": "string" },
        "label": { "type": "string", "description": "Human-facing label, when it differs from the API name." },
        "meaning": { "type": "string", "description": "What the field means, in plain language, including who or what writes it." },
        "interpretation": {
          "type": "array",
          "items": { "type": "string" },
          "description": "How to read the values: thresholds, bands, what each stored option signifies."
        },
        "valueMap": {
          "type": "object",
          "additionalProperties": { "type": "string" },
          "description": "Stored value to meaning, for dropdowns where the API value differs from the UI label."
        },
        "source": {
          "enum": ["human_entered", "system_derived", "integration_written", "formula", "ai_generated"],
          "description": "How the value gets there. These decay and fail differently, which a single authoritative flag collapses. `formula` matters twice: a formula field can be perfectly authoritative and still impossible to group or filter on. `ai_generated` should almost always pair with authoritative: false."
        },
        "authoritative": {
          "oneOf": [{ "type": "boolean" }, { "const": "human_owned" }],
          "description": "Whether to trust it. `human_owned` is the third state that binary trust misses: read it, compare against it, report agreement or disagreement with reasoning, never overwrite it."
        },
        "writePolicy": {
          "enum": ["read_only", "agent_writable", "force_set_by_automation", "human_owned"],
          "description": "What happens if an agent writes. `force_set_by_automation` is the dangerous one: the update returns 200 and reverts, so the recommendation looks applied and is not."
        },
        "freshness": {
          "type": "string",
          "description": "How current the value is and how stale is too stale for a decision. State whether it is a live value or a point-in-time snapshot."
        },
        "primarySource": {
          "type": "boolean",
          "description": "False for convenience rollups and summaries. Anything load-bearing should be confirmed against a primary record."
        },
        "scopeCaveats": {
          "type": "string",
          "description": "Where the meaning does not hold: specific record types, regions, currencies, or fiscal periods."
        }
      },
      "required": ["property", "meaning"]
    },
    {
      "title": "Rule entry",
      "properties": {
        "kind": { "const": "rule" },
        "ruleText": {
          "type": "string",
          "description": "The instruction the agent receives. Phrase exclusions as pre-flight checks to perform ('Before any query on this object, add these filters: ...'), never as descriptive facts ('Test records are excluded from reporting'), which get read and ignored."
        },
        "filterFragment": {
          "type": "string",
          "description": "The literal query predicate, where the rule has one. Prose definitions get re-derived on every run and mis-derived on some of them; a predicate is deterministic."
        },
        "fieldAllowlist": {
          "type": "array",
          "items": { "type": "string" },
          "description": "For allowed-object entries: the fields an agent may read. This is a semantic policy about what is worth sending, and never a substitute for platform field-level security. Both must exist and agree."
        },
        "outputShape": {
          "type": "array",
          "items": { "type": "string" },
          "description": "For output_shape entries: the numbered structure a recurring answer must follow, so an agent-built report matches the hand-built one every time."
        }
      },
      "required": ["ruleText"]
    }
  ]
}
