@reunionstudio/airlock-mcp 0.1.7 → 0.1.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -229,6 +229,38 @@ Airlock:
229
229
  - `airlock.admin.*` performs administrative changes and operational mutations.
230
230
  - `airlock.agent.*` performs governed user or agent work in the actor's scope.
231
231
 
232
+ Installed Airlock may also expose separate opt-in Snowflake-managed MCP
233
+ endpoints for `app_agent` and `app_observer`. They are distinct from this local
234
+ spec-workbench MCP server. Administrators manage them with
235
+ `admin.enable_mcp_server`, `admin.disable_mcp_server`, and
236
+ `observe.mcp_servers`. Their fixed allowlists contain five agent tools
237
+ (`airlock_my_access_context`, work, safe spec description, spec states, and file
238
+ state) and four observer tools (specs, work, spec states, and file state). They
239
+ never expose mutation, governed payload rows, restricted-reference reads, or
240
+ attachment bytes. Each client must authenticate as its own Snowflake principal;
241
+ workspace identity is not Airlock authority.
242
+
243
+ Treat every mutating human or automated agent as an attributable Snowflake
244
+ principal. Direct work uses that principal's Airlock assignments. Work for
245
+ another principal requires explicit Airlock delegation and
246
+ `on_behalf_of_user`; pass `delegation_id` only when Airlock reports more than
247
+ one matching active grant. Snowflake user type and agent-session metadata are
248
+ informational, not authority. Never map workspace roles, channel membership,
249
+ reactions, or chat approval into Airlock permissions.
250
+ A `DELEGATED_ACTION` event records the authorized attempt and actor/principal
251
+ identity; the mutation result and normal file, attachment, or workflow events
252
+ provide commitment evidence.
253
+
254
+ Stored-procedure events may expose
255
+ `EVENT_CONTEXT.snowflake_agent_active` as informational provenance. Never use
256
+ that marker to grant access, infer delegation, classify billing, or replace the
257
+ authenticated Snowflake principal.
258
+
259
+ A read-only channel may use a dedicated observer principal with
260
+ `app_observer`, a distinct Airlock role/assignment, and explicit per-spec
261
+ `observer_access`. Do not reuse that observer principal for mutation, and do
262
+ not share one mutating principal across human or automated actors.
263
+
232
264
  Use `agent.list_my_work` as the actor's single operational inbox for workflow,
233
265
  watcher, deadline, overdue, and exception work. Use `observe.work` for the
234
266
  read-only account-wide current-work projection and `observe.activity` for event
@@ -243,6 +275,19 @@ workflow, attachment, and exact-reference changes. Use `observe.spec_state` and
243
275
  `observe.file_state` only with observer/admin authority for account-wide state.
244
276
  Do not substitute global observer tokens for an agent's scoped token.
245
277
 
278
+ After every supported `agent.*` or intentional `admin.*` mutation, preserve a
279
+ bounded `airlock.commitment-receipt/v1` in the external workspace history.
280
+ Record the procedure and authenticated Snowflake principal; derive any
281
+ represented principal and delegation id from the actual procedure arguments.
282
+ Retain the target spec/path/filename, returned status/code/message and issues,
283
+ and timestamp. After a successful direct file mutation, call `agent.file_state`
284
+ with the returned target and retain its `UPLOAD_ID`, `FILE_STATE_ID`, workflow
285
+ state, attachment count, and reference count. Do not make that direct actor
286
+ state read after delegated work; leave `committed_state` null unless the actor
287
+ performs a separate independently authorized observation. Keep a denied
288
+ mutation as a receipt with no committed state. Never use a chat display name as
289
+ the Snowflake actor or describe workspace approval as an Airlock commitment.
290
+
246
291
  Required source references are exact governed evidence, not filename fields.
247
292
  When an active downstream-to-source link has `min_count > 0`, load the
248
293
  downstream file into Draft, discover eligible sources with
@@ -394,6 +439,8 @@ human decisions remain.
394
439
 
395
440
  - Local checks are not Airlock authority.
396
441
  - Installed Airlock procedures remain the execution contract.
442
+ - Every mutating actor keeps an attributable Snowflake principal; shared
443
+ integration identities and workspace roles are not authorization shortcuts.
397
444
  - Do not write directly to Airlock-owned tables, stages, generated views, or
398
445
  generated tables.
399
446
  - Do not put Airlock workflow state, reviewer comments, approval status, or
@@ -0,0 +1,20 @@
1
+ # Airlock MCP activation
2
+
3
+ Use the bundled Airlock skill when a task concerns Airlock specs, governed
4
+ Snowflake work, read-only governance observation, delegation, expectations,
5
+ attachments, workflow, state polling, or commitment receipts.
6
+
7
+ Before acting, preserve the authenticated Snowflake principal. Workspace roles
8
+ and chat identity are not Airlock authority. Use `airlock.agent.*` for governed
9
+ work, `airlock.observe.*` for read-only observation, and `airlock.admin.*` only
10
+ for intentional administration. Use explicit Airlock delegation when the
11
+ connected principal acts for another principal.
12
+
13
+ Do not confuse this local spec-workbench MCP server with the optional
14
+ Snowflake-managed MCP endpoints exposed by an installed Airlock Native App.
15
+ Those role-separated agent and observer endpoints preserve Snowflake identity
16
+ and expose only fixed read-only tools; they do not expose mutation or governed
17
+ payload rows.
18
+
19
+ Never store credentials in this plugin or its workspace files. Configure the
20
+ Airlock MCP connection through the supported environment or connection setup.
@@ -0,0 +1,23 @@
1
+ {
2
+ "name": "airlock-mcp",
3
+ "description": "Governed Airlock spec, observation, and commitment guidance for Snowflake principals.",
4
+ "version": "0.1.9",
5
+ "author": {
6
+ "name": "Reunion Studio",
7
+ "url": "https://github.com/reunionstudio/airlock-mcp"
8
+ },
9
+ "skills": [
10
+ "./.agents/skills"
11
+ ],
12
+ "mcpServers": {
13
+ "airlock": {
14
+ "type": "stdio",
15
+ "command": "npx",
16
+ "args": [
17
+ "-y",
18
+ "@reunionstudio/airlock-mcp@0.1.9",
19
+ "server"
20
+ ]
21
+ }
22
+ }
23
+ }
package/README.md CHANGED
@@ -28,15 +28,25 @@ Airlock MCP gives agents four kinds of Airlock help:
28
28
  procedures to inspect setup, access, activity, billing events, health,
29
29
  context packets, and governance maps before deciding what an app or agent
30
30
  should do.
31
+ 5. Workspace interoperability: preserve principal identity, poll scoped state,
32
+ commit only through installed procedures, and retain portable Airlock
33
+ receipts in external workspace history.
31
34
 
32
35
  ## Installed Airlock Contract
33
36
 
34
37
  Current Airlock separates procedure intent:
35
38
 
