4.6 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 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-6022333f4d4bcampaign: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_typeand optionalscope_iddecision, using the sharedPolicyDecisionshapeeffective_policy- optional
parent_policy effective_policy_sourcesparent_policy_sourcesblocked_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_sourcesis 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.