Metadata-Version: 2.5
Name: matrx-mandate-scan
Version: 0.2.2
Summary: The one Mandate reference scanner: finds every mandate carrier, every bypass, and reports contract-v1 JSON to the platform
Project-URL: Homepage, https://github.com/AI-Matrix-Engine/aidream
Project-URL: Repository, https://github.com/AI-Matrix-Engine/aidream
Project-URL: Issues, https://github.com/AI-Matrix-Engine/aidream/issues
Author-email: Matrx <admin@aimatrx.com>
Maintainer-email: Matrx <admin@aimatrx.com>
License: MIT
Keywords: governance,mandates,matrx,static-analysis
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Quality Assurance
Requires-Python: >=3.13
Requires-Dist: pyyaml>=6.0.3
Requires-Dist: tree-sitter-javascript>=0.23.1
Requires-Dist: tree-sitter-typescript>=0.23.2
Requires-Dist: tree-sitter>=0.25.0
Provides-Extra: submit
Requires-Dist: asyncpg>=0.31.0; extra == 'submit'
Description-Content-Type: text/markdown

# matrx-mandate-scan

The ONE Mandate reference scanner. It finds every place code reaches for
platform intelligence — through a Mandate carrier or around it — reports the
result to the database in one frozen contract, and screams (never blocks) when
something is unresolved, unmeasured, or bypassing the mandate system.

Built for the **Mandate Declaration & Usage Reporting** program
(`common-docs/projects/mandate-declaration-reporting/`), lane L2.

## Install / run

```bash
# from any repo, no checkout needed
uvx --from matrx-mandate-scan==0.2.1 matrx-mandate-scan check

# inside the aidream workspace (vendored via [tool.uv.sources])
uv run matrx-mandate-scan scan --root aidream --root packages/matrx-ai
```

## Commands

| Command | What it does |
|---|---|
| `scan` | Contract-v1 JSON on **stdout**, the red human report on **stderr**. |
| `report` | `scan`, then submit through `mandate.submit_scan_report(jsonb)` and file every red finding into `ops.system_error` (`source_app='mandate-scan'`) — once per OPEN finding `(repo, route, error_type)`, not once per revision; a finding already open at any earlier revision files nothing, and a recurrence after the patrol resolved it files a fresh row. |
| `check` | The release-path command: scan + report + reconcile. |
| `explain <file:line>` | What the scanner sees at one location, and why. |
| `--self-test` | The built-in RED→GREEN fixture suite. Runs from a `uvx` install. |

**Every command exits 0** unless `--strict` is passed. That is ruling **D23**:
a mandate check is loud and non-blocking; `--strict` exists for humans and for
the scheduled remediation task, never for a release script.

## What counts as a reference

Classification is **by carrier, never by the word or the path**. A dotted
string is a mandate key only where a carrier puts it — so `consumerId =
"extend.chat"` is not a reference and never becomes one.

Python carriers:

- `declare_mandate` / `declare_generated_mandate` / `declare_mandated_agent` → `declaration`
- `declare_mandate_family(prefix, members=...)` → `family_declaration`, one `constant` per resolvable member, `dynamic_family` when the iterable is computed
- `resolve_mandate` → `resolution`; `run_mandate` → `execution`
- `run_mandated(Cls)` / `Cls.run()` → `execution`
- a `NamedAgent` subclass `mandate_key` (including `type(name, (NamedAgent,), {...})`) → `declaration`
- `seed_agent_id=<uuid>` inside a `declare_*` → `seed_holder`
- `@mandate_passthrough` / `MandateKeyParam` → `passthrough`, with the caller attributed when it lives in the same module

TypeScript / TSX / JavaScript carriers (0.2.0, tree-sitter) — clients never
declare, they import keys from `@ai-matrx/agents/mandates`:

