Summary
Support multi-document policy composition and first-class scope ownership in the Kubernetes operator so pgroles can work cleanly in environments where platform/SRE teams and application teams share responsibility for PostgreSQL access control.
Today the operator reconciles a single PostgresPolicy into one core PolicyManifest. That works well for smaller setups, but it becomes awkward in larger environments where:
- platform/SRE wants to own connection settings, shared profiles, schema ownership, and privileged group roles
- application teams want to own their service roles and memberships
- multiple teams need independent review and GitOps workflows without editing one shared object
The operator already has same-database conflict detection between separate policies, but it does not yet support composing multiple fragments into one desired policy with explicit in-policy ownership boundaries.
Goals
- Support policy composition from multiple scoped inputs
- Make ownership boundaries explicit and validated before inspect/diff/apply
- Keep one reconcile loop, one lock, one plan, and one apply transaction per database target
- Reuse the same core composition semantics in the CLI and operator
- Preserve the current single-policy path for simpler users
Non-goals
- Reading arbitrary files from Git repos or ConfigMaps at reconcile time
- Letting independently reconciled fragments race on the same database
- Adding implicit "last writer wins" merge semantics
Proposed model
Introduce composition in pgroles-core first, then expose it through the operator.
Core concepts:
PolicyBundle
- shared profiles and auth provider metadata
- list of policy fragments to compose
PolicyFragment
- one team's scoped contribution
- declares resources plus an explicit ownership scope
ComposedPolicy
- the validated result of bundle + fragments
OwnershipIndex
- exact ownership of generated and declared objects
ManagedScope
- the set of database objects pgroles is allowed to inspect/diff/manage
For the operator API, the cleanest shape is:
PostgresPolicyBundle
- connection settings
- interval / mode / approval
- shared profiles
- selector or refs for fragments
PostgresPolicyFragment
- team-owned policy content
- explicit scope ownership
Only the bundle should reconcile. Fragments are inputs, not independently applying resources.
Scope ownership
Ownership should be explicit, not inferred after the fact.
Examples:
- schema facets such as
owner and bindings
- named roles
- membership edges
- exact grant keys
- exact default-privilege keys
- retirements by role name
If a fragment defines content outside its declared scope, the composed policy should fail validation.
If two fragments claim the same managed key, the bundle should go Conflict before any SQL is planned or applied.
Operator reconciliation semantics
The intended reconcile flow becomes:
- Reconcile
PostgresPolicyBundle
- Load matching
PostgresPolicyFragment resources
- Compose bundle + fragments through
pgroles-core
- Validate duplicate/conflicting claims and out-of-scope declarations
- Build one desired graph and one managed scope
- Inspect the live database only within that managed scope
- Diff and either apply or publish a plan
This keeps the existing lower-half operator behavior intact:
- same per-database locking
- same advisory locking
- same
PostgresPolicyPlan style plan lifecycle
- same status / events / OTLP observability model
Backward compatibility
- Keep
PostgresPolicy as the current single-document path
- Add composition as a new API path rather than changing existing semantics underneath users
- Reuse the same core composition library from CLI and operator so behavior stays aligned
Suggested phases
- Add composition and ownership primitives to
pgroles-core
- Add CLI support for validating/composing bundles and fragments
- Add
PostgresPolicyBundle / PostgresPolicyFragment CRDs
- Reconcile only bundles, using fragments as inputs
- Reuse existing plan/apply/status machinery at bundle level
- Document migration from single-policy to bundle/fragment workflows
Acceptance criteria
- Conflicting fragment ownership is rejected before database inspection
- Out-of-scope fragment declarations are rejected during validation
- Generated roles/grants/default privileges from schema/profile expansion participate in ownership checks
- One bundle reconcile produces one composed plan and one apply transaction
- Existing
PostgresPolicy users remain unaffected
Open questions
- Should fragments be selected by label selector, explicit refs, or both?
- Should
PostgresPolicyPlan stay bundle-scoped only, or also reference the fragment set digest used to compose it?
- Do we want bundle-local shared profiles only, or separately reusable cluster-scoped profile libraries later?
Summary
Support multi-document policy composition and first-class scope ownership in the Kubernetes operator so pgroles can work cleanly in environments where platform/SRE teams and application teams share responsibility for PostgreSQL access control.
Today the operator reconciles a single
PostgresPolicyinto one corePolicyManifest. That works well for smaller setups, but it becomes awkward in larger environments where:The operator already has same-database conflict detection between separate policies, but it does not yet support composing multiple fragments into one desired policy with explicit in-policy ownership boundaries.
Goals
Non-goals
Proposed model
Introduce composition in
pgroles-corefirst, then expose it through the operator.Core concepts:
PolicyBundlePolicyFragmentComposedPolicyOwnershipIndexManagedScopeFor the operator API, the cleanest shape is:
PostgresPolicyBundlePostgresPolicyFragmentOnly the bundle should reconcile. Fragments are inputs, not independently applying resources.
Scope ownership
Ownership should be explicit, not inferred after the fact.
Examples:
ownerandbindingsIf a fragment defines content outside its declared scope, the composed policy should fail validation.
If two fragments claim the same managed key, the bundle should go
Conflictbefore any SQL is planned or applied.Operator reconciliation semantics
The intended reconcile flow becomes:
PostgresPolicyBundlePostgresPolicyFragmentresourcespgroles-coreThis keeps the existing lower-half operator behavior intact:
PostgresPolicyPlanstyle plan lifecycleBackward compatibility
PostgresPolicyas the current single-document pathSuggested phases
pgroles-corePostgresPolicyBundle/PostgresPolicyFragmentCRDsAcceptance criteria
PostgresPolicyusers remain unaffectedOpen questions
PostgresPolicyPlanstay bundle-scoped only, or also reference the fragment set digest used to compose it?