# Airlock MCP Guide

Airlock MCP is the installable MCP interface for agents working with Airlock
specs, app repos, and governed workflows.

Agent work should be governed. Governance should be observable.

It is not the system of record. The Snowflake Native App, installed Airlock
stored procedures, and Snowflake-owned data remain authoritative.

Today, Airlock MCP does not open a Snowflake connection or call installed
Airlock procedures. Agents use an approved Snowflake access path for live
discovery and operational calls.

## Install

Install from npm:

```bash
npx @reunionstudio/airlock-mcp install
```

Dogfood directly from GitHub:

```bash
npx -y github:reunionstudio/airlock-mcp install --package github:reunionstudio/airlock-mcp
```

For Codex, install registers a local stdio server roughly equivalent to:

```bash
codex mcp add airlock -- npx -y @reunionstudio/airlock-mcp server
```

Airlock MCP also includes a native Cortex Code plugin manifest:

```bash
cortex plugin install reunionstudio/airlock-mcp
```

The plugin loads the same Airlock skill and pins the stdio MCP server to the
package version. Credentials remain in approved connection/environment setup,
never in the plugin manifest.

## Current Shape

The current package is a small Node installer and MCP launcher with a bundled
Python spec-building workbench. It helps agents:

- start a spec repo
- choose spec-first, app-first, or co-development mode
- draft, check, summarize, clone, import, and revise specs
- render sample records as Airlock-ready CSV
- render validate-only admin SQL
- seed app repos with local spec snapshots, sample records, helper folders, and
  an app manifest

Those snapshots are for coding, tests, and UI planning. Canonical specs live in
the specs repo or installed Airlock. Accepted records, evidence, workflow state,
and audit history live in Snowflake.

## Principal Identity

Every mutating human or automated agent should connect as an attributable
Snowflake principal. Direct work uses that principal's Airlock assignments.
Work for another principal uses explicit Airlock delegation and
`on_behalf_of_user`.

Snowflake user type, workspace roles, channel membership, reactions, and chat
approval do not create Airlock authority. A read-only channel may use a
dedicated scoped observer principal, but that principal must not be reused for
mutation. Stored-procedure activity may include the informational
`EVENT_CONTEXT.snowflake_agent_active` marker; it never changes access,
workflow, expectations, billing, or actor identity.

## Observer Integrations

`app_observer` is the Snowflake application role for read-only `observe.*`
calls. It does not itself grant account-wide visibility. Airlock role
assignments and each spec's `observer_access.observer_roles` determine which
specs, paths, and workflow states an observer can monitor. Account-wide audit
surfaces require `app_admin` or an assignment to the locked Airlock role
`global_observer`. That reserved role cannot own specs, receive guest or
workflow-review grants, or manage child roles.

Observe-only identities do not claim or consume a named Airlock seat.
`agent.*` calls and the built-in Streamlit app remain seat-gated. When an
existing identity becomes observe-only, an administrator must explicitly
unassign any seat it previously held after confirming it no longer uses those
surfaces.

Account-wide auditors can call `airlock.observe.observers(...)` for one flat
inventory of observer users, assignments, effective roles, spec/path/workflow
scope, and named-license posture. `active_seat_observer_only` identifies an
observer-only identity whose prior seat remains billable until
`airlock.admin.unassign_license(...)` is called.

Use a distinct scoped observer principal for each integration or chat channel.
This keeps attribution, spec scope, and revocation independent. Observe
procedures return governance metadata, not governed rows or attachment bytes.

```json
{
  "observer_access": {
    "enabled": true,
    "observer_roles": [
      {
        "role_name": "finance_observer",
        "path_filter": "finance",
        "workflow_states": ["Approved"]
      }
    ]
  }
}
```

### Set Up And Audit An Observer

Keep the Snowflake application role, Airlock assignment, spec scope, and named
seat as separate controls:

1. Give a dedicated Snowflake account role access to the installed app's
   read-only procedure family.
2. Create a distinct Airlock role and assignment for the observer principal.
3. Add that role to each intended spec's `observer_access`, using
   `path_filter` and `workflow_states` when appropriate.
4. Verify effective scope and billing with `observe.observers`, then audit
   assignment changes with `observe.admin_activity`.