- `useMandate` / `useMandateSet` / `useMandateChain` / `resolveMandate` → `resolution`
- `mandateExecutePath` → `execution`
- a `defaultMandateKey={…}` JSX attribute → `resolution`; a `mandateKey:` property
  is a key **in flight**, reported `passthrough` (the site that chose it is elsewhere)
- a `mandate:<key>` string ref — the clients' own stable UI identity for a
  Mandate-backed choice → `resolution`
- `MANDATE_KEYS.<identifier>` outside a carrier → `constant`
- a module-local object literal of generated keys (matrx-frontend's ambient
  ladder) → one `constant` per entry plus one `dynamic_family` at the computed
  read, named for the DECLARED family its members belong to
- `mandateKeyOfApp(…)` / `mandateKeyOfShortcut(…)` → `dynamic_family` under the
  DB-authored `app` / `shortcut` prefix
- importing `openai`, `ai`, `@anthropic-ai/sdk`, `@ai-sdk/*`, `@google/generative-ai`,
  `groq-sdk`, `cohere-ai`, `@mistralai/*`, `ollama` (a `import type` binds nothing
  and is not access), or fetching a provider host → `bypass`

Keys resolve through literals, template strings, module `const` bindings,
arrays, ternaries, `as`/`satisfies` casts, and — across modules — `import` /
`export` aliases and re-exports followed through the repo's own `tsconfig`
path mapping. `.js` / `.jsx` / `.mjs` / `.cjs` are scanned the same way.

**THE LOCKFILE ADAPTER.** A TypeScript key is checked against the key set the
repo actually SHIPS: the per-version snapshot inside the INSTALLED
`@ai-matrx/agents` (`mandates/snapshots/keys.<version>.json`), never the range in
`package.json` and never today's server state. A key the snapshot does not carry
is **`UNKNOWN_MANDATE`**; with no install at all nothing is confirmed or denied
and the package is reported `incomplete` with `UNMEASURED` — loud, never an
accusation the evidence cannot support.

Rust (0.2.0) — a **negative** adapter. Rust has no carrier, so every `.rs` file
is covered with zero references (never `unsupported_language`, which is a hole a
report still calls complete). A provider host in a request target, or a
`/mandates/` route literal, is a `bypass`.

Config carriers (JSON / YAML / TOML) — the **property name** is the carrier:
`mandate_key`, `mandateKey`, `defaultMandateKey`, `fallback_mandate_key`.
Anything else that merely looks key-shaped is `unclassified` and advisory.

Keys resolve through literals, module and function constants (UPPER or not),
attribute constants, f-strings, `+` concatenations, ternaries (both branches
become real references), tuple/list loop members, aliases, literal-container
subscripts, and one level of analyzable module-local wrapper function.

An argument that resolves to none of those → finding **`UNRESOLVED_KEY`**, plus
an `unresolved`-flagged reference: D21 says unreachable is a flag, never a
filter, so nothing is ever dropped from the inventory.

## Bypass detection and the ratchet

Importing a provider SDK (`anthropic`, `openai`, `groq`, `google.genai`,
`google.generativeai`, `litellm`, `xai`, `ollama`, `cohere`, `mistralai`) or
naming a provider host, anywhere outside
`packages/matrx-ai/matrx_ai/providers/**` and the one D10-approved module
(`conversation_labeler.py`), is a `bypass` reference. The exact standalone
RAG default embedding adapter (`packages/matrx-rag/matrx_rag/embeddings.py`) is
also an approved provider adapter: it is injected through `EmbeddingProvider`,
does not select a Mandate holder, and is independently ratcheted by
`scripts/check_raw_llm_clients.py`. No broader `matrx-rag` exemption exists.