36
39
  - `airlock.observe.*` is the read-only governance observation surface. It is
37
- available to `app_admin` and `app_observer` and is the preferred path for
38
- discovery, health checks, access explanation, governance maps, activity,
39
- billing event context, and list/detail context packets.
40
+ available at the Snowflake object layer to `app_admin` and `app_observer`.
41
+ Airlock assignments and per-spec `observer_access` rules scope ordinary
42
+ observers; account-wide surfaces require `app_admin` or the locked Airlock
43
+ role `global_observer`. That reserved role is observation-only: it cannot own
44
+ specs, receive guest/reviewer grants, or manage child roles.
45
+ Account-wide auditors should use `airlock.observe.observers(...)` for a flat
46
+ user, assignment, observer-role, spec/path/workflow, and license inventory.
47
+ `active_seat_observer_only` means the identity is observer-only now but still
48
+ has a billable seat that must be explicitly unassigned; role changes never
49
+ silently rewrite billing history.
40
50
  - `airlock.admin.*` is for admin changes and operational actions such as
41
51
  creating specs, changing roles, loading OKF bundles, rerunning setup, or
42
52
  deleting purge candidates.
@@ -44,22 +54,93 @@ Current Airlock separates procedure intent:
44
54
  specs, validating/loading data, workflow actions, attachments, delegations,
45
55
  and references.
46
56
 
57
+ Installed Airlock can also expose a deliberately small read-only subset through
58
+ separate Snowflake-managed Native App MCP servers for `app_agent` and
59
+ `app_observer`. They are opt-in, preserve the authenticated Snowflake principal,
60
+ and do not replace the public procedure API or this package's local workbench.
61
+ See [Native App managed MCP](docs/native-app-managed-mcp.md) for the fixed tool
62
+ allowlists, setup calls, identity proof, and security boundary.
63
+
64
+ Every mutating human or automated agent should connect as its own attributable
65
+ Snowflake principal. When it acts for another principal, use explicit Airlock
66
+ delegation. Snowflake user type, chat identity, channel membership, workspace
67
+ roles, and reactions do not create Airlock authority. A read-only channel may
68
+ use a distinct scoped observer principal, but that principal must not be reused
69
+ for mutation.
70
+
71
+ Stored-procedure activity includes the informational
72
+ `EVENT_CONTEXT.snowflake_agent_active` marker when Snowflake exposes it. The
73
+ marker identifies execution context for observation; it never changes Airlock
74
+ access, workflow, expectations, billing, or receipt identity.
75
+ A `DELEGATED_ACTION` event records an authorized attempt, not proof that its
76
+ mutation committed. Commitment receipts derive delegation identity from the
77
+ actual call arguments, and only successful direct file mutations are hydrated
78
+ through `agent.file_state`; delegated work leaves `committed_state` null unless
79
+ the actor performs a separate independently authorized observation.
80
+
47
81
  Use `airlock.agent.list_my_work(...)` for the current actor's unified workflow,
48
- watcher, deadline, overdue, and exception inbox. Use `airlock.observe.work(...)`
49
- for the read-only account-wide projection of current work, and
82
+ read-only observation, deadline, overdue, and exception inbox. Use
83
+ `airlock.observe.work(...)` for the read-only assignment-scoped projection of
84
+ current work, and
50
85
  `airlock.observe.activity(...)` for historical events. The older split work
51
86
  procedures are retired.
52
87
 
53
- Watcher agents should poll `airlock.agent.spec_state(...)` before retrieving a
54
- complete descriptor, file list, or governed dataset. Cache the
55
- authorization-scoped `STATE_TOKEN` and perform the heavier read only when it
56
- changes. Poll `airlock.agent.list_my_work(...)` independently because deadlines
57
- and expectation windows can change with time without rotating a spec token.
88
+ Polling agents should call `airlock.agent.spec_state(...)` for one known spec,
89
+ or `airlock.agent.spec_states(...)` for up to 100 known specs under one role
90
+ lens, before retrieving complete descriptors, file lists, or governed datasets.
91
+ Cache each authorization-scoped `STATE_TOKEN` and perform the heavier read only
92
+ when it changes. Poll `airlock.agent.list_my_work(...)` independently because
93
+ deadlines and expectation windows can change with time without rotating a spec
94
+ token.
58
95
  After selecting a logical file, compare
59
96
  `airlock.agent.file_state(...).FILE_STATE_ID`; that UUID rotates when its data,
60
- workflow, attachments, or exact source references change. Observer clients use
61
- the account-wide read-only `observe.spec_state(...)` and
62
- `observe.file_state(...)` equivalents.
97
+ workflow, attachments, exact source references, or retained version history
98
+ change. Observer clients use
99
+ the authorization-scoped, read-only `observe.spec_state(...)`,
100
+ `observe.spec_states(...)`, and `observe.file_state(...)` equivalents. Missing
101
+ or inaccessible specs are omitted from a batched result without disclosing the
102
+ cause; that omitted row is the terminal polling signal. Authorized query
103
+ failures remain procedure errors.
104
+
105
+ ### Observer Access And Seats
106
+
107
+ An observe-only integration does not claim or consume a named Airlock seat.
108
+ `airlock.agent.*` and the built-in Streamlit app remain seat-gated. Moving an
109
+ existing identity to observe-only does not silently remove a seat that was
110
+ previously assigned or claimed; an administrator should explicitly call
111
+ `airlock.admin.unassign_license(...)` after confirming the identity no longer
112
+ uses those surfaces.
113
+
114
+ Specs grant read-only visibility with canonical `observer_access` config:
115
+
116
+ ```json
117
+ {
118
+ "observer_access": {
119
+ "enabled": true,
120
+ "observer_roles": [
121
+ {
122
+ "role_name": "finance_observer",
123
+ "path_filter": "finance",
124
+ "workflow_states": ["Approved"]
125
+ }
126
+ ]
127
+ }
128
+ }
129
+ ```
130
+
131
+ Grant the integration Snowflake application role `app_observer`, create or
132
+ reuse the Airlock observer role, and assign the integration's Snowflake
133
+ username to it. Use a distinct observer principal per channel so attribution,
134
+ scope, and revocation remain independent. Observe procedures return governance
135
+ metadata only, never governed payload rows or attachment bytes.
136
+
137
+ Set up an observer in four explicit steps: grant the observer principal's Snowflake role
138
+ `<APP_NAME>.app_observer`, create and assign a distinct Airlock role, publish
139
+ that role in each intended spec's `observer_access`, then verify the result with
140
+ `observe.observers(...)`. Audit assignment creation, changes, and removal with
141
+ `observe.admin_activity(..., action_family => 'assignments')`; its structured
142
+ event context includes before/after state. See [docs/workflows.md](docs/workflows.md)
143
+ for the operating checklist and billing cleanup boundary.
63
144
 
64
145
  When building an app or workflow, prefer `observe.*` for read-only setup and
65
146
  monitoring questions, `agent.*` for governed submissions in the actor's scope,
