{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://docs.salesos.org/schemas/salesos.metadata.schema.json",
  "title": "SalesOS metadata source format",
  "description": "salesos.project.json and the component files under its sourceDir. Format version 1. The server validates the same rules on deploy.",
  "oneOf": [{ "$ref": "#/$defs/project" }],
  "$defs": {
    "project": {
      "title": "salesos.project.json",
      "type": "object",
      "required": ["formatVersion", "name"],
      "additionalProperties": false,
      "properties": {
        "$schema": { "type": "string" },
        "formatVersion": { "const": 1 },
        "name": { "type": "string", "minLength": 1, "maxLength": 100 },
        "description": { "type": "string" },
        "sourceDir": {
          "type": "string",
          "description": "Directory holding the component files, relative to the project root.",
          "default": "src",
          "pattern": "^[A-Za-z0-9_.-]+(/[A-Za-z0-9_.-]+)*$"
        }
      }
    },
    "customObject": {
      "title": "objects/<name>.object.json",
      "type": "object",
      "required": ["name", "label"],
      "additionalProperties": false,
      "properties": {
        "$schema": { "type": "string" },
        "name": { "type": "string", "pattern": "^[a-z][a-z0-9_]{0,62}$" },
        "label": { "type": "string", "minLength": 1 },
        "pluralLabel": { "type": "string" },
        "description": { "type": ["string", "null"] },
        "nameField": { "type": ["string", "null"], "description": "Text field that labels records in lookups and related lists." },
        "fields": {
          "type": "array",
          "items": {
            "type": "object",
            "description": "{ name, label, type, required?, options?, lookupTarget?, onDelete?, controllingField?, dependentValues?, trackHistory?, formula?, rollup? }. type includes LOOKUP and MASTER_DETAIL; lookupTarget is LEAD, CONTACT, ACCOUNT, OPPORTUNITY, TASK, CASE or custom:<object>."
          }
        }
      }
    },
    "customField": {
      "title": "fields/<ENTITY>.<name>.field.json",
      "type": "object",
      "required": ["entity", "name", "label", "fieldType"],
      "additionalProperties": false,
      "properties": {
        "$schema": { "type": "string" },
        "entity": { "enum": ["LEAD", "CONTACT", "ACCOUNT", "OPPORTUNITY", "PRODUCT", "QUOTE"] },
        "name": { "type": "string", "pattern": "^[A-Za-z_][A-Za-z0-9_]{0,63}$" },
        "label": { "type": "string", "minLength": 1 },
        "description": { "type": ["string", "null"] },
        "fieldType": {
          "enum": ["TEXT", "TEXTAREA", "NUMBER", "CURRENCY", "DATE", "DATETIME", "CHECKBOX", "PICKLIST", "MULTI_PICKLIST", "EMAIL", "PHONE", "URL", "LOOKUP", "FORMULA", "ROLLUP_SUMMARY"]
        },
        "isRequired": { "type": "boolean" },
        "isUnique": { "type": "boolean" },
        "defaultValue": { "type": ["string", "null"] },
        "picklistValues": {},
        "lookupEntity": { "type": ["string", "null"] },
        "lookupObject": { "type": ["string", "null"], "pattern": "^custom:[a-z][a-z0-9_]{0,62}$" },
        "lookupDeleteBehavior": { "enum": ["CLEAR", "RESTRICT", null] },
        "controllingField": { "type": ["string", "null"] },
        "dependentValues": { "type": ["object", "null"], "additionalProperties": { "type": "array", "items": { "type": "string" } } },
        "trackHistory": { "type": "boolean" },
        "precision": { "type": ["integer", "null"] },
        "minValue": { "type": ["number", "null"] },
        "maxValue": { "type": ["number", "null"] },
        "maxLength": { "type": ["integer", "null"] },
        "pattern": { "type": ["string", "null"] },
        "formulaConfig": {},
        "rollupConfig": {},
        "sortOrder": { "type": "integer" },
        "isActive": { "type": "boolean" }
      }
    },
    "validationRule": {
      "title": "validation-rules/<ENTITY>.<name>.rule.json",
      "type": "object",
      "required": ["entityType", "name", "conditions", "errorMessage"],
      "additionalProperties": false,
      "properties": {
        "$schema": { "type": "string" },
        "entityType": {
          "anyOf": [
            { "enum": ["LEAD", "CONTACT", "ACCOUNT", "OPPORTUNITY", "TASK", "CASE"] },
            { "type": "string", "pattern": "^custom:[a-z][a-z0-9_]{0,62}$" }
          ]
        },
        "name": { "type": "string", "minLength": 1 },
        "description": { "type": ["string", "null"] },
        "isActive": { "type": "boolean" },
        "appliesTo": { "enum": ["CREATE", "UPDATE", "BOTH"] },
        "conditions": { "type": "array", "items": { "type": "object" } },
        "logic": { "enum": ["AND", "OR"] },
        "errorMessage": { "type": "string", "minLength": 1 },
        "errorField": { "type": ["string", "null"] }
      }
    },
    "workflow": {
      "title": "workflows/<name>.workflow.json",
      "type": "object",
      "required": ["name", "triggerType", "triggerEntity"],
      "additionalProperties": false,
      "properties": {
        "$schema": { "type": "string" },
        "name": { "type": "string", "minLength": 1 },
        "description": { "type": ["string", "null"] },
        "triggerType": {
          "enum": ["RECORD_CREATED", "RECORD_UPDATED", "RECORD_DELETED", "FIELD_CHANGED", "STAGE_CHANGED", "TIME_BASED", "WEBHOOK", "MANUAL"]
        },
        "triggerEntity": { "type": "string" },
        "triggerConfig": { "type": "object" },
        "conditions": {},
        "actions": {
          "description": "Steps; CUSTOM_ACTION steps name config.actionApiName and FUNCTION steps config.functionApiName, which must exist in the package or the target."
        },
        "runOnce": { "type": "boolean" },
        "delayMinutes": { "type": "integer", "minimum": 0 }
      }
    },
    "customAction": {
      "title": "actions/<apiName>.action.json",
      "type": "object",
      "required": ["apiName", "label", "inputSchema", "outputSchema"],
      "additionalProperties": false,
      "properties": {
        "$schema": { "type": "string" },
        "apiName": { "type": "string", "pattern": "^[a-z][a-z0-9_]{1,47}$" },
        "label": { "type": "string", "minLength": 1 },
        "description": { "type": ["string", "null"] },
        "url": { "type": "string", "format": "uri", "description": "HTTPS endpoint; omit when `function` is set." },
        "function": { "type": ["string", "null"], "description": "apiName of the serverless function that implements the action." },
        "inputSchema": { "type": "object" },
        "outputSchema": { "type": "object" },
        "kind": { "enum": ["READ", "WRITE"] },
        "idempotent": { "type": "boolean" },
        "maxRetries": { "type": "integer", "minimum": 0, "maximum": 3 },
        "timeoutMs": { "type": "integer", "minimum": 500, "maximum": 30000 },
        "objectTypes": { "type": "array", "items": { "type": "string" } },
        "allowManual": { "type": "boolean" },
        "exposeAsTool": { "type": "boolean" },
        "toolDescription": { "type": ["string", "null"] },
        "inputMapping": {},
        "outputMapping": {}
      }
    },
    "beforeSaveHook": {
      "title": "hooks/<OBJECT>.<name>.hook.json",
      "type": "object",
      "required": ["objectType", "name"],
      "additionalProperties": false,
      "properties": {
        "$schema": { "type": "string" },
        "objectType": { "type": "string", "pattern": "^(LEAD|CONTACT|ACCOUNT|OPPORTUNITY|TASK|CASE|custom:[a-z][a-z0-9_]*)$" },
        "name": { "type": "string", "minLength": 1 },
        "description": { "type": ["string", "null"] },
        "operations": { "type": "array", "items": { "enum": ["CREATE", "UPDATE", "DELETE"] } },
        "url": { "type": "string", "format": "uri" },
        "function": { "type": ["string", "null"], "pattern": "^[a-z][a-z0-9_]{1,47}$", "description": "A serverless function (apiName) that decides instead of url." },
        "timeoutMs": { "type": "integer", "minimum": 250, "maximum": 5000 },
        "failureMode": { "enum": ["FAIL_OPEN", "FAIL_CLOSED"] },
        "position": { "type": "integer" }
      }
    },
    "namedCredential": {
      "title": "credentials/<apiName>.credential.json",
      "description": "Configuration only: secrets never move. A new credential that needs a secret arrives inactive.",
      "type": "object",
      "required": ["apiName", "label", "baseUrl"],
      "additionalProperties": false,
      "properties": {
        "$schema": { "type": "string" },
        "apiName": { "type": "string", "pattern": "^[a-z][a-z0-9_]{1,47}$" },
        "label": { "type": "string", "minLength": 1 },
        "description": { "type": ["string", "null"] },
        "baseUrl": { "type": "string", "format": "uri", "pattern": "^https://" },
        "authType": { "enum": ["NONE", "API_KEY_HEADER", "BEARER", "BASIC", "OAUTH2_CLIENT_CREDENTIALS"] },
        "headerName": { "type": ["string", "null"] },
        "username": { "type": ["string", "null"] },
        "tokenUrl": { "type": ["string", "null"], "format": "uri" },
        "clientId": { "type": ["string", "null"] },
        "scopes": { "type": "array", "items": { "type": "string" } },
        "staticHeaders": { "type": "object", "additionalProperties": { "type": "string" } },
        "timeoutMs": { "type": "integer", "minimum": 1000, "maximum": 10000 }
      }
    },
    "customEventType": {
      "title": "events/<apiName>.event.json",
      "description": "Published as custom.<apiName>. A changed schema becomes the target's next version.",
      "type": "object",
      "required": ["apiName", "label", "schema"],
      "additionalProperties": false,
      "properties": {
        "$schema": { "type": "string" },
        "apiName": { "type": "string", "pattern": "^[a-z][a-z0-9_]{1,47}$" },
        "label": { "type": "string", "minLength": 1 },
        "description": { "type": ["string", "null"] },
        "schema": { "type": "object", "description": "Payload JSON Schema (object of scalar properties)." },
        "publishAccess": { "enum": ["ADMINS", "MEMBERS"] }
      }
    },
    "afterSaveTrigger": {
      "title": "after-save/<OBJECT>.<name>.trigger.json",
      "type": "object",
      "required": ["objectType", "name", "operations", "function"],
      "additionalProperties": false,
      "properties": {
        "$schema": { "type": "string" },
        "objectType": { "type": "string", "pattern": "^(LEAD|CONTACT|ACCOUNT|OPPORTUNITY|TASK|CASE|custom:[a-z][a-z0-9_]*)$" },
        "name": { "type": "string", "minLength": 1 },
        "description": { "type": ["string", "null"] },
        "operations": { "type": "array", "items": { "enum": ["CREATE", "UPDATE", "DELETE"] }, "minItems": 1 },
        "function": { "type": "string", "pattern": "^[a-z][a-z0-9_]{1,47}$" },
        "timeoutMs": { "type": "integer", "minimum": 250, "maximum": 10000 },
        "position": { "type": "integer", "minimum": 0, "maximum": 1000 }
      }
    },
    "function": {
      "title": "functions/<apiName>/function.json (source in index.ts or index.js beside it)",
      "type": "object",
      "required": ["name"],
      "additionalProperties": false,
      "properties": {
        "$schema": { "type": "string" },
        "apiName": { "type": "string", "pattern": "^[a-z][a-z0-9_]{1,47}$", "description": "Must match the folder name; may be omitted." },
        "name": { "type": "string", "minLength": 1, "maxLength": 120 },
        "description": { "type": ["string", "null"] },
        "language": { "enum": ["typescript", "javascript"] },
        "envVars": { "type": "object", "additionalProperties": { "type": "string" } },
        "timeoutMs": { "type": "integer", "minimum": 1000, "maximum": 30000 },
        "memoryMb": { "type": "integer", "minimum": 16, "maximum": 128 },
        "allowedHosts": { "type": "array", "items": { "type": "string" } },
        "scopes": { "type": "array", "items": { "type": "string", "pattern": "^(READ|WRITE)_[A-Z_]+$" } },
        "httpEnabled": { "type": "boolean" },
        "eventTypes": { "type": "array", "items": { "type": "string" } },
        "schedule": { "type": ["string", "null"], "description": "Five-field UTC cron, at most every 5 minutes." },
        "runAsAdmin": { "type": "boolean" },
        "secrets": {
          "type": "array",
          "items": { "type": "string", "pattern": "^[A-Z_][A-Z0-9_]{0,63}$" },
          "description": "Secret names only. Values never leave an org; a deploy reports names missing in the target."
        }
      }
    },
    "pageLayout": {
      "title": "layouts/<OBJECT>.<profile|default>.layout.json",
      "type": "object",
      "required": ["objectType", "name", "config"],
      "additionalProperties": false,
      "properties": {
        "$schema": { "type": "string" },
        "objectType": { "enum": ["LEAD", "CONTACT", "ACCOUNT", "OPPORTUNITY"] },
        "profile": { "type": ["string", "null"], "description": "Profile name; null or omitted for the org default." },
        "name": { "type": "string", "minLength": 1 },
        "config": {
          "type": "object",
          "description": "Page layout config. App slots are written app/<appSlug>:<key> so they resolve to the target org's installation."
        }
      }
    },
    "page": {
      "title": "pages/<apiName>.page.json",
      "type": "object",
      "required": ["apiName", "label", "objectType", "definition"],
      "additionalProperties": false,
      "properties": {
        "$schema": { "type": "string" },
        "apiName": { "type": "string", "pattern": "^[a-z][a-z0-9_]{1,62}$" },
        "label": { "type": "string", "minLength": 1, "maxLength": 80 },
        "description": { "type": ["string", "null"] },
        "objectType": { "type": "string", "pattern": "^(LEAD|CONTACT|ACCOUNT|OPPORTUNITY|custom:[a-z][a-z0-9_]{0,62})$" },
        "recordTypeField": { "type": "string", "description": "The picklist that acts as the record type; required when an assignment names a recordType." },
        "assignments": {
          "type": "array",
          "items": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
              "profile": { "type": ["string", "null"], "description": "Profile name; null for everyone else." },
              "recordType": { "type": ["string", "null"], "description": "A value of recordTypeField; null for any." }
            }
          }
        },
        "definition": { "$ref": "#/$defs/pageDefinition" }
      }
    },
    "appPage": {
      "title": "app-pages/<apiName>.apppage.json",
      "type": "object",
      "required": ["apiName", "label", "slug", "definition"],
      "additionalProperties": false,
      "properties": {
        "$schema": { "type": "string" },
        "apiName": { "type": "string", "pattern": "^[a-z][a-z0-9_]{1,62}$" },
        "label": { "type": "string", "minLength": 1, "maxLength": 80 },
        "description": { "type": ["string", "null"] },
        "slug": { "type": "string", "pattern": "^[a-z0-9][a-z0-9-]{1,47}$", "description": "Opens at /dashboard/p/<slug>." },
        "icon": { "type": ["string", "null"] },
        "showInNav": { "type": "boolean" },
        "navOrder": { "type": "integer", "minimum": 0, "maximum": 999 },
        "audience": { "type": "array", "items": { "type": "string" }, "description": "Profile names that may open the page; empty for everyone." },
        "home": { "type": "array", "items": { "type": ["string", "null"] }, "description": "Profiles whose home page this is; null for everyone else." },
        "definition": { "$ref": "#/$defs/pageDefinition" }
      }
    },
    "pageDefinition": {
      "title": "Page builder definition (version 1)",
      "type": "object",
      "required": ["regions"],
      "properties": {
        "version": { "const": 1 },
        "regions": {
          "type": "object",
          "properties": {
            "header": { "type": "array", "items": { "$ref": "#/$defs/pageComponent" } },
            "main": { "type": "array", "items": { "$ref": "#/$defs/pageComponent" }, "description": "App pages only." },
            "sidebar": { "type": "array", "items": { "$ref": "#/$defs/pageComponent" } },
            "tabs": {
              "type": "array",
              "description": "Record pages only; at least one.",
              "items": {
                "type": "object",
                "required": ["id", "label"],
                "properties": {
                  "id": { "type": "string", "pattern": "^[a-z][a-z0-9-]{0,39}$" },
                  "label": { "type": "string", "maxLength": 60 },
                  "components": { "type": "array", "items": { "$ref": "#/$defs/pageComponent" } },
                  "visibility": { "type": "object" }
                }
              }
            }
          }
        }
      }
    },
    "pageComponent": {
      "type": "object",
      "required": ["id", "type"],
      "properties": {
        "id": { "type": "string", "pattern": "^[a-z0-9][a-z0-9-]{0,39}$" },
        "type": {
          "enum": ["field_section", "related_list", "custom_related_list", "activity_timeline", "metrics", "record_brief", "rich_text", "report_chart", "list_view", "app_slot", "flow_launcher", "quick_actions", "builtin_tab", "builtin_rail"]
        },
        "props": { "type": "object", "description": "Per type; app slots are app/<appSlug>:<key>, custom actions their apiName." },
        "visibility": {
          "type": "object",
          "properties": {
            "profiles": { "type": "array", "items": { "type": "string" }, "description": "Profile names." },
            "formFactor": { "enum": ["all", "desktop", "mobile"] },
            "match": { "enum": ["all", "any"] },
            "conditions": { "type": "array", "items": { "type": "object" } }
          }
        }
      }
    },
    "flow": {
      "title": "flows/<apiName>.flow.json",
      "type": "object",
      "required": ["apiName", "label", "type", "definition"],
      "additionalProperties": false,
      "properties": {
        "$schema": { "type": "string" },
        "apiName": { "type": "string", "pattern": "^[a-z][a-z0-9_]{1,47}$" },
        "label": { "type": "string", "minLength": 1, "maxLength": 120 },
        "description": { "type": ["string", "null"] },
        "type": { "enum": ["SCREEN", "AUTOLAUNCHED"] },
        "launchContexts": { "type": "array", "items": { "type": "string" }, "description": "record:<OBJECT>, record:custom:<api>, list:<OBJECT>, page, standalone, autolaunched." },
        "eventTypes": { "type": "array", "items": { "type": "string" }, "description": "Catalog event types that start an autolaunched flow." },
        "runMode": { "enum": ["USER", "SYSTEM"], "description": "SYSTEM runs record steps as the deploying admin." },
        "definition": {
          "type": "object",
          "required": ["start", "elements"],
          "description": "{ start, variables, elements } — the active version's definition. Interviews never travel.",
          "properties": {
            "start": { "type": "string" },
            "variables": { "type": "array", "items": { "type": "object" } },
            "elements": { "type": "array", "items": { "type": "object" } }
          }
        }
      }
    },
    "approvalProcess": {
      "title": "approval-processes/<ENTITY>.<name>.approval.json",
      "type": "object",
      "required": ["entity", "name", "steps"],
      "additionalProperties": false,
      "properties": {
        "$schema": { "type": "string" },
        "entity": { "type": "string", "pattern": "^(QUOTE|DISCOUNT|ORDER|CONTRACT|LEAD|CONTACT|ACCOUNT|OPPORTUNITY|CASE|TASK|custom:[a-z][a-z0-9_]{0,62})$" },
        "name": { "type": "string", "minLength": 1 },
        "description": { "type": ["string", "null"] },
        "conditions": { "type": "array", "items": { "type": "object" }, "description": "Entry criteria: [{ field, operator, value }], all must match." },
        "priority": { "type": "integer" },
        "lockRecord": { "type": "boolean" },
        "allowRecall": { "type": "boolean" },
        "statusField": { "type": ["string", "null"] },
        "statusValues": { "type": ["object", "null"], "additionalProperties": { "type": "string" } },
        "fieldUpdates": { "type": "object", "description": "{ onSubmit, onApprove, onReject, onRecall: [{ field, value }] }" },
        "steps": {
          "type": "array",
          "minItems": 1,
          "items": {
            "type": "object",
            "required": ["name", "approverType"],
            "properties": {
              "order": { "type": "integer" },
              "name": { "type": "string" },
              "description": { "type": ["string", "null"] },
              "approverType": { "enum": ["USER", "ROLE", "MANAGER", "SKIP_LEVEL_MANAGER"] },
              "approver": { "type": ["string", "null"], "description": "The approver's email (USER)." },
              "role": { "type": ["string", "null"], "description": "Profile name (ROLE)." },
              "autoApproveAfterHours": { "type": ["integer", "null"] },
              "autoRejectAfterHours": { "type": ["integer", "null"] },
              "requireComment": { "type": "boolean" },
              "allowDelegation": { "type": "boolean" }
            }
          }
        }
      }
    },
    "customLabel": {
      "title": "labels/<name>.label.json",
      "type": "object",
      "required": ["name", "value"],
      "additionalProperties": false,
      "properties": {
        "$schema": { "type": "string" },
        "name": { "type": "string", "pattern": "^[A-Za-z][A-Za-z0-9_]{0,79}$" },
        "value": { "type": "string", "maxLength": 4000 },
        "description": { "type": ["string", "null"] },
        "category": { "type": ["string", "null"] }
      }
    },
    "translation": {
      "title": "translations/<locale>.translation.json",
      "type": "object",
      "required": ["locale", "translations"],
      "additionalProperties": false,
      "properties": {
        "$schema": { "type": "string" },
        "locale": { "type": "string", "pattern": "^[a-z]{2,3}(-[A-Z]{2})?$" },
        "translations": {
          "type": "object",
          "description": "Key (label.<name>, object.<api>.label, field.<ENTITY>.<name>.label, flow.<api>.<element>.<…>, page.<api>.<component>.title) → text. Deploys add and update; they never delete keys you leave out.",
          "additionalProperties": { "type": "string", "maxLength": 4000 }
        }
      }
    },
    "profile": {
      "title": "profiles/<name>.profile.json",
      "description": "Module permissions (incl. custom:<object> modules) and field-level security, matched by profile name. Lists replace the profile's rows.",
      "type": "object",
      "required": ["name"],
      "additionalProperties": false,
      "properties": {
        "$schema": { "type": "string" },
        "name": { "type": "string", "minLength": 1, "maxLength": 100 },
        "description": { "type": ["string", "null"] },
        "permissions": {
          "type": "array",
          "items": {
            "type": "object",
            "required": ["module"],
            "properties": {
              "module": { "type": "string", "description": "leads, contacts, accounts, … or custom:<object>" },
              "canRead": { "type": "boolean" },
              "canCreate": { "type": "boolean" },
              "canEdit": { "type": "boolean" },
              "canDelete": { "type": "boolean" },
              "dataAccess": { "enum": ["NONE", "OWN", "TEAM", "ALL"] }
            }
          }
        },
        "fieldPermissions": {
          "type": "array",
          "description": "Only restricted fields are listed (a field with read and edit is the default).",
          "items": {
            "type": "object",
            "required": ["entityType", "fieldName"],
            "properties": {
              "entityType": { "type": "string", "pattern": "^(LEAD|CONTACT|ACCOUNT|OPPORTUNITY|TASK|CASE|custom:[a-z][a-z0-9_]{0,62})$" },
              "fieldName": { "type": "string", "description": "A field, or customFields.<name> on standard objects" },
              "canRead": { "type": "boolean" },
              "canEdit": { "type": "boolean" }
            }
          }
        }
      }
    },
    "report": {
      "title": "reports/<name>.report.json",
      "description": "A report definition (no schedule or recipients). New reports belong to the deployer.",
      "type": "object",
      "required": ["name", "type"],
      "additionalProperties": false,
      "properties": {
        "$schema": { "type": "string" },
        "name": { "type": "string", "minLength": 1 },
        "description": { "type": ["string", "null"] },
        "type": { "type": "string", "pattern": "^(leads|deals|accounts|contacts|activities|custom:[a-z][a-z0-9_]{0,62})$" },
        "fields": { "type": "array", "items": { "type": "string" } },
        "filters": { "type": "array", "items": { "type": "object" } },
        "groupBy": { "type": ["string", "null"] },
        "chartType": { "type": "string" }
      }
    },
    "dashboard": {
      "title": "dashboards/<name>.dashboard.json",
      "description": "A team or org dashboard (private dashboards belong to a person and do not move).",
      "type": "object",
      "required": ["name", "visibility"],
      "additionalProperties": false,
      "properties": {
        "$schema": { "type": "string" },
        "name": { "type": "string", "minLength": 1 },
        "description": { "type": ["string", "null"] },
        "visibility": { "enum": ["team", "org"] },
        "widgets": { "type": "array", "items": { "type": "object", "required": ["type"] } },
        "layout": {}
      }
    },
    "emailTemplate": {
      "title": "email-templates/<name>.email.json",
      "description": "A shared email template (private templates do not move).",
      "type": "object",
      "required": ["name", "subject", "bodyHtml"],
      "additionalProperties": false,
      "properties": {
        "$schema": { "type": "string" },
        "name": { "type": "string", "minLength": 1 },
        "description": { "type": ["string", "null"] },
        "subject": { "type": "string", "minLength": 1 },
        "bodyHtml": { "type": "string" },
        "bodyText": { "type": ["string", "null"] },
        "preheader": { "type": ["string", "null"] },
        "category": { "enum": ["NOTIFICATION", "TRANSACTIONAL", "MARKETING", "SALES", "FOLLOW_UP", "MEETING", "DIGEST", "CUSTOM"] },
        "status": { "enum": ["DRAFT", "ACTIVE", "ARCHIVED"] },
        "variables": { "type": "array", "items": { "type": "string" } },
        "useIrisBranding": { "type": "boolean" },
        "ctaText": { "type": ["string", "null"] },
        "ctaUrl": { "type": ["string", "null"] },
        "targetRoles": { "type": "array", "items": { "type": "string" }, "description": "Empty = everyone in the org" }
      }
    },
    "listView": {
      "title": "list-views/<ENTITY>.<name>.view.json",
      "description": "A shared list view (ORG or TEAM; private views do not move). TEAM views name their team.",
      "type": "object",
      "required": ["entity", "name", "visibility"],
      "additionalProperties": false,
      "properties": {
        "$schema": { "type": "string" },
        "entity": { "type": "string", "pattern": "^(LEAD|CONTACT|ACCOUNT|OPPORTUNITY|TASK|custom:[A-Za-z][A-Za-z0-9_]{0,62})$" },
        "name": { "type": "string", "minLength": 1 },
        "visibility": { "enum": ["ORG", "TEAM"] },
        "team": { "type": ["string", "null"], "description": "Team name (TEAM views)" },
        "filters": { "type": "object", "description": "View filters; record ids (filters.ids) are refused" },
        "columns": {},
        "sortBy": { "type": ["string", "null"] },
        "sortOrder": { "enum": ["asc", "desc", null] },
        "pageSize": { "type": ["integer", "null"] },
        "viewMode": { "type": ["string", "null"] }
      }
    },
    "assignmentRule": {
      "title": "assignment-rules/<ENTITY>.<name>.assignment.json",
      "description": "An assignment rule; assignees and the fallback owner are emails of active members. New rules arrive inactive unless the deploy activates them.",
      "type": "object",
      "required": ["entity", "name", "method"],
      "additionalProperties": false,
      "properties": {
        "$schema": { "type": "string" },
        "entity": { "enum": ["LEAD", "CONTACT", "OPPORTUNITY"] },
        "name": { "type": "string", "minLength": 1 },
        "description": { "type": ["string", "null"] },
        "method": { "enum": ["ROUND_ROBIN", "LOAD_BALANCED", "FIXED", "TERRITORY", "LEAD_SCORE"] },
        "priority": { "type": "integer" },
        "fallbackOwner": { "type": ["string", "null"], "description": "Email" },
        "notifyAssignee": { "type": "boolean" },
        "conditions": {
          "type": "array",
          "items": {
            "type": "object",
            "required": ["field", "operator"],
            "properties": {
              "field": { "type": "string" },
              "operator": { "type": "string" },
              "value": {},
              "logicalOperator": { "enum": ["AND", "OR"] }
            }
          }
        },
        "assignees": {
          "type": "array",
          "items": {
            "type": "object",
            "required": ["user"],
            "properties": {
              "user": { "type": "string", "description": "Email" },
              "weight": { "type": "integer" },
              "maxActiveRecords": { "type": ["integer", "null"] },
              "isActive": { "type": "boolean" }
            }
          }
        }
      }
    },
    "functionTest": {
      "title": "functions/<apiName>/tests/<name>.test.json",
      "description": "Runs the function in the sandbox with every host call answered from mocks: no network, no CRM data.",
      "type": "object",
      "required": ["expect"],
      "additionalProperties": false,
      "properties": {
        "$schema": { "type": "string" },
        "description": { "type": "string" },
        "input": {},
        "context": { "type": "object", "description": "{ record?: { object, id }, event?: { id, type } }" },
        "mocks": {
          "type": "object",
          "additionalProperties": false,
          "properties": {
            "secrets": { "type": "object", "additionalProperties": { "type": "string" } },
            "fetch": {
              "type": "array",
              "items": {
                "type": "object",
                "required": ["url"],
                "properties": {
                  "url": { "type": "string", "description": "Exact URL, or a prefix ending in *" },
                  "method": { "type": "string" },
                  "status": { "type": "integer" },
                  "headers": { "type": "object" },
                  "body": { "type": "string" },
                  "json": {},
                  "error": { "type": "object" }
                }
              }
            },
            "salesos": {
              "type": "array",
              "items": {
                "type": "object",
                "required": ["op"],
                "properties": {
                  "op": { "enum": ["get", "list", "create", "update", "delete", "graphql"] },
                  "object": { "type": "string" },
                  "id": { "type": "string" },
                  "result": {},
                  "error": { "type": "object" }
                }
              }
            }
          }
        },
        "expect": {
          "type": "object",
          "properties": {
            "status": { "enum": ["SUCCEEDED", "FAILED", "TIMEOUT"] },
            "output": { "description": "Deep equality" },
            "outputContains": { "description": "Subset match" },
            "error": { "type": "object", "properties": { "code": { "type": "string" }, "message": { "type": "string", "description": "Substring" } } },
            "calls": { "type": "array", "items": { "type": "object" }, "description": "Host calls that must have happened (subset match), e.g. { op: 'fetch', url }" },
            "logs": { "type": "string", "description": "Substring of stdout/stderr" }
          }
        }
      }
    },
    "flowTest": {
      "title": "tests/flows/<name>.test.json",
      "description": "Runs the flow in the interpreter with an in-memory interview store and record operations: nothing is written.",
      "type": "object",
      "required": ["flow", "expect"],
      "additionalProperties": false,
      "properties": {
        "$schema": { "type": "string" },
        "description": { "type": "string" },
        "flow": { "type": "string", "pattern": "^[a-z][a-z0-9_]{1,47}$" },
        "inputs": { "type": "object" },
        "record": { "type": "object", "description": "The launch record: { object, id, …fields }" },
        "answers": { "type": "array", "items": { "type": "object", "required": ["screen"], "properties": { "screen": { "type": "string" }, "values": { "type": "object" } } } },
        "data": { "type": "object", "description": "Records the flow can read: { lead: [{ id, … }], custom:invoice: [...] }", "additionalProperties": { "type": "array", "items": { "type": "object", "required": ["id"] } } },
        "mocks": { "type": "object", "properties": { "actions": { "type": "object" }, "functions": { "type": "object" } }, "description": "{ actions|functions: { <apiName>: { output } | { error: { code, message } } } }" },
        "expect": {
          "type": "object",
          "properties": {
            "status": { "enum": ["COMPLETED", "FAILED", "PAUSED"] },
            "outputs": { "type": "object", "description": "Subset match" },
            "path": { "type": "array", "items": { "type": "string" }, "description": "Element ids that must run, in order" },
            "notPath": { "type": "array", "items": { "type": "string" } },
            "records": { "type": "array", "items": { "type": "object", "required": ["op"] }, "description": "Writes that must happen: { op, object, data }" },
            "recordWrites": { "type": "integer" },
            "emails": { "type": "array", "items": { "type": "object" } },
            "error": { "type": "object" }
          }
        }
      }
    },
    "validationRuleTest": {
      "title": "tests/validation-rules/<name>.test.json",
      "type": "object",
      "required": ["rule", "cases"],
      "additionalProperties": false,
      "properties": {
        "$schema": { "type": "string" },
        "description": { "type": "string" },
        "rule": { "type": "string", "description": "ENTITY:name" },
        "cases": {
          "type": "array",
          "minItems": 1,
          "maxItems": 50,
          "items": {
            "type": "object",
            "required": ["record", "expect"],
            "properties": {
              "name": { "type": "string" },
              "record": { "type": "object" },
              "previous": { "type": "object" },
              "operation": { "enum": ["CREATE", "UPDATE"] },
              "expect": { "enum": ["pass", "fail"] },
              "message": { "type": "string", "description": "Substring of the rendered error message" }
            }
          }
        }
      }
    },
    "formulaTest": {
      "title": "tests/formulas/<name>.test.json",
      "type": "object",
      "required": ["field", "cases"],
      "additionalProperties": false,
      "properties": {
        "$schema": { "type": "string" },
        "description": { "type": "string" },
        "field": { "type": "string", "description": "ENTITY:name (custom field) or custom:<object>.<field>" },
        "cases": {
          "type": "array",
          "minItems": 1,
          "maxItems": 50,
          "items": { "type": "object", "required": ["record", "expect"], "properties": { "name": { "type": "string" }, "record": { "type": "object" }, "expect": {} } }
        }
      }
    }
  }
}
