This post continues from the twenty-third part. An architecture model that is only validated locally is only half protected. As soon as multiple people work on the model, CI is needed: automatic validation on every push, a visible diff on every pull request.
Installing Bausteinsicht in the Pipeline
GitHub Actions does not provide a Bausteinsicht action — installation is done manually via curl:
- name: Install Bausteinsicht
run: |
curl -Lo bausteinsicht.tar.gz \
https://github.com/docToolchain/Bausteinsicht/releases/latest/download/bausteinsicht_linux_amd64.tar.gz
tar xzf bausteinsicht.tar.gz
sudo mv bausteinsicht /usr/local/bin/
bausteinsicht --versionFor reproducible builds a fixed version is recommended instead of latest:
https://github.com/docToolchain/Bausteinsicht/releases/download/v0.5.0/bausteinsicht_linux_amd64.tar.gzvalidate as a Build Gate
bausteinsicht validate returns exit code 1 when the model contains errors — GitHub Actions aborts the job:
name: Architecture Validation
on: [push, pull_request]
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Bausteinsicht
run: |
curl -Lo bausteinsicht.tar.gz \
https://github.com/docToolchain/Bausteinsicht/releases/latest/download/bausteinsicht_linux_amd64.tar.gz
tar xzf bausteinsicht.tar.gz && sudo mv bausteinsicht /usr/local/bin/
- name: Validate architecture model
run: bausteinsicht validate --format json
working-directory: architecture/With --format json the output lands as structured JSON on stdout — easier to parse when you want to further process the error list.
What validate checks (→ Part 8):
All element IDs in relationships and views exist in the model
All tag IDs used are defined in
specification.tagsNo element references an unknown type
Views do not have an empty
includeblock
diff in Pull Requests
bausteinsicht diff compares two model states and shows which elements, relationships, and views have changed.
In the PR context, HEAD~1 vs HEAD is the sensible comparison:
- name: Compute model diff
id: diff
working-directory: architecture/
run: |
DIFF=$(bausteinsicht diff HEAD~1 HEAD --format json 2>/dev/null || echo '{}')
echo "result=$DIFF" >> $GITHUB_OUTPUT
- name: Comment diff on PR
if: github.event_name == 'pull_request'
uses: actions/github-script@v7
with:
script: |
const diff = JSON.parse('${{ steps.diff.outputs.result }}');
if (!diff.changes || diff.changes.length === 0) return;
const body = [
'## 🏗 Architecture Changes',
diff.changes.map(c => `- **${c.type}** \`${c.id}\`: ${c.description}`).join('\n')
].join('\n');
github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body
});For the very first commit in a branch (HEAD~1 does not exist), diff returns an error — the || echo '{}' handles this case. |
SVG Export After Every Push
Having architecture diagrams as SVG in the repository means: previews in GitHub, direct embedding in documentation, no local installation needed to view the diagrams.
- name: Export architecture diagrams
run: bausteinsicht export --format svg --output out/
working-directory: architecture/
- name: Upload SVG artifacts
uses: actions/upload-artifact@v4
with:
name: architecture-diagrams
path: architecture/out/*.svg
retention-days: 30Optionally: commit the exported SVGs directly back to the branch:
- name: Commit exported SVGs
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add architecture/out/*.svg
git diff --staged --quiet || git commit -m "chore: update architecture diagrams [skip ci]"
git pushThe [skip ci] prevents an infinite loop: the commit created by Actions does not trigger a new CI run.
Complete Workflow
name: Architecture CI
on:
push:
paths:
- 'architecture/**'
pull_request:
paths:
- 'architecture/**'
jobs:
architecture:
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 2 # for diff HEAD~1
- name: Install Bausteinsicht
run: |
curl -Lo bausteinsicht.tar.gz \
https://github.com/docToolchain/Bausteinsicht/releases/latest/download/bausteinsicht_linux_amd64.tar.gz
tar xzf bausteinsicht.tar.gz && sudo mv bausteinsicht /usr/local/bin/
- name: Validate model
run: bausteinsicht validate --format json
working-directory: architecture/
- name: Compute diff (PR only)
if: github.event_name == 'pull_request'
id: diff
working-directory: architecture/
run: |
DIFF=$(bausteinsicht diff HEAD~1 HEAD --format json 2>/dev/null || echo '{}')
echo "result=$DIFF" >> $GITHUB_OUTPUT
- name: Comment diff on PR
if: github.event_name == 'pull_request'
uses: actions/github-script@v7
with:
script: |
const diff = JSON.parse('${{ steps.diff.outputs.result }}');
if (!diff.changes || diff.changes.length === 0) return;
const body = [
'## 🏗 Architecture Changes',
diff.changes.map(c => `- **${c.type}** \`${c.id}\`: ${c.description}`).join('\n')
].join('\n');
github.rest.issues.createComment({
owner: context.repo.owner, repo: context.repo.repo,
issue_number: context.issue.number, body
});
- name: Export SVGs
run: bausteinsicht export --format svg --output out/
working-directory: architecture/
- name: Commit SVGs to main
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add architecture/out/*.svg
git diff --staged --quiet || git commit -m "chore: update architecture diagrams [skip ci]"
git pushpaths: ['architecture/**'] ensures the workflow only runs when something in the model has actually changed — not on every commit. |
Example Model
The example for this part (model with developer and CI/CD actors) is located at teil_24.jsonc.
This is what the result looks like in draw.io (bausteinsicht sync):
The draw.io file for this can be found here: teil_24.drawio
Generated PNG files via bausteinsicht export --image-format png:


Generated PlantUML diagrams via bausteinsicht export-diagram:
Up Next: Migration
CI protects the existing model. But what if you are coming from another tool? The next part covers how to migrate from draw.io, PlantUML, and Structurizr to Bausteinsicht — what can be done automatically and what needs to be transferred manually.
Official documentation: User Manual · Tutorial on doctoolchain.org