```sql
GRANT APPLICATION ROLE <APP_NAME>.app_observer
  TO ROLE FINANCE_OBSERVER;

CALL <APP_NAME>.ADMIN.CREATE_ROLES(
  ARRAY_CONSTRUCT(
    OBJECT_CONSTRUCT(
      'role_name', 'finance_observer',
      'description', 'Read-only finance monitoring'
    )
  ),
  FALSE
);

CALL <APP_NAME>.ADMIN.CREATE_ASSIGNMENTS(
  ARRAY_CONSTRUCT(
    OBJECT_CONSTRUCT(
      'assignment_name', 'buzz_finance_observer',
      'user_id', 'BUZZ_FINANCE',
      'assigned_role', 'finance_observer'
    )
  ),
  FALSE
);

CALL <APP_NAME>.OBSERVE.OBSERVERS(
  user_id => 'BUZZ_FINANCE',
  observer_role => 'finance_observer',
  spec_name => 'finance_requests',
  include_inactive => FALSE
);

CALL <APP_NAME>.OBSERVE.ADMIN_ACTIVITY(
  procedure_name => 'create_assignments',
  action_family => 'assignments',
  limit_rows => 50
);
```

`observe.observers` reports direct and effective observer roles, spec/path/
workflow scope, current/global status, agent access, active-license state, and
`BILLING_POSTURE`. `free_observer_only` is the expected result for a dedicated
observe-only identity. `active_seat_observer_only` means an earlier named seat
remains billable; after confirming the identity no longer needs `agent.*` or
Streamlit, remove it explicitly with
`ADMIN.UNASSIGN_LICENSE('BUZZ_FINANCE')`.

Assignment create, alter, and drop events expose structured
`EVENT_CONTEXT.before`, `EVENT_CONTEXT.after`, and
`EVENT_CONTEXT.changed_fields`, so audit tools do not need to parse event
descriptions.

## Workspace Commitment Receipts

An external workspace is where discussion, drafts, and automation may happen.
It is not an Airlock authorization layer. A `DELEGATED_ACTION` event records an
authorized attempt, not proof that its mutation committed. After each supported
`agent.*` or intentional `admin.*` mutation, retain an
`airlock.commitment-receipt/v1` in workspace history with:

- the procedure and authenticated Snowflake actor;
- the represented principal and delegation id derived from the actual call
  arguments;
- the target spec, path, and filename;
- the returned stable status, code, message, and issues; and
- the timestamp.

After a successful direct file mutation, call `agent.file_state` with the
returned target and retain its `UPLOAD_ID`, `FILE_STATE_ID`, workflow state,
attachment count, and reference count. Do not make that direct actor state read
after delegated work; leave `committed_state` null unless the actor performs a
separate independently authorized observation. A denied call is still a useful
receipt, but it has no committed state. Workspace approval alone is never an
Airlock commitment.

See [Principal and workspace interoperability](principal_workspace_interoperability.md)
for the complete authority matrix, polling contract, and JSON receipt shape.

## From Process To Specs

Airlock MCP starts with the process a person wants to improve, not with an empty
table. It asks where information comes in, where action goes out, and which
decision or output should become a governed record.

Use the OODA loop as a practical design lens:

1. **Observe:** identify the real interfaces, signals, files, forms, messages,
   APIs, and other artifacts the process already uses.
2. **Orient:** turn those observations into one useful row grain with durable
   identifiers, business timestamps, typed fields, evidence, references, and
   access rules.
3. **Decide:** capture the governed choice, actor, time, evidence, rationale,
   and separation of duties.
4. **Act:** model the controlled action that follows. That action creates new
   observations and the loop continues.

Start with one small useful spec. Keep the rest of the process visible as a plan
for later specs instead of forcing an entire operating loop into one contract.
This is design guidance from Airlock MCP; installed Airlock remains the
authority for validation, access, workflow, and accepted records.

## Generic Native App, Purpose-Built Interfaces

Airlock's Streamlit Native App is a dependable generic operating and fallback
surface. It supports administration, inspection, governed evidence, workflow,
and safe manual action. It is not intended to become a universal domain
application builder.

