@reunionstudio/airlock-mcp 0.1.6 → 0.1.8

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.
@@ -194,6 +194,19 @@ Use three delivery modes:
194
194
  - Co-development: develop the app and specs together, keeping the contract and
195
195
  experience visible side by side.
196
196
 
197
+ Treat Airlock's built-in Streamlit Native App as a generic operating and
198
+ fallback surface. It is appropriate for administration, inspection, evidence,
199
+ workflow, and occasional manual action. It is not intended to become the best
200
+ domain application for every spec.
201
+
202
+ Recommend a purpose-built app when repeated, high-value work such as
203
+ reimbursement review, vendor onboarding, or employee-record changes benefits
204
+ from specialized summaries, calculations, terminology, evidence layout, or
205
+ controls. The app owns that experience while Airlock owns access, validation,
206
+ expectations, evidence, workflow, activity, and observable governance. Keep UI
207
+ layout and aggregation choices in app code unless they are genuinely governed
208
+ business semantics shared across interfaces.
209
+
197
210
  Start by identifying:
198
211
 
199
212
  1. The app goal: what the user needs to orient around or decide.
@@ -216,6 +229,63 @@ Airlock:
216
229
  - `airlock.admin.*` performs administrative changes and operational mutations.
217
230
  - `airlock.agent.*` performs governed user or agent work in the actor's scope.
218
231
 
232
+ Treat every mutating human or automated agent as an attributable Snowflake
233
+ principal. Direct work uses that principal's Airlock assignments. Work for
234
+ another principal requires explicit Airlock delegation and
235
+ `on_behalf_of_user`; pass `delegation_id` only when Airlock reports more than
236
+ one matching active grant. Snowflake user type and agent-session metadata are
237
+ informational, not authority. Never map workspace roles, channel membership,
238
+ reactions, or chat approval into Airlock permissions.
239
+ A `DELEGATED_ACTION` event records the authorized attempt and actor/principal
240
+ identity; the mutation result and normal file, attachment, or workflow events
241
+ provide commitment evidence.
242
+
243
+ Stored-procedure events may expose
244
+ `EVENT_CONTEXT.snowflake_agent_active` as informational provenance. Never use
245
+ that marker to grant access, infer delegation, classify billing, or replace the
246
+ authenticated Snowflake principal.
247
+
248
+ A read-only channel may use a dedicated observer principal with
249
+ `app_observer`, a distinct Airlock role/assignment, and explicit per-spec
250
+ `observer_access`. Do not reuse that observer principal for mutation, and do
251
+ not share one mutating principal across human or automated actors.
252
+
253
+ Use `agent.list_my_work` as the actor's single operational inbox for workflow,
254
+ watcher, deadline, overdue, and exception work. Use `observe.work` for the
255
+ read-only account-wide current-work projection and `observe.activity` for event
256
+ history. Do not call the retired split work procedures.
257
+
258
+ For efficient watcher loops, call `agent.spec_state` first and cache the
259
+ role-scoped `STATE_TOKEN`. Retrieve `describe_spec`, file listings, or governed
260
+ data only after it changes. Poll `agent.list_my_work` independently because
261
+ deadlines and expectation windows can change without a spec mutation. For one selected logical file, compare
262
+ `agent.file_state(...).FILE_STATE_ID`; it rotates for data replacement,
263
+ workflow, attachment, and exact-reference changes. Use `observe.spec_state` and
264
+ `observe.file_state` only with observer/admin authority for account-wide state.
265
+ Do not substitute global observer tokens for an agent's scoped token.
266
+
267
+ After every supported `agent.*` or intentional `admin.*` mutation, preserve a
268
+ bounded `airlock.commitment-receipt/v1` in the external workspace history.
269
+ Record the procedure and authenticated Snowflake principal; derive any
270
+ represented principal and delegation id from the actual procedure arguments.
271
+ Retain the target spec/path/filename, returned status/code/message and issues,
272
+ and timestamp. After a successful direct file mutation, call `agent.file_state`
273
+ with the returned target and retain its `UPLOAD_ID`, `FILE_STATE_ID`, workflow
274
+ state, attachment count, and reference count. Do not make that direct actor
275
+ state read after delegated work; leave `committed_state` null unless the actor
276
+ performs a separate independently authorized observation. Keep a denied
277
+ mutation as a receipt with no committed state. Never use a chat display name as
278
+ the Snowflake actor or describe workspace approval as an Airlock commitment.
279
+
280
+ Required source references are exact governed evidence, not filename fields.
281
+ When an active downstream-to-source link has `min_count > 0`, load the
282
+ downstream file into Draft, discover eligible sources with
283
+ `agent.list_eligible_source_files`, pin exact manifest rows with
284
+ `agent.add_file_reference`, and then call
285
+ `agent.edit_file_workflow(action => 'advance')`. Branch on
286
+ `SOURCE_REFERENCE_REQUIRED` and `SOURCE_REFERENCE_CHECK_FAILED`; do not bypass
287
+ the gate by copying an identifier into submitted business data.
288
+
219
289
  For app-first work, prefer `observe.procedures`, `observe.specs`,
220
290
  `observe.spec`, `observe.governance_map`, `observe.explain_access`,
221
291
  `observe.health`, `observe.activity`, `observe.admin_activity`,
@@ -227,6 +297,36 @@ as `admin.list_specs`, `admin.describe_role`, `admin.get_spec`, or
227
297
  Use `admin.*` only when the app is intentionally changing Airlock setup or
228
298
  running an admin operation.
229
299
 
