116 lines
5.2 KiB
Markdown
116 lines
5.2 KiB
Markdown
# 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`.
|
|
|
|
```json
|
|
{
|
|
"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.
|
|
|
|
## 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.
|