<!-- SPDX-License-Identifier: Apache-2.0 -->
**Status:** beta · **Owner:** Developer Relations · **License:** Apache-2.0

# kye-pgvector — KYE retrieval Data-Admissibility gate

> **retrieval is not authority; recall is not a mandate.**

A thin gate that sits **in front of** a customer's [pgvector](https://github.com/pgvector/pgvector)
similarity query and binds retrieved context to **purpose + authority BEFORE**
it feeds an agent action. The retrieval half of the Haystack + pgvector
reference pair (the agent half is [`../haystack`](../haystack/README.md)).

## What it is — and what it is not

KYE governs *whether retrieved data may be used for an action*. It does **not**:

- **adopt pgvector into its own evidence path** — KYE is vectorless-by-design
  (§71) and stays **Cloudflare-native (§16)** for its own infra. pgvector is the
  **customer's** store.
- **ship a database driver or open a connection** — the gate wraps a
  caller-supplied `query_fn(params) -> rows`. Your query, your tenant DB, your
  connection. The gate only decides admissibility and records reliance.
- **invent a new schema or a second PDP client** — it reuses the §31
  data-governance envelopes, the §62 source pin, and the one KYE PDP via the
  canonical Python SDK's `authorize()`.

## How it works

```
  agent wants context
          │
          ▼
  ┌───────────────────────────┐   check(req): tenant scope present? (§0.11)
  │ PgVectorAdmissibilityGate │──▶ ask the ONE KYE PDP: may THIS actor, for
  └───────────────────────────┘    THIS purpose, at THIS classification, in
          │  allow                  THIS tenant, rely on retrieved context?
          ▼
  your real pgvector query_fn(params)   ◀── runs ONLY on allow
          │
          ▼
  each row → reliance pin (kye.evidence.tool_call_pin.v1)
          + data-access event (kye.data_access_evidence_event.v1)
```

On any outcome other than `allow`, **your query never runs**, a
`kye.agent.refusal.v1` is emitted, and `rows` is empty.

| Emitted (canonical, reused) | When |
|---|---|
| `kye.data_use_manifest.v1` (§31) | every attempt — purpose + classification binding |
| `kye.agent.refusal.v1` (§52) | on deny / step_up / quarantine / constrain |
| `kye.evidence.tool_call_pin.v1` (§62) | per returned row — the reliance record |
| `kye.data_access_evidence_event.v1` (§31) | on allow — rows returned |

## Install

```bash
pip install kye-pgvector           # gate core, zero required deps
pip install 'kye-pgvector[sdk]'    # + the canonical KYE PDP client (kye-sdk)
```

## Quickstart

```python
from kye_pgvector import PgVectorAdmissibilityGate, RetrievalRequest, decide_via_sdk
from kye_sdk import KyeClient   # the ONE PDP client — reused, never re-implemented

gate = PgVectorAdmissibilityGate(
    decide=decide_via_sdk(KyeClient(base_url="https://api.kyeprotocol.com")),
)

def my_pgvector_query(params):
    # YOUR real query against YOUR tenant DB — the gate never touches it directly
    return db.execute(
        "SELECT id, version, freshness, text FROM kb "
        "WHERE tenant_id = %(tenant)s ORDER BY embedding <-> %(q)s LIMIT %(k)s",
        params,
    ).fetchall()

result = gate.guard_query(
    RetrievalRequest(
        tenant_id="kye:tenant:acme",          # §0.11 — required; no tenant = refused
        actor="kye:agent:acme:support-rag",
        principal="kye:org:acme",
        purpose="answer_customer_query",       # the purpose the data may be used for
        classification="internal",
        collection="support_kb",
        query_params={"tenant": "acme", "q": embedding, "k": 5,
                      "downstream_action": "draft_reply"},
    ),
    my_pgvector_query,
)

if result["outcome"] == "allow":
    feed_agent(result["rows"])                # bound to reliance pins in result["reliance"]
else:
    handle_refusal(result["decision"])        # your query never ran
```

A fully runnable, network-free version is in
[`examples/quickstart.py`](examples/quickstart.py):

```bash
python examples/quickstart.py
```

## Fail-closed by construction

- A retrieval with **no tenant scope** is refused before any PDP call (§0.11).
- A gate with **no PDP wired** refuses rather than default-allow (§0.4).
- An **unrecognised verdict** normalises to `deny`.

## Test

```bash
pip install pytest
python -m pytest public/oss/framework-adapters/pgvector/tests -q
```

## Files

- `src/kye_pgvector/gate.py` — the admissibility gate + reliance records + verdict normalisation.
- `examples/quickstart.py` — a runnable, network-free demo (permitted vs disallowed purpose).
- `tests/test_gate.py` — pytest unit tests (no database, no network).
