Zum Inhalt springen

UIAP Policy Extension

FeldWert
StatusDraft
Version0.1
Datum2026-03-27
Abhängigkeiten[UIAP-CORE]
EditorenPatrick

UIAP Policy Extension v0.1 definiert, wie eine Anwendung Regeln für:

  • erlaubte Agent-Aktionen,
  • Bestätigungen,
  • sensible Daten,
  • Redaktion,
  • Audit,
  • Human Handoff

maschinenlesbar beschreibt und zur Laufzeit auswertet.

Die Extension baut auf UIAP Core v0.1, Capability Model v0.1, Web Profile v0.1 und Action Runtime v0.1 auf.

Die Schlüsselwörter MUSS, DARF NICHT, SOLLTE, KANN in diesem Dokument sind normativ gemäss RFC 2119 und BCP 14 zu interpretieren, wenn und nur wenn sie in GROSSBUCHSTABEN erscheinen.

type ExtensionId = "uicp.policy";
type ExtensionVersion = "0.1";

Eine Implementierung, die diese Erweiterung nutzt, MUSS sie im Handshake aushandeln:

{
"id": "uicp.policy",
"versions": ["0.1"],
"required": false
}
  1. Policy ist lokal durchsetzbar. Eine App oder Bridge MUSS eine Policy-Entscheidung auch dann durchsetzen können, wenn der Agent etwas anderes will.
  2. Policy ist entscheidungsorientiert, nicht promptorientiert.
  3. Policy trennt Berechtigung, Risiko, Sensitivität, Audit-Pflicht und Handoff-Pflicht.
  4. Policy MUSS vor jeder nicht-trivialen Action ausgewertet werden.
  5. Policy DARF nie schwächer sein als Browser- oder Plattformgrenzen.

type PrincipalType = "user" | "agent" | "bridge" | "observer" | "system";
interface PolicyPrincipal {
type: PrincipalType;
id: string;
roles?: string[];
grants?: PolicyGrant[];
}
type PolicyGrant =
| "observe"
| "guide"
| "draft"
| "act"
| "admin"
| "read.sensitive"
| "read.secret"
| "write.sensitive"
| "billing"
| "identity"
| "security";
  • observe: UI lesen, aber nichts verändern
  • guide: Highlight/Fokus/Navigation ohne Seiteneffekt
  • draft: reversible Entwürfe oder Feldvorschläge
  • act: normale operative Actions
  • admin: privilegierte Actions
  • zusätzliche Grants regeln sensible Domänen und Daten
type DataClass =
| "public"
| "internal"
| "personal"
| "sensitive"
| "credential"
| "secret"
| "payment"
| "legal";
type SideEffectClass =
| "none"
| "local_ui"
| "internal_persist"
| "external_message"
| "identity_change"
| "billing_change"
| "security_change"
| "irreversible";
type PolicyEffect =
| "allow"
| "confirm"
| "deny"
| "handoff";
type PolicyReasonCode =
| "grant_missing"
| "route_denied"
| "target_denied"
| "risk_confirm"
| "risk_blocked"
| "sensitive_data"
| "secret_data"
| "credential_data"
| "external_effect"
| "privileged_action"
| "user_activation_missing"
| "human_actor_required"
| "unsafe_retry"
| "redaction_required"
| "policy_default";