300
+ For a structural spec change with active files, treat
301
+ `SPEC_MIGRATION_REQUIRED` as a governed lifecycle, not an error to bypass. Use
302
+ `admin.create_spec_revision`, `admin.create_spec_migration`,
303
+ `admin.validate_spec_migration`, `admin.approve_spec_migration`,
304
+ `admin.activate_spec_migration`, bounded `admin.run_spec_migration` calls,
305
+ `observe.spec_migrations`, `observe.spec_migration`, and
306
+ `admin.retire_spec_migration`. Before activation, an abandoned `draft`,
307
+ `planned`, `validated`, or `approved` migration may be released with
308
+ `admin.cancel_spec_migration`; never describe cancellation as rollback after
309
+ activation. Preserve the full
310
+ proposed config, use only the bounded declarative transform over immutable
311
+ `column_id` values, and surface stable status, issue, progress, and lineage
312
+ fields. Never invent direct SQL/Python migration escape hatches.
313
+
314
+ If activation returns `SPEC_MIGRATION_ACTIVATED_WITH_REPAIR_REQUIRED`, the
315
+ target is already active. Repair the reported target view or materialized
316
+ table, call `admin.rebuild_access_index(TRUE)`, and re-read
317
+ `observe.spec_migration`; do not retry activation. Airlock intentionally keeps
318
+ stale surfaces unavailable and compiled agent access invalidated until repair.
319
+
320
+ The migration runner claims bounded work before stage or manifest effects.
321
+ Treat header renames as transformed successors, never zero-copy. For sequential
322
+ migrations, effective source currency comes from append-only valid
323
+ attestations. Re-attest an upgraded active file with unknown provenance by
324
+ loading its existing staged path; Airlock validates its exact digest and updates
325
+ the manifest in place. `source_preserved` keeps transformed source bytes out of
326
+ ordinary retention purge. `SPEC_MIGRATION_ACTIVE` blocks a second data-contract
327
+ revision until retirement but still permits metadata and governance repairs on
328
+ the current data version.
329
+
230
330
  Restricted references are one-record interaction contracts for read-only
231
331
  reference specs. If `agent.describe_spec`, `observe.spec`,
232
332
  `observe.spec_config`, or `observe.reference_context` shows
@@ -328,6 +428,8 @@ human decisions remain.
328
428
 
329
429
  - Local checks are not Airlock authority.
330
430
  - Installed Airlock procedures remain the execution contract.
431
+ - Every mutating actor keeps an attributable Snowflake principal; shared
432
+ integration identities and workspace roles are not authorization shortcuts.
331
433
  - Do not write directly to Airlock-owned tables, stages, generated views, or
332
434
  generated tables.
333
435
  - Do not put Airlock workflow state, reviewer comments, approval status, or
@@ -0,0 +1,14 @@
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
+ Never store credentials in this plugin or its workspace files. Configure the
14
+ 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.8",
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.8",
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,12 +54,124 @@ Current Airlock separates procedure intent:
44
54
  specs, validating/loading data, workflow actions, attachments, delegations,
45
55
  and references.
46
56
 
57
+ Every mutating human or automated agent should connect as its own attributable
58
+ Snowflake principal. When it acts for another principal, use explicit Airlock
59
+ delegation. Snowflake user type, chat identity, channel membership, workspace
60
+ roles, and reactions do not create Airlock authority. A read-only channel may
61
+ use a distinct scoped observer principal, but that principal must not be reused
62
+ for mutation.
63
+
64
+ Stored-procedure activity includes the informational
65
+ `EVENT_CONTEXT.snowflake_agent_active` marker when Snowflake exposes it. The
66
+ marker identifies execution context for observation; it never changes Airlock
67
+ access, workflow, expectations, billing, or receipt identity.
68
+ A `DELEGATED_ACTION` event records an authorized attempt, not proof that its
69
+ mutation committed. Commitment receipts derive delegation identity from the
70
+ actual call arguments, and only successful direct file mutations are hydrated
71
+ through `agent.file_state`; delegated work leaves `committed_state` null unless
72
+ the actor performs a separate independently authorized observation.
73
+
74
+ Use `airlock.agent.list_my_work(...)` for the current actor's unified workflow,
75
+ read-only observation, deadline, overdue, and exception inbox. Use
76
+ `airlock.observe.work(...)` for the read-only assignment-scoped projection of
77
+ current work, and
78
+ `airlock.observe.activity(...)` for historical events. The older split work
79
+ procedures are retired.
80
+
81
+ Polling agents should call `airlock.agent.spec_state(...)` before retrieving a
82
+ complete descriptor, file list, or governed dataset. Cache the
83
+ authorization-scoped `STATE_TOKEN` and perform the heavier read only when it
84
+ changes. Poll `airlock.agent.list_my_work(...)` independently because deadlines
85
+ and expectation windows can change with time without rotating a spec token.
86
+ After selecting a logical file, compare
87
+ `airlock.agent.file_state(...).FILE_STATE_ID`; that UUID rotates when its data,
88
+ workflow, attachments, exact source references, or retained version history
89
+ change. Observer clients use
90
+ the authorization-scoped, read-only `observe.spec_state(...)` and
91
+ `observe.file_state(...)` equivalents. A missing object or revoked observer
92
+ grant returns no row, which is the terminal polling signal; authorized query
93
+ failures remain procedure errors.
94
+
95
+ ### Observer Access And Seats
96
+
97
+ An observe-only integration does not claim or consume a named Airlock seat.
98
+ `airlock.agent.*` and the built-in Streamlit app remain seat-gated. Moving an
99
+ existing identity to observe-only does not silently remove a seat that was
100
+ previously assigned or claimed; an administrator should explicitly call
101
+ `airlock.admin.unassign_license(...)` after confirming the identity no longer
102
+ uses those surfaces.
103
+
104
+ Specs grant read-only visibility with canonical `observer_access` config:
105
+
106
+ ```json
107
+ {
108
+ "observer_access": {
109
+ "enabled": true,
110
+ "observer_roles": [
111
+ {
112
+ "role_name": "finance_observer",
113
+ "path_filter": "finance",
114
+ "workflow_states": ["Approved"]
115
+ }
116
+ ]
117
+ }
118
+ }
119
+ ```
120
+
121
+ Grant the integration Snowflake application role `app_observer`, create or
122
+ reuse the Airlock observer role, and assign the integration's Snowflake
123
+ username to it. Use a distinct observer principal per channel so attribution,
124
+ scope, and revocation remain independent. Observe procedures return governance
125
+ metadata only, never governed payload rows or attachment bytes.
126
+
127
+ Set up an observer in four explicit steps: grant the observer principal's Snowflake role
128
+ `<APP_NAME>.app_observer`, create and assign a distinct Airlock role, publish
129
+ that role in each intended spec's `observer_access`, then verify the result with
130
+ `observe.observers(...)`. Audit assignment creation, changes, and removal with
131
+ `observe.admin_activity(..., action_family => 'assignments')`; its structured
132
+ event context includes before/after state. See [docs/workflows.md](docs/workflows.md)
133
+ for the operating checklist and billing cleanup boundary.
134
+
47
135
  When building an app or workflow, prefer `observe.*` for read-only setup and
