Skip to content

v0.13.0 — Per-Table Principal Access Control

Choose a tag to compare

@flyersworder flyersworder released this 24 Apr 19:30
· 16 commits to main since this release

Highlights

Add optional per-table allow/blocklist gates keyed on an opaque caller identity (email, Webex ID, employee number — any string). Designed for two deployment shapes:

  • Chainlit app — one authenticated user per session. Pass a static string: create_tools(contract, caller_principal="alice@co.com").
  • Webex room bot — one long-lived bot instance serving multiple users. Pass a zero-arg callable reading a contextvars.ContextVar set per incoming message: create_tools(contract, caller_principal=lambda: current_sender.get()).

Fail-closed by default: any allowed_principals or blocked_principals field on a table requires identification. Query-time gating is authoritative — denied queries never reach the database (verified by a spy-adapter integration test).

YAML

allowed_tables:
  - schema: analytics
    tables: [orders]                      # open to all
  - schema: hr
    tables: [salaries]
    allowed_principals: [alice@co.com]    # allowlist
  - schema: raw
    tables: [audit_log]
    blocked_principals: [intern@co.com]   # blocklist

API

New keyword-only caller_principal parameter on both Validator and create_tools, accepting str | Callable[[], str | None] | None. Principal type alias and resolve_principal helper are re-exported from the package root.

Error messages

Two-tier diagnostics distinguish "not declared" from "declared but restricted to other principals (caller: 'X')" — actionable for both humans and LLMs.

Example

examples/ops_agent now demonstrates the feature end-to-end. Try:

uv run python examples/ops_agent/agent.py --caller intern@co.com "Show recent deploys"

The intern is in blocked_principals on sre.deploys, so the query is denied; sre.incidents remains queryable (principal access is per-table).

Known limitation

to_system_prompt() currently renders the unscoped table list — an LLM serving a restricted caller may still be told about tables it can't query, wasting retry budget on queries that will be blocked at validation time. Query-time gating remains authoritative. Principal-aware prompt rendering is a candidate follow-up.

Bundled dependency cleanup

This release also requires ai-agent-contracts>=0.3.1, which moved litellm to an optional extra. The default lock shrinks from 108 → 78 resolved packages, dropping aiohttp (10 CVEs), openai, tiktoken, tokenizers, and friends from the core graph.

See CHANGELOG.md for details.