Skip to main content

AI Integrity Receipts — cryptographic receipts for commits with declared AI involvement

Project description

Invariant Systems

AIIR — AI Integrity Receipts

AI wrote your code. Here's the receipt. 🧾

PyPI License: Apache-2.0 Zero Dependencies GitLab CI/CD Catalog AIIR Receipted

AIIR terminal demo — pip install aiir && aiir --pretty


Why?

AI writes 30–50% of new code at most companies. Copilot, ChatGPT, Claude, Cursor — it all goes into git commit with no systematic record of what was human and what was machine.

EU AI Act transparency obligations are phasing in now — general-purpose AI transparency rules from August 2025, high-risk system requirements from August 2026. AI-generated code with no provenance is a compliance gap.

Your auditors will ask: "Which code was AI-generated? Can you prove it?"

Who asks What they need
SOC 2 / ISO 27001 auditor Tamper-evident record of AI involvement per commit
EU AI Act compliance Supports transparency and provenance evidence
Insurance underwriter Record of AI involvement per commit
Engineering leadership "We track all AI code" — with cryptographic receipts

AIIR answers that question — for every commit with declared AI involvement. One command. Zero dependencies. Apache 2.0.


What's a receipt?

┌─ Receipt: g1-a3f8b2c1d4e5f6a7b8c9d0e1...
│  Commit:  c4dec85630
│  Subject: feat: add new auth middleware
│  Author:  Jane Dev <jane@example.com>
│  Files:   4 changed
│  AI:      YES (copilot)
│  Hash:    sha256:7f3a...
│  Time:    2026-03-06T09:48:59Z
└──────────────────────────────────────────

Every commit gets a content-addressed receipt — a JSON object that records what was committed, who wrote it, and whether AI was involved. Change one character? The hash breaks. That's integrity (tamper detection).

For authenticity (proving who generated the receipt), enable Sigstore keyless signing — see Sigstore signing below. Without signing, receipts prove internal consistency but not provenance — anyone who can run aiir on the same commit can produce an equivalent receipt.


Try It

pip install aiir
cd your-repo
aiir --pretty

That's it. Your last commit now has a receipt in .aiir/receipts.jsonl — a tamper-evident JSONL ledger that auto-indexes and deduplicates. Run it again: same commit, zero duplicates. Zero dependencies. Python 3.9+.


Every Platform. One Command.

🤖 MCP Tool — Let your AI do it

Any MCP-aware AI assistant (Claude, Copilot, Cursor) can discover and use AIIR as a tool. Add to your MCP config:

{
  "mcpServers": {
    "aiir": {
      "command": "aiir-mcp-server",
      "args": ["--stdio"]
    }
  }
}

Now your AI assistant generates receipts automatically after writing code. It says: "I just committed code for you. Let me generate an AIIR receipt."

💻 CLI — Run it yourself

# Receipt the last commit (auto-saves to .aiir/receipts.jsonl)
aiir --pretty

# Receipt a whole PR branch
aiir --range origin/main..HEAD --pretty

# Only AI-authored commits, save to directory (CI mode)
aiir --ai-only --output .receipts/

# Print JSON to stdout for piping (bypasses ledger)
aiir --json | jq .receipt_id

# JSON Lines output for streaming / piping
aiir --range HEAD~5..HEAD --jsonl | jq .receipt_id

# Custom ledger location
aiir --ledger .audit/

# Verify a receipt hasn't been tampered with
aiir --verify receipt.json

⚙️ GitHub Action — Automate it in CI

# .github/workflows/aiir.yml — signed receipts (default)
name: AIIR
on:
  push:
    tags-ignore: ['**']   # Don't receipt tag pushes (receipts the whole history)
  pull_request:

permissions:
  id-token: write   # Required for Sigstore keyless signing
  contents: read

jobs:
  receipt:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - uses: invariant-systems-ai/aiir@v1
        with:
          output-dir: .receipts/

Signing is on by default — each receipt gets a Sigstore bundle (Fulcio certificate + Rekor transparency log entry). Artifacts uploaded automatically when output-dir is set.

Unsigned (opt out of signing, no permissions needed)
name: AIIR
on:
  push:
    tags-ignore: ['**']
  pull_request:

jobs:
  receipt:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - uses: invariant-systems-ai/aiir@v1
        with:
          sign: false