48
136
  monitoring questions, `agent.*` for governed submissions in the actor's scope,
49
137
  and `admin.*` only for intentional administrative mutation. Do not use retired
50
138
  admin read wrappers such as `admin.list_specs`, `admin.describe_role`, or
51
139
  `admin.list_events`; use the matching observe procedures instead.
52
140
 
141
+ Structural spec changes with active data use Airlock's governed migration
142
+ lifecycle. Agents should preserve the proposed config and guide an
143
+ administrator through immutable revision, declarative migration planning,
144
+ all-row proof, approval, activation, bounded execution, observable lineage, and
145
+ source retirement. `SPEC_MIGRATION_REQUIRED` is a safety boundary; it is not a
146
+ reason to mutate Airlock-owned storage directly.
147
+
148
+ Migration execution claims bounded work before storage effects. Renamed headers
149
+ produce transformed successors, sequential migrations rely on append-only
150
+ validation attestations, and upgraded files with unknown provenance are
151
+ re-attested in place through normal `load_data`. `source_preserved` protects
152
+ transformed source bytes from ordinary retention purge, while
153
+ `SPEC_MIGRATION_ACTIVE` prevents another data-contract revision until the
154
+ current migration retires.
155
+
156
+ ## Built-In UI And Purpose-Built Apps
157
+
158
+ Airlock's Streamlit Native App is deliberately a generic operating and fallback
159
+ surface. It supports administration, inspection, governed data and attachment
160
+ review, workflow visibility, and safe manual action. It should not be treated as
161
+ a universal domain application builder.
162
+
163
+ Repeated, high-value decisions such as reimbursements, vendor onboarding, or
164
+ employee-record changes may deserve purpose-built interfaces. Airlock MCP should
165
+ recommend app-first or co-development for those cases and help build the custom
166
+ surface against Airlock contracts. The app owns domain summaries,
167
+ calculations, terminology, and interaction design; Airlock continues to own
168
+ access, validation, expectations, evidence, workflow, activity, and observable
169
+ governance.
170
+
171
+ Do not add UI layout or aggregation fields to a spec merely to make the generic
172
+ Native App imitate custom software. Presentation belongs in app code unless it
173
+ is genuinely governed business semantics shared across interfaces.
174
+
53
175
  ## Install
54
176
 
55
177
  Dogfood directly from GitHub:
@@ -64,6 +186,19 @@ Install from npm:
64
186
  npx @reunionstudio/airlock-mcp install
