{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://smartproperty-admin-guide.pages.dev/api/v1/schema.json",
  "title": "SmartProperty Admin Guide retrieval index (v1)",
  "description": "Machine-readable index of published administrator documentation. Served as static files; there is no query interface. Every url is relative to meta.base_url. Only content whose Status is 'Published' in the source Sheets appears. This schema validates articles.json; see 'resources' for the other files and the def that validates each.",
  "type": "object",
  "required": [
    "articles",
    "faqs",
    "glossary",
    "workflow_guides",
    "releases",
    "meta"
  ],
  "properties": {
    "articles": {
      "type": "array",
      "description": "Evergreen reference: 'How do I X?'. Stable across releases.",
      "items": {
        "type": "object",
        "required": [
          "id",
          "title",
          "module",
          "roles",
          "actions",
          "url",
          "content",
          "content_hash",
          "summary"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Google Doc ID. Stable across retitles, unlike url."
          },
          "title": {
            "type": "string"
          },
          "module": {
            "type": "string"
          },
          "roles": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "actions": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "url": {
            "type": "string",
            "description": "Site-relative. Prepend meta.base_url."
          },
          "last_reviewed": {
            "type": "string"
          },
          "content": {
            "type": "string",
            "description": "Article body as Markdown. Images are asset links, never inline data: URIs."
          },
          "content_hash": {
            "type": "string",
            "description": "Changes only when content changes. Use it to skip re-indexing."
          },
          "summary": {
            "type": "string",
            "description": "One-line gist, derived from the body. Enough to choose a document without fetching it."
          },
          "audience": {
            "type": "string",
            "description": "All, Association or Club. SmartProperty Admin content is never built into this site, so that value can never appear here."
          },
          "also_in": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Secondary modules this article is cross-listed under. One canonical url; these are additional shelves, not copies."
          }
        }
      }
    },
    "faqs": {
      "type": "array",
      "items": {
        "type": "object",
        "required": [
          "id",
          "question",
          "module",
          "roles",
          "answer",
          "content_hash",
          "summary"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Derived from the question text; churns if the question is rewritten."
          },
          "question": {
            "type": "string"
          },
          "module": {
            "type": "string"
          },
          "roles": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "answer": {
            "type": "string"
          },
          "content_hash": {
            "type": "string"
          },
          "summary": {
            "type": "string"
          }
        }
      }
    },
    "glossary": {
      "type": "array",
      "description": "Canonical terminology. do_not_use lists terms that must not appear on any surface.",
      "items": {
        "type": "object",
        "required": [
          "id",
          "term",
          "definition",
          "content_hash"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Derived from the term; churns if the term is renamed."
          },
          "term": {
            "type": "string"
          },
          "definition": {
            "type": "string"
          },
          "do_not_use": {
            "type": "string"
          },
          "content_hash": {
            "type": "string"
          }
        }
      }
    },
    "workflow_guides": {
      "type": "array",
      "description": "End-to-end guides spanning several modules, including the per-role onboarding paths.",
      "items": {
        "type": "object",
        "required": [
          "id",
          "title",
          "roles",
          "modules",
          "url",
          "content",
          "content_hash",
          "summary"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Google Doc ID. Stable across retitles."
          },
          "title": {
            "type": "string"
          },
          "roles": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "modules": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "url": {
            "type": "string"
          },
          "content": {
            "type": "string"
          },
          "content_hash": {
            "type": "string"
          },
          "summary": {
            "type": "string",
            "description": "One-line gist, derived from the body. Enough to choose a document without fetching it."
          }
        }
      }
    },
    "releases": {
      "type": "array",
      "description": "Dated, immutable release records. Not articles: a release note is a point-in-time record, an article is evergreen.",
      "items": {
        "type": "object",
        "required": [
          "id",
          "effective_date",
          "versions",
          "categories",
          "action_required",
          "summary",
          "url",
          "content_hash"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "effective_date": {
            "type": "string"
          },
          "versions": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "categories": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "action_required": {
            "type": "boolean"
          },
          "summary": {
            "type": "string"
          },
          "url": {
            "type": "string"
          },
          "content_hash": {
            "type": "string"
          }
        }
      }
    },
    "meta": {
      "type": "object",
      "required": [
        "schema_version",
        "base_url",
        "content_hash",
        "api"
      ],
      "properties": {
        "schema_version": {
          "type": "string",
          "description": "Bumped only for a breaking shape change."
        },
        "base_url": {
          "type": "string",
          "description": "Read from site_url in mkdocs.yml. Never hardcode a host; read this."
        },
        "content_hash": {
          "type": "string",
          "description": "Digest of every content block. One comparison answers 'has anything changed?'."
        },
        "releases_coverage": {
          "type": "object",
          "description": "How far back the release record is complete. Before complete_since, absence of a release is not evidence there was none.",
          "properties": {
            "complete_since": {
              "type": "string"
            },
            "latest": {
              "type": [
                "string",
                "null"
              ]
            },
            "count": {
              "type": "integer"
            }
          }
        },
        "api": {
          "type": "object",
          "description": "Discovery. Present in every resource so a consumer that only knows one path can find the rest without being told where to look. resources.document is a URI template taking an entry id.",
          "required": [
            "current",
            "schema",
            "resources"
          ],
          "properties": {
            "current": {
              "type": "string"
            },
            "schema": {
              "type": "string"
            },
            "discovery": {
              "type": "string"
            },
            "resources": {
              "type": "object",
              "required": [
                "catalog",
                "corpus",
                "document",
                "glossary",
                "releases"
              ],
              "properties": {
                "catalog": {
                  "type": "string"
                },
                "corpus": {
                  "type": "string"
                },
                "document": {
                  "type": "string"
                },
                "glossary": {
                  "type": "string"
                },
                "releases": {
                  "type": "string"
                }
              }
            },
            "legacy": {
              "type": "object",
              "description": "Unversioned paths still served. status \"supported\" means no removal date; withdrawal is a coordinated deprecation."
            }
          }
        }
      }
    }
  },
  "resources": {
    "articles.json": {
      "path": "/api/v1/articles.json",
      "validates": "this schema (the root)",
      "use": "The whole corpus in one fetch. Bulk indexing."
    },
    "catalog.json": {
      "path": "/api/v1/catalog.json",
      "validates": "#/$defs/catalog",
      "use": "Titles, summaries, roles and coverage. No bodies. Keep this resident and use it to decide what to read. Filter on roles, which you know from the asking user. Do not require a module first: modules are an authoring taxonomy and a question does not announce which one it belongs to."
    },
    "articles/<id>.json": {
      "path": "/api/v1/articles/<id>.json",
      "validates": "#/$defs/document",
      "use": "One document, fetched once the catalog has chosen it. <id> is the id the catalog carries. An id that stops resolving means the content was unpublished."
    },
    "glossary.json": {
      "path": "/api/v1/glossary.json",
      "validates": "#/$defs/glossaryFile",
      "use": "Canonical vocabulary. This is not retrieval material: load it into a consumer prompt so it governs phrasing everywhere, not only in answers drawn from documentation. do_not_use lists terms that must not appear on any surface."
    },
    "releases.json": {
      "path": "/api/v1/releases.json",
      "validates": "#/$defs/releasesFile",
      "use": "The timeline, for \"what changed\" questions. meta.releases_coverage bounds the claim: before complete_since, absence is not evidence."
    },
    "index.json": {
      "path": "/api/index.json",
      "validates": "not schema-bound",
      "use": "Discovery root. Secondary to meta.api, which is embedded in every resource and is what an existing consumer will actually encounter."
    },
    "articles.json (unversioned)": {
      "path": "/api/articles.json",
      "validates": "this schema (the root)",
      "use": "The original path, byte-identical to /api/v1/articles.json. It has a live consumer and is supported with no removal date. Every field it carried before the versioned contract still exists with the same name, type and value; everything added is additive."
    },
    "terminology.json": {
      "path": "/api/v1/terminology.json",
      "validates": "not schema-bound",
      "use": "Club vocabulary rules, extracted from the product's own terminology pipe. Content in this index is written in Association vocabulary. A consumer answering a Club customer MUST apply these rules before speaking, or it will use words the customer does not see on screen. Apply in order and honour the guards."
    }
  },
  "$defs": {
    "catalog": {
      "type": "object",
      "required": [
        "entries",
        "coverage",
        "meta"
      ],
      "properties": {
        "entries": {
          "type": "array",
          "items": {
            "type": "object",
            "required": [
              "id",
              "kind",
              "title",
              "summary",
              "roles"
            ],
            "properties": {
              "id": {
                "type": "string"
              },
              "kind": {
                "type": "string",
                "description": "article | workflow_guide | faq"
              },
              "title": {
                "type": "string"
              },
              "summary": {
                "type": "string"
              },
              "roles": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "module": {
                "type": "string"
              },
              "modules": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "actions": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "url": {
                "type": "string"
              },
              "last_reviewed": {
                "type": "string"
              }
            }
          }
        },
        "coverage": {
          "type": "object",
          "description": "What the guide contains. Without this a consumer cannot tell \"no article exists\" from \"I failed to find it\", and the second is where an agent starts inventing procedures.",
          "required": [
            "articles",
            "by_module",
            "note"
          ],
          "properties": {
            "articles": {
              "type": "integer"
            },
            "workflow_guides": {
              "type": "integer"
            },
            "faqs": {
              "type": "integer"
            },
            "glossary_terms": {
              "type": "integer"
            },
            "releases": {
              "type": "integer"
            },
            "by_module": {
              "type": "object"
            },
            "note": {
              "type": "string"
            }
          }
        },
        "meta": {
          "type": "object"
        }
      }
    },
    "document": {
      "type": "object",
      "required": [
        "id",
        "kind",
        "title",
        "content",
        "content_hash",
        "base_url",
        "schema_version"
      ],
      "properties": {
        "id": {
          "type": "string"
        },
        "kind": {
          "type": "string"
        },
        "title": {
          "type": "string"
        },
        "summary": {
          "type": "string"
        },
        "content": {
          "type": "string"
        },
        "content_hash": {
          "type": "string"
        },
        "url": {
          "type": "string"
        },
        "base_url": {
          "type": "string",
          "description": "Repeated here so a document is self-contained when fetched alone."
        },
        "schema_version": {
          "type": "string"
        }
      }
    },
    "glossaryFile": {
      "type": "object",
      "required": [
        "glossary",
        "meta"
      ],
      "properties": {
        "glossary": {
          "type": "array",
          "description": "Canonical terminology. do_not_use lists terms that must not appear on any surface.",
          "items": {
            "type": "object",
            "required": [
              "id",
              "term",
              "definition",
              "content_hash"
            ],
            "properties": {
              "id": {
                "type": "string",
                "description": "Derived from the term; churns if the term is renamed."
              },
              "term": {
                "type": "string"
              },
              "definition": {
                "type": "string"
              },
              "do_not_use": {
                "type": "string"
              },
              "content_hash": {
                "type": "string"
              }
            }
          }
        },
        "meta": {
          "type": "object"
        }
      }
    },
    "releasesFile": {
      "type": "object",
      "required": [
        "releases",
        "meta"
      ],
      "properties": {
        "releases": {
          "type": "array",
          "description": "Dated, immutable release records. Not articles: a release note is a point-in-time record, an article is evergreen.",
          "items": {
            "type": "object",
            "required": [
              "id",
              "effective_date",
              "versions",
              "categories",
              "action_required",
              "summary",
              "url",
              "content_hash"
            ],
            "properties": {
              "id": {
                "type": "string"
              },
              "effective_date": {
                "type": "string"
              },
              "versions": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "categories": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "action_required": {
                "type": "boolean"
              },
              "summary": {
                "type": "string"
              },
              "url": {
                "type": "string"
              },
              "content_hash": {
                "type": "string"
              }
            }
          }
        },
        "meta": {
          "type": "object"
        }
      }
    }
  }
}