Note: Without signing, receipts are tamper-evident (hash integrity) but not tamper-proof (anyone who can run aiir on the same commit can recreate a matching receipt). For cryptographic non-repudiation, use the signed default above.

🪝 pre-commit Hook — Receipt every commit locally

# .pre-commit-config.yaml
repos:
  - repo: https://github.com/invariant-systems-ai/aiir
    rev: v1.0.9
    hooks:
      - id: aiir

Runs post-commit (after your commit is created, since it needs the commit SHA to generate a receipt). Customise with args:

      - id: aiir
        args: ["--ai-only", "--output", ".receipts"]

🦊 GitLab CI — Receipt merge requests and pushes

CI/CD Catalog component (recommended — browse in Catalog):

# .gitlab-ci.yml — one line
include:
  - component: gitlab.com/invariant-systems/aiir/receipt@1
    inputs:
      stage: test

All inputs are optional and typed:

Input Type Default Description
stage string test Pipeline stage
version string 1.0.9 AIIR version from PyPI
ai-only boolean false Only receipt AI-authored commits
output-dir string .aiir-receipts Artifact output directory
artifact-expiry string 90 days Artifact retention
extra-args string "" Additional CLI flags

Legacy include (still works — no Catalog required):

include:
  - remote: 'https://raw.githubusercontent.com/invariant-systems-ai/aiir/v1.0.9/templates/gitlab-ci.yml'

Self-hosted GitLab? Mirror the repo and use project: instead:

include:
  - project: 'your-group/aiir'
    ref: 'v1.0.9'
    file: '/templates/gitlab-ci.yml'

Customise via pipeline variables: AIIR_VERSION, AIIR_AI_ONLY, AIIR_EXTRA_ARGS, AIIR_ARTIFACT_EXPIRY. See templates/gitlab-ci.yml for the full template.

🐳 Docker — Run anywhere

# Receipt the current repo (mount it in)
docker run --rm -v "$(pwd):/repo" -w /repo invariantsystems/aiir --pretty

# AI-only, save receipts
docker run --rm -v "$(pwd):/repo" -w /repo invariantsystems/aiir --ai-only --output .receipts/

Works in any CI/CD system that supports container steps — Tekton, Buildkite, Drone, Woodpecker, etc.

More CI/CD Platforms

