Relationships
The Relationships domain is the graph layer of the EDM. A Relationship is a typed, directed edge between any two entities. You can link a Person to a TechnologyAsset, a Vulnerability to a CloudResource, or a RiskItem to a ComplianceControl — and then traverse those connections.
Relationship Schema
| Field | Type | Required | Description |
|---|---|---|---|
id | string | auto | Prefixed ID: rel-{ulid} |
fromId | string | yes | Source entity ID |
fromType | string | yes | Entity type (e.g. person, group, cloudResource) |
toId | string | yes | Target entity ID |
toType | string | yes | Entity type |
relation | string | yes | Relationship label (see below) |
metadata | object | no | Arbitrary JSON key-value pairs |
createdAt | string | auto | ISO 8601 UTC |
Built-in Relation Types
| Relation | Meaning |
|---|---|
member_of | Person or Group is a member of a Group |
owns | Person owns an Asset or TechnologyAsset |
assigned_to | Asset is assigned to a Person |
has_entitlement | Person holds an AccessEntitlement |
has_license | Person is assigned a SoftwareLicense |
affects | Vulnerability affects an Asset |
mitigates | Control or RiskItem mitigates a Vulnerability |
evidences | Artifact evidences a ComplianceControl |
depends_on | Asset depends on another Asset |
reports_to | Person reports to another Person |
custom | User-defined — set metadata.label for display |
Create a Relationship
import { CpodClient } from '@cpod/sdk'
const sdk = CpodClient.fromEnv()
// Alice has admin entitlement on GitHub Enterprise
const rel = await sdk.relationships.create({
fromId: 'per-alice123',
fromType: 'person',
toId: 'ent-github-admin',
toType: 'accessEntitlement',
relation: 'has_entitlement',
metadata: { grantedBy: 'per-manager456', grantedAt: '2024-06-01' },
})
// rel.id → "rel-01HXYZ..."
// Vulnerability affects a cloud resource
await sdk.relationships.create({
fromId: 'vul-abc123',
fromType: 'vulnerability',
toId: 'cld-xyz789',
toType: 'cloudResource',
relation: 'affects',
})Query Relationships
Find all relationships from or to a given entity, optionally filtered by relation type or the type of the connected entity.
// All entitlements Alice holds
const rels = await sdk.relationships.list({
fromId: 'per-alice123',
fromType: 'person',
relation: 'has_entitlement',
limit: 50,
})
for (const r of rels.items) {
console.log(r.toType, r.toId, r.metadata)
}
// All people who have access to a specific entitlement
const holders = await sdk.relationships.list({
toId: 'ent-github-admin',
toType: 'accessEntitlement',
relation: 'has_entitlement',
})
// All vulnerabilities affecting a specific asset
const vulns = await sdk.relationships.list({
toId: 'cld-xyz789',
toType: 'cloudResource',
relation: 'affects',
})Get & Delete a Relationship
const rel = await sdk.relationships.get('rel-abc123')
// Revoke an entitlement
await sdk.relationships.delete('rel-abc123')Relationships do not cascade. Deleting a Person does not automatically remove their Relationship edges. Clean up edges explicitly before or after deleting an entity, or use the purge flag on delete: sdk.people.delete('per-abc123', { purgeRelationships: true }).
Traversal Queries
For multi-hop graph traversal — e.g. “all vulnerabilities affecting assets owned by people in this group” — use the traverse API:
const result = await sdk.relationships.traverse({
startId: 'grp-engineering',
startType: 'group',
steps: [
{ relation: 'member_of', direction: 'inbound', targetType: 'person' },
{ relation: 'assigned_to', direction: 'inbound', targetType: 'cloudResource' },
{ relation: 'affects', direction: 'inbound', targetType: 'vulnerability' },
],
limit: 100,
})
// result.nodes → Vulnerability[]
// result.paths → full path arrays for provenance