interface PolicyDocument {
modelVersion: "0.1";
extension: "uicp.policy";
profile?: string; // z. B. "[email protected]"
defaults: PolicyDefaults;
rules: PolicyRule[];
redaction?: RedactionRule[];
audit?: AuditPolicy;
handoff?: HandoffPolicy;
metadata?: Record<string, unknown>;
}
interface PolicyDefaults {
onSafeRisk: PolicyEffect; // RECOMMENDED: "allow"
onConfirmRisk: PolicyEffect; // RECOMMENDED: "confirm"
onBlockedRisk: PolicyEffect; // RECOMMENDED: "handoff"
onUnknownAction: PolicyEffect; // RECOMMENDED: "deny"
onSensitiveRead: PolicyEffect; // RECOMMENDED: "confirm"
onSecretRead: PolicyEffect; // RECOMMENDED: "deny"
}
interface PolicyRule {
id: string;
enabled?: boolean;
priority?: number; // higher wins
when: PolicyPredicate;
effect: PolicyEffect;
obligations?: PolicyObligation[];
reason?: string;
}
interface PolicyPredicate {
actionIds?: string[];
routeIds?: string[];
stableIds?: string[];
roles?: UIRole[];
riskLevels?: RiskLevel[];
riskTags?: RiskTag[];
dataClasses?: DataClass[];
sideEffectClasses?: SideEffectClass[];
principals?: string[]; // principal.id
principalTypes?: PrincipalType[];
requiredGrants?: PolicyGrant[];
executionModes?: ExecutionMode[];
}
type PolicyObligation =
| {
type: "audit";
level?: AuditLevel;
}
| {
type: "redact";
paths: string[];
replacement?: string;
}
| {
type: "limitExecutionModes";
modes: ExecutionMode[];
}
| {
type: "requireVerification";
policy: "any" | "all";
signals?: SuccessSignal[];
}
| {
type: "requireUserActivation";
}
| {
type: "requireHumanActor";
reason?: string;
}
| {
type: "maxAttempts";
value: number;
};

interface PolicyContext {
sessionId?: string;
revision?: RevisionId;
principal: PolicyPrincipal;
actionId: ActionId;
target?: {
ref?: TargetRef;
stableId?: StableId;
role?: UIRole;
name?: string;
scopeId?: ScopeId;
documentId?: DocumentId;
};
risk?: RiskDescriptor;
dataClasses?: DataClass[];
sideEffectClass?: SideEffectClass;
executionMode?: ExecutionMode;
routeId?: string;
userActivation?: {
isActive?: boolean;
hasBeenActive?: boolean;
};
retryOfActionHandle?: string;
attempt?: number;
args?: Record<string, unknown>;
metadata?: Record<string, unknown>;
}

interface PolicyDecision {
decision: PolicyEffect;
reasonCodes: PolicyReasonCode[];
obligations?: PolicyObligation[];
effectiveExecutionModes?: ExecutionMode[];
redactions?: RedactionPlan[];
audit?: AuditDirective;
cacheTtlMs?: number;
}
interface RedactionPlan {
path: string;
replacement: string;
}
type AuditLevel = "none" | "decision" | "result" | "full";
interface AuditDirective {
level: AuditLevel;
emitRecord: boolean;
}

interface PolicyGetPayload {}
interface PolicyDocumentPayload {
policy: PolicyDocument;
revision?: string;
}

interface PolicyEvaluatePayload {
context: PolicyContext;
}
interface PolicyDecisionPayload {
contextHash?: string;
decision: PolicyDecision;
}

interface PolicyChangedPayload {
revision: string;
reason?: "role_change" | "tenant_change" | "feature_flag" | "policy_update" | string;
policy: PolicyDocument;
}

interface PolicyAuditPayload {
record: PolicyAuditRecord;
}
interface PolicyAuditRecord {
auditId: string;
ts: string;
sessionId?: string;
principal: PolicyPrincipal;
actionId?: ActionId;
target?: TargetRef;
decision: PolicyEffect;
reasonCodes: PolicyReasonCode[];
obligations?: PolicyObligation[];
sideEffectClass?: SideEffectClass;
outcome?: "preflight" | "granted" | "confirmed" | "executed" | "failed" | "denied" | "handoff";
stateRevision?: RevisionId;
metadata?: Record<string, unknown>;
}

Ein Policy Executor MUSS Entscheidungen in dieser Reihenfolge ableiten:

  1. Explizite Deny-Regeln
  2. Grant-Prüfung
  3. Target-/Route-Blocklisten
  4. Datenklassen und Redaktionspflicht
  5. Risk Level und Risk Tags
  6. Seiteneffektklasse
  7. User-Activation-/Human-Actor-Pflichten
  8. Defaults
  • safe + ausreichende Grants → allow
  • confirmconfirm
  • blockedhandoff
  • secret/credential lesen ohne Spezialgrant → deny
  • nicht-idempotenter Retry bei sideEffectState="unknown"deny oder handoff

Redaktion MUSS getrennt von Action-Zulässigkeit modelliert werden.

