Controls
Controls are how you attach compliance and security requirements to your architecture — "this database must encrypt at rest", "this relationship must use mTLS", "this service must emit audit logs." ArchRails enforces two flavors: CALM controls (you author the requirement, ArchRails validates your config matches the schema) and AR-CTRL controls (built-in architectural checks that fire on PRs without per-tenant configuration).
CALM controls vs. AR-CTRL controls
Two distinct things travel under the word "control" in ArchRails. Knowing which is which avoids hours of confused debugging.
CALM controls Authored by you, on a node / relationship / flow. Each requirement points at a JSON Schema (the rule) and a config document (your implementation). Validator fetches both and runs JSON Schema validation. Failure → AR-CTRL-001. AR-CTRL controls Built-in architectural checks that ship with ArchRails. No per-tenant config; fire on PRs based on the changed files + graph context. Examples: AR-CTRL-AUTHZ-001 (auth removed from governed code), AR-CTRL-DB-BOUNDARY-001 (service talks to a DB it isn't permitted to).
CALM controls are for your rules (your compliance team's requirements, your security org's standards). AR-CTRL controls are for universal rules that apply to every CALM architecture — you don't author them, you just benefit from them.
Anatomy of a CALM control
A control lives under the controls key of any node, relationship, or
flow. The shape is:
"controls": { // One key per control "domain" — e.g. encryption, tls, auth, retention "encryption": { "description": "Data must be encrypted at rest with AES-256 or stronger.", "requirements": [ { "control-requirement-url": "https://controls.myorg.io/encryption-at-rest.schema.json", "control-config-url": "https://controls.myorg.io/orders-db/encryption.config.json" } ] } }
Three things to know:
-
control-requirement-urlis a JSON Schema document describing the shape any conforming config must have. Authored once, referenced many times. -
control-config-urlis your implementation — a JSON document declaring "here's how this node satisfies the requirement" (algorithm, key rotation cadence, audit log location, whatever the schema requires). -
A control domain can have multiple
requirements. Each is validated independently; one failure doesn't suppress the others.
Attaching to nodes and relationships
On a node:
{
"unique-id": "orders-db",
"node-type": "database",
"name": "Orders Database",
"description": "Holds order ledger. PII inside (customer email, billing address).",
"data-classification": "PII",
"controls": {
"encryption": {
"description": "At-rest encryption required for PII stores.",
"requirements": [
{
"control-requirement-url": "https://controls.myorg.io/encryption-at-rest.schema.json",
"control-config-url": "https://controls.myorg.io/orders-db/encryption.config.json"
}
]
}
}
}
On a relationship:
{
"unique-id": "orders-server-to-orders-db",
"relationship-type": {
"connects": {
"source": { "node": "orders-server" },
"destination": { "node": "orders-db" }
}
},
"protocol": "tls",
"controls": {
"tls": {
"description": "All connections to PII stores must use TLS 1.3.",
"requirements": [
{
"control-requirement-url": "https://controls.myorg.io/tls-1-3.schema.json",
"control-config-url": "https://controls.myorg.io/orders-edge/tls.config.json"
}
]
}
}
}
The AR-CTRL catalog
A non-exhaustive sample of what ships built-in. These fire on PRs without any per-tenant configuration — they're activated by the architecture shape itself:
AR-CTRL-001 CALM_CONTROL_REQUIREMENT_VIOLATION Your config doesn't satisfy the schema. AR-CTRL-002 CALM_CONTROL_FETCH_ERROR A control URL couldn't be fetched. AR-CTRL-003 CALM_CONTROL_MISSING_URLS Requirement entry is missing URLs. AR-CTRL-AUTHZ-001 Authorization guard removed from governed code. AR-CTRL-DB-BOUNDARY-001 Service called a DB it isn't permitted to talk to. AR-CTRL-PUBLIC-EXPOSURE-001 Internal node became externally reachable. AR-CTRL-SECRET-001 Secret-shaped string committed to governed file. AR-CTRL-SENSITIVE-FIXTURE-001 Real-looking PII/PHI/PCI in a test fixture. AR-CTRL-SENSITIVE-LOG-001 Sensitive value emitted to a logger.
Reviewers see the rule ID and a one-sentence message on the PR review surface, along with a deep-link into the dashboard's offending-line view. The full catalog with one-line descriptions is in the dashboard's Dashboard → PR Reviews → any review.
Evidence and config URLs
The control-config-url isn't just a pointer — it's
evidence. Auditors ask "show me how this PII store satisfies your
encryption-at-rest standard," you reply with the URL, they fetch the config
document. The config typically declares:
- What encryption algorithm and key length is used.
- Who manages the key (KMS account ID, HSM vendor, rotation cadence).
- Where audit logs of key access are written.
- The control owner (person or team).
Host config files alongside your other governance documents — an internal GitHub repo, a Confluence-published JSON export, or an S3 bucket with bucket policy enforcing read-only access. ArchRails just needs an HTTPS GET to work.
Worked example · encrypt PII at rest
1. Author the requirement schema at
https://controls.myorg.io/encryption-at-rest.schema.json:
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://controls.myorg.io/encryption-at-rest.schema.json",
"type": "object",
"required": ["algorithm", "key_length_bits", "key_manager", "rotation_days", "owner"],
"properties": {
"algorithm": { "enum": ["AES-256-GCM", "AES-256-CBC", "ChaCha20-Poly1305"] },
"key_length_bits": { "const": 256 },
"key_manager": { "enum": ["aws-kms", "gcp-kms", "azure-keyvault", "on-prem-hsm"] },
"rotation_days": { "type": "integer", "maximum": 365 },
"owner": { "type": "string" }
}
}
2. Author the orders-db config at
https://controls.myorg.io/orders-db/encryption.config.json:
{
"algorithm": "AES-256-GCM",
"key_length_bits": 256,
"key_manager": "aws-kms",
"rotation_days": 90,
"owner": "data-platform@myorg.io"
}
3. Attach to the node in your CALM file (see Attaching to nodes above for the syntax).
The next PR runs validation. ArchRails fetches both URLs, validates the config
against the schema, and reports AR-CTRL-001 if anything fails (wrong
algorithm, rotation past 365 days, missing owner). A passing PR includes a
green checkmark in the review surface with the requirement name and config URL
cited — auditor-ready evidence on every change.
Using the audit trail in your audit
The PR-level evidence trail is what makes controls useful for audit. Each PR that lands on a governed branch generates an immutable record of which requirements were checked and what their config evaluated to — per-control, per-merge, per-signer. Common uses:
- Change-management evidence — PR review surface + AR-CTRL-AUTHZ-001 + AR-CTRL-PUBLIC-EXPOSURE-001 give you a per-merge record of "no unauthorized changes to production-supporting systems."
-
Logical-access assertions — CALM
authcontrols + AR-CTRL-AUTHZ-001 provide the per-service authorization assertion auditors look for. -
Transmission integrity — CALM
tlscontrols on relationships document encrypted-in-transit enforcement edge-by-edge.
The dashboard surfaces a per-control evidence export so you can hand auditors a JSON file rather than a dashboard tour. Mapping the evidence into your specific control-framework catalog is a customer-by-customer exercise — talk to your auditor and your engagement manager about the right way to bind the evidence to your control IDs. See Dashboard → PR Reviews → Evidence export when you're ready to ship one.
Common mistakes
I attached a control but every PR fires AR-CTRL-003.
Means one or both URLs are missing from the requirement entry. Both
control-requirement-url and control-config-url are
required — the schema URL alone tells ArchRails what to check, but
without the config URL there's nothing to check. Add both, and if you don't
yet have a config, point at an empty {} JSON file —
AR-CTRL-001 will fire instead, which is the right signal ("the config
doesn't satisfy the schema").
My schema fetch keeps timing out.
ArchRails caches fetched schemas per Lambda invocation, but the initial fetch needs to succeed within 30 seconds. If your control URLs are behind SSO or a corporate proxy, ArchRails can't reach them — AR-CTRL-002 fires and the requirement is skipped. Host control documents on public HTTPS or on the same internal artifact store as your other CI dependencies.
I want to suppress a built-in AR-CTRL rule for a specific node.
Reach out to support before doing this — AR-CTRL rules exist because they catch real incidents. If suppression is genuinely the right call (e.g., a node that's intentionally externally reachable and AR-CTRL-PUBLIC-EXPOSURE-001 is the wrong signal), the dashboard's per-node suppression flow records the reason and the suppressing user, so audit trail isn't lost.
Why two URL fields instead of one?
Separation of concerns. The schema is authored once by the compliance team and shared across hundreds of nodes ("our encryption-at-rest standard"). The config is per-node ("here's how this store implements it"). Putting them in one document would force every node to redeclare the schema or invent a side channel for shared schemas. Two URLs keeps both cleanly versionable.