Bitbucket Pipelines
# bitbucket-pipelines.yml
pipelines:
  default:
    - step:
        name: AIIR Receipt
        image: python:3.11
        script:
          - pip install aiir
          - aiir --pretty --output .receipts/
        artifacts:
          - .receipts/**

Full template with PR support: templates/bitbucket-pipelines.yml

Azure DevOps
# azure-pipelines.yml
steps:
  - task: UsePythonVersion@0
    inputs: { versionSpec: '3.11' }
  - script: pip install aiir && aiir --pretty --output .receipts/
    displayName: 'Generate AIIR receipt'
  - publish: .receipts/
    artifact: aiir-receipts

Full template with PR/CI detection: templates/azure-pipelines.yml

CircleCI
# .circleci/config.yml
jobs:
  receipt:
    docker:
      - image: cimg/python:3.11
    steps:
      - checkout
      - run: pip install aiir && aiir --pretty --output .receipts/
      - store_artifacts:
          path: .receipts

Full template: templates/circleci/config.yml

Jenkins
// Jenkinsfile
pipeline {
    agent { docker { image 'python:3.11' } }
    stages {
        stage('AIIR Receipt') {
            steps {
                sh 'pip install aiir && aiir --pretty --output .receipts/'
                archiveArtifacts artifacts: '.receipts/**'
            }
        }
    }
}

Full template: templates/jenkins/Jenkinsfile


What It Detects

AIIR detects three categories of signals in commit metadata:

Declared AI assistance

Commits where a human or tool explicitly attributed AI involvement.

Signal Examples
Copilot Co-authored-by: Copilot, Co-authored-by: GitHub Copilot
ChatGPT Generated by ChatGPT, Co-authored-by: ChatGPT
Claude Generated by Claude, Co-authored-by: Claude
Cursor Generated by Cursor, Co-authored-by: Cursor
Amazon Q / CodeWhisperer amazon q, codewhisperer, Co-authored-by: Amazon Q
Devin Co-authored-by: Devin, devin[bot]
Gemini gemini code assist, google gemini, gemini[bot]
Tabnine tabnine in commit metadata
Aider aider: prefix in commit messages
Generic markers AI-generated, LLM-generated, machine-generated
Git trailers Generated-by:, AI-assisted:, Tool: git trailers

Automation / bot activity

Commits made by CI bots and automated dependency tools. These are not gen-AI assistance — they indicate automated (non-human) authorship.

Signal Examples
Dependabot dependabot[bot] as author
Renovate renovate[bot] as author
Snyk snyk-bot as author
CodeRabbit coderabbit[bot] as author
GitHub Actions github-actions[bot] as author
DeepSource deepsource[bot] as author

Note: Since v1.0.4, bot and AI signals are fully separated. A Dependabot commit gets is_bot_authored: true and authorship_class: "bot-generated", not is_ai_authored: true. The authorship_class field provides a single structured value: "human", "ai-assisted", "bot-generated", or "ai+bot".

Detection limitations

AI detection uses heuristic_v2 — matching on commit metadata signals.

What it catches:

  • Commits with Co-authored-by: Copilot or similar trailers
  • Bot-authored commits (Dependabot, Renovate) — classified separately as bot-generated
  • Explicit AI markers (AI-generated, Generated by ChatGPT, etc.)

What it doesn't catch:

  • Copilot inline completions (no metadata trace by default)
  • ChatGPT/Claude copy-paste without attribution
  • Squash-merged AI branches with clean messages
  • Amended commits that remove AI trailers

This is by design — AIIR receipts what's declared, not what's hidden.


Deep Dive

Ledger — .aiir/ directory

By default, aiir appends receipts to a local JSONL ledger:

.aiir/
├── receipts.jsonl   # One receipt per line (append-only)
└── index.json       # Auto-maintained lookup index

Why a ledger?

  • One file to commitgit add .aiir/ is your entire audit trail
  • Auto-deduplicates — re-running aiir on the same commit is a no-op
  • Git-friendly — append-only JSONL means clean diffs and easy git blame
  • Queryablejq, grep, and wc -l all work naturally

index.json tracks every commit SHA, receipt count, and authorship breakdown:

{
  "version": 1,
  "receipt_count": 42,
  "ai_commit_count": 7,
  "bot_commit_count": 3,
  "ai_percentage": 16.7,
  "unique_authors": 5,
  "first_receipt": "2026-03-01T10:00:00Z",
  "latest_timestamp": "2026-03-06T09:48:59Z",
  "commits": {
    "c4dec85630...": { "receipt_id": "g1-a3f8...", "ai": true, "bot": false, "authorship_class": "ai-assisted", "author": "jane@example.com" },
    "e7b1a9f203...": { "receipt_id": "g1-b2c1...", "ai": false, "bot": true, "authorship_class": "bot-generated", "author": "dependabot[bot]" }
  }
}

Output modes:

Flag Behaviour
(none) Append to .aiir/receipts.jsonl (default)
--ledger .audit/ Append to custom ledger directory
--json Print JSON to stdout — no ledger write*
--jsonl Print JSON Lines to stdout — no ledger write*
--output dir/ Write individual files to dir/ — no ledger write*
--pretty Human-readable summary to stderr (combines with any mode)

* Adding --ledger explicitly overrides and writes to both destinations.

Tip: Add .aiir/ to your repo. It becomes a permanent, auditable, append-only record of every receipted commit.

Receipt format

Each receipt is a content-addressed JSON document:

{
  "type": "aiir.commit_receipt",
  "schema": "aiir/commit_receipt.v1",
  "receipt_id": "g1-a3f8b2c1d4e5f6a7b8c9d0e1f2a3b4",
  "content_hash": "sha256:7f3a...",
  "timestamp": "2026-03-06T09:48:59Z",
  "commit": {
    "sha": "c4dec85630232666aba81b6588894a11d07e5d18",
    "author": { "name": "Jane Dev", "email": "jane@example.com" },
    "subject": "feat: add receipt generation to CI",
    "diff_hash": "sha256:b2c1...",
    "files_changed": 4
  },
  "ai_attestation": {
    "is_ai_authored": true,
    "signals_detected": ["message_match:co-authored-by: copilot"],
    "signal_count": 1,
    "is_bot_authored": false,
    "bot_signals_detected": [],
    "bot_signal_count": 0,
    "authorship_class": "ai-assisted",
    "detection_method": "heuristic_v2"
  },
  "extensions": {}
}

Content-addressed = the receipt_id is derived from SHA-256 of the receipt's canonical JSON. Change any field → hash changes → receipt invalid.

is_ai_authored = true when AI coding tools are detected (Copilot, ChatGPT, Claude, etc.). is_bot_authored = true when automation/CI bots are detected (Dependabot, Renovate, etc.). authorship_class = structured category: "human", "ai-assisted", "bot-generated", or "ai+bot". These are fully independent — a Dependabot commit gets bot-generated, not ai-assisted.

Note: Unsigned receipts are tamper-evident, not tamper-proof. Anyone who can re-run aiir on the same commit can recreate a matching receipt. For cryptographic non-repudiation, enable Sigstore signing.

Receipt identity depends on repository provenance. The provenance.repository field (your git remote get-url origin) is part of the content hash. The same commit will produce a different receipt_id if the remote URL changes — for example after a fork, a repo rename, or adding an origin to a previously local-only repo. If you need stable receipt identity as a durable external reference, generate receipts after your remote is configured. Fields inside extensions (such as namespace) are not part of the content hash, so they can change without invalidating a receipt.

GitHub Action — inputs & outputs

Inputs

Input Description Default
ai-only Only receipt AI-authored commits false
commit-range Specific commit range (e.g., main..HEAD) Auto-detected from event
output-dir Directory to write receipt JSON files (prints to log)
sign Sign receipts with Sigstore true

Outputs

Output Description
receipt_count Number of receipts generated
ai_commit_count Number of AI-authored commits detected
receipts_json Full JSON array of all receipts (set to "OVERFLOW" if >1 MB)
receipts_overflow "true" when receipts_json exceeded 1 MB and was truncated

⚠️ Security note on receipts_json: Contains commit metadata which may include shell metacharacters. Never interpolate directly into run: steps via ${{ }}. Write to a file instead.

Example: PR Comment with AI Summary

name: AI Audit Trail
on: pull_request

jobs:
  receipt:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - uses: invariant-systems-ai/aiir@v1
        id: receipt
        with:
          output-dir: .receipts/

      - name: Comment on PR
        if: steps.receipt.outputs.ai_commit_count > 0
        uses: actions/github-script@v7
        with:
          script: |
            const count = '${{ steps.receipt.outputs.ai_commit_count }}';
            const total = '${{ steps.receipt.outputs.receipt_count }}';
            github.rest.issues.createComment({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              body: `🔐 **AIIR**: ${total} commits receipted, ${count} AI-authored.\n\nReceipts uploaded as build artifacts.`
            });

Example: Enforce Receipt Policy

name: Require AI Receipts
on: pull_request

jobs:
  receipt-gate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - uses: invariant-systems-ai/aiir@v1
        id: receipt

      - name: Enforce receipt policy
        if: steps.receipt.outputs.ai_commit_count > 0
        run: |
          echo "✅ ${{ steps.receipt.outputs.ai_commit_count }} AI commits receipted"
Sigstore signing

Optionally sign receipts with Sigstore keyless signing for cryptographic non-repudiation:

name: AIIR (Signed)
on:
  push:
    tags-ignore: ['**']
  pull_request:

permissions:
  id-token: write
  contents: read

jobs:
  receipt:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - uses: invariant-systems-ai/aiir@v1
        with:
          output-dir: .receipts/
          sign: true

Fork PRs: GitHub does not grant OIDC tokens to fork pull requests. If your project accepts external contributions, either use sign: false for PRs or conditionally skip signing on forks. AIIR will detect the missing credential and fail with a clear error rather than hanging.

Each receipt gets an accompanying .sigstore bundle:

  • Fulcio certificate — short-lived cert proving the signer's OIDC identity
  • Rekor transparency log — tamper-evident public record
  • Signature — cryptographic binding

Verify locally:

# Basic: checks signature is valid (any signer)
aiir --verify receipt.json --verify-signature

# Recommended: pin to a specific CI identity for non-repudiation
aiir --verify receipt.json --verify-signature \
  --signer-identity "https://github.com/myorg/myrepo/.github/workflows/aiir.yml@refs/heads/main" \
  --signer-issuer "https://token.actions.githubusercontent.com"

⚠️ Always use --signer-identity and --signer-issuer in production. Without identity pinning, verification accepts any valid Sigstore signature. That proves someone signed it, but not your CI signed it. Extensions fields are not part of the content hash and should not be treated as security-relevant.

Install signing support: pip install aiir[sign]

MCP server details

The AIIR MCP server exposes two tools:

Tool Description
aiir_receipt Generate receipts for commit(s). Accepts commit, range, ai_only, pretty.
aiir_verify Verify a receipt file's integrity. Accepts file path.

Install globally:

pip install aiir

Claude Desktop (claude_desktop_config.json):

{
  "mcpServers": {
    "aiir": {
      "command": "aiir-mcp-server",
      "args": ["--stdio"]
    }
  }
}

VS Code / Copilot (.vscode/mcp.json):

{
  "servers": {
    "aiir": {
      "command": "aiir-mcp-server",
      "args": ["--stdio"]
    }
  }
}

Cursor (.cursor/mcp.json):

{
  "mcpServers": {
    "aiir": {
      "command": "aiir-mcp-server",
      "args": ["--stdio"]
    }
  }
}

The server uses the same zero-dependency core as the CLI. No extra packages needed.

Detection internals

See THREAT_MODEL.md for full STRIDE analysis, DREAD scoring, and attack trees.

Homoglyph detection covers 36 Cyrillic/Greek confusables via NFKC normalization + a targeted confusable map. This is partial coverage (~0.4% of Unicode confusables.txt). Adversarial homoglyph evasion using rare scripts remains a known limitation — see S-02 in the threat model.


Dogfood: This Repo Receipts Itself

AIIR eats its own dogfood. Every commit to main gets a cryptographic receipt committed back to .receipts/ automatically.

Verify it yourself:

pip install aiir
for f in .receipts/*.json; do aiir --verify "$f"; done

The dogfood CI workflow generates receipts on every push to main and commits them back to the repo. Locally, the pre-commit hook receipts every commit at post-commit stage.


Security

Extensive security controls. 660+ tests. Zero dependencies.

See SECURITY.md and THREAT_MODEL.md.


About

Built by Invariant Systems, Inc.

License: Apache-2.0 — See LICENSE

Trademarks: "AIIR", "AI Integrity Receipts", and "Invariant Systems" are trademarks of Invariant Systems, Inc. See TRADEMARK.md.

Enterprise: The AIIR open-source library is and will remain free under Apache-2.0. Invariant Systems may offer additional commercial products and services (hosted verification, enterprise dashboards, SLA-backed APIs) under separate terms.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

aiir-1.0.9.tar.gz (67.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

aiir-1.0.9-py3-none-any.whl (57.0 kB view details)

Uploaded Python 3

File details

Details for the file aiir-1.0.9.tar.gz.

File metadata

  • Download URL: aiir-1.0.9.tar.gz
  • Upload date:
  • Size: 67.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for aiir-1.0.9.tar.gz
Algorithm Hash digest
SHA256 decd160112578a0023de999e93980ba9eaed0d5ba7bc88039cdb654e3c2a4f4a
MD5 a19f3e8a03802f79aa7caecc6c281f52
BLAKE2b-256 267b9f3830ff6219946e83da612ca1f14afc4a8d77d3a9af044a3a92fac3cd52

See more details on using hashes here.

Provenance

The following attestation bundles were made for aiir-1.0.9.tar.gz:

Publisher: publish.yml on invariant-systems-ai/aiir

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file aiir-1.0.9-py3-none-any.whl.

File metadata

  • Download URL: aiir-1.0.9-py3-none-any.whl
  • Upload date:
  • Size: 57.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for aiir-1.0.9-py3-none-any.whl
Algorithm Hash digest
SHA256 b39256e8bac68e4bcb5fbcff39e6db4908dcd3b698f18cc7f9d8ffd8d447b257
MD5 cbca052ffa0f17b641727847c402e995
BLAKE2b-256 6b181285be537fd3de0ea0cfad59c0d523c1475af9d18e7d1e2fd0026a5ed47a

See more details on using hashes here.

Provenance

The following attestation bundles were made for aiir-1.0.9-py3-none-any.whl:

Publisher: publish.yml on invariant-systems-ai/aiir

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page