65
187
  ```
66
188
 
189
+ Install the bundled Cortex Code plugin from GitHub:
190
+
191
+ ```bash
192
+ cortex plugin install reunionstudio/airlock-mcp
193
+ ```
194
+
195
+ The `.cortex-plugin/plugin.json` manifest loads the same Airlock skill and pins
196
+ the stdio MCP server to this package version. Keep Snowflake and MCP credentials
197
+ outside the manifest. When the Cortex Code CLI is available locally, validate a
198
+ checkout with `cortex plugin validate` before distributing it. Account-local
199
+ Cortex Extension sharing remains subject to Snowflake's current preview and
200
+ RBAC requirements.
201
+
67
202
  Today this package is a small installer and MCP launcher. For Codex, install
68
203
  registers a local stdio server with:
69
204
 
@@ -82,6 +217,10 @@ person through process discovery, and entering the bundled spec-building
82
217
  workbench when a first spec is ready to draft. It also guides agents building
83
218
  apps or workflows that use specs the user already has access to.
84
219
 
220
+ See [principal and workspace interoperability](docs/principal-workspace-interoperability.md)
221
+ for the identity matrix, state-polling contract, and portable commitment
222
+ receipt.
223
+
85
224
  Workspace summaries are structured spec cards. They present the current spec
86
225
  core, file rules, attachment policy, guest access, column rules, sample record
87
226
  shape, note-file status, and local check status so Codex can reflect the draft
@@ -221,6 +360,31 @@ For app-first work against installed Airlock, start with `observe.procedures`,
221
360
  before designing direct SQL helpers. These payloads are intended to be useful
222
361
  to agents as well as humans.
223
362
 
363
+ For operational queues, call `agent.list_my_work` in the actor's scope. An
364
+ observer may call `observe.work` for current work in its assigned scope;
365
+ historical questions belong to `observe.activity`.
366
+
367
+ Required source references are a governed submission contract. When a
368
+ downstream spec has an active source link with `min_count > 0`, load the file
369
+ into Draft, discover eligible evidence with `agent.list_eligible_source_files`,
370
+ pin exact manifest rows with `agent.add_file_reference`, and only then call
371
+ `agent.edit_file_workflow(action => 'advance')`. Missing, removed, or
372
+ wrong-workflow-state evidence returns `SOURCE_REFERENCE_REQUIRED` without
373
+ moving the file. Do not replace the exact Airlock reference with a filename or
374
+ identifier copied into business data.
375
+
376
+ Structural spec changes with active files use Airlock's governed two-version
377
+ migration lifecycle. Treat `SPEC_MIGRATION_REQUIRED` as a request to create an
378
+ immutable revision and migration, validate and approve it, activate the target,
379
+ drain bounded batches with `admin.run_spec_migration`, inspect progress and
380
+ lineage through `observe.spec_migration`, and retire the source only after the
381
+ evidence gate passes. Use `observe.spec_migrations` to discover and filter
382
+ migrations. Before activation, an abandoned `draft`, `planned`, `validated`, or
383
+ `approved` migration may be released with `admin.cancel_spec_migration`;
384
+ cancellation is not rollback after activation. The transform language is intentionally limited to
385
+ mechanical mappings, defaults, and safe casts; semantic transforms belong in a
386
+ purpose-built process that reloads through normal Airlock validation.
387
+
224
388
  Restricted references are one-record interaction contracts. When
225
389
  `observe.reference_context`, `observe.spec_config`, or `agent.describe_spec`
226
390
  shows `restricted_reference` or `reference_config.restricted_reference`, agents
@@ -46,6 +46,15 @@ access uses Snowflake download links when available. Successful PDF page
46
46
  previews emit metadata-only `ATTACHMENT_PREVIEW` activity. This UI capability
47
47
  does not grant MCP clients direct stage access.
48
48
 
49
+ The Native App is deliberately a generic operating and fallback surface. It
50
+ should support administration, inspection, evidence, workflow, and safe manual
51
+ action without accumulating every domain's presentation rules. Airlock MCP
52
+ should recommend app-first or co-development when repeated, high-value work
53
+ deserves a purpose-built interface. The custom app owns domain summaries,
54
+ calculations, terminology, and controls while Airlock remains the governed
55
+ backend. UI layout and aggregation hints should not be added to specs merely to
56
+ polish the generic app.
57
+
49
58
  The connector package and MCP server live in `reunionstudio/airlock-mcp`.
50
59
  This workbench provides the spec-building implementation inside that one
51
60
  installed Airlock MCP experience.
@@ -0,0 +1,101 @@
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 the actor's visible contract, data,
30
+ and access state;
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` and `observe.file_state` for a scoped observer principal;
34
+ 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
+ ## Commitment receipts
43
+
44
+ After every supported `agent.*` or intentional `admin.*` mutation, store a
45
+ bounded receipt in the workspace event history:
46
+
47
+ ```json
48
+ {
49
+ "schema": "airlock.commitment-receipt/v1",
50
+ "procedure": "airlock.agent.load_data",
51
+ "actor": {
52
+ "snowflake_principal": "CSMITH",
53
+ "on_behalf_of": "ASMITH",
54
+ "delegation_id": null
55
+ },
56
+ "target": {
57
+ "spec_name": "reimbursements",
58
+ "path": "asmith",
59
+ "filename": "expense_2026_08_08"
60
+ },
61
+ "outcome": {
62
+ "status": "ok",
63
+ "code": "LOADED",
64
+ "message": null,
65
+ "issues": []
66
+ },
67
+ "committed_state": null,
68
+ "operation_identity": {},
69
+ "recorded_at": "2026-08-08T12:00:00Z"
70
+ }
71
+ ```
72
+
73
+ Bind `actor.snowflake_principal` to the authenticated connection identity, not
74
+ a chat display name. Preserve the procedure's stable status, code, message,
75
+ and issues. Derive `on_behalf_of` and `delegation_id` from the actual procedure
76
+ arguments and reject conflicting receipt metadata.
77
+
78
+ After a successful direct file mutation, call `agent.file_state` with the
79
+ returned spec, path, and filename to populate `committed_state`. Do not make
80
+ that direct actor state read after delegated work because `agent.file_state`
81
+ has no delegated call shape. Leave `committed_state` null unless the actor
82
+ performs a separate independently authorized observation. For attachment
83
+ mutations, retain the returned `ATTACHMENT_ID` in `operation_identity`.
84
+
85
+ A `DELEGATED_ACTION` event records the authorized delegated attempt and its
86
+ actor/principal identity. It does not by itself prove that the requested
87
+ mutation committed; use the procedure outcome and normal mutation events as
88
+ commitment evidence.
89
+
90
+ Denied mutations are receipts too. Preserve the denial code and issues, omit
91
+ committed state, and never describe workspace approval as an Airlock
92
+ commitment.
93
+
94
+ ## Cortex Code plugin
95
+
96
+ This repository includes `.cortex-plugin/plugin.json`, which loads the bundled
97
+ Airlock skill and starts the pinned Airlock MCP package over stdio. Install from
98
+ the Git repository with Cortex Code's plugin command, inspect the version, and
99
+ keep credentials outside the plugin manifest. Account-local Cortex Extension
100
+ sharing may be used where enabled; follow Snowflake's current preview and RBAC
101
+ requirements.
package/docs/workflows.md CHANGED
@@ -154,6 +154,17 @@ unless the user asks to change specs. Start by asking for:
154
154
  - the available Airlock/Snowflake access path
155
155
  - identity, evidence, timestamp, approval, and separation-of-duties needs
156
156
 
157
+ Airlock's built-in Streamlit Native App is a generic operating and fallback
158
+ surface, not a universal domain application builder. Use it for administration,
159
+ inspection, governed evidence, workflow, and occasional manual action. For
160
+ repeated high-value decisions, prefer a purpose-built app when domain-specific
161
+ summaries, calculations, evidence layout, terminology, or controls materially
162
+ improve the work.
163
+
164
+ Keep those presentation choices in app code. Do not add layout or aggregation
165
+ fields to an Airlock spec merely to make the generic UI imitate custom
166
+ software. Airlock remains the governed backend for either surface.
167
+
157
168
  Use the installed Airlock procedure grammar:
158
169
 
159
170
  - `airlock.observe.*` is read-only and should answer discovery, governance,
@@ -163,6 +174,125 @@ Use the installed Airlock procedure grammar:
163
174
  delegations.
164
175
  - `airlock.admin.*` is for administrative mutation and operational changes.
165
176
 