Repeated, high-value decisions such as reimbursement review, vendor onboarding,
or employee-record changes may deserve purpose-built interfaces. Airlock MCP
can help build those apps in app-first or co-development mode. The custom app
owns domain summaries, calculations, terminology, evidence layout, and
interaction design. Airlock remains the governed backend for access,
validation, expectations, evidence, workflow, activity, and observability.

Do not add UI layout or aggregation fields to a spec merely to make the generic
Native App imitate custom software. Presentation belongs in app code unless it
is genuinely governed business semantics shared across interfaces.

## Installed Airlock Commands

Use the current installed procedure grammar when an app or agent talks to
Airlock:

The examples below assume the installed application object is named
`airlock`. Use the actual installed object name in each Snowflake account.

- `airlock.observe.*` is read-only governance observation for setup, activity,
  billing events, health, access explanations, governance maps, and context
  packets. It is the right starting point for audit/compliance monitoring and
  app-first discovery.
- `airlock.agent.*` is governed user or agent work in the actor's scope:
  role/spec discovery, validation, loading, workflow actions, attachments,
  references, and delegations.
- `airlock.admin.*` is administrative mutation and operational change:
  create/alter/drop specs and roles, assignments, setup repair, OKF bundle
  loads, retention cleanup, and license administration.

Useful observe calls for app-first work:

```sql
CALL airlock.observe.procedures(schema_name => 'observe');
CALL airlock.observe.specs();
CALL airlock.observe.spec('budget_requests');
CALL airlock.observe.governance_map(subject_type => 'user', subject_name => 'CSMITH');
CALL airlock.observe.explain_access('CSMITH', 'budget_requests', 'load_data', 'asmith');
CALL airlock.observe.health();
CALL airlock.observe.activity(limit_rows => 100);
CALL airlock.observe.billing_events(max_rows => 1000);
```

Do not use retired admin read wrappers such as `admin.list_specs`,
`admin.describe_role`, `admin.get_spec`, or `admin.list_events`. Use observe
list/detail procedures instead.

## Efficient Agent Loops

Do not retrieve complete descriptors, file lists, or governed datasets merely
to learn whether something changed.

- Poll `airlock.agent.spec_state(...)` for one known spec or
  `airlock.agent.spec_states(...)` for up to 100 known specs under one role
  lens. Cache each authorization-scoped `STATE_TOKEN` and perform the heavier
  read only when a token changes.
- Poll `airlock.agent.list_my_work(...)` independently. Deadlines, expectation
  windows, and exceptions can change with time without rotating a spec token.
- After choosing one logical file, poll `airlock.agent.file_state(...)` and
  compare `FILE_STATE_ID`. It rotates when the file's data, workflow,
  attachments, exact source references, or retained version history change.
- Observer clients use `airlock.observe.spec_state(...)`,
  `airlock.observe.spec_states(...)`, `airlock.observe.file_state(...)`, and
  `airlock.observe.work(...)` for the corresponding authorization-scoped
  read-only view. `app_admin` and assigned `global_observer` callers receive the
  account-wide form.

```sql
CALL airlock.agent.spec_state('budget_requests');
CALL airlock.agent.spec_states(
  ARRAY_CONSTRUCT('budget_requests', 'finance')
);
CALL airlock.agent.list_my_work('budget_requests');
CALL airlock.agent.file_state('budget_requests', 'finadmin', 'fy27_budget');
```

Treat a missing or omitted state row as the terminal polling signal. It may
mean the object does not exist, was removed, or is no longer visible to the
caller; do not guess which condition occurred. Plural polling preserves request
order after case-insensitive deduplication. Failures after authorization remain
procedure errors rather than being converted to absence.

## Governed Spec Evolution

When a structural spec change with active files returns
`SPEC_MIGRATION_REQUIRED`, treat that as the start of Airlock's governed
two-version lifecycle rather than an error to bypass. Create an immutable
revision and migration, validate and approve it, activate the target, drain
bounded batches with `admin.run_spec_migration`, inspect progress and lineage
through `observe.spec_migration`, and retire the source only after Airlock's
evidence gate reports it drained. Use `observe.spec_migrations` to discover and
filter lifecycle work. Before activation, an abandoned `draft`, `planned`,
`validated`, or `approved` migration may be released with
`admin.cancel_spec_migration`; cancellation is not rollback after activation.

