{ "$schema": "http://json-schema.org/draft-07/schema#", "$id": "https://example.invalid/persona-engine.schema.json", "title": "Persona Engine Configuration", "description": "Draft-07 by decision OTD-005: validated with the built-in Test-Json -SchemaFile cmdlet, whose validator reliably supports draft-04/06/07 only. Do not introduce 2019-09 or 2020-12 constructs. This schema is validation layer 2 of 4; semantic rules (VR-002) and safety rules (VR-003) are enforced in PowerShell, not here.", "type": "object", "required": ["configVersion", "engine", "dataSources", "personas", "rules"], "additionalProperties": false, "properties": { "configVersion": { "type": "string", "pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+$", "description": "Semantic version of this configuration. A downgrade is a safety violation (VR-003)." }, "metadata": { "type": "object", "additionalProperties": true, "properties": { "owner": { "type": "string" }, "changeReference": { "type": "string" }, "description": { "type": "string" } } }, "engine": { "type": "object", "required": ["targetAttribute", "approvedWritableAttributes"], "additionalProperties": false, "properties": { "targetAttribute": { "type": "string", "minLength": 1, "description": "The single attribute the engine may write. Must also appear in approvedWritableAttributes (semantic layer). Example placeholder: extension__" }, "approvedWritableAttributes": { "type": "array", "minItems": 1, "uniqueItems": true, "items": { "type": "string", "minLength": 1 } }, "maxConditionDepth": { "type": "integer", "minimum": 1, "maximum": 10, "default": 5, "description": "RE-004. The ceiling of 10 is a hard limit; the configured value may be lower." }, "summaryInterval": { "type": "integer", "minimum": 0, "default": 25, "description": "FR-020. Zero suppresses interim summaries; a final summary is always produced." }, "defaultMembershipMode": { "type": "string", "enum": ["direct", "transitive"], "default": "direct" }, "evaluationErrorThreshold": { "type": "integer", "minimum": 0, "description": "Optional. Count of EvaluationError results above which the run reports failure." } } }, "dataSources": { "type": "object", "required": ["groups", "roles"], "additionalProperties": false, "properties": { "groups": { "type": "object", "required": ["enabled"], "additionalProperties": false, "properties": { "enabled": { "type": "boolean" }, "membershipMode": { "type": "string", "enum": ["direct", "transitive"] } } }, "roles": { "type": "object", "required": ["enabled"], "additionalProperties": false, "properties": { "enabled": { "type": "boolean" }, "includeEligible": { "type": "boolean", "default": false, "description": "Out of scope for v1 unless authorization is confirmed and the provider is implemented." } } } } }, "logging": { "type": "object", "additionalProperties": false, "properties": { "destination": { "type": "string", "enum": ["file", "stream", "both"], "default": "both", "description": "OTD-006. Additional transports are added behind the sink function, not by widening this enum without a version change." }, "path": { "type": "string", "description": "NDJSON output file for 'file'/'both' destinations. Defaults to /logs/persona-engine-audit.ndjson when unset." }, "resultsFileName": { "type": "string", "minLength": 1, "default": "results.csv", "description": "Per-account results CSV (AccountObjectId, UserPrincipalName, persona/status), written alongside the audit log and overwritten on every summary." }, "traceConditionValues": { "type": "boolean", "default": false, "description": "Diagnostic only. Enabling this without explicit acknowledgement is a safety finding (VR-003)." }, "acknowledgeConditionTracing": { "type": "boolean", "default": false, "description": "Explicit acknowledgement that condition-value tracing writes evaluated attribute values into audit records. Required by VR-003 whenever traceConditionValues is true. Kept in the configuration rather than passed as a command-line flag so the acknowledgement is reviewable in the change that enables tracing." } } }, "personas": { "type": "array", "minItems": 1, "uniqueItems": true, "items": { "type": "string", "minLength": 1, "not": { "enum": ["EvaluationError"] } }, "description": "Defined persona catalogue. EvaluationError is an execution result and must never be declared as a persona." }, "rules": { "type": "array", "minItems": 1, "items": { "$ref": "#/definitions/rule" } } }, "definitions": { "rule": { "type": "object", "required": ["id", "name", "description", "enabled", "priority", "persona", "match"], "additionalProperties": false, "properties": { "id": { "type": "string", "minLength": 1 }, "name": { "type": "string", "minLength": 1 }, "description": { "type": "string", "minLength": 1 }, "enabled": { "type": "boolean" }, "priority": { "type": "integer", "minimum": 0 }, "persona": { "type": "string", "minLength": 1, "not": { "enum": ["Unclassified", "EvaluationError"] }, "description": "Unclassified is a processing result, not a rule outcome (VR-002)." }, "match": { "$ref": "#/definitions/conditionGroup" }, "tags": { "type": "array", "items": { "type": "string" } }, "owner": { "type": "string" }, "changeReference": { "type": "string" }, "effectiveDate": { "type": "string", "format": "date", "description": "Metadata only in v1. It must not gate evaluation — a date-dependent decision would break determinism (Principle I)." }, "notes": { "type": "string" }, "testCases": { "type": "array", "items": { "type": "object", "required": ["name", "expectedMatch"], "additionalProperties": true, "properties": { "name": { "type": "string" }, "expectedMatch": { "type": "boolean" }, "user": { "type": "object" } } } } } }, "conditionGroup": { "type": "object", "required": ["operator", "conditions"], "additionalProperties": false, "properties": { "operator": { "type": "string", "enum": ["all", "any"] }, "conditions": { "type": "array", "minItems": 1, "items": { "anyOf": [ { "$ref": "#/definitions/conditionGroup" }, { "$ref": "#/definitions/condition" } ] } } } }, "condition": { "type": "object", "required": ["type", "operator"], "additionalProperties": false, "properties": { "type": { "type": "string", "enum": ["property", "membership", "role"] }, "property": { "type": "string", "minLength": 1 }, "operator": { "type": "string", "enum": [ "equals", "notEquals", "contains", "notContains", "startsWith", "endsWith", "matchesRegex", "in", "notIn", "isNull", "isNotNull", "memberOf", "notMemberOf" ] }, "value": { "type": "string" }, "values": { "type": "array", "minItems": 1, "uniqueItems": true, "items": { "type": "string" } }, "groupObjectIds": { "type": "array", "minItems": 1, "uniqueItems": true, "items": { "$ref": "#/definitions/guid" } }, "roleIds": { "type": "array", "minItems": 1, "uniqueItems": true, "items": { "type": "string", "minLength": 1 } }, "membershipMode": { "type": "string", "enum": ["direct", "transitive"] }, "caseSensitive": { "type": "boolean", "default": false, "description": "Reserved. Condition-level case sensitivity is out of scope for v1; the schema accepts the key so a later version does not require a breaking change." } }, "allOf": [ { "if": { "properties": { "type": { "const": "property" } }, "required": ["type"] }, "then": { "required": ["property"] } }, { "if": { "properties": { "type": { "const": "membership" } }, "required": ["type"] }, "then": { "required": ["groupObjectIds"] } }, { "if": { "properties": { "type": { "const": "role" } }, "required": ["type"] }, "then": { "required": ["roleIds"] } }, { "if": { "properties": { "operator": { "enum": ["in", "notIn"] } }, "required": ["operator"] }, "then": { "required": ["values"] } }, { "if": { "properties": { "operator": { "enum": ["isNull", "isNotNull"] } }, "required": ["operator"] }, "then": { "allOf": [ { "not": { "required": ["value"] } }, { "not": { "required": ["values"] } } ] } } ] }, "guid": { "type": "string", "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$" } } }