1
Repo docs POLICY CONTRACTS
Albrecht Degering edited this page 2026-07-09 11:42:41 +02:00

Mirrored from /mnt/DATA/git/govoplan-core/docs/POLICY_CONTRACTS.md. Origin: repository. Active tasks and changing state belong in Gitea issues; this wiki page is durable project context.


GovOPlaN Policy Contracts

GovOPlaN has several policy families that are moving out of core into owning modules. The shared kernel contract keeps their decision and provenance shape consistent while each module still owns its domain rules.

Current Policy Inventory

Policy area Current owner Runtime surface Notes
Privacy retention govoplan-policy routes with compatibility helpers in core /api/v1/admin/privacy-retention/policies/{scope} and /explain System, tenant, user, group, and campaign sources merge into the effective retention policy. Parent locks block lower-level widening.
Mail profile policy govoplan-mail /api/v1/mail/policies/{scope} Uses the same source-step path format for system, tenant, owner, and campaign provenance.
RBAC/access policy govoplan-access access capabilities in govoplan_core.core.access Permission decisions should use access capability contracts. Explain responses should adopt PolicyDecision when an API-level explanation is added.
Governance defaults govoplan-admin plus govoplan-access materializer admin settings, governance template routes, access materialization capability System governance can block tenant-local groups, roles, and API keys.
Delegation and ownership policy access/campaign/mail/files modules capability checks and owner-scoped APIs Source provenance should use this contract when policies become externally explainable.

Policy Decision

The shared DTO lives in govoplan_core.core.policy.PolicyDecision.

{
  "allowed": false,
  "reason": "Parent retention policy locks lower-level changes.",
  "source_path": [
    {
      "scope_type": "system",
      "scope_id": null,
      "path": "system",
      "label": "System",
      "applied_fields": ["allow_lower_level_limits"],
      "policy": {}
    }
  ],
  "requirements": ["raw_campaign_json_retention_days"],
  "details": {
    "blocked_fields": ["raw_campaign_json_retention_days"]
  }
}

allowed is the effective answer for the checked action. reason is a stable, human-readable summary. source_path lists the policy sources that explain the answer. requirements lists machine-readable blockers or prerequisites, and details carries domain-specific structured context.

Every source step should be concrete enough for an operator to understand the decision without knowing internal merge rules. Use real scope labels such as System, Tenant, Owner user, Group, or a campaign/profile name. Include the stable path, the fields applied by that step, and the local policy fragment that caused them. This lets UIs render explanations like System: Allow > Tenant: Deny without override without additional lookups. If a policy family cannot expose the full local fragment for security reasons, it must still include a redacted structured value that identifies the applied field and the effective allow/deny or lock state.

Source Path Format

Policy source paths are stable string identifiers for provenance steps:

  • system
  • <scope_type>:<url-encoded-scope-id>

Supported scope types are system, tenant, user, group, and campaign. Examples:

  • tenant:4a45b4fe-1d86-43ce-9d10-6022333f4d4b
  • campaign:campaign%2Fwith%20space

Use policy_source_path() and parse_policy_source_path() instead of building or splitting these strings manually.

Retention Explain Endpoint

GET /api/v1/admin/privacy-retention/policies/{scope_type}/explain returns:

  • scope_type and optional scope_id
  • decision, using the shared PolicyDecision shape
  • effective_policy
  • optional parent_policy
  • effective_policy_sources
  • parent_policy_sources
  • blocked_fields

The endpoint is read-only. Enforcement remains in the existing policy write path. For lower-level scopes, blocked_fields is derived from the parent policy's allow_lower_level_limits; clients can use it to disable local controls before attempting a write.

Frontend Contract

Policy UIs must:

  • render effective source provenance when effective_policy_sources is present
  • display a field-level path when the source data is shown next to a specific setting, using concrete source labels and stop at the first non-overridable deny/lock
  • disable local field controls when the parent policy sets that field's lower-level limit to false
  • avoid sending locked fields or re-enable attempts in save payloads
  • show inherited values separately from local overrides

The core WebUI helper privacyRetentionParentAllowsField() centralizes the field-lock decision used by the retention editor and its lightweight module tests.