Compliance as Code

Declarative Ownership

Use convention-based map rules to model enterprise-scale asset ownership and responsibility at scale.

Graph-Native Responsibility Architecture

Handling ownership and responsibility in a hybrid environment goes beyond simple string tags on an asset. A string property like owner = "team-alpha" lacks semantic meaning: is “team-alpha” a cost center, a legal entity, or an engineering squad? Can they be audited?

Transitioning to a Graph-Native Responsibility Architecture turns organizational roles and entities into first-class graph citizens. This enables deep queries, such as finding the exact legal entity acting as a data processor for a specific database instance.

The Core Ontology

Instead of placing flat properties on an application or database, we introduce a dedicated set of organizational resources and relations. These organizational concepts are not hardcoded backend magic — they are defined by simply creating standard CSV asset files in your data directory, just like any other resource type.

Resources

These organizational concepts are defined by simply creating standard CSV asset files in your data directory:

  • party (data/assets/party.csv): The ultimate legal counterparty (e.g., “Acme Corp”, “Cloud Vendor LLC”). It holds canonical legal and corporate information.
  • provider (data/assets/provider.csv): The service offering or operational team (e.g., “team-alpha”, “aws-europe”). Each provider is linked to a legal party.
  • role (data/assets/role.csv): A formally defined organizational function (e.g., owner, maintainer, data_processor).

Relations

  • HAS_RESPONSIBILITY: Links an infrastructure asset (e.g., Application, Server) to the operational provider that manages it. The relation property { role: "..." } defines the specific function they perform.
  • IS_PERFORMED_BY: Links the logical role definition to the provider fulfilling it, establishing a clear RACI matrix.
  • OPERATED_BY: Links the operational provider to its legally accountable party.

Architecture Diagram

graph TD
    App[Application] -- "HAS_RESPONSIBILITY<br>{role: maintainer}" --> Prov[Provider: team-alpha]
    DB[Database] -- "HAS_RESPONSIBILITY<br>{role: data_processor}" --> ProvExt[Provider: external-db-vendor]
    
    Role1[Role: maintainer] -- IS_PERFORMED_BY --> Prov
    Role2[Role: data_processor] -- IS_PERFORMED_BY --> ProvExt
    
    Prov -- OPERATED_BY --> Party[Party: internal-org]
    ProvExt -- OPERATED_BY --> PartyExt[Party: External Vendor Inc.]

Implementation Example: Using map

For enterprise-scale ownership and responsibility modeling, rescile provides an advanced, convention-based pattern in compliance files: [control.target.map].

Instead of writing dozens of rules to link assets to owners, maintainers, and data processors, you can define the convention in a single block.

Example Foundation Data

To make this work, subject matter experts simply provide the organizational hierarchy as standard CSV files:

data/assets/party.csv

name,jurisdiction,type
internal-org,EU,Internal
External Vendor Inc.,US,Vendor

data/assets/provider.csv

name,operated_by,service_category
team-alpha,internal-org,Engineering
external-db-vendor,External Vendor Inc.,Managed Services

data/assets/role.csv

name,description
maintainer,Responsible for technical uptime and patching
data_processor,Legal entity processing data on behalf of the controller

Note how provider.csv naturally creates the OPERATED_BY link to the party via the operated_by column. By showing the raw data first, the connection between vendor = "team-alpha" on an application and the provider.csv table becomes immediately clear.

data/compliance/ownership.toml

[[control]]
id = "OWN-1"
name = "Map Asset Roles to Providers"
[[control.target]]
[control.target.map]
# 1. Find all resources of this type to get the list of possible roles (e.g., "owner", "maintainer").
derived_type = "role"
# 2. Scan these asset types for properties that match the role names.
origin_resource_types = ["application", "database"]
# 3. The value of the property is the name of a resource of this type.
target_resource_type = "provider"
# 4. Create a relation with this type from the asset to the provider.
primary_relation_type = "HAS_RESPONSIBILITY"
# 5. Add a property to the new relation indicating which role it represents.
property_on_relation = "role"
# 6. (Optional) Create a second link from the role definition itself to the provider.
link_relation_to_target = true
secondary_relation_type = "IS_PERFORMED_BY"
# 7. Use overrides to map properties like 'vendor' to the 'maintainer' role.
[control.target.map.property_map_overrides]
vendor = "maintainer"

This single rule scans hundreds of assets. If it finds an application with a property vendor = "team-alpha", it performs two actions:

  1. It creates the primary responsibility link: (application) -[HAS_RESPONSIBILITY {role: "maintainer"}]-> (provider:team-alpha).
  2. Because link_relation_to_target is true, it also creates a secondary link modeling which provider performs the role: (role:maintainer) -[IS_PERFORMED_BY]-> (provider:team-alpha).
graph TD
    subgraph "Primary Responsibility Link"
        App["application<br>vendor: team-alpha"] -- "HAS_RESPONSIBILITY<br>{role: maintainer}" --> Provider["provider<br><b>team-alpha</b>"]
    end
    subgraph "Secondary Role Performance Link"
        Role["role<br><b>maintainer</b>"] -- "IS_PERFORMED_BY" --> Provider
    end

This pattern is the key to managing organizational responsibility at scale.

  1. The map feature dynamically reads the graph (via derived_type = "role"). If a user adds a new role (e.g., data_steward) to role.csv, the graph automatically maps the data_steward property on all assets to the respective provider.
  2. The specialized map performs an M × N mapping (Asset Types × Role Types) in one block.

Creating an Ownership Report

Once the graph models these responsibilities explicitly, you can automatically generate comprehensive ownership and compliance reports. By using Rescile’s output templating, you traverse the HAS_RESPONSIBILITY edges to extract the provider and roll up to the legal party.

data/output/ownership_report.toml

origin_resource = "application"

[[output]]
resource_type = "ownership_report"
name = "report-{{ origin_resource.name }}"
filename = "ownership-{{ origin_resource.name }}.md"
mimetype = "text/markdown"
template = '''
# Responsibility Report: {{ origin_resource.name }}

## Asset Details
* **Type:** Application
* **Environment:** {{ origin_resource.environment | default(value="Unknown") }}

## Responsibilities

{% for provider in origin_resource.HAS_RESPONSIBILITY | default(value=[]) %}
### Role: {{ provider._relation.role | capitalize }}
* **Operational Provider:** {{ provider.name }}
* **Legal Entity:** {{ provider.OPERATED_BY[0].name | default(value="Internal") }}
* **Service Category:** {{ provider.service_category | default(value="N/A") }}
{% else %}
*No responsibilities mapped.*
{% endfor %}
'''