@@ -115,6 +196,19 @@ Install from npm:
115
196
  npx @reunionstudio/airlock-mcp install
116
197
  ```
117
198
 
199
+ Install the bundled Cortex Code plugin from GitHub:
200
+
201
+ ```bash
202
+ cortex plugin install reunionstudio/airlock-mcp
203
+ ```
204
+
205
+ The `.cortex-plugin/plugin.json` manifest loads the same Airlock skill and pins
206
+ the stdio MCP server to this package version. Keep Snowflake and MCP credentials
207
+ outside the manifest. When the Cortex Code CLI is available locally, validate a
208
+ checkout with `cortex plugin validate` before distributing it. Account-local
209
+ Cortex Extension sharing remains subject to Snowflake's current preview and
210
+ RBAC requirements.
211
+
118
212
  Today this package is a small installer and MCP launcher. For Codex, install
119
213
  registers a local stdio server with:
120
214
 
@@ -133,6 +227,10 @@ person through process discovery, and entering the bundled spec-building
133
227
  workbench when a first spec is ready to draft. It also guides agents building
134
228
  apps or workflows that use specs the user already has access to.
135
229
 
230
+ See [principal and workspace interoperability](docs/principal-workspace-interoperability.md)
231
+ for the identity matrix, state-polling contract, and portable commitment
232
+ receipt.
233
+
136
234
  Workspace summaries are structured spec cards. They present the current spec
137
235
  core, file rules, attachment policy, guest access, column rules, sample record
138
236
  shape, note-file status, and local check status so Codex can reflect the draft
@@ -273,8 +371,8 @@ before designing direct SQL helpers. These payloads are intended to be useful
273
371
  to agents as well as humans.
274
372
 
275
373
  For operational queues, call `agent.list_my_work` in the actor's scope. An
276
- observer may call `observe.work` for all current work; historical questions
277
- belong to `observe.activity`.
374
+ observer may call `observe.work` for current work in its assigned scope;
375
+ historical questions belong to `observe.activity`.
278
376
 
279
377
  Required source references are a governed submission contract. When a
280
378
  downstream spec has an active source link with `min_count > 0`, load the file
package/SECURITY.md CHANGED
@@ -12,8 +12,25 @@ operation surface.
12
12
  - Validates MCP server names before passing them to `codex mcp add`.
13
13
  - Passes npm or GitHub package specs as argv entries, never through a shell.
14
14
  - Rejects package specs with whitespace or control characters.
15
- - Exposes guidance only; it does not read local secrets, call Snowflake, mutate
16
- files, or contact network services.
15
+ - Does not read local secrets, call Snowflake, or contact network services.
16
+ Workbench tools write only the project files explicitly requested by the
17
+ caller.
18
+
19
+ ## Installed Native App MCP
20
+
21
+ Installed Airlock can separately expose opt-in Snowflake-managed MCP endpoints.
22
+ They are not hosted by this npm package and do not inherit local workspace
23
+ identity. Their fixed agent and observer allowlists expose only read-only access
24
+ context, work, safe spec discovery, and compact spec/file state. They exclude
25
+ administrative or governed mutation, governed payload rows, restricted-reference
26
+ reads, and attachment bytes.
27
+
28
+ Each client must authenticate as its own Snowflake principal with a role that
29
+ holds the matching Airlock application role and can use the configured
30
+ warehouse. The endpoint adapters call Airlock's existing handlers, so procedure
31
+ authorization, role lenses, observer scope, and license policy remain the
32
+ security boundary. Only fixed adapters in the role-accessible versioned `mcp`
33
+ schema are granted; Airlock's private `internal` schema remains ungranted.
17
34
 
18
35
  ## Production Rules
19
36
 
@@ -36,10 +36,23 @@ product surface.
36
36
 
37
37
  ## MCP Surface
38
38
 
39
+ There are two complementary MCP surfaces:
40
+
41
+ - This package is a local bootstrap and spec-workbench server. It does not call
42
+ Snowflake or become an Airlock authorization layer.
43
+ - Installed Airlock may expose optional Snowflake-managed Native App MCP
44
+ servers over nine fixed read-only adapters. Those endpoints execute inside
45
+ Snowflake as the authenticated principal and retain the existing `agent.*`
46
+ and `observe.*` policy checks.
47
+
48
+ See [Native App managed MCP](native-app-managed-mcp.md) for the installed
49
+ surface. The remainder of this section describes this package's local server.
50
+
39
51
  The bootstrap server exposes:
40
52
 
41
53
  - prompt: `airlock-start`
42
54
  - resource: `airlock://getting-started`
55
+ - resource: `airlock://native-app-managed-mcp`
43
56
  - orientation tool: `airlock_start`
44
57
  - workbench tools: `airlock_doctor`, `airlock_init_repo`,
45
58
  `airlock_init_app_context`, `airlock_list_patterns`,
