Skip to content

PolicyEngineService

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-18 Policy
Platform / plane Governance / Trust & Operations
Deployment granularity module of host · host GovernanceHost
Status target · ConnectSoft.Factory.Governance.Policy
Built from connectsoft-microservice · libraries: connectsoft-extensions-compliance, connectsoft-extensions-saas-abstractions, connectsoft-extensions-messaging-model, connectsoft-extensions-persistence-model-nhibernate, connectsoft-extensions-audit-net
Decisions ADR-0015, ADR-0021, ADR-0022
Traceability FR-01 FR-04 FR-05 FR-06 FUX-01 FUX-05 NFR-01 NFR-04 XL-04 XL-05 XL-10

Purpose and responsibilities

The original design's policy question (§43) is: WHO can run WHICH AGENT using WHICH MODELS against WHICH DATA with WHICH TOOLS in WHICH ENVIRONMENT under WHICH BUDGET. PolicyEngineService answers it deterministically from bundles — Published policy assets (BC-04) typed into rules per domain (run acceptance, model access, tool execution, knowledge access, memory promotion, connection grants, deployment, spend, data handling) for the platform, a tenant or a project, where each narrower scope may only tighten the wider one (D18) — and records every decision immutably with its effect (allow, deny, require-approval, prohibited), obligations, matched rules and reasons (policy-decision-v1). Runs pin the active revision at acceptance; a decision under a pinned revision is reproducible.

It absorbs PolicyDefinitionService, PolicyEvaluationService, MemoryPolicyService and TenantIsolationPolicyService from the June-2026 catalog: one engine, several domains.

Context and boundaries

flowchart LR
    S["PolicyEngineService"]
    RunControlService["RunControlService"] -->|grpc| S
    AgentRuntimeService["AgentRuntimeService"] -->|grpc| S
    ModelRouterService["ModelRouterService"] -->|grpc| S
    ToolAdapterService["ToolAdapterService"] -->|grpc| S
    ContextBuilderService["ContextBuilderService"] -->|grpc| S
    MemoryPromotionService["MemoryPromotionService"] -->|grpc| S
    IntegrationService["IntegrationService"] -->|grpc| S
    StudioBff["StudioBff"] -->|sync-http| S
    S -->|event| AgentRegistryService["AgentRegistryService"]
    S -->|grpc| ProjectService["ProjectService"]
    S -->|grpc| EnvironmentService["EnvironmentService"]
    S -->|event| AuditService["AuditService"]
    S -. "PolicyRevisionActivated" .-> E_PolicyRevisionActivated(("event"))
    S -. "PolicyRevisionRetired" .-> E_PolicyRevisionRetired(("event"))
    S -. "PolicyDecisionRecorded" .-> E_PolicyDecisionRecorded(("event"))
Hold "Alt" / "Option" to enable pan & zoom

Example bundle (the §43 YAML, typed):

scope: project:booking-saas
parent: tenant:connectsoft
rules:
  - id: shell-read
    domain: ToolExecution
    match: { action: "tool:shell-sandbox", environmentClass: Production, operation: read }
    effect: Allow
  - id: shell-write-prod
    domain: ToolExecution
    match: { action: "tool:shell-sandbox", environmentClass: Production, operation: write }
    effect: RequireApproval
    obligations: { actionClass: production }
  - id: db-update
    domain: ToolExecution
    match: { action: "tool:database", operation: update }
    effect: Prohibited
  - id: deploy-staging
    domain: Deployment
    match: { environmentClass: Staging }
    effect: Allow
  - id: deploy-prod
    domain: Deployment
    match: { environmentClass: Production }
    effect: RequireApproval
    obligations: { actionClass: production, approverRule: release-managers }
  - id: pii-external-models
    domain: ModelAccess
    match: { dataClass: Restricted, privacyClass: External }
    effect: Prohibited

Domain model

PolicyBundle — Runtime read model of a Published policy asset — a versioned set of rules per domain for one scope (the YAML of the original design §43 made typed), with the revision id runs pin at acceptance. (catalog entry)

Field Type Required Description
revision string yes Policy revision id pinned by runs (ADR-0014).
tenantId string yes
scope enum:PolicyScope yes
scopeId string yes Tenant id or project id; platform for platform scope.
releaseId string yes Published asset release (BC-04).
parentRevision string no The wider-scope revision this bundle tightens.
rulesRef string yes Artifact reference of the rule document.
rulesHash hash yes
domains list:string yes
active bool yes
activatedAt datetime no

Invariants:

  • A tenant bundle may only tighten the platform bundle and a project bundle may only tighten the tenant bundle; activation validates monotonic tightening per rule and refuses otherwise.
  • Bundles are immutable per revision; a change is a new Published release and a new revision; accepted runs keep their pinned revision.
  • Prohibited effects cannot be overridden by any approval; RequireApproval names the action class the approval must bind to (D21).

