Files
govoplan-core/docs/POLICY_CONTRACTS.md
2026-07-14 13:22:10 +02:00

6.0 KiB

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 implementation and routes, with compatibility helpers in core /api/v1/admin/privacy-retention/policies/{scope} and /explain; capability policy.privacyRetention 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.

The retention implementation lives in govoplan-policy (govoplan_policy.backend.retention). Core keeps govoplan_core.privacy.retention only as a compatibility facade for older imports. Effective/scoped retention behavior dispatches through the policy.privacyRetention capability; core does not import policy implementation code as a hidden fallback when the module is disabled or no runtime is active. New backend code should import policy-owned retention behavior from govoplan-policy or request the capability, not add new implementation logic to core.

The retention API DTOs live in govoplan_core.privacy.schemas. PrivacyRetentionPolicyItem, PrivacyRetentionPolicyPatchItem, RETENTION_POLICY_FIELD_KEYS, and default_allow_lower_level_limits() are platform contracts because admin, access compatibility, and policy routes expose the same stable retention payload shape. The policy engine's internal PrivacyRetentionPolicy and PrivacyRetentionPolicyPatch models stay in govoplan-policy, because they carry implementation validators and merge behavior that are not generic API contracts.

Tenant administration DTOs remain owned by govoplan-tenancy; access keeps matching compatibility DTOs only for its legacy admin surface. Admin overview responses remain module-local because the same counters are exposed from different menu contexts and are not yet a separately versioned platform API.

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.