Skip to content

ApprovalPolicyService

Target

This page describes intended behaviour or structure. No implementation is claimed unless a section is explicitly marked Current implementation or Evidence.

Bounded context BC-20 Approval Policy
Platform / plane Governance / Trust & Operations
Deployment granularity module of host · host GovernanceHost
Status target · ConnectSoft.Factory.Governance.Approvals
Built from connectsoft-microservice · libraries: connectsoft-extensions-saas-abstractions, connectsoft-extensions-messaging-model, connectsoft-extensions-persistence-model-nhibernate, connectsoft-extensions-audit-net
Decisions ADR-0014, ADR-0015
Traceability FR-06 FUX-05 NFR-04 XL-05 XL-02

Purpose and responsibilities

The ownership map splits approvals in two: instances and decisions are Run Control's (BC-03); the policy is here. ApprovalPolicyService holds the rules per scope and action class (D21 classes: shared-write, external, production, publication, spend) with the roles that may decide (Reviewer, Workspace admin; Platform admin as break-glass), the separation-of-duties constraints of ADR-0015 point 6 (never the requester, never the author), quorum, expiry and break-glass allowance. At request time it routes (eligible approvers, expiry); at decision time it validates (eligibility now, exclusions, timeliness, quorum). Project rules may only tighten tenant rules.

Context and boundaries

flowchart LR
    S["ApprovalPolicyService"]
    RunControlService["RunControlService"] -->|grpc| S
    StudioBff["StudioBff"] -->|sync-http| S
    S -->|event| PolicyEngineService["PolicyEngineService"]
    S -->|grpc| ProjectService["ProjectService"]
    S -->|sync-http| ext_identity_platform["ext-identity-platform"]
    S -. "ApprovalRuleActivated" .-> E_ApprovalRuleActivated(("event"))
    S -. "ApprovalRouted" .-> E_ApprovalRouted(("event"))
    S -. "ApprovalDecisionValidated" .-> E_ApprovalDecisionValidated(("event"))
Hold "Alt" / "Option" to enable pan & zoom

Domain model

ApprovalRule — A rule for one scope and action class — approver roles, separation-of-duties constraints, quorum, expiry, break-glass allowance and the policy revision it belongs to. (catalog entry)

Field Type Required Description
ruleId string yes
tenantId string yes
scope string yes platform
actionClass string yes shared-write
actionPattern string no Optional narrowing to an action family (tool id
approverRoles list:string yes
excludeRequester bool yes
excludeAuthor bool yes For publication actions the asset author may not approve.
quorum int yes
expiryMinutes int yes
breakGlassAllowed bool yes
policyRevision string yes
active bool yes

Invariants:

  • excludeRequester is always true (ADR-0015 point 6); a rule that sets it false is refused at activation.
  • A project rule may only tighten the tenant rule (more roles excluded, shorter expiry, higher quorum, break-glass disabled), never loosen it.
  • Expiry never exceeds the platform maximum; an expired request can only fail the action, never be revived.

Events: ApprovalRuleActivated · Tables: ApprovalRules · Repository: ApprovalRuleRepository

RoutingEvaluation — The record of routing one approval request (eligible approvers, expiry) or validating one decision (eligibility, separation of duties, timeliness) under the rule in force. (catalog entry)

Field Type Required Description
evaluationId guid yes
tenantId string yes
approvalId guid yes Run Control approval request id.
runId guid no
ruleId string yes
actionClass string yes
requesterId string yes
authorId string no
eligibleApprovers list:string no Resolved subjects or groups at routing time.
deciderId string no
outcome enum:RoutingOutcome yes
breakGlass bool yes
expiresAt datetime yes
evaluatedAt datetime yes

Invariants:

  • A decision by the requester or the author is Invalid regardless of role; a Platform admin break-glass decision is valid only with a reason and when the rule allows it, and is always audited.
  • Eligibility is evaluated against memberships at decision time, not at routing time; a revoked role invalidates a pending decision.

Events: ApprovalRouted, ApprovalDecisionValidated · Tables: RoutingEvaluations · Repository: RoutingEvaluationRepository

erDiagram
    ApprovalRule {
        string ruleId PK
        string tenantId
        string scope
        string actionClass
        string actionPattern
        list_string approverRoles
        bool excludeRequester
        bool excludeAuthor
        int quorum
        int expiryMinutes
        bool breakGlassAllowed
        string policyRevision
        bool active
    }
    RoutingEvaluation {
        guid evaluationId PK
        string tenantId
        guid approvalId
        guid runId
        string ruleId
        string actionClass
        string requesterId
        string authorId
        list_string eligibleApprovers
        string deciderId
        enum_RoutingOutcome outcome
        bool breakGlass
        datetime expiresAt
        datetime evaluatedAt
    }
Hold "Alt" / "Option" to enable pan & zoom

Entities and value objects

None modelled

No entities or value objects are modelled for this item yet.

Data model and storage

Store Role Engine / profile Tables
Governance store primary azure-sql / nhibernate-sqlserver ApprovalRules, RoutingEvaluations
Table Owner Columns Indexes
dbo.ApprovalRules ApprovalRule 13 1
dbo.RoutingEvaluations RoutingEvaluation 14 1

Full DDL: appendix.

APIs

Method Path Operation Scope Side effects Idempotency Gateway Summary
GET /factory/approval-rules ListApprovalRules factory.policies.read read natural GW-03 Rules per scope and action class with roles
POST /factory/projects/{projectId}/approval-rules/validate ValidateProjectApprovalRules factory.policies.write sandbox natural GW-03 Validate project rules against tenant rules (tighten only).
GET /factory/approvals/{approvalId}/routing GetApprovalRouting factory.approvals.read read natural GW-03 Who may decide a request
gRPC service Method Streaming Visibility Side effects Summary
ApprovalPolicy Route none internal read Resolve the rule
ApprovalPolicy ValidateDecision none internal read Validate that a decider is eligible now

Messages

Event Topic Scope / v Aggregate Consumers Summary
ApprovalRuleActivated approval-policy.rules.v1.approval-rule-activated integration / 1 ApprovalRule StudioBff, AuditService A rule revision is in force.
ApprovalRouted approval-policy.routing.v1.approval-routed integration / 1 RoutingEvaluation StudioNotificationService, DashboardService, AuditService A request was routed to eligible approvers (or found none).
ApprovalDecisionValidated approval-policy.routing.v1.approval-decision-validated integration / 1 RoutingEvaluation AuditService, DashboardService A decision was validated or found invalid.

ApprovalRuleActivated payload:

Field Type Required Description
ruleId string yes
tenantId string yes
scope string yes
actionClass string yes
policyRevision string yes

ApprovalRouted payload:

Field Type Required Description
evaluationId guid yes
approvalId guid yes
tenantId string yes
outcome enum:RoutingOutcome yes
expiresAt datetime yes

ApprovalDecisionValidated payload:

Field Type Required Description
evaluationId guid yes
approvalId guid yes
tenantId string yes
deciderId string yes
outcome enum:RoutingOutcome yes
breakGlass bool yes
Command Handled by Result events Summary
Route RoutingEvaluation ApprovalRouted
ValidateDecision RoutingEvaluation ApprovalDecisionValidated
Event Producer Handler Purpose
PolicyRevisionActivated PolicyEngineService LoadApprovalRules Approval rules are part of the policy bundle revision.
MembershipChanged ProjectService InvalidateEligibilityCache Re-evaluate eligibility when roles change.
DelegationChanged ProjectService InvalidateEligibilityCache Delegations change who may approve (D18).

Workers and jobs

None modelled

No workers are modelled for this item yet.

Actors and grains

None modelled

No grains are modelled for this item yet.

Sagas and long-running flows

None modelled

No sagas are modelled for this item yet.

sequenceDiagram
    participant RC as RunControlService
    participant AP as ApprovalPolicyService
    participant S as Studio inbox
    participant R as Reviewer
    RC->>AP: Route (action class, scope, requester, author)
    AP-->>RC: rule, eligible approvers, expiry
    RC-)S: ApprovalRequested
    R->>RC: decision with request version
    RC->>AP: ValidateDecision (decider, request)
    AP->>AP: eligible now, not requester, not author, unexpired, quorum
    AP-->>RC: Eligible or Invalid with reason
