ArchRails verifies PRs against your CALM architecture graph — checking node boundaries, allowed connections, and interface contracts deterministically. Every violation is traceable to a CALM node or relationship. No generic "best practices" guesses.
ArchRails doesn't invent standards. It enforces what your team defines in a CALM document, and cites every node it used.
If a node, relationship, or interface constraint isn't in your calm.json, ArchRails won't enforce it. Full stop.
Feedback cites the exact CALM node ID, relationship ID, or interface that triggered it — so teams align faster and argue less.
Reviews resolve only the CALM nodes touched by the PR diff. Less noise, fewer false positives, no cross-service contamination.
This is the architecture graph ArchRails renders for every pull request. Drag nodes. Hover for details. Click any node to see how violations trace back to your CALM definition.
OrderService calls PaymentService directly — must route via service-api-gatewayOrderDatabase accessed via HTTP — node declares JDBC:5432 onlyInventoryService changes scoped to service-inventory — no leakagePaste your calm.json to instantly check for interface violations, missing controls, and structural issues — then render your architecture graph interactively. No account required.
archrails.yml.ARCHRAILS_TENANT_ID and auth uses X-ArchRails-Token. GITHUB_TOKEN is auto-provisioned by Actions.Drop this file into .github/workflows/archrails.yml. Set three repository secrets — GITHUB_TOKEN is provided automatically by GitHub Actions.
# ArchRails Architecture Governance — GitHub CI Mode
# Required secrets: ARCHRAILS_TENANT_ID · ARCHRAILS_TOKEN · ARCHRAILS_WEBHOOK_URL
# GITHUB_TOKEN is auto-provisioned by Actions — no configuration needed.
name: ArchRails Architecture Check
on:
pull_request:
types: [opened, synchronize, reopened, ready_for_review]
jobs:
archrails-check:
name: Architecture Governance
runs-on: ubuntu-latest
permissions:
contents: read # read repo contents for diff fetch
pull-requests: write # post review comments
statuses: write # write commit status
steps:
- name: Send PR event to ArchRails
env:
ARCHRAILS_TENANT_ID: ${{ secrets.ARCHRAILS_TENANT_ID }}
ARCHRAILS_TOKEN: ${{ secrets.ARCHRAILS_TOKEN }}
ARCHRAILS_URL: ${{ secrets.ARCHRAILS_WEBHOOK_URL }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_EVENT: ${{ toJson(github.event) }}
RUN_ID: ${{ github.run_id }}
RUN_ATTEMPT: ${{ github.run_attempt }}
run: |
# Inject archrails_tenant_id and github_token into the PR event payload.
# github_token lets downstream Lambdas fetch the diff and post review
# results without a GitHub App installation token.
PAYLOAD=$(echo "$PR_EVENT" | jq \
--arg tenant_id "$ARCHRAILS_TENANT_ID" \
--arg github_token "$GITHUB_TOKEN" \
'. + {archrails_tenant_id: $tenant_id, github_token: $github_token}')
HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" \
--max-time 30 \
-X POST "$ARCHRAILS_URL" \
-H "Content-Type: application/json" \
-H "X-GitHub-Event: pull_request" \
-H "X-GitHub-Delivery: ${RUN_ID}-${RUN_ATTEMPT}" \
-H "X-ArchRails-CI: github" \
-H "X-ArchRails-Token: $ARCHRAILS_TOKEN" \
-d "$PAYLOAD")
echo "ArchRails response: $HTTP_STATUS"
# 202 = queued | 204 = ignored (draft, disabled, etc.)
# 4xx/5xx = configuration problem — surface it.
if [[ "$HTTP_STATUS" -ge 400 ]]; then
echo "::error::ArchRails rejected the payload (HTTP $HTTP_STATUS). Check ARCHRAILS_TOKEN and ARCHRAILS_TENANT_ID."
exit 1
fi
echo "ArchRails review queued successfully."
https://api.archrails.io/webhook
GitLab integration runs via CI pipeline. Add the job below to your .gitlab-ci.yml and set three CI/CD variables.
stages:
- review
archrails_review:
stage: review
image: alpine:3.20
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
before_script:
- apk add --no-cache curl
script:
- |
curl --silent --fail -X POST "${ARCHRAILS_ENDPOINT}" \
-H "X-Gitlab-Token: ${GITLAB_WEBHOOK_SECRET}" \
-H "X-Gitlab-Event: Merge Request Hook" \
-H "Content-Type: application/json" \
-d "{
\"archrails_tenant_id\": \"${ARCHRAILS_TENANT_ID}\",
\"object_kind\": \"merge_request\",
\"project\": {
\"id\": ${CI_PROJECT_ID},
\"path_with_namespace\": \"${CI_PROJECT_PATH}\"
},
\"object_attributes\": {
\"iid\": ${CI_MERGE_REQUEST_IID},
\"action\": \"update\",
\"last_commit\": { \"id\": \"${CI_COMMIT_SHA}\" }
}
}"
archrails.yml ready to merge. You never write config by hand. If you want to author or validate a calm.json directly, the free CALM Visualizer is available with no account required.calm.json. Every comment is traceable to a CALM node ID.calm.json at the repo root describes your full system graph. The mapping section in config.yaml ties each service directory to its CALM node. ArchRails resolves only the nodes touched by the PR diff — a payment service change won't trigger inventory rules.calm.json, and get you to your first enforced PR review. The CALM Visualizer is free to use immediately — no account needed.archrails.yml. You review it, merge it, and enforcement starts. The scan runs once on initial setup; after that, ArchRails enforces on every PR without any additional configuration.
Founder & Engineer
ArchRails started as a practical system: keep architecture consistent as teams scale — without relying on tribal knowledge. By grounding reviews in a CALM architecture graph rather than generic training data, every violation is provable and every comment is traceable.
Start with one repo and one CALM document. ArchRails will enforce it on every pull request.
🏗️ CALM Architecture Review
OrderServicecallsPaymentServicedirectly. CALM noderel-connects-order-paymentrequires routing viaservice-api-gateway.OrderDatabaseaccessed via HTTP. Nodedb-ordersdeclaresJDBC:5432only.InventoryServicechanges scoped toservice-inventory— no cross-node leakage detected.