The built-in transform is intentionally limited to mechanical column mappings,
defaults, and safe casts keyed by immutable `column_id` values. Semantic
transformations belong in a purpose-built process that loads its result through
normal Airlock validation and preserves governed source references. See the
[stored procedure lifecycle](api-v1.html#spec-revisions-and-governed-migration)
for exact call signatures.

## Restricted References And Usage Limits

A restricted reference is a one-record interaction contract, not a browsable
dataset. When `observe.reference_context`, `observe.spec_config`, or
`agent.describe_spec` reports `restricted_reference`, do not enumerate the
reference, build a populated picker, or call broad
`agent.select_reference_data` for that object path. Start with a known lookup
value from the case or work context and call `agent.get_reference_record` with
the configured `object_key`, purpose, and role lens.

Airlock returns at most one record, applies configured row filters, and checks
active `action_limit` Expectations before returning data. Counted reads emit
safe `REFERENCE_READ` activity. Callers should branch on codes including
`RESTRICTED_REFERENCE_DENIED`, `NON_UNIQUE_LOOKUP_KEY`, `PURPOSE_REQUIRED`,
`USAGE_LIMIT_BLOCKED`, and `REFERENCE_READ_EVENT_FAILED`, and expose
`USAGE_CONTEXT` values such as `action_limit_used` and `action_time_period`.
Observers can inspect policy and usage through `observe.reference_context`,
`observe.usage_limits`, `observe.usage_limit`, and `observe.explain_access`
without querying raw reference rows.

## Native App Attachment Preview

The Airlock Streamlit Native App can inspect governed evidence without moving
attachments into another document service. Images and text preview inline.
PDFs up to 100 MB and 2,000 pages render one selected page at a time, with a
compact page stepper and a bounded three-page render window that warms the next
two pages in the session cache. PDFs larger than 12 MB require an explicit open
action.

PDF rendering is bounded by timeout, dimensions, output bytes, concurrency,
private session storage, cache size, and expiry. Full-file access remains a
short-lived Snowflake download when available, and successful page reads emit
metadata-only `ATTACHMENT_PREVIEW` activity. Preview does not grant agents or
users direct access to Airlock-owned stages.

## Main Tools

- `airlock_start`: orientation for a project.
- `airlock_doctor`: verify bundled workbench assets.
- `airlock_init_repo`: bootstrap a Git-backed specs repo.
- `airlock_init_app_context`: seed an app repo with spec snapshots and helper
  files.
- `airlock_list_patterns` and `airlock_show_pattern`: inspect starter patterns.
- `airlock_init_workspace`: create a draft workspace.
- `airlock_list_workspaces`: inspect active or archived drafts.
- `airlock_check_workspace`: validate a draft workspace locally.
- `airlock_summary`: summarize the current spec card.
- `airlock_next`: re-enter a draft with next actions.
- `airlock_export_csv`: render `sample.records.json` as CSV.
- `airlock_render_sql`: render validate-only Airlock admin SQL.

## Delivery Modes

Use `spec-first` when the business process and governed record should be modeled
before an app exists.

Use `app-first` when the user already has specs or installed Airlock contracts
and wants an app, dashboard, queue, or workflow that reads from and submits
through those contracts.

Use `co-development` when the app and specs are evolving together. Keep the spec
track and app track visible so row grain, access, workflow, screens, reads, and
governed writes stay aligned.

## Rules For Agents

Ask for real artifacts early: CSV or Excel files, JSON samples, API docs,
schemas, forms, screenshots, PDFs, exports, message examples, or other content
people already use.

Prefer current API docs, real exports, samples, schemas, and user-provided
artifacts over starter library shapes when they conflict.

Do not write directly to Airlock-owned tables, stages, generated views, or
workflow state. Use installed procedures and preserve structured outputs.

Do not treat local app-context snapshots as canonical. They are build aids, not
the record.

## Related Docs

- [Airlock Stored Procedure Reference](api-v1.html)
- [Stored Procedure Markdown Contract](airlock-api-v1.md)
- [Airlock Tools for AI Agents](airlock-tools.md)
- [Verified reimbursement workflow](../reimbursement-case-study.md)
- [Experimental spec patterns](https://github.com/reunionstudio/airlock-specs) are examples for builders, not connectors or the installed Airlock contract.