interface RedactionRule {
id: string;
when: {
dataClasses?: DataClass[];
stableIds?: StableId[];
routeIds?: string[];
};
applyTo: Array<"snapshot" | "signal" | "returnValue" | "audit">;
replacement?: string; // default: "[REDACTED]"
}
  1. Redaktion DARF ein Objekt teilweise maskieren, ohne die Action selbst zu verbieten.
  2. credential und secret SOLLTEN standardmäßig redaktiert werden.
  3. Audit-Daten SOLLTEN stärker redaktiert werden als Laufzeitdaten.

Browser und Plattform verlangen in manchen Fällen echte Nutzerinteraktion oder vertrauenswürdige Eingaben. Deshalb ist handoff kein Fehler, sondern ein normaler Policy-Ausgang. navigator.userActivation liefert den Aktivierungszustand, Event.isTrusted unterscheidet Browser-/User-Agent-generierte Events von dispatchEvent()-Events, und HTMLElement.click() ersetzt nicht automatisch jede user-activation-gated Situation. ([MDN Web Docs][2])

interface HandoffPolicy {
triggers: HandoffTrigger[];
defaultMessage?: string;
}
type HandoffTrigger =
| "user_activation_required"
| "credential_entry"
| "payment_approval"
| "external_auth"
| "captcha"
| "legal_acknowledgement"
| "ambiguity"
| "security_sensitive";
  • Wenn eine Obligation requireUserActivation enthält und userActivation.isActive !== true, MUSS die Entscheidung mindestens handoff sein.
  • Wenn requireHumanActor aktiv ist, DARF keine autonome Ausführung folgen.
  • handoff SOLLTE einen menschenlesbaren Grundtext erzeugen können.

Eine konforme [email protected]-Implementierung MUSS:

  • uicp.policy.get und uicp.policy.evaluate unterstützen,
  • PolicyDocument und PolicyDecision validieren,
  • confirm, deny, handoff und allow unterscheiden,
  • Redaction Rules anwenden können,
  • Audit-Records erzeugen können,
  • blocked Risk härter behandeln als confirm.

{
"modelVersion": "0.1",
"extension": "uicp.policy",
"profile": "[email protected]",
"defaults": {
"onSafeRisk": "allow",
"onConfirmRisk": "confirm",
"onBlockedRisk": "handoff",
"onUnknownAction": "deny",
"onSensitiveRead": "confirm",
"onSecretRead": "deny"
},
"rules": [
{
"id": "deny-credentials",
"priority": 100,
"when": {
"dataClasses": ["credential", "secret"]
},
"effect": "deny",
"obligations": [
{
"type": "audit",
"level": "decision"
}
]
},
{
"id": "confirm-create-video",
"priority": 50,
"when": {
"actionIds": ["video.create"]
},
"effect": "confirm",
"obligations": [
{
"type": "requireVerification",
"policy": "all",
"signals": [
{ "kind": "route.changed", "pattern": "/videos/:id" },
{ "kind": "toast.contains", "text": "erstellt" }
]
},
{
"type": "audit",
"level": "result"
}
]
}
],
"redaction": [
{
"id": "mask-secrets",
"when": {
"dataClasses": ["secret", "credential"]
},
"applyTo": ["snapshot", "audit", "returnValue"],
"replacement": "[REDACTED]"
}
],
"audit": {
"level": "result",
"includeArgs": false,
"includeReturnValue": false
},
"handoff": {
"triggers": [
"user_activation_required",
"credential_entry",
"payment_approval",
"external_auth"
],
"defaultMessage": "Bitte übernimm diesen Schritt selbst."
}
}

  • [UIAP-CORE] UIAP Core v0.1
  • [RFC2119] Key words for use in RFCs to Indicate Requirement Levels, BCP 14
  • Policy-Dokumente SOLLTEN nur von vertrauenswürdigen Quellen geladen werden.
  • Eine lokale deny-Entscheidung MUSS immer Vorrang haben und DARF NICHT von einem externen Service überstimmt werden.
  • Audit-Logs MÜSSEN manipulationssicher gespeichert werden.
  • Sensitive Daten MÜSSEN vor der Protokollierung redaktiert werden.
  • requireUserActivation SOLLTE für alle irreversiblen oder extern wirksamen Aktionen gesetzt sein.
VersionDatumÄnderungen
0.12026-03-27Initialer Entwurf