5f125c34f2
Summaries now show elapsed wall-clock time since the run started, and every summary (interim and final) overwrites a results.csv (Object ID, UPN, persona/status) next to the audit log, so an operator has a plain export without parsing NDJSON. ConfigPath also now defaults to ./config/persona-engine.json instead of requiring -ConfigPath every run. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
285 lines
10 KiB
JSON
285 lines
10 KiB
JSON
{
|
|
"$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_<EXTENSION-APP-ID>_<APPROVED-PERSONA-ATTRIBUTE-NAME>"
|
|
},
|
|
"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 <current-directory>/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}$"
|
|
}
|
|
}
|
|
}
|