177
+ For work discovery, use `agent.list_my_work` as the current actor's unified
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` before larger reads. Cache its
213
+ authorization-scoped `STATE_TOKEN` and retrieve descriptors, file lists, or
214
+ governed data only when it changes. Poll `agent.list_my_work` independently
215
+ because deadlines and expectation windows can change without rotating the spec
216
+ token. Once the app tracks one logical file,
217
+ compare `agent.file_state(...).FILE_STATE_ID` to detect data replacement,
218
+ workflow movement, attachment changes, exact-reference changes, or retained
219
+ version-history changes. Observer principals use the authorization-scoped,
220
+ read-only `observe.spec_state` and `observe.file_state` equivalents. A missing
221
+ object or revoked grant returns no row as the terminal polling signal;
222
+ authorized query failures remain procedure errors.
223
+
224
+ Every mutating human or automated agent should connect as an attributable
225
+ Snowflake principal. When it acts for another principal, use explicit Airlock
226
+ delegation and `on_behalf_of_user`; pass `delegation_id` only when multiple
227
+ active grants match. Workspace roles, channel membership, reactions, and chat
228
+ approvals remain collaboration context, not Airlock authority.
229
+ A `DELEGATED_ACTION` event records the authorized attempt and actor/principal
230
+ identity; it does not by itself prove that the requested mutation committed.
231
+
232
+ When activity includes `EVENT_CONTEXT.snowflake_agent_active`, treat it as
233
+ informational execution provenance. It does not grant authority, prove
234
+ delegation, or change policy and billing.
235
+
236
+ After each supported `agent.*` or intentional `admin.*` mutation, store an
237
+ `airlock.commitment-receipt/v1` in workspace history. Preserve the authenticated
238
+ Snowflake actor and derive any represented principal and delegation id from the
239
+ actual call arguments. Retain the target, stable status/code/message/issues,
240
+ and timestamp. For a successful direct file mutation, follow with
241
+ `agent.file_state` and retain `UPLOAD_ID`, `FILE_STATE_ID`, workflow state,
242
+ attachment count, and reference count. Do not make that direct actor state read
243
+ after delegated work; leave `committed_state` null unless the actor performs a
244
+ separate independently authorized observation. A denied call is still a
245
+ receipt, but it has no committed state. See
246
+ `docs/principal-workspace-interoperability.md` for the complete shape.
247
+
248
+ For downstream work that requires governed source evidence, use the installed
249
+ source-reference sequence: load the downstream Draft, call
250
+ `agent.list_eligible_source_files`, pin exact source manifest rows with
251
+ `agent.add_file_reference`, then advance workflow. Active source links with
252
+ `min_count > 0` are enforced by `agent.edit_file_workflow`; required sources
253
+ must still exist, remain unremoved, and match any configured
254
+ `required_workflow_status`. Branch on `SOURCE_REFERENCE_REQUIRED` or
255
+ `SOURCE_REFERENCE_CHECK_FAILED` and show the returned `ISSUES` instead of
256
+ guessing at alternate source identifiers.
257
+
258
+ When an app or administrator changes a spec's data contract, do not work around
259
+ `SPEC_MIGRATION_REQUIRED` with direct table, stage, or view changes. Use the
260
+ installed governed lifecycle: `admin.create_spec_revision`,
261
+ `admin.create_spec_migration`, `admin.validate_spec_migration`,
262
+ `admin.approve_spec_migration`, `admin.activate_spec_migration`, bounded calls
263
+ to `admin.run_spec_migration`, inspection through
264
+ `observe.spec_migrations` and `observe.spec_migration`, and finally
265
+ `admin.retire_spec_migration`. Before activation, use
266
+ `admin.cancel_spec_migration` to release an abandoned `draft`, `planned`,
267
+ `validated`, or `approved` migration. Cancellation is not rollback after
268
+ activation. The
269
+ transform is declarative and keyed by immutable `column_id` values; arbitrary
270
+ SQL or Python is not part of the contract. Approval is intent, activation is
271
+ the write-version switch, and retirement is a separate guarded action.
272
+
273
+ Treat `SPEC_MIGRATION_ACTIVATED_WITH_REPAIR_REQUIRED` as a committed activation,
274
+ not a retryable pre-activation failure. Follow the returned issues to rebuild
275
+ the target view or materialized table, then call
276
+ `admin.rebuild_access_index(TRUE)` and re-read `observe.spec_migration`. Airlock
277
+ keeps stale read surfaces unavailable and compiled agent access invalidated
278
+ until repair succeeds.
279
+
280
+ `admin.run_spec_migration` claims each bounded item batch before stage or
281
+ manifest side effects. A header rename always creates a transformed successor;
282
+ zero-copy is reserved for stored headers and values that already satisfy the
283
+ target contract. Use append-only valid attestations, not only the manifest's
284
+ original version pointer, when a file moves through sequential migrations.
285
+
286
+ An upgraded active file may have `validation_provenance_status = 'unknown'`.
287
+ Re-load its existing staged path through `load_data`: after validation and an
288
+ exact digest recheck, Airlock re-attests the existing manifest in place instead
289
+ of creating a duplicate stage-path owner. A `source_preserved` migration keeps
290
+ transformed source bytes outside ordinary retention purge; `forward_only`
291
+ allows normal retention. While a migration is active,
292
+ `SPEC_MIGRATION_ACTIVE` blocks another data-contract revision until retirement,
293
+ although metadata, access, policy, expectation, and workflow repairs remain
294
+ allowed on the current data version.
295
+
166
296
  For app-first work, start with observe payloads such as `observe.procedures`,
167
297
  `observe.specs`, `observe.spec`, `observe.governance_map`,
168
298
  `observe.explain_access`, `observe.health`, `observe.activity`,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reunionstudio/airlock-mcp",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
4
4
  "description": "Single-install MCP interface for Airlock agents.",
5
5
  "type": "module",
6
6
  "repository": {
@@ -27,8 +27,10 @@
27
27
  "files": [
28
28
  "bin",
29
29
  "docs",
30
- "src",
30
+ "src/*.mjs",
31
+ "src/airlock_mcp/*.py",
31
32
  ".agents",
33
+ ".cortex-plugin",
32
34
  "patterns",
33
35
  "schemas",
34
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.6",
30
+ version="0.1.8",
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.6"
3
+ __version__ = "0.1.8"
@@ -20,6 +20,12 @@ The canonical specs live in the specs repo or installed Airlock. Files here are
20
20
  snapshots, samples, generated helpers, and planning context for app development.
21
21
  Refresh them when the canonical spec changes.
22
22
 
23
+ Airlock's built-in Streamlit Native App is a generic operating and fallback
24
+ surface. A purpose-built app is the preferred place for domain-specific
25
+ summaries, calculations, evidence layout, terminology, and controls when those
26
+ features materially improve repeated, high-value work. Keep presentation in
27
+ app code rather than adding UI layout or aggregation hints to Airlock specs.
28
+
23
29
  Use:
24
30
 
25
31
  - `specs.manifest.json` to track which specs the app reads from or writes to.
@@ -36,6 +42,28 @@ Installed Airlock separates procedure intent:
36
42
  - `agent.*`: governed agent work in the actor's scope.
37
43
  - `admin.*`: administrative mutation and operational changes.
38
44
 
45
+ Use `agent.list_my_work` for the actor's unified current-work inbox,
46
+ `observe.work` for account-wide current work, and `observe.activity` for
47
+ historical events. The older split workflow/expectation work calls are retired.
48
+
49
+ Required source references are exact governed evidence. For an active source
50
+ link with `min_count > 0`, load the downstream Draft, discover eligible files
51
+ with `agent.list_eligible_source_files`, pin exact manifest rows with
52
+ `agent.add_file_reference`, and then advance workflow. Missing or invalid
53
+ evidence returns `SOURCE_REFERENCE_REQUIRED` without moving the file.
54
+
55
+ Structural spec changes with active files use a governed two-version migration
56
+ lifecycle. Treat `SPEC_MIGRATION_REQUIRED` as a request to create an immutable
57
+ revision and migration, validate and approve it, activate the target, run
58
+ bounded migration batches, list work with `observe.spec_migrations`, inspect
59
+ evidence with `observe.spec_migration`, and retire the source only after Airlock
60
+ reports that it is drained. Before activation, an abandoned `draft`, `planned`,
61
+ `validated`, or `approved` migration may be cancelled with
62
+ `admin.cancel_spec_migration`; cancellation is not rollback. Use only the
63
+ declarative mechanical transform keyed by immutable `column_id`; semantic
64
+ transforms belong in a purpose-built process that reloads through normal
65
+ Airlock validation.
66
+
39
67
  Prefer `observe.*` for app read-side setup and monitoring questions. Use
40
68
  `observe.admin_activity` for broad admin mutation and maintenance audit
41
69
  questions, and `observe.spec_admin_activity` for one spec's definition-change
@@ -72,6 +100,13 @@ APP_AGENTS = """# Airlock App Guidance
72
100
 
73
101
  This repo may contain application code that uses Airlock specs.
74
102
 
103
+ Treat the built-in Streamlit Native App as a generic operating and fallback
104
+ surface. Build a purpose-built interface when domain-specific presentation
105
+ materially improves repeated, high-value work. The custom app owns the
106
+ experience; Airlock remains the governed backend for access, validation,
107
+ expectations, evidence, workflow, activity, and observability. Do not add UI
108
+ layout or aggregation fields to specs solely for the built-in app.
109
+
75
110
  ## Modes
76
111
 
77
112
  - Spec-first: design governed specs before building the app surface.
@@ -104,6 +139,18 @@ Installed Airlock procedure grammar:
104
139
  - `admin.*` is administrative mutation. Do not use retired admin read wrappers;
105
140
  use observe list/detail procedures instead.
106
141
 
142
+ Use `agent.list_my_work` for the current actor's unified work inbox. Use
143
+ `observe.work` for account-wide current work and `observe.activity` for event
144
+ history.
145
+
146
+ For structural spec changes with active files, treat
147
+ `SPEC_MIGRATION_REQUIRED` as the start of Airlock's governed two-version
148
+ lifecycle. Use immutable revisions, bounded `admin.run_spec_migration` calls,
149
+ `observe.spec_migrations` discovery, `observe.spec_migration` evidence, and
150
+ guarded source retirement. Before activation, `admin.cancel_spec_migration` may
151
+ release an abandoned migration; it is not a post-activation rollback. Do not bypass
152
+ the lifecycle with direct stage, table, or view changes.
153
+
107
154
  For restricted reference specs, do not enumerate protected object paths or use
108
155
  broad `agent.select_reference_data`. Use `agent.get_reference_record` for a
109
156
  known lookup value and purpose, and use `observe.reference_context`,
@@ -173,9 +220,13 @@ def _manifest(mode: str, entries: list[dict[str, Any]]) -> dict[str, Any]:
173
220
  "installed_airlock_contract": {
174
221
  "observe": "read-only governance observation, context, activity, health, billing events, and access explanation",
175
222
  "agent": "governed agent work in the actor scope",
223
+ "work": "agent.list_my_work is the actor inbox; observe.work is account-wide current work; observe.activity is history",
224
+ "required_source_references": "load Draft, list eligible sources, pin exact manifest rows, then advance; SOURCE_REFERENCE_REQUIRED blocks missing evidence",
225
+ "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",
176
226
  "admin": "administrative mutation and operational changes",
177
227
  "restricted_reference": "one-record reference lookup through agent.get_reference_record; do not enumerate protected reference paths",
178
228
  "attachment_preview": "governed Streamlit preview emits metadata-only ATTACHMENT_PREVIEW; MCP clients do not get direct stage access",
229
+ "application_surface": "built-in Streamlit is a generic operating/fallback surface; purpose-built apps own domain UI and use Airlock as the governed backend",
179
230
  },
180
231
  "specs": entries,
181
232
  "tracks": {
@@ -99,6 +99,14 @@ Airlock context. It creates `airlock/specs.manifest.json`,
99
99
  folders. These files are app-local references, not canonical specs. Canonical
100
100
  specs live in the specs repo or installed Airlock.
101
101
 
102
+ Treat Airlock's built-in Streamlit Native App as a generic operating and
103
+ fallback surface. It supports administration, inspection, evidence, workflow,
104
+ and safe manual action; it is not a universal domain application builder.
105
+ Recommend a purpose-built app when repeated, high-value work benefits from
106
+ domain-specific summaries, calculations, evidence layout, terminology, or
107
+ controls. Keep those presentation choices in app code. Do not add UI layout or
108
+ aggregation fields to a spec merely to improve the generic app.
109
+
102
110
  Identify:
103
111
 
104
112
  - the app goal and decision the app should support
@@ -118,6 +126,28 @@ Installed Airlock separates procedure intent:
118
126
  - `agent.*`: governed agent work in the actor's scope.
119
127
  - `admin.*`: administrative mutation and operational changes.
120
128
 
129
+ Use `agent.list_my_work` for the actor's unified current-work inbox,
130
+ `observe.work` for account-wide current work, and `observe.activity` for
131
+ historical events. The older split workflow/expectation work calls are retired.
132
+
133
+ For active source links with `min_count > 0`, load the downstream file into
134
+ Draft, discover eligible evidence with `agent.list_eligible_source_files`, pin
135
+ exact manifest rows with `agent.add_file_reference`, and then advance workflow.
136
+ Missing, removed, or wrong-state evidence returns `SOURCE_REFERENCE_REQUIRED`
137
+ without moving the file.
138
+
139
+ For a structural spec change with active files, treat
140
+ `SPEC_MIGRATION_REQUIRED` as a governed two-version lifecycle. Create the
141
+ immutable revision and migration, validate and approve it, activate the target,
142
+ run bounded `admin.run_spec_migration` batches, inspect progress and lineage
143
+ with `observe.spec_migration`, and retire the source only after it is drained.
144
+ Use `observe.spec_migrations` to discover lifecycle work. Before activation,
145
+ `admin.cancel_spec_migration` may release an abandoned `draft`, `planned`,
146
+ `validated`, or `approved` migration; it is not rollback after activation. Use
147
+ the bounded declarative transform for mechanical changes; semantic
148
+ transforms belong in a purpose-built process that reloads through normal
149
+ Airlock validation.
150
+
121
151
  For app-first work, use observe payloads such as `observe.procedures`,
122
152
  `observe.specs`, `observe.spec`, `observe.governance_map`,
123
153
  `observe.explain_access`, `observe.health`, `observe.activity`,
package/src/mcp.mjs CHANGED
@@ -44,7 +44,7 @@ export function handleMcpRequest(message) {
44
44
  },
45
45
  serverInfo: {
46
46
  name: "airlock",
47
- version: "0.1.6",
47
+ version: "0.1.8",
48
48
  },
49
49
  instructions:
50
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.",
package/src/text.mjs CHANGED
@@ -62,9 +62,62 @@ administrative mutation. Prefer observe payloads such as
62
62
  \`observe.activity\`, \`observe.admin_activity\`, \`observe.spec_admin_activity\`,
63
63
  and \`observe.billing_events\` before inventing custom read paths. For \`alter_spec\`
64
64
  activity, use \`CHANGED_SECTIONS\` and \`CHANGED_FIELDS\` to triage what changed
65
- before fetching version snapshots. Offer to run \`airlock-mcp init-app-context\`
65
+ before fetching version snapshots. For polling loops, cache the role-scoped
66
+ \`agent.spec_state(...).STATE_TOKEN\` and do larger reads only when it changes.
67
+ After selecting one logical file, compare
68
+ \`agent.file_state(...).FILE_STATE_ID\` to detect data, workflow, attachment,
69
+ exact-reference, or retained version-history changes. Observer principals may use
70
+ the authorization-scoped \`observe.spec_state\` and \`observe.file_state\`
71
+ equivalents. A missing object or revoked observer grant returns no row as the
72
+ terminal polling signal; authorized query failures remain errors. Holding
73
+ \`app_observer\` opens only the read-only procedure family; Airlock
74
+ assignments plus \`observer_access.observer_roles\` determine spec visibility,
75
+ and account-wide observation requires \`app_admin\` or the locked Airlock role
76
+ \`global_observer\`. That reserved role cannot own specs, receive guest/reviewer
77
+ grants, or manage child roles. Observe-only calls do not consume a named seat, while
78
+ \`agent.*\` and Streamlit remain seat-gated. Use a distinct Snowflake username
79
+ per observer channel principal. For account-wide audits, call
80
+ \`observe.observers(...)\` to join observer assignment/scope with agent-access
81
+ and license posture. \`active_seat_observer_only\` means the identity remains
82
+ billable until an administrator explicitly calls \`admin.unassign_license\`;
83
+ never infer billing from role names alone. When asked to configure an observer
84
+ principal, apply four explicit controls: grant its dedicated Snowflake role
85
+ \`<APP_NAME>.app_observer\`, create and assign a distinct Airlock role, publish
86
+ that role through each spec's \`observer_access\`, then verify scope and billing
87
+ with \`observe.observers(...)\`. Audit assignment create, alter, and drop
88
+ through \`observe.admin_activity(..., action_family => 'assignments')\`; use
89
+ the structured \`EVENT_CONTEXT.before\`, \`after\`, and \`changed_fields\`
90
+ instead of parsing descriptions. Offer to run \`airlock-mcp init-app-context\`
66
91
  in the app repo to seed \`airlock/specs.manifest.json\`, spec snapshots, sample records, and
67
92
  generated helper folders. Help code the app using approved Airlock/Snowflake access paths.
93
+ Treat every mutating human or automated agent as an attributable Snowflake
94
+ principal. Direct work uses that principal's assignments; work for another
95
+ principal uses explicit Airlock delegation and \`on_behalf_of_user\`. Workspace
96
+ roles, channel membership, reactions, and chat identity do not create Airlock
97
+ authority. Treat \`EVENT_CONTEXT.snowflake_agent_active\` as informational
98
+ provenance only. A \`DELEGATED_ACTION\` event records an authorized attempt, not
99
+ proof that its mutation committed. After each supported mutation, retain an
100
+ \`airlock.commitment-receipt/v1\` with the authenticated actor, delegation
101
+ identity derived from the actual call arguments, target, stable result, and
102
+ timestamp. Hydrate successful direct file mutations with \`agent.file_state\` so
103
+ the receipt includes \`UPLOAD_ID\`, \`FILE_STATE_ID\`, and current workflow state.
104
+ Leave delegated \`committed_state\` null unless the actor performs a separate
105
+ independently authorized observation.
106
+ Treat Airlock's built-in Streamlit app as a generic operating and fallback
107
+ surface, not a universal domain app. When a repeated, high-value decision needs
108
+ domain-specific summaries, calculations, evidence layout, terminology, or
109
+ controls, recommend a purpose-built app and use Airlock as its governed backend.
110
+ Keep those presentation choices in app code; do not add layout or aggregation
111
+ fields to a spec merely to improve the generic UI.
112
+ When a structural spec change with active files returns
113
+ \`SPEC_MIGRATION_REQUIRED\`, use Airlock's governed two-version lifecycle:
114
+ create the immutable revision and migration, validate and approve it, activate
115
+ the target, drain bounded \`admin.run_spec_migration\` batches, inspect
116
+ \`observe.spec_migrations\` and \`observe.spec_migration\`, and retire the source
117
+ only after it is drained. Before activation, an abandoned migration may use
118
+ \`admin.cancel_spec_migration\`; cancellation is not post-activation rollback.
119
+ Use the declarative mechanical transform; semantic transforms belong in a
120
+ purpose-built process that reloads through normal Airlock validation.
68
121
  When a reference spec declares \`restricted_reference\` or
69
122
  \`reference_config.restricted_reference\`, do not enumerate values, build a
70
123
  populated picker, or call broad \`agent.select_reference_data\`. Get the lookup
@@ -111,6 +164,7 @@ Airlock MCP will offer:
111
164
  - spec design with the bundled workbench
112
165
  - Airlock operating patterns for OODA loops and separation of duties
113
166
  - read-only observe procedures for governance maps, health, access explanation, activity, billing events, and context packets
167
+ - scoped spec and file state tokens for efficient polling
114
168
  - app context seeding with spec snapshots and manifests
115
169
  - app and workflow coding against existing Airlock specs
116
170
  - observe specs for controlled interface ingestion
@@ -204,7 +258,38 @@ read-side discovery with observe payloads such as \`observe.procedures\`,
204
258
  \`observe.explain_access\`, \`observe.health\`, \`observe.activity\`,
205
259
  \`observe.admin_activity\`, \`observe.spec_admin_activity\`, and \`observe.billing_events\`;
206
260
  for \`alter_spec\` activity, use \`CHANGED_SECTIONS\` and \`CHANGED_FIELDS\` to
207
- triage what changed before fetching version snapshots. Do not use retired admin
261
+ triage what changed before fetching version snapshots. For polling loops, poll
262
+ \`agent.spec_state\` and compare its scoped \`STATE_TOKEN\` before larger reads;
263
+ compare \`agent.file_state(...).FILE_STATE_ID\` for one selected file. Observer
264
+ principals use \`observe.spec_state\` and \`observe.file_state\` for
265
+ authorization-scoped read-only state. File-state UUIDs include retained
266
+ version-history changes. A missing object or revoked grant returns no row as
267
+ the terminal polling signal; authorized query failures remain errors.
268
+ \`app_observer\` grants access to the
269
+ read-only procedure family; Airlock assignments and
270
+ \`observer_access.observer_roles\` grant spec visibility. Observe-only calls do
271
+ not consume a named seat. Account-wide access requires \`app_admin\` or the
272
+ locked \`global_observer\` Airlock role, which cannot own specs, receive
273
+ guest/reviewer grants, or manage child roles. Account-wide auditors should call
274
+ \`observe.observers(...)\` for flattened observer scope and license posture;
275
+ \`active_seat_observer_only\` requires explicit \`admin.unassign_license\` to
276
+ stop future seat billing. Observer setup should use a dedicated Snowflake
277
+ username and account role, the \`<APP_NAME>.app_observer\` application role, a
278
+ distinct Airlock role/assignment, and explicit per-spec \`observer_access\`.
279
+ Verify it with \`observe.observers\` and audit structured assignment before/after
280
+ evidence with \`observe.admin_activity(..., action_family => 'assignments')\`.
281
+ Every mutating human or automated agent must keep an attributable Snowflake
282
+ principal. Use explicit Airlock delegation when it acts for someone else; never
283
+ turn workspace membership or chat approval into Airlock authority. Treat
284
+ \`EVENT_CONTEXT.snowflake_agent_active\` as informational provenance only. A
285
+ \`DELEGATED_ACTION\` event records an authorized attempt, not proof of commitment.
286
+ Store each supported mutation as an \`airlock.commitment-receipt/v1\`, preserving
287
+ the authenticated actor, delegation identity derived from the actual call
288
+ arguments, stable status/code/issues, target, and timestamp. Follow successful
289
+ direct file mutations with \`agent.file_state\` to retain \`UPLOAD_ID\`,
290
+ \`FILE_STATE_ID\`, and workflow state. Leave delegated \`committed_state\` null
291
+ unless the actor performs a separate independently authorized observation.
292
+ Do not use retired admin
208
293
  read wrappers such as \`admin.list_specs\`, \`admin.describe_role\`, or
209
294
  \`admin.list_events\`. If a reference spec declares \`restricted_reference\` or
210
295
  \`reference_config.restricted_reference\`, do not enumerate the protected
@@ -216,7 +301,17 @@ must use Airlock procedures instead of direct stage reads. Airlock MCP can
216
301
  seed an app repo with \`airlock/specs.manifest.json\`, spec snapshots, sample
217
302
  records, and generated helper folders. The app should submit decisions,
218
303
  approvals, actions, comments, or follow-ups through Airlock spec contracts, not
219
- direct table writes.
304
+ direct table writes. Treat the built-in Streamlit app as a generic
305
+ operating/fallback surface. Recommend a purpose-built app when recurring,
306
+ high-value domain work benefits from specialized summaries, calculations,
307
+ evidence presentation, terminology, or controls. Keep those choices in app code
308
+ rather than adding UI layout or aggregation hints to Airlock specs. For a
309
+ structural spec change with active files, treat \`SPEC_MIGRATION_REQUIRED\` as
310
+ the governed two-version lifecycle: use immutable revisions, bounded
311
+ \`admin.run_spec_migration\` calls, \`observe.spec_migrations\` discovery,
312
+ \`observe.spec_migration\` evidence, and guarded source retirement instead of
313
+ direct table, stage, or view changes. \`admin.cancel_spec_migration\` is only a
314
+ pre-activation abandonment path, not rollback.
220
315
  In co-development mode, keep the spec track and app track visible side by side.`;
221
316
  }
222
317