Events: PolicyRevisionActivated, PolicyRevisionRetired · Tables: PolicyBundles, PolicyRules · Repository: PolicyBundleRepository

PolicyDecision — An immutable recorded decision (policy-decision-v1) for one subject, action and resource under one revision — effect, obligations, matched rules and reasons. (catalog entry)

Field Type Required Description
decisionId guid yes
tenantId string yes
projectId string no
environmentId string no
runId guid no
subject json yes Identity kind and id (user, agent release, workload), roles, delegated scope
domain enum:PolicyDomain yes
action string yes Action identifier, for example tool:propose-change-set or model:invoke:code-agent
actionClass string yes Side-effect class after environment raising.
resource json yes Resource descriptor: tool, profile, source, connection, environment, data class
inputsHash hash no
policyRevision string yes
effect enum:PolicyEffect yes
obligations json no Redaction, limits, approver rule reference, logging requirements
matchedRules list:string yes
reasons list:string yes
evaluatedAt datetime yes

Invariants:

  • Decisions are immutable and reference the exact revision; the same inputs under the same revision give the same effect (deterministic evaluation).
  • Every effect of RequireApproval names the action class; the approval that follows (BC-03, BC-20) binds to this decision id.
  • Decisions never contain prompt text, secrets or payloads; they reference hashes.

Events: PolicyDecisionRecorded · Tables: PolicyDecisions · Repository: PolicyDecisionRepository

erDiagram
    PolicyBundle {
        string revision PK
        string tenantId
        enum_PolicyScope scope
        string scopeId
        string releaseId
        string parentRevision
        string rulesRef
        hash rulesHash
        list_string domains
        bool active
        datetime activatedAt
    }
    PolicyDecision {
        guid decisionId PK
        string tenantId
        string projectId
        string environmentId
        guid runId
        json subject
        enum_PolicyDomain domain
        string action
        string actionClass
        json resource
        hash inputsHash
        string policyRevision
        enum_PolicyEffect effect
        json obligations
        list_string matchedRules
        list_string reasons
        datetime evaluatedAt
    }
    PolicyRule {
        string ruleId

        enum_PolicyDomain domain

        json match

        enum_PolicyEffect effect

        json obligations

        int priority

    }
    PolicyBundle ||--o{ PolicyRule : contains
Hold "Alt" / "Option" to enable pan & zoom

Entities and value objects

PolicyRule (entity, in PolicyBundle) — One typed rule of a domain.

Field Type Required Description
ruleId string yes
domain enum:PolicyDomain yes
match json yes Subject, action, resource and environment predicates
effect enum:PolicyEffect yes
obligations json no
priority int yes

Data model and storage

Store Role Engine / profile Tables
Governance store primary azure-sql / nhibernate-sqlserver PolicyBundles, PolicyRules, PolicyDecisions
Immutable audit store blob blob / azure-blob-immutable —
Cache and coordination cache redis / azure-cache —
Table Owner Columns Indexes
dbo.PolicyBundles PolicyBundle 11 1
dbo.PolicyRules PolicyRule 8 0
dbo.PolicyDecisions PolicyDecision 17 2

Full DDL: appendix.

APIs

Method Path Operation Scope Side effects Idempotency Gateway Summary
GET /factory/policies ListPolicyBundles factory.policies.read read natural GW-03 Active and retired bundles per scope with revisions
GET /factory/policies/{revision} GetPolicyBundle factory.policies.read read natural GW-03 The rules of a revision (typed view of the YAML).
POST /factory/projects/{projectId}/policy/validate ValidateProjectPolicy factory.policies.write sandbox natural GW-03 Validate a project override against the tenant bundle: every rule must tighten, never loosen (D18).
POST /factory/policies/simulate SimulatePolicy factory.policies.write sandbox natural GW-03 What-if evaluation of a candidate bundle against recorded decisions of a period (sandbox; later-stage depth).
GET /factory/runs/{runId}/policy-decisions ListRunDecisions factory.runs.read read natural GW-03 Decisions recorded for a run with effects
gRPC service Method Streaming Visibility Side effects Summary
Policy Decide none internal read Evaluate subject
Policy GetActiveRevision none internal read The revision a new run must pin for its scope.
Policy ValidateTightening none internal read Check that a candidate bundle only tightens its parent; used at publication of policy assets.

Messages

Event Topic Scope / v Aggregate Consumers Summary
PolicyRevisionActivated policy.bundles.v1.policy-revision-activated integration / 1 PolicyBundle ApprovalPolicyService, RunControlService, StudioBff, AuditService, DashboardService A bundle revision is active for its scope; new runs pin it.
PolicyRevisionRetired policy.bundles.v1.policy-revision-retired integration / 1 PolicyBundle StudioBff, AuditService A revision is no longer active; pinned runs finish on it.
PolicyDecisionRecorded policy.decisions.v1.policy-decision-recorded integration / 1 PolicyDecision AuditService, DashboardService A decision was evaluated and recorded.

PolicyRevisionActivated payload:

Field Type Required Description
revision string yes
tenantId string yes
scope enum:PolicyScope yes
scopeId string yes
rulesHash hash yes

PolicyRevisionRetired payload:

Field Type Required Description
revision string yes
tenantId string yes
scope enum:PolicyScope yes

PolicyDecisionRecorded payload:

Field Type Required Description
decisionId guid yes
tenantId string yes
runId guid no
domain enum:PolicyDomain yes
action string yes
actionClass string yes
effect enum:PolicyEffect yes
policyRevision string yes
Command Handled by Result events Summary
ActivateRevision PolicyBundle PolicyRevisionActivated, PolicyRevisionRetired
Decide PolicyDecision PolicyDecisionRecorded
Event Producer Handler Purpose
AssetReleasePublished AgentRegistryService ActivatePolicyBundle Activate Published policy assets after tightening validation.
AssetReleaseRevoked AgentRegistryService RetirePolicyBundle Retire revoked policy revisions.

Workers and jobs

Worker Trigger Output events Retry Idempotency key Summary
DecisionRetentionSweeper schedule 0 2 * * * — fixed decisionId Moves decisions older than the hot window to the immutable audit store and keeps the 7-year evidence copy.

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 X as Caller (runtime, gateway, executor…)
    participant P as PolicyEngineService
    participant A as AuditService
    X->>P: Decide (subject, domain, action, class, resource, revision)
    P->>P: resolve bundle chain platform → tenant → project
    P->>P: match rules by priority, tightening semantics
    P->>P: record decision (effect, obligations, reasons)
    P-)A: PolicyDecisionRecorded
    P-->>X: decision id, effect, obligations
