Inspiration
I've watched layered architectures look perfect in design docs and slowly fall apart in real codebases. One import line in a big merge request — domain code reaching into the web layer — and nobody catches it until the boundaries are gone.
I wanted something that turns those agreements into executable rules, not another diagram that rots. When GitLab Orbit launched as a knowledge graph over imports, merge requests, and code structure, it clicked: the graph can see what humans and single-file linters miss. Merge Passport is my answer — architecture policy as code, enforced on every merge request with Orbit and a GitLab Duo Flow.
What it does
Merge Passport reads a declarative policy file (merge-passport.yml) that defines:
- Layers — which directories belong to domain, web, billing, etc.
- Forbidden edges — e.g. domain must not import web
- Required tests — domain changes must include matching test updates
On each merge request it produces a structured PASS or FAIL report — not open-ended chat. It checks:
- Direct violations — a file in one layer importing another (e.g.
domain → web) - Transitive chains — multi-hop paths like
orders → shared → webwhere no single file breaks the rule, but the architecture does - Missing tests — policy-defined test coverage on changed paths
It runs three ways:
- CLI —
merge-passport checkfor local use and CI - GitLab CI — passport job on every MR and on
main - Duo Flow — mention the flow on an MR; a three-step agent workflow (context → imports → passport check) uses Orbit and returns an actionable verdict
The flow is published publicly in the GitLab AI Catalog (MIT licensed). Any project can enable it.
Demo video: https://youtu.be/z7uBzdNYTII
How I built it
I built this solo for the GitLab Transcend Hackathon (Showcase Track).
Stack:
| Piece | Technology |
|---|---|
| Policy engine + CLI | Python (merge_passport/) |
| Knowledge graph | GitLab Orbit via glab orbit remote query |
| Agent platform | GitLab Duo Flow + project Agent Skill |
| Catalog publish | AI Catalog Sync CI component |
| Demo fixtures | Layered Python app + transitive demo (examples/transitive_demo/) |
Architecture:
merge-passport.yml— single source of truth for layer rulesmerge_passport/— parses policy, maps files to layers, validates imports, emits structured reportsorbit_client.py— Orbit queries for MR changed files and project-wideImportedSymboledges; unit tests lock the query DSL shapetransitive.py— walks Orbit's import graph for multi-hop forbidden pathsflows/merge-passport-flow.yml— three-agent Duo Flow synced to AI Catalog on git tagsskills/merge-passport/SKILL.md— teaches agents how to query Orbit and run checks.gitlab-ci.yml— test, passport, catalog validate, and catalog-sync jobs
I used AI assistants (Cursor, Claude) to iterate quickly on CI, catalog schema errors, and the demo script — but every design decision, Orbit query, and policy rule is mine and verified with tests.
Repo: https://gitlab.com/gitlab-ai-hackathon/transcend/6613781
Challenges I ran into
CI was a puzzle. The passport job failed because python:3.12-slim has no git, xargs needed --changed-files in the right place, and the branch smoke check tripped my own required_tests rule by listing domain files without test files.
AI Catalog publish took several tag attempts. I hit: missing deploy stage, invalid trigger name (pipeline → pipeline_hooks), duplicate group_id in flow consumers vs CI enable_in_project, and fine-grained PAT permissions that created the flow but couldn't auto-enable it in the group. I fixed each one, published on tag 0.1.3, and enabled the flow manually at project level.
Orbit vs local AST. Orbit's ImportedSymbol reflects the indexed default branch, not unmerged MR code. I documented this honestly and label every finding Source: orbit or Source: local_ast so users know what they're looking at.
The transitive demo. The most compelling violation — architecture erosion through a chain no single file reveals — only works with Orbit's project-wide graph. I built examples/transitive_demo/ and a polling script to wait for Orbit indexing before recording the video.
Solo scope. I had to resist building a general-purpose linter for every language and stay focused: one policy file, one flow, one clear story.
Accomplishments that I'm proud of
- Shipped a public Duo Flow in the AI Catalog — not just a chatbot, but workflow automation with PASS/FAIL output
- Meaningful Orbit integration — MR diff traversal,
ImportedSymbollookups, and transitive import chains that AST alone cannot detect - Policy as code that teams can copy: drop in
merge-passport.yml, customize layers, enable the flow - End-to-end pipeline — local CLI, GitLab CI, Duo Flow, and catalog publish all exercising the same validator
- Honest engineering — source attribution on every violation, Orbit query contracts pinned by unit tests
- Complete Showcase deliverables as one person: MIT repo, green CI, catalog publish, and a 2:36 demo video
What I learned
- Orbit is user-scoped — every query needs
project_id; the DSL distinguishes single-node (node) vs multi-node (nodes+relationships) shapes - AI Catalog sync has strict schema rules — trigger names, stage names, and consumer config must match the component exactly; reading the sync job log beats guessing
- Architecture drift is often indirect — the violation that matters is frequently a chain, not a single import; that's where a knowledge graph earns its place
- Agents work best with deterministic backends — the Duo Flow orchestrates, but
merge-passport checkgives reliable PASS/FAIL for CI and demos - Solo hackathon shipping — small scope, good fixtures (
demo/violation, transitive demo), and fixing CI early saves demo day
What's next for Merge Passport
- GitLab CI component — one-line
include:for teams adopting Merge Passport - MR comments from the flow — post the structured report directly on the merge request
- Multi-language support — Go/Ruby import detection via Orbit
ImportedSymbol - Symbol-level boundaries — use Orbit
CALLSedges, not just module imports - Group-level enablement — smoother catalog sync with classic PAT + Maintainer permissions
Log in or sign up for Devpost to join the conversation.