Metadata-Version: 2.5
Name: chancel
Version: 0.1.0
Summary: Provable scope isolation for AI retrieval - firm instructions travel, matter data never crosses the wall, enforced below the model boundary.
Project-URL: Repository, https://github.com/Nobel-Co/chancel
Project-URL: Documentation, https://nobel-co.github.io/chancel/
Author-email: "Barret J. Nobel" <contact@barretnobel.com>
License-Expression: MIT
License-File: LICENSE
Keywords: ethical-wall,isolation,multitenancy,rag,retrieval,security
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Security
Requires-Python: >=3.12
Requires-Dist: httpx==0.28.1
Requires-Dist: pydantic==2.13.4
Requires-Dist: typer==0.27.1
Provides-Extra: all
Requires-Dist: anthropic==0.124.0; extra == 'all'
Requires-Dist: fastembed==0.8.0; extra == 'all'
Requires-Dist: openai==3.3.1; extra == 'all'
Requires-Dist: qdrant-client==1.19.0; extra == 'all'
Provides-Extra: anthropic
Requires-Dist: anthropic==0.124.0; extra == 'anthropic'
Provides-Extra: local
Requires-Dist: fastembed==0.8.0; extra == 'local'
Provides-Extra: openai
Requires-Dist: openai==3.3.1; extra == 'openai'
Provides-Extra: qdrant
Requires-Dist: qdrant-client==1.19.0; extra == 'qdrant'
Description-Content-Type: text/markdown

# chancel

**Provable scope isolation for AI retrieval.** Firm-level instructions travel everywhere; a
client matter's data provably never crosses a matter boundary — and the boundary is enforced
*below the model boundary*, so it survives swapping the AI provider.

[![CI](https://img.shields.io/github/actions/workflow/status/Nobel-Co/chancel/ci.yml?branch=main&label=CI)](https://github.com/Nobel-Co/chancel/actions)
[![coverage](https://img.shields.io/badge/coverage-93.6%25-brightgreen)](https://github.com/Nobel-Co/chancel)
[![PyPI](https://img.shields.io/pypi/v/chancel)](https://pypi.org/project/chancel/)
[![license](https://img.shields.io/badge/license-MIT-blue)](LICENSE)

**Demo matrix — regenerated by CI on every push.** The table below is the literal output of
`chancel demo --no-llm`. CI runs it on every push and fails the build if a single cell diverges
from what this repo claims. (The badges 404 until the repo is public; that is expected.)

## The proof, in one table

```
finding                 backend   provider      expected  actual  ok
----------------------  --------  ------------  --------  ------  --
canary-leak             isolated  echo          clean     clean   ✓
canary-leak             isolated  hostile_echo  clean     clean   ✓
unrepresentable-call    isolated  n/a           denied    denied  ✓
deletion-verifiability  isolated  n/a           clean     clean   ✓
canary-leak             filtered  echo          clean     clean   ✓
canary-leak             filtered  hostile_echo  clean     clean   ✓
unrepresentable-call    filtered  n/a           leaked    leaked  ✓
deletion-verifiability  filtered  n/a           leaked    leaked  ✓
canary-leak             shared    echo          leaked    leaked  ✓
canary-leak             shared    hostile_echo  leaked    leaked  ✓
unrepresentable-call    shared    n/a           leaked    leaked  ✓
deletion-verifiability  shared    n/a           leaked    leaked  ✓

PASS: 12/12 cells matched their predicted color
```

This is the literal output of `chancel demo --no-llm`. It runs from a **cold clone with no API
key and no Docker** — the demo uses an in-memory store and a deterministic offline embedder.
"Green" does not mean "no leak." It means every cell behaved as *predicted*: `isolated` clean,
`shared` leaking as designed, `filtered` red exactly where it is supposed to be red. A `✗` would
be the alarm — a backend no longer behaving the way this repo says it does.

## Quickstart

```bash
git clone https://github.com/Nobel-Co/chancel && cd chancel
uv sync
just demo          # or: uv run chancel demo --no-llm
```

## The three backends

Each backend implements the *same* logical vocabulary — one firm collection plus one collection
per matter — and differs only in how that maps onto physical storage and whether the boundary is
a structural fact or a runtime decision.

| backend | layout | what it is | the finding it must face |
| --- | --- | --- | --- |
| **`isolated`** | one physical collection per space + a firm collection | **the claim being defended** | holds all four findings — a cross-space read has no signature to express |
| **`filtered`** | one shared collection, `space_id` payload, `must`-filter every query | the vendor-default multitenancy pattern | **leaks** the unrepresentable-call and deletion-verifiability findings; the sparse-IDF side channel shifts one matter's scores by another's vocabulary |
| **`shared`** | one shared collection, no filter, system prompt says "don't cross matters" | what much shipped software actually does | **leaks** everything, starting with a direct canary read |

The `shared` backend is deliberately weak and is asserted red in CI: fixing it would delete the
negative example the suite needs. See [architecture.md](docs/architecture.md) for the full
module contract.

## Why collection-per-space when the vendor recommends against it

The vector-store vendor's own guidance is to prefer a single shared collection over
collection-per-tenant, and it is right — but that is a **scaling** recommendation, not a security
one. Its exact words:

> "Creating a separate collection for each tenant is rarely the most efficient approach… Only
> create multiple collections when you have a limited number of tenants that need strict
> isolation."

A law firm's matters are exactly that named exception: a bounded number of tenants that need
strict isolation, where the isolation benefit outweighs the operational cost of extra
collections. `chancel` occupies the exception on purpose. See
[ADR 0003](docs/adr/0003-collection-per-space-departs-from-vendor-default.md).

## Fork in five minutes

The base install runs the unit and conformance suites green in under a minute, with **no API
keys and no Docker** — the reference `inmemory` store and `hash_stub` embedder carry the whole
default path.

```bash
uv sync
just test          # unit + integration + conformance
just demo          # the leak matrix above
```

Adding a provider or a store is implementing one protocol and passing `tests/conformance/` —
there is a test that proves a fresh adapter drops in with **zero edits** to the suite. See
[adding-a-provider.md](docs/adding-a-provider.md) and
[adding-a-store.md](docs/adding-a-store.md).

## This is a demonstration, not a product

`chancel` demonstrates exactly one idea end to end: a provider-neutral policy gate that makes a
cross-matter read structurally unrepresentable, with a red/green test suite proving it against
the two architectures the industry actually ships. It carries **zero real data** — the corpus is
synthetic and generated by a script.

**Out of scope** (named here so their omission is not mistaken for oversight):
authentication, RBAC, multi-user sessions, key management, retention, egress DLP,
answer-quality evaluation, any UI, and any hosted deployment.

## Documentation

- [Why](docs/why.md) — the migration argument, the memory tiers, and why the wall belongs below
  the provider boundary.
- [Architecture](docs/architecture.md) — the request path, the three backends side by side, the
  memory-promotion gate, and the module-by-module contract.
- [Threat model](docs/threat-model.md) — what it defends, what it does not, and the trust
  boundaries.
- [Architecture Decision Records](docs/adr/0001-name-and-enforcement-boundary.md) — the name
  gate, the enforcement boundary, the vendor departure, and the audit hash chain.

MIT licensed. See [LICENSE](LICENSE).