Hold "Alt" / "Option" to enable pan & zoom

Options and configuration

Section Key Type Default Reloadable Secret Summary
Governance:Policy DecisionCacheSeconds int 30 yes no Cache identical decisions per revision for hot paths; never across revisions.
Governance:Policy PlatformSpendThresholdUsd decimal 10 yes no Platform spend threshold referenced by Spend rules (D21); projects may lower it.

Metrics and observability

Metric Instrument Unit Dimensions Summary
connectsoft.factory.policy.decisions counter {decision} tenant, domain, effect Decisions by effect.
connectsoft.factory.policy.decision_latency histogram s domain Evaluation latency.
connectsoft.factory.policy.active_revisions updown {revision} scope Active revisions per scope.
connectsoft.factory.policy.tightening_rejections counter {bundle} tenant Overrides refused for loosening.

Security

Authentication client-credentials
Scopes factory.policies.read (Read bundles and decisions.); factory.policies.write (Validate and simulate (Workspace admin); factory.runs.read (Decisions of a run.)
Roles Reviewer, Operator, WorkspaceAdmin, PlatformAdmin, Observer
Tenant isolation row-filter
Data classification restricted
Max side-effect class sandbox
Secrets —

Dependencies

Outbound

Target Kind Purpose Failure mode Required
AgentRegistryService event Policy bundles as Published assets. queue yes
ProjectService grpc Scope and membership roles of subjects. fail-fast yes
EnvironmentService grpc Environment class for class raising. fail-fast yes
AuditService event Decisions as evidence. queue yes

Inbound

Target Kind Purpose Failure mode Required
RunControlService grpc Acceptance decisions — yes
AgentRuntimeService grpc Decisions before gated calls. — yes
ModelRouterService grpc Decisions per invocation. — yes
ToolAdapterService grpc Decisions per tool call. — yes
ContextBuilderService grpc Classification and redaction obligations. — yes
MemoryPromotionService grpc Promotion policy. — yes
IntegrationService grpc Grant policy. — yes
StudioBff sync-http Policy views and validation. — yes

Failure and recovery

Scenario Detection Recovery Data impact RTO (min)
Rule document unreadable activation validation fails revision not activated; the previous stays active none 0
Decision latency above budget latency histogram per-revision decision cache; more replicas; callers keep their timeouts and fail closed none 15

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

The MCP governance toolkit integration offers policy paths, fail-closed and audit options for MCP tools only; ConnectSoft.Extensions.ResourceServer offers capability-based authorization. No bundle, revision, domain model or recorded decision exists.

Later capabilities

Policy simulation against recorded decisions at depth; policy drift detection across tenants; agent-authored policy proposals reviewed by Platform admins; per-run policy explanations in the Studio.

Decisions and traceability

Family Ids
FR FR-01, FR-04, FR-05, FR-06
FUX FUX-01, FUX-05
NFR NFR-01, NFR-04
XL XL-04, XL-05, XL-10