# External Agent Provenance

Airlock authorizes work from the authenticated Snowflake principal. External
workspace identifiers explain where a request came from; they never decide who
may commit it.

This distinction applies to Buzz, Grok Bot, Slack/Teams-style workspaces,
OpenClaw, custom MCP clients, and future agent systems.

## Authority And Provenance

Authority is derived inside Snowflake from:

- the authenticated Snowflake `CURRENT_USER()` / `USER_ID`;
- the active Snowflake application role (`app_agent`, `app_observer`, or
  `app_admin`);
- Airlock assignments and role lenses;
- spec, path, workflow, expectation, and observer policy; and
- explicit Airlock delegation when one principal acts for another.

The following values are provenance only:

- source-system user, bot, agent, or public-key identifiers;
- workspace, community, channel, thread, message, or event identifiers;
- reactions, chat approvals, display names, and workspace roles; and
- external correlation or trace identifiers.

An adapter must never translate those external values into an Airlock principal,
role, assignment, or delegation. In particular, a Buzz public key or Grok Bot
name cannot authorize a Snowflake mutation.

## Envelope

Store a bounded envelope with the external workspace's commitment receipt:

```json
{
  "schema": "airlock.external-provenance/v1",
  "source_system": "buzz",
  "source_instance_id": "instance_7f3c",
  "workspace_id": "workspace_91b2",
  "channel_id": "channel_finance",
  "thread_id": "thread_26a8",
  "event_id": "message_8134",
  "source_actor_id": "pubkey_fingerprint_2d6a",
  "correlation_id": "commit_01J6J2Y4N8"
}
```

Rules:

- Use stable opaque ids or one-way fingerprints, not free-form chat text.
- Do not include governed rows, attachment contents, credentials, authentication secrets,
  personal data, or regulated data.
- Do not add client-asserted fields such as `snowflake_principal`,
  `authorized_role`, `approved_by`, `on_behalf_of`, or `delegation_id` to this
  envelope. Those belong in the Airlock receipt and must be derived from the
  authenticated session, actual call arguments, and procedure result.
- Bound every field and reject unrecognized keys before durable storage.
- Preserve one `correlation_id` across the workspace decision, Snowflake query
  tag, Airlock procedure result, and workspace commitment receipt.

## Query Tag Correlation

An external SQL/driver adapter may set a compact query tag before an Airlock
call:

```sql
ALTER SESSION SET QUERY_TAG =
  '{"airlock":{"v":1,"source":"buzz","correlation_id":"commit_01J6J2Y4N8"}}';

CALL airlock.agent.load_data(...);
```

The query tag is evidence in Snowflake Query History, not an authorization
claim. Keep it compact and metadata-safe. Airlock does not request broad
`SNOWFLAKE.ACCOUNT_USAGE` privileges or copy account query history into its own
tables. A customer audit role can correlate the query tag with Airlock activity
using account-native Query History/Access History permissions.

Airlock mutation events already add
`event_context.snowflake_agent_active` from
`SYS_CONTEXT('SNOWFLAKE$CURRENT', 'IS_AGENT_ACTIVATED')`. Snowflake reports this
as true for external agents connected through a managed MCP server. This is
informational provenance and never broadens authority.

The read-only Native App MCP tools do not accept a provenance argument. Adding
untrusted context to every polling call would widen the tool schema without
changing policy. Keep read correlation in the MCP client or Snowflake history;
attach the full envelope only to an external commitment receipt.

## Identity Proof

Airlock packages separate read-only MCP servers for `app_agent` and
`app_observer`. Enable them explicitly with `admin.enable_mcp_server`; they are
not created during app installation.

Before using MCP for governed work, prove principal preservation with two
independently issued, role-restricted Snowflake programmatic access tokens:

```bash
export AIRLOCK_MCP_TOKEN_ASMITH='<programmatic access token>'
export AIRLOCK_MCP_TOKEN_BSMITH='<programmatic access token>'

uv run python scripts/run_batch62_mcp_identity_probe.py \
  --endpoint 'https://<account>/api/v2/databases/<app>/schemas/core/mcp-servers/airlock_agent_mcp' \
  --principal ASMITH=AIRLOCK_MCP_TOKEN_ASMITH \
  --principal BSMITH=AIRLOCK_MCP_TOKEN_BSMITH
```

The probe uses `tools/list`, calls `airlock_my_access_context`, and requires the
returned `USER_ID` values to match and remain distinct. Tokens stay in
environment variables and are never printed or written to the report. The
probe sends a PAT only to the exact Airlock agent MCP path on a
`*.snowflakecomputing.com` account host; it rejects alternate hosts, MCP server
paths, redirects, query strings, and fragments before constructing the request.

Restrict each PAT to the least-privileged Snowflake role needed for the MCP
interface and keep its lifetime short. Each user should have a usable
`DEFAULT_WAREHOUSE` and the matching Airlock application role. Every principal
must authenticate independently; never reuse one PAT as a shared integration
identity.

## Shared Integration Warning

If several people or agents share one Snowflake credential, Airlock correctly
sees one principal. The external system has then become a meta-permission layer,
and Airlock cannot independently prove which participant acted. Keep such an
identity read-only, or replace it with attributable principals before allowing
mutation. Workspace logs are useful supporting evidence, but they do not repair
lost Snowflake attribution.