- in the baseline file → **`CONVERSION_PENDING`** (flag `conversion_pending`)
- not in it → **`NEW_BYPASS`** (D20: no new ones)
- code executed by a computed name or path, **however spelled** — `import_module(x)`,
  `__import__(x)`, `runpy.run_module(x)` / `run_path(p)`, a spec builder
  (`find_spec`, `spec_from_file_location`, `spec_from_loader`, `SourceFileLoader`,
  `zipimporter`, `get_loader`) whose spec the same **function** executes, an
  `exec_module`/`module_from_spec` on a spec built elsewhere, a `load_module()`,
  a loader reached through `getattr(importlib, "import_module")`,
  `__builtins__["__import__"]` or an alias chain, or a `load_declared_module`
  bound from anywhere but the seam → **`UNRESOLVED_IMPORT`**. A LITERAL
  `exec("...")`/`eval("...")` is parsed and scanned like source; computed code is
  arbitrary execution, not an import — content-guard's rule, silent here.
  A builder's registered NAME is a target too: `SourceFileLoader("openai", p)` is
  a bypass whatever the path says. Classification is by the act, not the
  spelling: re-spelling an import as the three-line spec dance (six copies
  existed on 2026-09-12) earns nothing.
- `load_declared_module(x)` from `matrx_utils.module_loading` → no finding. It is
  THE seam for every registry that names modules as data (tool paths, adapter
  paths, declaring modules): it refuses every provider SDK name at runtime, so
  the load is bounded by fact. Its own body is the one sanctioned computed load.
- a relative target inside the calling package — `import_module(".x", __name__)`,
  PEP 562 lazy exports — → no finding: it can only reach modules this scan reads
  on their own. A relative target anchored on a literal provider package
  (`import_module("." + x, "openai")`) is a `bypass`.
- a computed load inside the provider layer (`matrx_ai/providers/**`) → no
  finding: reaching SDKs is that layer's job, and bypass is already silent there.
- a dynamic import whose target the resolver closes to a set of NON-provider
  modules (the lazy-export idiom: `import_module(_EXPORTS.get(name), __name__)`
  over a module-level literal table of the package's own submodules) → no
  finding: every module it can reach is named, the full scan reads each of
  them on its own, and none is a provider SDK. A closed set that CONTAINS a
  provider stays `UNRESOLVED_IMPORT` — never promoted to the `NEW_BYPASS`
  claim, never silently clean (2026-09-12).

The baseline is `<repo>/scripts/mandate_bypass_baseline.json`, read by default
by every entry point when it exists (`--baseline` overrides; no file = a
baseline of zero, which is the correct reading, not a missing one).

The sole open-import ABI is a package CLI's caller-selected host bootstrap:
the direct `import_module()` argument must be a function parameter annotated
`Annotated[str, "matrx.external_bootstrap_module"]`. It keeps a package
independent of every possible host while making the boundary explicit; the
selected host is outside that package's scan surface. This is not a general
dynamic-import exemption: computed prefixes, wrappers, provider roots, and
every unmarked parameter still produce `UNRESOLVED_IMPORT`.

`--write-baseline` regenerates the file and **refuses to write a larger one**.
No entry in the baseline is an approved class; every one is a defect awaiting
conversion.

## Coverage is mandatory output

Every file is scanned or listed with a reason (`generated`, `test_fixture`,
`parse_error`, `unsupported_language`). Any `parse_error` makes the package
`verification_status = incomplete` and files an `UNMEASURED` finding.

## Reference identity

`sha256(repo_slug · package_path · file_path · symbol · occurrence_n ·
reference_type · mandate_key_or_prefix)`.

`repo_slug` is always the repo the scan ran in, resolved from
`git remote get-url origin` through the `platform.repo` mirror in
`matrx_mandate_scan/repo.py`. **An unknown remote is `UNMEASURED`, never a
guessed slug** — a folder name is not a repo identity.

## Boundaries

Per `docs/packages/PACKAGE_DOCTRINE.md`, this package imports **neither
`aidream` nor `matrx-orm`**. It carries the AST walkers that used to live in
`scripts/audit_mandate_wiring.py` and
`aidream/services/mandates/code_truth.py`; those two modules now import them
from here, so there is exactly one definition of "what a carrier looks like".
