Integration · GitLab CI 4 min read · May 2026

# Architectural Governance in GitLab CI

Pre-generation enforcement catches most violations before code is written. CI enforcement catches the rest — running Mneme's checks against every merge request diff, blocking pipelines that violate architectural decisions.

By Theo Valmis · Published May 2026

## Why CI enforcement matters

- Generation-time hooks cover interactive and agentic Claude Code sessions
- Code can also arrive from manual edits, other tools, or agents without hook coverage
- CI enforcement is the backstop — every commit checked regardless of how it was produced
- Positions governance at three layers: generation time → MR review → CI gate

## How it works

- `mneme check` validates a file or diff against the decision corpus in `.mneme/project_memory.json`
- Run against changed files in a merge request to surface any architectural violations
- Exit code 2 = violation found → pipeline job fails → MR blocked
- Exit code 0 = no violations → pipeline job passes

## Example pipeline

    # .gitlab-ci.yml (governance job)
    mneme-governance:
      stage: test
      image: python:3.11-slim
      script:
        - pip install mneme-hq
        - git diff --name-only $CI_MERGE_REQUEST_DIFF_BASE_SHA...HEAD | xargs -I{} mneme check {}
      rules:
        - if: $CI_PIPELINE_SOURCE == "merge_request_event"

## Status

- CI enforcement gate is Phase 1 current focus — see the roadmap
- The `mneme check` command is available today via `pip install mneme-hq`
- The GitLab CI job above is the reference integration pattern
- Managed GitLab CI template artifact coming in Q3 2026

## Layered governance model

Layer 1: Generation-time enforcement via Claude Code hooks
Layer 2: Merge request review (human + AI)
Layer 3: CI gate via Mneme in GitLab CI

## What the gate actually checks

The CI step runs `mneme check --mode strict` against the MR diff and the project's decision corpus. The check returns a structured verdict per touched file, with three possible outcomes:

- PASS — the diff respects the relevant decisions. Exit code 0, pipeline green.
- WARN — the diff introduces something the corpus flags as policy-bordering (an unapproved dependency, a scope-boundary edge case). Exit code 0 in default mode, surfaces a structured note in the job log.
- FAIL — the diff violates a hard architectural decision. Exit code 2 in strict mode, fails the job, blocks the MR until resolved or explicitly overridden.

Override events are themselves tracked: an override is a decision record with a rationale, scope, and expiry, committed as part of the same MR. Weakening a constraint costs the same as introducing one — a structured edit reviewers can see, not a quiet bypass.

## FAQ

Should we run this on every merge request or only on the default branch?
Every merge request. The point of CI enforcement is to surface violations at MR time when the cost of fixing is lowest. Running only on the default branch means failures land after merge, which defeats the gating purpose. The check is fast (deterministic keyword scoring, no embedding model) so per-pipeline cost is negligible.
What if a merge request needs to override a decision intentionally?
Add an override record to `project_memory.json` as part of the same MR — with rationale, scope, and expiry. The check picks it up automatically. The override is reviewable in the MR diff like any other change, so weakening a constraint is a visible decision rather than a silent merge.
How does this compare to running other linters or scanners in GitLab CI?
Different layers. Linters check syntax and style; SAST scanners check for security issues; Mneme enforces architectural decisions against the project's own decision corpus. They compose: the typical pipeline sequence is lint → mneme check → tests → SAST. See the GitHub Actions integration for the equivalent pattern on GitHub.

## Related reading

- the GitHub Actions integration
- the Claude Code hook
- the Cursor integration