Hold "Alt" / "Option" to enable pan & zoom

Options and configuration

Section Key Type Default Reloadable Secret Summary
Governance:Approvals MaxExpiryMinutes int 4320 yes no Platform maximum for approval expiry (72 h).
Governance:Approvals BreakGlassRequiresReason bool true no no Break-glass decisions need a reason and are always audited.

Metrics and observability

Metric Instrument Unit Dimensions Summary
connectsoft.factory.approval_policy.routings counter {routing} tenant, actionClass, outcome Routings by outcome.
connectsoft.factory.approval_policy.invalid_decisions counter {decision} tenant, outcome Rejected decisions by reason.
connectsoft.factory.approval_policy.break_glass counter {decision} tenant Break-glass decisions.

Security

Authentication client-credentials
Scopes factory.policies.read (Read rules.); factory.policies.write (Validate project rules.); factory.approvals.read (Routing of a request.)
Roles Reviewer, WorkspaceAdmin, PlatformAdmin, Observer
Tenant isolation row-filter
Data classification restricted
Max side-effect class sandbox
Secrets —

Dependencies

Outbound

Target Kind Purpose Failure mode Required
PolicyEngineService event Rule revisions. queue yes
ProjectService grpc Memberships and delegations for eligibility. fail-fast yes
ext-identity-platform sync-http Platform admin claims and groups. fail-fast yes

Inbound

Target Kind Purpose Failure mode Required
RunControlService grpc Route at request time; ValidateDecision at decision time. — yes
StudioBff sync-http Rule and routing views. — yes

Failure and recovery

Scenario Detection Recovery Data impact RTO (min)
No eligible approver for a class in a project routing outcome NoEligibleApprover request stays Pending with a Studio task for the Workspace admin to grant a Reviewer; expiry fails the run none 0
Membership revoked while a decision is pending eligibility re-check decision Invalid; request re-routed none 0

NFR targets

Target Class value (pilot-interactive) Override
latency_p95_ms 2000 —
availability_pct 99.5 —
throughput_rps 50 —
rpo_minutes 15 —
rto_minutes 240 —
cost_class low —

Deployment

Granularity module of host · host GovernanceHost
Topologies saas-default, hybrid, on-prem, hybrid-compute
Runtime —; replicas —–—
Ports —

Current implementation

Nothing exists; the Config Platform prototype (September 2026) demonstrates digest-bound approvals, separation of duties and a grant matrix in the UI and is the UX reference for this service.

Later capabilities

Quorum approvals with per-item outcomes for bulk decisions; time-of-day and on-call routing; approval SLAs with escalation.

Decisions and traceability

Family Ids
FR FR-06
FUX FUX-05
NFR NFR-04
XL XL-05, XL-02