{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://woven-docs-yl1.pages.dev/data/capi.schema.json",
  "title": "Woven CAPI JSON 0.1.0",
  "type": "object",
  "additionalProperties": false,
  "required": ["format", "capi_v", "app", "name", "version", "model", "entities", "commands", "roles", "errors", "rules", "asks", "flows", "platforms"],
  "properties": {
    "format": {"const": "woven-capi"},
    "capi_v": {"type": "integer", "minimum": 1},
    "app": {"$ref": "#/$defs/slug"},
    "name": {"type": "string", "minLength": 1},
    "version": {"$ref": "#/$defs/version"},
    "model": {
      "type": "object", "additionalProperties": false,
      "required": ["entities", "values"],
      "properties": {
        "entities": {"type": "object", "minProperties": 1, "additionalProperties": {"$ref": "#/$defs/modelEntity"}},
        "values": {"type": "object", "additionalProperties": {"$ref": "#/$defs/valueDecl"}}
      }
    },
    "entities": {"type": "object", "minProperties": 1, "additionalProperties": {"$ref": "#/$defs/entity"}},
    "commands": {"type": "object", "additionalProperties": {"$ref": "#/$defs/command"}},
    "roles": {
      "type": "object", "additionalProperties": false,
      "required": ["owner", "person", "all", "ownerCols", "writeRoles"],
      "properties": {
        "owner": {"$ref": "#/$defs/ident"},
        "person": {"type": "object", "additionalProperties": false, "required": ["entity", "key"], "properties": {"entity": {"$ref": "#/$defs/ident"}, "key": {"$ref": "#/$defs/ident"}}},
        "all": {"type": "object", "minProperties": 1, "additionalProperties": {"type": "string"}},
        "ownerCols": {"type": "object", "additionalProperties": {"type": "array", "items": {"$ref": "#/$defs/ident"}, "uniqueItems": true}},
        "writeRoles": {"type": "object", "additionalProperties": {"type": "array", "items": {"$ref": "#/$defs/ident"}, "uniqueItems": true}}
      }
    },
    "errors": {"type": "object", "minProperties": 1, "additionalProperties": {"type": "string", "minLength": 1}},
    "rules": {"type": "array", "items": {"$ref": "#/$defs/rule"}},
    "asks": {"type": "array", "minItems": 1, "items": {"$ref": "#/$defs/ask"}},
    "flows": {"type": "array", "items": {"$ref": "#/$defs/flow"}},
    "platforms": {"$ref": "#/$defs/platforms"}
  },
  "$defs": {
    "slug": {"type": "string", "pattern": "^[a-z][a-z0-9]{0,30}$"},
    "ident": {"type": "string", "pattern": "^[A-Za-z][A-Za-z0-9_]*$"},
    "version": {
      "type": "object", "additionalProperties": false, "required": ["major", "minor", "patch"],
      "properties": {"major": {"type": "integer", "minimum": 0}, "minor": {"type": "integer", "minimum": 0}, "patch": {"type": "integer", "minimum": 0}}
    },
    "scalar": {"enum": ["text", "nat", "int", "frac", "time", "flag", "bytes"]},
    "columnTy": {
      "oneOf": [
        {"type": "object", "additionalProperties": false, "required": ["kind", "entity"], "properties": {"kind": {"const": "key"}, "entity": {"$ref": "#/$defs/ident"}}},
        {"type": "object", "additionalProperties": false, "required": ["kind", "scalar"], "properties": {"kind": {"const": "scalar"}, "scalar": {"enum": ["text", "nat", "frac", "time", "flag"]}}},
        {"type": "object", "additionalProperties": false, "required": ["kind", "fields"], "properties": {"kind": {"const": "record"}, "fields": {"type": "array", "items": {"enum": ["text", "nat", "frac", "time", "flag"]}}}}
      ]
    },
    "ty": {
      "oneOf": [
        {"type": "object", "additionalProperties": false, "required": ["kind"], "properties": {"kind": {"enum": ["unit", "sealed"]}}},
        {"type": "object", "additionalProperties": false, "required": ["kind", "scalar"], "properties": {"kind": {"const": "scalar"}, "scalar": {"$ref": "#/$defs/scalar"}}},
        {"type": "object", "additionalProperties": false, "required": ["kind", "entity"], "properties": {"kind": {"enum": ["key", "row", "filter", "patch", "query", "rows", "change", "search", "ref"]}, "entity": {"$ref": "#/$defs/ident"}}},
        {"type": "object", "additionalProperties": false, "required": ["kind", "value"], "properties": {"kind": {"const": "value"}, "value": {"$ref": "#/$defs/ident"}}},
        {"type": "object", "additionalProperties": false, "required": ["kind", "of"], "properties": {"kind": {"enum": ["opt", "list"]}, "of": {"$ref": "#/$defs/ty"}}}
      ]
    },
    "field": {"type": "object", "additionalProperties": false, "required": ["name", "type", "kind"], "properties": {"name": {"$ref": "#/$defs/ident"}, "type": {"$ref": "#/$defs/columnTy"}, "kind": {"enum": ["indexable", "sealed"]}}},
    "modelEntity": {
      "type": "object", "additionalProperties": false,
      "required": ["names", "key", "keyType", "fields", "filterCols", "patchCols", "searchFields", "rowTyped", "fieldsNamed"],
      "properties": {
        "names": {"type": "object", "additionalProperties": false, "required": ["row", "key", "filter", "patch", "query", "rows", "change", "search", "ref"], "properties": {"row": {"$ref": "#/$defs/ident"}, "key": {"$ref": "#/$defs/ident"}, "filter": {"$ref": "#/$defs/ident"}, "patch": {"$ref": "#/$defs/ident"}, "query": {"$ref": "#/$defs/ident"}, "rows": {"$ref": "#/$defs/ident"}, "change": {"$ref": "#/$defs/ident"}, "search": {"$ref": "#/$defs/ident"}, "ref": {"$ref": "#/$defs/ident"}}, "additionalProperties": {"$ref": "#/$defs/ident"}},
        "key": {"$ref": "#/$defs/ident"},
        "keyType": {"$ref": "#/$defs/scalar"},
        "fields": {"type": "array", "minItems": 1, "items": {"$ref": "#/$defs/field"}},
        "filterCols": {"type": "array", "uniqueItems": true, "items": {"$ref": "#/$defs/ident"}},
        "patchCols": {"type": "array", "uniqueItems": true, "items": {"$ref": "#/$defs/ident"}},
        "searchFields": {"type": "array", "items": {"$ref": "#/$defs/valueField"}},
        "rowTyped": {"type": "boolean"}, "fieldsNamed": {"type": "boolean"}
      }
    },
    "valueDecl": {"type": "object", "additionalProperties": false, "required": ["tsName", "form", "fields"], "properties": {"tsName": {"$ref": "#/$defs/ident"}, "form": {"enum": ["record", "words", "tagged", "opaque"]}, "fields": {"type": "array", "items": {"$ref": "#/$defs/valueField"}}}},
    "valueField": {"type": "object", "additionalProperties": false, "required": ["name", "type"], "properties": {"name": {"$ref": "#/$defs/ident"}, "type": {"$ref": "#/$defs/ty"}}},
    "audience": {
      "oneOf": [
        {"type": "object", "additionalProperties": false, "required": ["kind"], "properties": {"kind": {"const": "anyone"}}},
        {"type": "object", "additionalProperties": false, "required": ["kind", "field"], "properties": {"kind": {"const": "self"}, "field": {"$ref": "#/$defs/ident"}}},
        {"type": "object", "additionalProperties": false, "required": ["kind", "role"], "properties": {"kind": {"const": "role"}, "role": {"$ref": "#/$defs/ident"}}},
        {"type": "object", "additionalProperties": false, "required": ["kind", "group", "via"], "properties": {"kind": {"const": "memberOf"}, "group": {"$ref": "#/$defs/ident"}, "via": {"$ref": "#/$defs/ident"}}},
        {"type": "object", "additionalProperties": false, "required": ["kind", "group", "via", "stamp", "back"], "properties": {"kind": {"const": "since"}, "group": {"$ref": "#/$defs/ident"}, "via": {"$ref": "#/$defs/ident"}, "stamp": {"$ref": "#/$defs/ident"}, "back": {"type": "integer", "minimum": 0}}},
        {"type": "object", "additionalProperties": false, "required": ["kind", "field", "value"], "properties": {"kind": {"const": "is"}, "field": {"$ref": "#/$defs/ident"}, "value": {"$ref": "#/$defs/dataVal"}}},
        {"type": "object", "additionalProperties": false, "required": ["kind", "field", "target", "audience"], "properties": {"kind": {"const": "along"}, "field": {"$ref": "#/$defs/ident"}, "target": {"$ref": "#/$defs/ident"}, "audience": {"$ref": "#/$defs/audience"}}},
        {"type": "object", "additionalProperties": false, "required": ["kind", "left", "right"], "properties": {"kind": {"enum": ["both", "either"]}, "left": {"$ref": "#/$defs/audience"}, "right": {"$ref": "#/$defs/audience"}}},
        {"type": "object", "additionalProperties": false, "required": ["kind", "audience"], "properties": {"kind": {"const": "not"}, "audience": {"$ref": "#/$defs/audience"}}}
      ]
    },
    "dataVal": {"oneOf": [{"type": "null"}, {"type": "string"}, {"type": "integer", "minimum": 0}, {"type": "boolean"}, {"type": "object", "additionalProperties": false, "required": ["key"], "properties": {"key": {"type": "integer", "minimum": 0}}}, {"type": "object", "additionalProperties": false, "required": ["frac"], "properties": {"frac": {"type": "object", "additionalProperties": false, "required": ["num", "den"], "properties": {"num": {"type": "integer"}, "den": {"type": "integer", "minimum": 0}}}}}, {"type": "object", "additionalProperties": false, "required": ["record"], "properties": {"record": {"type": "object", "additionalProperties": {"oneOf": [{"type": "string"}, {"type": "integer", "minimum": 0}, {"type": "boolean"}, {"type": "object", "additionalProperties": false, "required": ["frac"], "properties": {"frac": {"type": "object", "additionalProperties": false, "required": ["num", "den"], "properties": {"num": {"type": "integer"}, "den": {"type": "integer", "minimum": 0}}}}}]}}}}]},
    "visibility": {"type": "object", "additionalProperties": false, "required": ["read", "write", "remove", "windows"], "properties": {"read": {"$ref": "#/$defs/audience"}, "write": {"$ref": "#/$defs/audience"}, "remove": {"$ref": "#/$defs/audience"}, "windows": {"type": "array", "uniqueItems": true, "items": {"enum": ["unsignedClaim", "plainBag"]}}}},
    "entity": {
      "type": "object", "additionalProperties": false,
      "required": ["holding", "verbs", "kept", "onDevice", "refuses", "visibility", "scope", "persisted", "large"],
      "properties": {
        "holding": {"enum": ["served", "constant", "modelOnly"]},
        "verbs": {"type": "array", "uniqueItems": true, "items": {"enum": ["get", "list", "watch", "put", "change", "remove"]}},
        "kept": {"type": "array", "uniqueItems": true, "items": {"enum": ["get", "list", "watch", "put", "change", "remove"]}},
        "onDevice": {"type": "array", "uniqueItems": true, "items": {"enum": ["get", "list", "watch", "put", "change", "remove"]}},
        "refuses": {"type": "object", "additionalProperties": {"type": "array", "uniqueItems": true, "items": {"$ref": "#/$defs/ident"}}},
        "visibility": {"$ref": "#/$defs/visibility"},
        "scope": {"oneOf": [{"type": "null"}, {"$ref": "#/$defs/ident"}]},
        "persisted": {"type": "boolean"}, "large": {"type": "boolean"}
      }
    },
    "command": {"type": "object", "additionalProperties": false, "required": ["kind", "request", "answer", "ent", "errors", "retry"], "properties": {"kind": {"enum": ["query", "command", "subscribe"]}, "request": {"$ref": "#/$defs/ty"}, "answer": {"$ref": "#/$defs/ty"}, "ent": {"oneOf": [{"type": "null"}, {"$ref": "#/$defs/ident"}]}, "errors": {"type": "array", "uniqueItems": true, "items": {"$ref": "#/$defs/ident"}}, "retry": {"enum": ["never", "backoff", "reopen"]}}},
    "op": {"oneOf": [
      {"type": "object", "additionalProperties": false, "required": ["kind", "verb", "entity"], "properties": {"kind": {"const": "crud"}, "verb": {"enum": ["get", "list", "watch", "put", "change", "remove"]}, "entity": {"$ref": "#/$defs/ident"}}},
      {"type": "object", "additionalProperties": false, "required": ["kind", "verb", "entity"], "properties": {"kind": {"const": "onDevice"}, "verb": {"enum": ["get", "list", "watch", "put", "change", "remove"]}, "entity": {"$ref": "#/$defs/ident"}}},
      {"type": "object", "additionalProperties": false, "required": ["kind", "command"], "properties": {"kind": {"const": "named"}, "command": {"$ref": "#/$defs/ident"}}}
    ]},
    "ask": {"type": "object", "additionalProperties": false, "required": ["id", "op", "mode", "window", "order", "handles"], "properties": {"id": {"$ref": "#/$defs/ident"}, "op": {"$ref": "#/$defs/op"}, "mode": {"enum": ["once", "keptOpen"]}, "window": {"type": "integer", "minimum": 0}, "order": {"type": "array", "uniqueItems": true, "items": {"$ref": "#/$defs/ident"}}, "handles": {"type": "object", "additionalProperties": false, "required": ["errors", "failures"], "properties": {"errors": {"type": "array", "uniqueItems": true, "items": {"$ref": "#/$defs/ident"}}, "failures": {"type": "array", "uniqueItems": true, "items": {"enum": ["door", "wire", "decode", "timeout", "auth"]}}}}}},
    "rule": {"type": "object", "additionalProperties": false, "required": ["id", "statement"], "properties": {"id": {"$ref": "#/$defs/ident"}, "statement": {"type": "string", "minLength": 1}}},
    "usage": {"type": "object", "additionalProperties": false, "required": ["stmt", "rowOut", "rowScan", "rowWrite", "fStep", "sigCheck"], "properties": {"stmt": {"type": "integer", "minimum": 0}, "rowOut": {"type": "integer", "minimum": 0}, "rowScan": {"type": "integer", "minimum": 0}, "rowWrite": {"type": "integer", "minimum": 0}, "fStep": {"type": "integer", "minimum": 0}, "sigCheck": {"type": "integer", "minimum": 0}}},
    "flow": {"type": "object", "additionalProperties": false, "required": ["id", "calls", "budget"], "properties": {"id": {"$ref": "#/$defs/ident"}, "calls": {"type": "array", "minItems": 1, "items": {"type": "object", "additionalProperties": false, "required": ["ask", "times", "size"], "properties": {"ask": {"$ref": "#/$defs/ident"}, "times": {"type": "integer", "minimum": 1}, "size": {"type": "integer", "minimum": 0}}}}, "budget": {"$ref": "#/$defs/usage"}}},
    "platforms": {
      "type": "object", "additionalProperties": false, "required": ["hosts", "needs", "degraded"],
      "properties": {
        "hosts": {"type": "array", "uniqueItems": true, "items": {"enum": ["web", "android", "ios", "desktop"]}},
        "needs": {"type": "array", "items": {"type": "object", "additionalProperties": false, "required": ["capability", "required"], "properties": {"capability": {"enum": ["camera", "microphone", "photoPick", "filePick", "share", "openFile", "push", "localNotify", "backgroundWake", "secureStore", "biometricUnlock", "passkey", "sqlite", "keyValue", "rtc", "haptics", "clipboard", "keepAwake", "viewShot", "audioRoute"]}, "required": {"type": "boolean"}}}},
        "degraded": {"type": "array", "items": {"type": "object", "additionalProperties": false, "required": ["platform", "capability", "fallback"], "properties": {"platform": {"enum": ["web", "android", "ios", "desktop"]}, "capability": {"enum": ["camera", "microphone", "photoPick", "filePick", "share", "openFile", "push", "localNotify", "backgroundWake", "secureStore", "biometricUnlock", "passkey", "sqlite", "keyValue", "rtc", "haptics", "clipboard", "keepAwake", "viewShot", "audioRoute"]}, "fallback": {"oneOf": [{"const": "without"}, {"type": "object", "additionalProperties": false, "required": ["instead"], "properties": {"instead": {"enum": ["camera", "microphone", "photoPick", "filePick", "share", "openFile", "push", "localNotify", "backgroundWake", "secureStore", "biometricUnlock", "passkey", "sqlite", "keyValue", "rtc", "haptics", "clipboard", "keepAwake", "viewShot", "audioRoute"]}}}]}}}}
      }
    }
  }
}