@@ -0,0 +1,67 @@
1
+ # Native App Managed MCP
2
+
3
+ Installed Airlock can optionally expose separate Snowflake-managed MCP servers
4
+ for agent-scoped and observer-scoped reads. The stored procedures remain the
5
+ authoritative API; MCP is a small transport adapter over existing policy checks.
6
+
7
+ This is distinct from the local `airlock-mcp` package. The package helps agents
8
+ design specs and build Airlock-aware software. It does not automatically query
9
+ Snowflake. The Native App endpoints run in the consumer account and require an
10
+ independently authenticated Snowflake principal.
11
+
12
+ ## Enablement
13
+
14
+ Airlock does not create MCP objects during installation. An app administrator
15
+ must opt in and name a usable warehouse:
16
+
17
+ ```sql
18
+ CALL airlock.admin.enable_mcp_server('agent', 'AGENT_WH');
19
+ CALL airlock.admin.enable_mcp_server('observer', 'OBSERVER_WH');
20
+ CALL airlock.observe.mcp_servers();
21
+ ```
22
+
23
+ Use `validate_only => TRUE` to check the fixed plan without DDL. Disable an
24
+ endpoint with `airlock.admin.disable_mcp_server('agent')` or `'observer'`.
25
+ Disable and re-enable to change its warehouse or refresh its specification
26
+ after an Airlock upgrade.
27
+
28
+ The authenticated Snowflake role needs warehouse access and the matching
29
+ Airlock application role. Each mutating human or automated agent must retain
30
+ its own Snowflake principal; never use a shared MCP credential as a
31
+ meta-permission layer.
32
+
33
+ ## Fixed Read-Only Tools
34
+
35
+ The agent endpoint exposes:
36
+
37
+ - `airlock_my_access_context`
38
+ - `airlock_list_my_work`
39
+ - `airlock_describe_spec`
40
+ - `airlock_spec_states`
41
+ - `airlock_file_state`
42
+
43
+ The observer endpoint exposes:
44
+
45
+ - `airlock_observe_specs`
46
+ - `airlock_observe_work`
47
+ - `airlock_observe_spec_states`
48
+ - `airlock_observe_file_state`
49
+
50
+ Neither endpoint exposes mutation, `admin.*`, governed row reads,
51
+ restricted-reference reads, or attachment bytes. Agent calls retain ordinary
52
+ assignment, role-lens, and named-license checks. Observer calls retain
53
+ assignment-scoped observer access and do not claim a named license.
54
+
55
+ Snowflake's managed SQL transport requires scalar `VARIANT` results, so the
56
+ servers target fixed adapters in Airlock's versioned `mcp` schema. Those
57
+ adapters invoke the same handlers as the public read procedures. Only the fixed
58
+ adapters are granted; Airlock's private `internal` schema remains ungranted.
59
+
60
+ ## Principal Proof
61
+
62
+ Before relying on a new PAT-based client, run Airlock's
63
+ `scripts/run_batch62_mcp_identity_probe.py` with two independently issued,
64
+ role-restricted Snowflake programmatic access tokens. The proof must return two
65
+ distinct matching `USER_ID` values from `airlock_my_access_context`. External
66
+ workspace, channel, bot, or public-key identifiers are provenance only and
67
+ never confer Airlock authority.
@@ -0,0 +1,106 @@
1
+ # Principal and workspace interoperability
2
+
3
+ Airlock treats a workspace as a place for discussion, drafts, monitoring, and
4
+ automation. It does not treat workspace roles, channel membership, reactions,
5
+ or chat approvals as Snowflake authority.
6
+
7
+ ## Required identity model
8
+
9
+ - Every mutating human or automated agent connects as its own attributable
10
+ Snowflake principal.
11
+ - Direct work uses that principal's Airlock assignments.
12
+ - Work for another principal uses an explicit Airlock delegation and
13
+ `on_behalf_of_user`; pass `delegation_id` only when Airlock reports multiple
14
+ matching active grants.
15
+ - Snowflake user type and agent-session metadata are informational. They do not
16
+ create Airlock authority or change policy, workflow, expectations, or
17
+ billing.
18
+ - Stored-procedure activity exposes `event_context.snowflake_agent_active` when
19
+ Snowflake supplies the execution-context marker. Use it for observation only;
20
+ the authenticated principal remains the authoritative actor.
21
+ - A read-only channel may use a dedicated observer principal with
22
+ `<APP_NAME>.app_observer` and explicit per-spec `observer_access`. Never reuse
23
+ that principal for mutation.
24
+
25
+ ## Polling
26
+
27
+ Use state tokens before larger reads:
28
+
29
+ - `agent.spec_state(...).STATE_TOKEN` for one actor-visible spec, or
30
+ `agent.spec_states(...)` for up to 100 known specs under one role lens;
31
+ - `agent.file_state(...).FILE_STATE_ID` for one visible file's data, workflow,
32
+ attachment, reference, and retained-version state;
33
+ - `observe.spec_state`, `observe.spec_states`, and `observe.file_state` for a
34
+ scoped observer principal; and
35
+ - `agent.list_my_work` or `observe.work` on a separate cadence for deadlines
36
+ and current work.
37
+
38
+ A missing state row is the terminal result for that cursor. Return to discovery
39
+ instead of inferring whether the object was deleted, retired, or became
40
+ inaccessible.
41
+
42
+ Plural spec polling preserves first-request order after case-insensitive
43
+ deduplication and omits missing or inaccessible targets without identifying the
44
+ cause. Use it for one logical multi-spec polling cycle; keep the singular call
45
+ for one target.
46
+
47
+ ## Commitment receipts
48
+
49
+ After every supported `agent.*` or intentional `admin.*` mutation, store a
50
+ bounded receipt in the workspace event history:
51
+
52
+ ```json
53
+ {
54
+ "schema": "airlock.commitment-receipt/v1",
55
+ "procedure": "airlock.agent.load_data",
56
+ "actor": {
57
+ "snowflake_principal": "CSMITH",
58
+ "on_behalf_of": "ASMITH",
59
+ "delegation_id": null
60
+ },
61
+ "target": {
62
+ "spec_name": "reimbursements",
63
+ "path": "asmith",
64
+ "filename": "expense_2026_08_08"
65
+ },
66
+ "outcome": {
67
+ "status": "ok",
68
+ "code": "LOADED",
69
+ "message": null,
70
+ "issues": []
71
+ },
72
+ "committed_state": null,
73
+ "operation_identity": {},
74
+ "recorded_at": "2026-08-08T12:00:00Z"
75
+ }
76
+ ```
77
+
78
+ Bind `actor.snowflake_principal` to the authenticated connection identity, not
79
+ a chat display name. Preserve the procedure's stable status, code, message,
80
+ and issues. Derive `on_behalf_of` and `delegation_id` from the actual procedure
81
+ arguments and reject conflicting receipt metadata.
82
+
83
+ After a successful direct file mutation, call `agent.file_state` with the
84
+ returned spec, path, and filename to populate `committed_state`. Do not make
85
+ that direct actor state read after delegated work because `agent.file_state`
86
+ has no delegated call shape. Leave `committed_state` null unless the actor
87
+ performs a separate independently authorized observation. For attachment
88
+ mutations, retain the returned `ATTACHMENT_ID` in `operation_identity`.
89
+
90
+ A `DELEGATED_ACTION` event records the authorized delegated attempt and its
91
+ actor/principal identity. It does not by itself prove that the requested
92
+ mutation committed; use the procedure outcome and normal mutation events as
93
+ commitment evidence.
94
+
95
+ Denied mutations are receipts too. Preserve the denial code and issues, omit
96
+ committed state, and never describe workspace approval as an Airlock
97
+ commitment.
98
+
99
+ ## Cortex Code plugin
100
+
101
+ This repository includes `.cortex-plugin/plugin.json`, which loads the bundled
102
+ Airlock skill and starts the pinned Airlock MCP package over stdio. Install from
103
+ the Git repository with Cortex Code's plugin command, inspect the version, and
104
+ keep credentials outside the plugin manifest. Account-local Cortex Extension
105
+ sharing may be used where enabled; follow Snowflake's current preview and RBAC
106
+ requirements.
package/docs/workflows.md CHANGED
@@ -175,19 +175,76 @@ Use the installed Airlock procedure grammar:
175
175
  - `airlock.admin.*` is for administrative mutation and operational changes.
176
176
 
177
177
  For work discovery, use `agent.list_my_work` as the current actor's unified
