mati 0.1.2

An enforcement layer for codebase knowledge: confirmed gotchas gate what AI agents read and edit at the hook level. Not a passive memory store.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
name: CI

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

env:
  CARGO_TERM_COLOR: always
  RUST_BACKTRACE: 1
  # Fail rustdoc on broken intra-doc links, private items linked from
  # public APIs, etc. Surfaces breakage that `cargo check` doesn't catch.
  RUSTDOCFLAGS: -D warnings

jobs:
  check:
    name: Check & Lint
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: dtolnay/rust-toolchain@stable
        with:
          components: rustfmt, clippy

      - uses: Swatinem/rust-cache@v2

      - name: cargo fmt
        run: cargo fmt --all -- --check

      - name: cargo clippy
        run: cargo clippy --locked --all-targets -- -D warnings

      - name: cargo check
        run: cargo check --locked --all-targets

      # Verify the binary builds without default features so the `semantic`
      # feature gate (candle / usearch) cannot rot silently.
      - name: cargo check --no-default-features
        run: cargo check --locked --no-default-features --all-targets

      # rustdoc with -D warnings (set via RUSTDOCFLAGS at the workflow level)
      # catches broken intra-doc links and other doc-only regressions.
      - name: cargo doc
        run: cargo doc --locked --no-deps --all-features

  # MSRV CI gate intentionally NOT enforced.
  #
  # ADR-008 documents MSRV at 1.82 as project intent. However, transitive
  # deps in the Rust ecosystem (notably clap_lex 1.1.0+) ship Cargo.toml
  # manifests using syntax newer than the 1.82 Cargo can parse, so a
  # `runs-on: rust-toolchain@1.82.0` check fails on manifest parsing
  # before reaching mati's own source. The MSRV declaration in Cargo.toml
  # remains advisory documentation; enforcement would require either
  # (a) pinning specific transitive versions (brittle) or (b) bumping
  # MSRV and updating ADR-008.

  test:
    name: Test (${{ matrix.os }})
    # Run the default-included test suite on both Linux and macOS.
    # macOS coverage exists because mati ships as a Claude Code plugin and
    # most users run it on macOS — APFS fsync semantics, BSD socket nuances,
    # and the `logd` watchdog have caused real failures invisible to a
    # Linux-only matrix.
    #
    # SKIPPED ON PULL REQUESTS: this job takes >15min on cold cache and
    # blocks PR feedback. It still runs on every push to main so coverage
    # is preserved after merge. To re-enable for PRs, remove the `if:` line.
    if: github.event_name == 'push'
    strategy:
      fail-fast: false
      matrix:
        os: [ubuntu-latest, macos-latest]
    runs-on: ${{ matrix.os }}
    steps:
      - uses: actions/checkout@v4

      - uses: dtolnay/rust-toolchain@stable

      - uses: Swatinem/rust-cache@v2

      # Use cargo-nextest for the default test pass. Nextest runs each
      # test in its own subprocess (vs cargo test's shared-binary model),
      # which prevents tokio runtime / SurrealKV / OnceLock state from
      # accumulating across hundreds of tests in one process. The matching
      # local profile is the `default` profile in `.config/nextest.toml`.
      - uses: taiki-e/install-action@nextest

      - name: cargo nextest run
        run: cargo nextest run --locked --all-targets --profile ci

      # Nextest does not run doc tests (rustdoc API limitations). The
      # `--doc` pass remains on vanilla `cargo test`. This stays
      # single-binary but doc tests are short and CPU-light, so it's fine.
      - name: cargo test --doc
        run: cargo test --locked --doc

  ignored-tests:
    # The `#[ignore]`d integration tests cover subprocess lifecycle,
    # crash recovery, concurrent socket dispatch, and the panic-hook
    # pre-opened-fd path. They're slow and depend on a writable `~/`,
    # so they don't run by default — but they do gate merges.
    #
    # SKIPPED ON PULL REQUESTS: subprocess tests are >15min on cold cache.
    # Runs on push to main only. To re-enable for PRs, remove the `if:` line.
    name: Ignored integration tests (${{ matrix.os }})
    if: github.event_name == 'push'
    strategy:
      fail-fast: false
      matrix:
        os: [ubuntu-latest, macos-latest]
    runs-on: ${{ matrix.os }}
    steps:
      # Full history, not the default shallow (fetch-depth: 1) clone:
      # smoke_git_history_accessible asserts the mati repo has >=5 commits,
      # which a 1-commit shallow checkout fails.
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - uses: dtolnay/rust-toolchain@stable

      - uses: Swatinem/rust-cache@v2

      - uses: taiki-e/install-action@nextest

      - name: cargo nextest run -- --ignored
        # Ignored subprocess tests race each other on `~/.mati/<slug>/`
        # lifecycle logs and per-project sockets, so they need serial
        # execution. `--test-threads=1` and nextest's per-subprocess
        # isolation together give the strongest guarantee.
        # `stress_700000_edges_linux_kernel_scale` runs here too and is
        # fine at threads=1.
        #
        # QUARANTINED via `-E` — two daemon-startup races that fail
        # deterministically on the CI runners (all retries) but pass locally,
        # so they cannot be fixed blind. Tracked as WI-21; re-include each as
        # its race is fixed:
        #   - serve_exits_cleanly_on_sigterm_after_client_disconnect: drops
        #     stdin before the MCP `initialize` completes -> `serve_failed`
        #     instead of the clean idle-wait -> SIGTERM path.
        #   - metrics_snapshot_reflects_dispatch_traffic: queries the metrics
        #     snapshot before it is initialized -> `version: None`.
        # `not kind(bench)` is restated so the filterset is self-contained
        # regardless of how `-E` composes with the profile default-filter.
        run: cargo nextest run --locked --all-targets --profile ci --run-ignored only --test-threads 1 -E 'not kind(bench) and not test(serve_exits_cleanly_on_sigterm_after_client_disconnect) and not test(metrics_snapshot_reflects_dispatch_traffic)'

  benches:
    # Benches are not run for wall-time (criterion regressions need a
    # baseline + stable hardware that GH Actions does not provide). We
    # only gate that they *compile*, so an SLO-relevant bench can't bit-rot
    # silently between feature changes. Real bench runs happen locally
    # against ADR-010 SLOs.
    #
    # SKIPPED ON PULL REQUESTS: criterion's release build is >10min on
    # cold cache. Runs on push to main only. To re-enable for PRs, remove
    # the `if:` line.
    name: Benches compile
    if: github.event_name == 'push'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: dtolnay/rust-toolchain@stable

      - uses: Swatinem/rust-cache@v2

      - name: cargo bench --no-run
        run: cargo bench --locked --no-run

  feature-matrix:
    name: Feature matrix (cargo hack)
    runs-on: ubuntu-latest
    # `cargo check --all-targets` exercises one feature combination at a time.
    # `cargo hack --feature-powerset` exercises every combination, which
    # catches refactors inside `#[cfg(feature = "semantic")]` blocks that
    # compile fine under `--all-features` but break under default features.
    # mati-cloud may enable different combinations than the OSS binary, so
    # this catches integration breaks before the downstream consumer sees
    # them.
    #
    # Currently mati has one optional feature (`semantic`), so the powerset
    # is two combinations. Cheap and informative.
    steps:
      - uses: actions/checkout@v4

      - uses: dtolnay/rust-toolchain@stable

      - uses: Swatinem/rust-cache@v2

      - name: Install cargo-hack
        uses: taiki-e/install-action@v2
        with:
          tool: cargo-hack

      - name: cargo hack check --feature-powerset
        # NOTE: `--no-dev-deps` was tried here but conflicts with `--locked`
        # — cargo-hack rewrites Cargo.toml in-place, which causes cargo
        # to demand a Cargo.lock regeneration that `--locked` forbids.
        # The current powerset is just two combos (`[]` and `[semantic]`),
        # so the overhead of including dev-deps is negligible.
        run: cargo hack check --locked --feature-powerset

  semver:
    name: Public API semver check
    runs-on: ubuntu-latest
    # DISABLED pre-publish: cargo-semver-checks needs a meaningful baseline
    # to compare against. Pre-1.0 the project is allowed to make breaking
    # changes within 0.x.y; pre-publish there is no crates.io baseline and
    # no git tag to compare to. Per-PR comparison against `origin/main`
    # produces noise (PRs that legitimately break pre-1.0 API would fail)
    # without a corresponding consumer that pins to versions.
    #
    # Re-enable when:
    #   - The crate is published to crates.io (set `version-tag-prefix: v`
    #     and remove `baseline-rev`), OR
    #   - A `v0.1.0` git tag exists (change `baseline-rev: origin/main` to
    #     `baseline-rev: v0.1.0`).
    #
    # Remove the `if: false` below to re-enable.
    if: false
    # `mati_core` is consumed by the enterprise binary (mati-cloud) as a
    # Cargo crate. An accidental breaking change to the public API surface
    # without a major-version bump forces downstream rework. This job
    # compares the PR's `mati_core` API against the `main` branch and fails
    # if it finds a violation.
    steps:
      - uses: actions/checkout@v4
        with:
          # cargo-semver-checks needs the baseline ref's history to compare
          # against, not just the current commit.
          fetch-depth: 0

      - uses: dtolnay/rust-toolchain@stable

      - uses: Swatinem/rust-cache@v2

      - name: cargo semver-checks (vs main)
        uses: obi1kenobi/cargo-semver-checks-action@v2
        with:
          # Cargo *package* name (not the [lib] name). The lib `mati_core`
          # lives inside the `mati` package; the action looks up packages
          # by their `[package].name` field.
          package: mati
          # `origin/main` rather than `main`: on PR runs GitHub Actions
          # checks out the PR head, so the bare name `main` is not a
          # local ref. `fetch-depth: 0` populates `origin/main` but does
          # not create a local `main` branch, so `git rev-parse main^{tree}`
          # would fail. The remote-tracking ref resolves cleanly.
          baseline-rev: origin/main

  audit:
    name: Security audit
    runs-on: ubuntu-latest
    # Fail fast on RUSTSEC vulnerabilities (default cargo-audit behavior:
    # exit non-zero on Vulnerability entries, warnings allowed). Five
    # accepted unmaintained/unsound advisories are documented in
    # DECISIONS.md; new entries should be triaged before silencing here.
    steps:
      - uses: actions/checkout@v4

      - uses: dtolnay/rust-toolchain@stable

      - name: Install cargo-audit
        uses: taiki-e/install-action@v2
        with:
          tool: cargo-audit

      - name: cargo audit
        run: cargo audit

  supply-chain:
    name: Supply chain (cargo-deny)
    runs-on: ubuntu-latest
    # Broader supply-chain gate than `cargo audit` alone. Runs three of
    # cargo-deny's four checks against `deny.toml`:
    #
    #   bans       — duplicate versions + wildcard version specifiers
    #   advisories — same RUSTSEC vulnerability stream as cargo-audit,
    #                plus optional yanked-crate detection (subsumes audit
    #                job; kept separate for ADR-007 traceability)
    #   sources    — only crates.io, no git deps, no unknown registries
    #
    # The fourth check (licenses) is INTENTIONALLY SKIPPED. Customer
    # license-key validation belongs in mati-cloud per CLAUDE.md "What
    # this repo must NEVER include". Dependency license scanning for the
    # commercial consumer is mati-cloud's responsibility on its final
    # binary, not ours upstream.
    steps:
      - uses: actions/checkout@v4

      - uses: dtolnay/rust-toolchain@stable

      - name: Install cargo-deny
        uses: taiki-e/install-action@v2
        with:
          tool: cargo-deny

      - name: cargo deny check (bans + advisories + sources)
        run: cargo deny check bans advisories sources

      # Zero-network attestation: the DEFAULT build — what `cargo install mati`
      # ships, and what runs the enforcement hot path — must link NO network /
      # HTTP-client crate. This makes CLAUDE.md's invariant ("DENY/ALLOW/receipt
      # is local, zero network, always ... never phones home") machine-enforced
      # and a permanent regression guard. The opt-in `semantic` feature downloads
      # an embedding model via hf-hub and is the one documented exception, so it
      # is intentionally excluded here (this check uses --no-default-features).
      - name: Zero-network attestation (default build links no HTTP client)
        run: |
          set -euo pipefail
          net=$(cargo tree --no-default-features --edges normal --prefix none --format '{p}' \
                | awk '{print $1}' | sort -u \
                | grep -iE '^(reqwest|hyper|hyper-util|hyper-rustls|hyper-tls|h2|ureq|isahc|surf|attohttpc|curl|curl-sys)$' \
                || true)
          if [ -n "$net" ]; then
            echo "::error::network-capable crate(s) in the DEFAULT (enforcement) build — violates the zero-network invariant (CLAUDE.md):"
            echo "$net"
            exit 1
          fi
          echo "OK: default build links no network/HTTP-client crate"

  no-network:
    name: No-network runtime attestation
    runs-on: ubuntu-latest
    # Behavioral companion to the build-time "Zero-network attestation" above.
    # The dep-ban proves no HTTP-CLIENT CRATE is linked; it structurally CANNOT
    # prove the absence of a raw socket call — std::net, tokio/net (which
    # `tokio = ["full"]` enables in the default build), or a shelled-out fetch.
    # This job proves the property at the SYSCALL boundary instead, which is
    # dependency-agnostic:
    #
    #   (1) syscall audit  — run the enforcement path under strace and FAIL on
    #       any connect()/sendto()/sendmsg() to an AF_INET/AF_INET6 address. This
    #       is the airtight "never phones home / zero telemetry" proof: it catches
    #       ANY outbound IP connection regardless of how it is made.
    #   (2) offline liveness — run the same path with networking removed (network
    #       namespace) and assert it still works, proving the hot path has no
    #       hidden network dependency. (Local Unix-socket IPC is unaffected — it
    #       is filesystem-scoped, not network-namespace-scoped.)
    #
    # MATI_DISABLE_AUTO_SPAWN=1 keeps the background daemon from forking, so the
    # traced process tree stays bounded and uses direct store access.
    steps:
      - uses: actions/checkout@v4
      - uses: dtolnay/rust-toolchain@stable
      - uses: Swatinem/rust-cache@v2

      - name: Install strace
        run: sudo apt-get update -qq && sudo apt-get install -y -qq strace

      - name: Build default (enforcement) binary
        run: cargo build --locked --bin mati

      - name: No-network runtime attestation
        run: |
          set -euo pipefail
          BIN="$PWD/target/debug/mati"
          export MATI_DISABLE_AUTO_SPAWN=1

          work="$(mktemp -d)"
          export HOME="$work/home"; mkdir -p "$HOME"
          repo="$work/repo"; mkdir -p "$repo"
          git -C "$repo" init -q
          git -C "$repo" config user.email ci@example.com
          git -C "$repo" config user.name  ci
          printf 'fn main() { println!("hi"); }\n' > "$repo/main.rs"
          git -C "$repo" add -A
          git -C "$repo" -c commit.gpgsign=false commit -q -m init

          # JSON payload for the hook (file avoids nested-quoting in bash -c).
          payload="$work/payload.json"
          printf '%s' '{"session_id":"ci","tool_input":{"file_path":"main.rs"}}' > "$payload"

          # Enforcement-relevant surface. Exit codes are tolerated (doctor exits
          # non-zero as a CI gate; hook-decide may deny) — the audit inspects
          # SYSCALLS, not status. $0=binary, $1=repo, $2=payload.
          seq='
            "$0" init --path "$1" --no-hooks      >/dev/null 2>&1 || true
            "$0" doctor --json                    >/dev/null 2>&1 || true
            ( cd "$1" && "$0" status )            >/dev/null 2>&1 || true
            "$0" hook-decide claude-pre-read <"$2" >/dev/null 2>&1 || true
          '

          echo "== (1) syscall audit: no outbound IP connection during the enforcement path =="
          trace="$work/net.strace"
          strace -f -e trace=%network -o "$trace" bash -c "$seq" "$BIN" "$repo" "$payload" || true
          # Transparency: report any AF_INET socket() even if never connected.
          if grep -qE 'socket\(AF_INET' "$trace"; then
            echo "note: an AF_INET/AF_INET6 socket() was created (not necessarily egress):"
            grep -nE 'socket\(AF_INET' "$trace" | head
          fi
          egress=$(grep -nE '(connect|sendto|sendmsg)\([^)]*sa_family=AF_INET' "$trace" || true)
          if [ -n "$egress" ]; then
            echo "::error::enforcement path attempted an outbound IP connection — violates zero-network / never-phones-home:"
            echo "$egress"
            exit 1
          fi
          echo "  OK: zero connect()/sendto()/sendmsg() to any AF_INET/AF_INET6 address"

          echo "== (2) offline liveness: enforcement path runs with networking removed =="
          if unshare -rn true 2>/dev/null; then UNS="unshare -rn";
          elif sudo unshare -n true 2>/dev/null; then UNS="sudo -E unshare -n";
          else UNS=""; fi
          if [ -z "$UNS" ]; then
            echo "::warning::no usable network-namespace isolation on this runner; skipping offline-liveness (the syscall audit already proves no egress)"
          else
            echo "  using: $UNS"
            $UNS env HOME="$HOME" MATI_DISABLE_AUTO_SPAWN=1 "$BIN" init --path "$repo" --no-hooks >/dev/null
            echo "  OK: 'mati init' completes with no network namespace"
          fi
          echo "zero-network runtime attestation: PASS"

  eval-corpus:
    name: Eval / regression corpus
    runs-on: ubuntu-latest
    # Enforcement eval corpus (idea 4). Replays a labeled corpus through the
    # pure enforcement functions — DETECTION (classify_command +
    # extract_file_path: which file a bash command reads) and DECISION
    # (evaluate(): Allow/Advisory/Deny/…) — and gates on the committed baseline:
    # a NEW miss fails CI (regression), and a FIXED gap also fails (forcing an
    # honest baseline update that ratchets recall up). Runs on PRs — unlike the
    # full `test` job, which is push-to-main only — so the enforcement-
    # correctness number is visible on every change. The same corpus is embedded
    # in the binary and surfaced by `mati eval --json`. Pure functions: no
    # store, daemon, or network.
    steps:
      - uses: actions/checkout@v4
      - uses: dtolnay/rust-toolchain@stable
      - uses: Swatinem/rust-cache@v2

      - name: Run enforcement eval corpus
        run: cargo test --locked --test eval_corpus -- --nocapture