178
- inbox. Observers use `observe.work` for account-wide current work and
179
- `observe.activity` for historical events. Do not recreate separate workflow
180
- and expectation inbox calls in app code.
181
-
182
- Watcher loops should call `agent.spec_state` before larger reads. Cache its
183
- authorization-scoped `STATE_TOKEN` and retrieve descriptors, file lists, or
184
- governed data only when it changes. Poll `agent.list_my_work` independently
185
- because deadlines and expectation windows can change without rotating the spec
186
- token. Once the app tracks one logical file,
178
+ inbox. Observers use `observe.work` for current work within their assignment and
179
+ spec scope, and `observe.activity` for scoped historical events. `app_admin`
180
+ and the locked Airlock role `global_observer` receive the account-wide forms.
181
+ Do not recreate separate workflow and expectation inbox calls in app code.
182
+
183
+ For an account-wide observer audit, call `observe.observers(...)`. It flattens
184
+ the current user assignment, effective observer role, spec/path/workflow scope,
185
+ and named-license posture. Treat `active_seat_observer_only` as an explicit
186
+ administrative cleanup state: observer calls are free, but a previously claimed
187
+ seat remains billable until `admin.unassign_license(...)` is called.
188
+
189
+ When asked to set up an observer principal for a channel, keep these controls
190
+ separate and apply them in order:
191
+
192
+ 1. Grant a dedicated Snowflake account role the installed app's
193
+ `<APP_NAME>.app_observer` application role.
194
+ 2. Create a distinct Airlock role with `admin.create_roles(...)`, then assign
195
+ the observer principal's Snowflake username with
196
+ `admin.create_assignments(...)`.
197
+ 3. Add that role to each intended spec's `observer_access.observer_roles`, using
198
+ `path_filter` and `workflow_states` when narrower visibility is required.
199
+ 4. As `app_admin` or `global_observer`, verify the result with
200
+ `observe.observers(...)` and audit the setup through
201
+ `observe.admin_activity(procedure_name => 'create_assignments', action_family
202
+ => 'assignments', ...)`.
203
+
204
+ Assignment create, alter, and drop activity includes structured
205
+ `EVENT_CONTEXT.before`, `EVENT_CONTEXT.after`, and
206
+ `EVENT_CONTEXT.changed_fields`. Expect `free_observer_only` when the principal
207
+ has no agent access or active seat. If it reports `active_seat_observer_only`,
208
+ confirm the principal no longer needs `agent.*` or Streamlit before calling
209
+ `admin.unassign_license(...)`. Never share one Snowflake username across
210
+ channels merely to simplify setup; that collapses attribution and revocation.
211
+
212
+ Watcher loops should call `agent.spec_state` for one known spec or
213
+ `agent.spec_states` for up to 100 known specs under one role lens before larger
214
+ reads. Cache each authorization-scoped `STATE_TOKEN` and retrieve descriptors,
215
+ file lists, or governed data only when it changes. Poll `agent.list_my_work`
216
+ independently because deadlines and expectation windows can change without
217
+ rotating a spec token. Once the app tracks one logical file,
187
218
  compare `agent.file_state(...).FILE_STATE_ID` to detect data replacement,
188
- workflow movement, attachment changes, or exact-reference changes. Observer
189
- services may use the global read-only `observe.spec_state` and
190
- `observe.file_state` equivalents.
219
+ workflow movement, attachment changes, exact-reference changes, or retained
220
+ version-history changes. Observer principals use the authorization-scoped,
221
+ read-only `observe.spec_state`, `observe.spec_states`, and `observe.file_state`
222
+ equivalents. Missing or inaccessible specs are omitted from a plural result as
223
+ the terminal polling signal; authorized query failures remain procedure errors.
224
+
225
+ Every mutating human or automated agent should connect as an attributable
226
+ Snowflake principal. When it acts for another principal, use explicit Airlock
227
+ delegation and `on_behalf_of_user`; pass `delegation_id` only when multiple
228
+ active grants match. Workspace roles, channel membership, reactions, and chat
229
+ approvals remain collaboration context, not Airlock authority.
230
+ A `DELEGATED_ACTION` event records the authorized attempt and actor/principal
231
+ identity; it does not by itself prove that the requested mutation committed.
232
+
233
+ When activity includes `EVENT_CONTEXT.snowflake_agent_active`, treat it as
234
+ informational execution provenance. It does not grant authority, prove
235
+ delegation, or change policy and billing.
236
+
237
+ After each supported `agent.*` or intentional `admin.*` mutation, store an
238
+ `airlock.commitment-receipt/v1` in workspace history. Preserve the authenticated
239
+ Snowflake actor and derive any represented principal and delegation id from the
240
+ actual call arguments. Retain the target, stable status/code/message/issues,
241
+ and timestamp. For a successful direct file mutation, follow with
242
+ `agent.file_state` and retain `UPLOAD_ID`, `FILE_STATE_ID`, workflow state,
243
+ attachment count, and reference count. Do not make that direct actor state read
244
+ after delegated work; leave `committed_state` null unless the actor performs a
245
+ separate independently authorized observation. A denied call is still a
246
+ receipt, but it has no committed state. See
247
+ `docs/principal-workspace-interoperability.md` for the complete shape.
191
248
 
192
249
  For downstream work that requires governed source evidence, use the installed
193
250
  source-reference sequence: load the downstream Draft, call
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reunionstudio/airlock-mcp",
3
- "version": "0.1.7",
3
+ "version": "0.1.9",
4
4
  "description": "Single-install MCP interface for Airlock agents.",
5
5
  "type": "module",
6
6
  "repository": {
@@ -30,6 +30,7 @@
30
30
  "src/*.mjs",
31
31
  "src/airlock_mcp/*.py",
32
32
  ".agents",
33
+ ".cortex-plugin",
33
34
  "patterns",
34
35
  "schemas",
35
36
  "workspaces",
package/setup.py CHANGED
@@ -27,7 +27,7 @@ def data_files():
27
27
 
28
28
  setup(
29
29
  name="airlock-mcp",
30
- version="0.1.7",
30
+ version="0.1.9",
31
31
  description="Codex-first workbench and CLI for drafting Airlock specs.",
32
32
  packages=find_packages("src"),
33
33
  package_dir={"": "src"},
@@ -1,3 +1,3 @@
1
1
  """Airlock MCP CLI package."""
2
2
 
3
- __version__ = "0.1.7"
3
+ __version__ = "0.1.9"
@@ -42,6 +42,14 @@ Installed Airlock separates procedure intent:
42
42
  - `agent.*`: governed agent work in the actor's scope.
43
43
  - `admin.*`: administrative mutation and operational changes.
44
44
 
45
+ Installed Airlock may separately expose opt-in Snowflake-managed MCP endpoints
46
+ for `app_agent` and `app_observer`. They use fixed read-only tool allowlists,
47
+ preserve each client's authenticated Snowflake principal, and never expose
48
+ mutation, governed payload rows, restricted-reference reads, or attachment
49
+ bytes. They are distinct from the local Airlock MCP spec workbench. Manage them
50
+ with `admin.enable_mcp_server`, `admin.disable_mcp_server`, and
51
+ `observe.mcp_servers`.
52
+
45
53
  Use `agent.list_my_work` for the actor's unified current-work inbox,
46
54
  `observe.work` for account-wide current work, and `observe.activity` for
47
55
  historical events. The older split workflow/expectation work calls are retired.
@@ -139,6 +147,12 @@ Installed Airlock procedure grammar:
139
147
  - `admin.*` is administrative mutation. Do not use retired admin read wrappers;
140
148
  use observe list/detail procedures instead.
141
149
 
150
+ An installed Airlock app may also provide separate opt-in Snowflake-managed MCP
151
+ endpoints for `app_agent` and `app_observer`. Their fixed tools are read-only,
152
+ retain the authenticated Snowflake principal, and exclude mutation and governed
153
+ payload reads. Do not confuse those endpoints with the local Airlock MCP spec
154
+ workbench or treat workspace identity as Airlock authority.
155
+
142
156
  Use `agent.list_my_work` for the current actor's unified work inbox. Use
143
157
  `observe.work` for account-wide current work and `observe.activity` for event
144
158
  history.
@@ -224,6 +238,7 @@ def _manifest(mode: str, entries: list[dict[str, Any]]) -> dict[str, Any]:
224
238
  "required_source_references": "load Draft, list eligible sources, pin exact manifest rows, then advance; SOURCE_REFERENCE_REQUIRED blocks missing evidence",
225
239
  "spec_migration": "immutable two-version lifecycle; list with observe.spec_migrations, run bounded admin migration batches, inspect observe.spec_migration evidence, cancel only before activation, and retire only after the source is drained",
226
240
  "admin": "administrative mutation and operational changes",
241
+ "native_mcp": "optional role-separated Snowflake-managed read-only endpoints; fixed agent and observer tools preserve Snowflake principal identity and never expose mutation or governed payload rows",
227
242
  "restricted_reference": "one-record reference lookup through agent.get_reference_record; do not enumerate protected reference paths",
228
243
  "attachment_preview": "governed Streamlit preview emits metadata-only ATTACHMENT_PREVIEW; MCP clients do not get direct stage access",
229
244
  "application_surface": "built-in Streamlit is a generic operating/fallback surface; purpose-built apps own domain UI and use Airlock as the governed backend",
@@ -126,6 +126,15 @@ Installed Airlock separates procedure intent:
126
126
  - `agent.*`: governed agent work in the actor's scope.
127
127
  - `admin.*`: administrative mutation and operational changes.
128
128
 
129
+ Installed Airlock may also expose separate opt-in Snowflake-managed MCP
130
+ endpoints for `app_agent` and `app_observer`. They are distinct from this local
131
+ spec-workbench MCP server and contain only fixed read-only tools for access
132
+ context, work, safe spec discovery, and compact spec/file state. They preserve
133
+ the authenticated Snowflake principal and never expose mutation, governed
134
+ payload rows, restricted-reference reads, or attachment bytes. Administrators
135
+ manage them through `admin.enable_mcp_server`, `admin.disable_mcp_server`, and
136
+ `observe.mcp_servers`.
137
+
129
138
  Use `agent.list_my_work` for the actor's unified current-work inbox,
130
139
  `observe.work` for account-wide current work, and `observe.activity` for
131
140
  historical events. The older split workflow/expectation work calls are retired.
package/src/mcp.mjs CHANGED
@@ -1,9 +1,15 @@
1
1
  import readline from "node:readline";
2
2
 
3
- import { PROTOCOL_VERSION, airlockPrompt, gettingStartedText } from "./text.mjs";
3
+ import {
4
+ PROTOCOL_VERSION,
5
+ airlockPrompt,
6
+ gettingStartedText,
7
+ nativeAppManagedMcpText,
8
+ } from "./text.mjs";
4
9
  import { WORKBENCH_TOOLS, callWorkbenchTool } from "./workbench.mjs";
5
10
 
6
11
  const GETTING_STARTED_URI = "airlock://getting-started";
12
+ const NATIVE_APP_MANAGED_MCP_URI = "airlock://native-app-managed-mcp";
7
13
 
8
14
  const START_TOOL = {
9
15
  name: "airlock_start",
@@ -44,10 +50,10 @@ export function handleMcpRequest(message) {
44
50
  },
45
51
  serverInfo: {
46
52
  name: "airlock",
47
- version: "0.1.7",
53
+ version: "0.1.9",
48
54
  },
49
55
  instructions:
50
- "Airlock MCP helps agents improve processes with Airlock specs and build apps or workflows that use existing specs. Use airlock_start for orientation or the airlock_* tools to bootstrap, draft, check, summarize, export, and render specs.",
56
+ "Airlock MCP is the local spec workbench and app-context bootstrap server. Use airlock_start for orientation or the airlock_* tools to bootstrap, draft, check, summarize, export, and render specs. Installed Airlock can separately expose opt-in Snowflake-managed read-only MCP endpoints; read airlock://native-app-managed-mcp for their fixed tools and security boundary.",
51
57
  });
52
58
  }
53
59
 
@@ -122,20 +128,30 @@ export function handleMcpRequest(message) {
122
128
  description: "How to start building and using Airlock specs with Codex.",
123
129
  mimeType: "text/markdown",
124
130
  },
131
+ {
132
+ uri: NATIVE_APP_MANAGED_MCP_URI,
133
+ name: "Installed Airlock Native App MCP",
134
+ description: "Opt-in Snowflake-managed read-only endpoints exposed by an installed Airlock Native App.",
135
+ mimeType: "text/markdown",
136
+ },
125
137
  ],
126
138
  });
127
139
  }
128
140
 
129
141
  if (method === "resources/read") {
130
- if (params?.uri !== GETTING_STARTED_URI) {
142
+ if (params?.uri !== GETTING_STARTED_URI && params?.uri !== NATIVE_APP_MANAGED_MCP_URI) {
131
143
  return makeError(id, -32602, `unknown resource: ${params?.uri || ""}`);
132
144
  }
145
+ const text =
146
+ params.uri === NATIVE_APP_MANAGED_MCP_URI
147
+ ? nativeAppManagedMcpText()
148
+ : gettingStartedText("Home");
133
149
  return makeResponse(id, {
134
150
  contents: [
135
151
  {
136
152
  uri: params.uri,
137
153
  mimeType: "text/markdown",
138
- text: gettingStartedText("Home"),
154
+ text,
139
155
  },
140
156
  ],
141
157
  });
package/src/text.mjs CHANGED
@@ -56,20 +56,61 @@ access, which specs are read sources, which spec records decisions or actions,
56
56
  and where the app should run. Teach the installed Airlock procedure split:
57
57
  \`airlock.observe.*\` is read-only governance observation,
58
58
  \`airlock.agent.*\` is governed agent work, and \`airlock.admin.*\` is
59
- administrative mutation. Prefer observe payloads such as
59
+ administrative mutation. Installed Airlock may also expose separate opt-in
60
+ Snowflake-managed MCP endpoints for the agent and observer application roles.
61
+ Those endpoints have fixed read-only tool allowlists and preserve the
62
+ authenticated Snowflake principal; they are not this local spec-workbench MCP
63
+ server. They never expose mutation, governed payload rows, restricted-reference
64
+ reads, or attachment bytes. Prefer observe payloads such as
60
65
  \`observe.procedures\`, \`observe.specs\`, \`observe.spec\`,
61
66
  \`observe.governance_map\`, \`observe.explain_access\`, \`observe.health\`,
62
67
  \`observe.activity\`, \`observe.admin_activity\`, \`observe.spec_admin_activity\`,
63
68
  and \`observe.billing_events\` before inventing custom read paths. For \`alter_spec\`
64
69
  activity, use \`CHANGED_SECTIONS\` and \`CHANGED_FIELDS\` to triage what changed
65
- before fetching version snapshots. For watcher loops, cache the role-scoped
66
- \`agent.spec_state(...).STATE_TOKEN\` and do larger reads only when it changes.
70
+ before fetching version snapshots. For polling loops, use
71
+ \`agent.spec_state(...).STATE_TOKEN\` for one known spec or
72
+ \`agent.spec_states(...)\` for up to 100 known specs under one role lens, and
73
+ do larger reads only when a token changes.
67
74
  After selecting one logical file, compare
68
75
  \`agent.file_state(...).FILE_STATE_ID\` to detect data, workflow, attachment,
69
- or exact-reference changes. Observer services may use the account-wide
70
- \`observe.spec_state\` and \`observe.file_state\` equivalents. Offer to run \`airlock-mcp init-app-context\`
76
+ exact-reference, or retained version-history changes. Observer principals may use
77
+ the authorization-scoped \`observe.spec_state\`, \`observe.spec_states\`, and
78
+ \`observe.file_state\` equivalents. Missing or inaccessible specs are omitted
79
+ from a plural result without disclosing the cause; that omitted row is the
80
+ terminal polling signal. Authorized query failures remain errors. Holding
81
+ \`app_observer\` opens only the read-only procedure family; Airlock
82
+ assignments plus \`observer_access.observer_roles\` determine spec visibility,
83
+ and account-wide observation requires \`app_admin\` or the locked Airlock role
84
+ \`global_observer\`. That reserved role cannot own specs, receive guest/reviewer
85
+ grants, or manage child roles. Observe-only calls do not consume a named seat, while
86
+ \`agent.*\` and Streamlit remain seat-gated. Use a distinct Snowflake username
87
+ per observer channel principal. For account-wide audits, call
88
+ \`observe.observers(...)\` to join observer assignment/scope with agent-access
89
+ and license posture. \`active_seat_observer_only\` means the identity remains
90
+ billable until an administrator explicitly calls \`admin.unassign_license\`;
91
+ never infer billing from role names alone. When asked to configure an observer
92
+ principal, apply four explicit controls: grant its dedicated Snowflake role
93
+ \`<APP_NAME>.app_observer\`, create and assign a distinct Airlock role, publish
94
+ that role through each spec's \`observer_access\`, then verify scope and billing
95
+ with \`observe.observers(...)\`. Audit assignment create, alter, and drop
96
+ through \`observe.admin_activity(..., action_family => 'assignments')\`; use
97
+ the structured \`EVENT_CONTEXT.before\`, \`after\`, and \`changed_fields\`
98
+ instead of parsing descriptions. Offer to run \`airlock-mcp init-app-context\`
71
99
  in the app repo to seed \`airlock/specs.manifest.json\`, spec snapshots, sample records, and
72
100
  generated helper folders. Help code the app using approved Airlock/Snowflake access paths.
101
+ Treat every mutating human or automated agent as an attributable Snowflake
102
+ principal. Direct work uses that principal's assignments; work for another
103
+ principal uses explicit Airlock delegation and \`on_behalf_of_user\`. Workspace
104
+ roles, channel membership, reactions, and chat identity do not create Airlock
105
+ authority. Treat \`EVENT_CONTEXT.snowflake_agent_active\` as informational
106
+ provenance only. A \`DELEGATED_ACTION\` event records an authorized attempt, not
107
+ proof that its mutation committed. After each supported mutation, retain an
108
+ \`airlock.commitment-receipt/v1\` with the authenticated actor, delegation
109
+ identity derived from the actual call arguments, target, stable result, and
110
+ timestamp. Hydrate successful direct file mutations with \`agent.file_state\` so
111
+ the receipt includes \`UPLOAD_ID\`, \`FILE_STATE_ID\`, and current workflow state.
112
+ Leave delegated \`committed_state\` null unless the actor performs a separate
113
+ independently authorized observation.
73
114
  Treat Airlock's built-in Streamlit app as a generic operating and fallback
74
115
  surface, not a universal domain app. When a repeated, high-value decision needs
75
116
  domain-specific summaries, calculations, evidence layout, terminology, or
@@ -131,7 +172,8 @@ Airlock MCP will offer:
131
172
  - spec design with the bundled workbench
132
173
  - Airlock operating patterns for OODA loops and separation of duties
133
174
  - read-only observe procedures for governance maps, health, access explanation, activity, billing events, and context packets
134
- - scoped spec and file state tokens for efficient watcher polling
175
+ - the optional role-separated Snowflake-managed MCP endpoints exposed by installed Airlock
176
+ - scoped spec and file state tokens for efficient polling
135
177
  - app context seeding with spec snapshots and manifests
136
178
  - app and workflow coding against existing Airlock specs
137
179
  - observe specs for controlled interface ingestion
@@ -142,6 +184,78 @@ Airlock MCP will offer:
142
184
  - airlock-specs library patterns as starting points, checked against current artifacts`;
143
185
  }
144
186
 
187
+ export function nativeAppManagedMcpText() {
188
+ return `# Installed Airlock Native App MCP
189
+
190
+ This npm package runs a local spec workbench and app-context bootstrap MCP
191
+ server. An installed Airlock Native App can separately expose two opt-in,
192
+ Snowflake-managed MCP endpoints for governed operational discovery. Enabling
193
+ those endpoints does not turn this local server into a Snowflake client.
194
+
195
+ ## Enable And Inspect
196
+
197
+ An Airlock administrator selects a warehouse and enables each interface
198
+ explicitly. Replace \`airlock\` with the installed application object name when
199
+ it differs:
200
+
201
+ \`\`\`sql
202
+ CALL airlock.admin.enable_mcp_server('agent', 'AGENT_WH', TRUE);
203
+ CALL airlock.admin.enable_mcp_server('agent', 'AGENT_WH');
204
+ CALL airlock.admin.enable_mcp_server('observer', 'OBSERVER_WH');
205
+ CALL airlock.observe.mcp_servers();
206
+ \`\`\`
207
+
208
+ The first call is validate-only. Use
209
+ \`airlock.admin.disable_mcp_server('agent')\` or \`'observer'\` to remove an
210
+ endpoint. Disable and re-enable an endpoint to change its warehouse or refresh
211
+ its fixed tool specification after an Airlock upgrade. The client principal's
212
+ Snowflake role must be able to use the selected warehouse and hold the matching
213
+ Airlock application role.
214
+
215
+ ## Fixed Tool Allowlists
216
+
217
+ The agent endpoint, \`core.airlock_agent_mcp\`, exposes only:
218
+
219
+ - \`airlock_my_access_context\`
220
+ - \`airlock_list_my_work\`
221
+ - \`airlock_describe_spec\`
222
+ - \`airlock_spec_states\`
223
+ - \`airlock_file_state\`
224
+
225
+ The observer endpoint, \`core.airlock_observer_mcp\`, exposes only:
226
+
227
+ - \`airlock_observe_specs\`
228
+ - \`airlock_observe_work\`
229
+ - \`airlock_observe_spec_states\`
230
+ - \`airlock_observe_file_state\`
231
+
232
+ Neither endpoint exposes \`admin.*\`, governed or administrative mutation,
233
+ governed payload rows, restricted-reference reads, or attachment bytes. Existing
234
+ Airlock procedure authorization remains the policy boundary. Except for the
235
+ non-claiming \`airlock_my_access_context\` tool, agent tools retain ordinary
236
+ named-license enforcement and may auto-assign a license when consumer policy
237
+ allows it. Observer tools neither require nor assign a named license.
238
+
239
+ ## Identity And Transport Boundary
240
+
241
+ Every MCP client should authenticate as its own Snowflake principal. A shared
242
+ token, workspace role, channel identity, or external agent id does not create
243
+ Airlock authority. Treat external identity metadata as provenance only; use
244
+ Airlock delegation when one authenticated principal acts for another.
245
+
246
+ The endpoint specifications target scalar \`VARIANT\` adapters in Airlock's
247
+ role-accessible, versioned \`mcp\` transport schema because Snowflake's managed
248
+ MCP SQL API consumes JSONV2 rather than Arrow table results. The adapters call
249
+ the same read-only handlers and retain the same authorization, role-lens,
250
+ observer-scope, and license checks as the public procedures. Only the fixed
251
+ adapters are granted from \`mcp\`; Airlock's private \`internal\` schema remains
252
+ ungranted.
253
+
254
+ Use \`airlock_my_access_context\` first when validating a new agent connection,
255
+ and require that independently issued credentials return their exact distinct
256
+ \`USER_ID\` values.`;
257
+ }
258
+
145
259
  export function gettingStartedText(project) {
146
260
  return `# Airlock MCP
147
261
 
@@ -165,6 +279,13 @@ Airlock MCP gives agents four kinds of Airlock help:
165
279
  context packets, and governance maps before deciding what an app or agent
166
280
  should do.
167
281
 
282
+ Installed Airlock can also expose separate opt-in, role-separated,
283
+ Snowflake-managed read-only MCP endpoints. They preserve the authenticated
284
+ Snowflake principal and use fixed agent and observer tool allowlists. They are
285
+ distinct from this local spec-workbench server. Read
286
+ \`airlock://native-app-managed-mcp\` for the exact tools, lifecycle procedures,
287
+ licensing behavior, and security boundary.
288
+
168
289
  Start in a Git-backed specs repo such as ${specsRepoName(project)}. GitHub is
169
290
  the recommended default when the user has it set up, but any normal repository
170
291
  works. If Codex is creating the repo, ask where the directory should live before
@@ -225,11 +346,41 @@ read-side discovery with observe payloads such as \`observe.procedures\`,
225
346
  \`observe.explain_access\`, \`observe.health\`, \`observe.activity\`,
226
347
  \`observe.admin_activity\`, \`observe.spec_admin_activity\`, and \`observe.billing_events\`;
227
348
  for \`alter_spec\` activity, use \`CHANGED_SECTIONS\` and \`CHANGED_FIELDS\` to
228
- triage what changed before fetching version snapshots. For watcher loops, poll
229
- \`agent.spec_state\` and compare its scoped \`STATE_TOKEN\` before larger reads;
349
+ triage what changed before fetching version snapshots. For polling loops, use
350
+ \`agent.spec_state\` for one known spec or \`agent.spec_states\` for up to 100
351
+ known specs under one role lens, and compare scoped \`STATE_TOKEN\` values before
352
+ larger reads;
230
353
  compare \`agent.file_state(...).FILE_STATE_ID\` for one selected file. Observer
231
- services use \`observe.spec_state\` and \`observe.file_state\` for account-wide
232
- read-only state. Do not use retired admin
354
+ principals use \`observe.spec_state\`, \`observe.spec_states\`, and
355
+ \`observe.file_state\` for authorization-scoped read-only state. File-state
356
+ UUIDs include retained version-history changes. Missing or inaccessible specs
357
+ are omitted from a plural result as the terminal polling signal; authorized
358
+ query failures remain errors.
359
+ \`app_observer\` grants access to the
360
+ read-only procedure family; Airlock assignments and
361
+ \`observer_access.observer_roles\` grant spec visibility. Observe-only calls do
362
+ not consume a named seat. Account-wide access requires \`app_admin\` or the
363
+ locked \`global_observer\` Airlock role, which cannot own specs, receive
364
+ guest/reviewer grants, or manage child roles. Account-wide auditors should call
365
+ \`observe.observers(...)\` for flattened observer scope and license posture;
366
+ \`active_seat_observer_only\` requires explicit \`admin.unassign_license\` to
367
+ stop future seat billing. Observer setup should use a dedicated Snowflake
368
+ username and account role, the \`<APP_NAME>.app_observer\` application role, a
369
+ distinct Airlock role/assignment, and explicit per-spec \`observer_access\`.
370
+ Verify it with \`observe.observers\` and audit structured assignment before/after
371
+ evidence with \`observe.admin_activity(..., action_family => 'assignments')\`.
372
+ Every mutating human or automated agent must keep an attributable Snowflake
373
+ principal. Use explicit Airlock delegation when it acts for someone else; never
374
+ turn workspace membership or chat approval into Airlock authority. Treat
375
+ \`EVENT_CONTEXT.snowflake_agent_active\` as informational provenance only. A
376
+ \`DELEGATED_ACTION\` event records an authorized attempt, not proof of commitment.
377
+ Store each supported mutation as an \`airlock.commitment-receipt/v1\`, preserving
378
+ the authenticated actor, delegation identity derived from the actual call
379
+ arguments, stable status/code/issues, target, and timestamp. Follow successful
380
+ direct file mutations with \`agent.file_state\` to retain \`UPLOAD_ID\`,
381
+ \`FILE_STATE_ID\`, and workflow state. Leave delegated \`committed_state\` null
382
+ unless the actor performs a separate independently authorized observation.
383
+ Do not use retired admin
233
384
  read wrappers such as \`admin.list_specs\`, \`admin.describe_role\`, or
234
385
  \`admin.list_events\`. If a reference spec declares \`restricted_reference\` or
235
386
  \`reference_config.restricted_reference\`, do not enumerate the protected