@mortar-ai/skill 0.3.0 → 0.6.0

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.
package/README.md CHANGED
@@ -1,11 +1,11 @@
1
1
  # @mortar-ai/skill
2
2
 
3
3
  [Agent Skills](https://agentskills.io/) package for building on **Mortar** (the
4
- BaaS that AI bundles talk to). Install the canonical `SKILL.md` into a compatible
5
- agent's skill directory to teach it the SDK
6
- accessors, CLI commands, MCP tools, and most importantly — the
7
- tenant-isolation + `auth.tenant_id()` RLS rules that are the easiest
8
- thing to get wrong.
4
+ BaaS that AI bundles talk to). Install the canonical `SKILL.md` and its
5
+ `references/` into a compatible agent's skill directory to teach it the SDK
6
+ core invariants plus on-demand capability references. The split keeps Fabric's
7
+ loaded prompt small without hiding the storage, realtime, functions, auth, or
8
+ database guidance when a task needs it.
9
9
 
10
10
  ## Install
11
11
 
@@ -17,8 +17,9 @@ mortar-skill-install
17
17
 
18
18
  Then restart Claude Code (or `/skill reload`).
19
19
 
20
- The same versioned `SKILL.md` is packaged into Fabric Harness. `manifest.json`
21
- binds the npm version and Harness preinstall to its SHA-256 digest; the Fabric
20
+ The same versioned `SKILL.md` and `references/` package is built into Fabric
21
+ Harness. `manifest.json` binds the npm version and Harness preinstall to a
22
+ deterministic package SHA-256 digest; the Fabric
22
23
  Server rejects a Market install when the running Harness reports different
23
24
  bytes. npm is an external distribution channel and is not required by Fabric at
24
25
  runtime.
@@ -32,28 +33,26 @@ experience.
32
33
 
33
34
  The skill teaches Claude:
34
35
 
35
- - **SDK surface** — the `mortar.from()` / `auth` / `storage` /
36
- `realtime` / `compute` / `usage` / `cron` / `queue` / `comms`
37
- accessors + the account-scope `AccountClient`; which one to reach for
38
- - **Transaction boundaries** — atomic row batches and filtered mutations,
39
- trusted cross-table row + PostgreSQL queue transactions, and why privileged
40
- transaction clients must never enter browser/mobile bundles
41
- - **Storage consistency** — immutable physical object versions, atomic metadata
42
- pointer switches, durable cleanup, and why object storage is not part of a
43
- PostgreSQL ACID transaction
44
- - **CLI commands** — `mortar login / use / db / storage / compute /
45
- cron / queue / domains / comms / keys / tokens / topup / usage`
46
- - **MCP tools** — the 40 `mortar_*` tools and when an agent should use
47
- them vs the SDK
48
- - **Tenant isolation + RLS** — `auth.tenant_id()` (Mortar's analog of
49
- Supabase's `auth.uid()`), the `tenant_id = auth.tenant_id()` policy
50
- shape, and why every per-project table carries a `tenant_id`
51
- - **Architecture** the 9 primitive × (5 core + 3 extension) feature
52
- model, and why the SDK shape the billing shape
53
- - **Common patterns** auth (email+password / phone-OTP), data CRUD,
54
- file upload + signed URLs, realtime subscribe, queue + cron
55
- - **Anti-patterns** — assuming Fabric runs on Mortar; using `namespace`
56
- instead of `tenant_id`; hand-rolling RLS the SDK already enforces
36
+ - **Core contract** — Fabric-owned client/config files, schema-first workflow,
37
+ one migration operation per call, supported migration kinds, access-policy
38
+ invariants, version boundaries, and completion checks.
39
+ - **Database reference** — CRUD, filters, atomic row mutations, trusted
40
+ cross-table transactions, and ACID boundaries.
41
+ - **Auth reference** email/password, phone OTP, sessions, password reset, and
42
+ public-access policy choices.
43
+ - **Storage reference** — upload/download, signed URLs, immutable physical
44
+ versions, metadata pointers, CORS, and row/object consistency boundaries.
45
+ - **Realtime reference** — Postgres changes, broadcast, presence, unsubscribe,
46
+ and teardown.
47
+ - **Functions + automation** — invoke/deploy boundaries, cron, queues, and
48
+ transactional enqueue.
49
+ - **Analytics + operations** — capture/flush, usage, managed logs, primitive
50
+ billing attribution, and object-egress evidence.
51
+ - **Trusted services** communications, encrypted secrets, accounts/projects,
52
+ keys/PATs, custom domains, top-up, and capacity.
53
+ - **Architecture reference** all 9 infrastructure primitives, the open
54
+ capability registry, application modules, usage accounting, and
55
+ credential/trust surfaces.
57
56
 
58
57
  ## Custom install location
59
58
 
@@ -70,8 +69,9 @@ Idempotent — overwrites in place.
70
69
 
71
70
  ## Maintainer release
72
71
 
73
- `SKILL.md` is the only hand-edited instruction source. Regenerate the manifest
74
- and Harness copy before testing or publishing:
72
+ `SKILL.md` and `references/` are the hand-edited instruction sources. Keep the
73
+ core short and move capability detail into a routed reference. Regenerate the
74
+ manifest and Harness copy before testing or publishing:
75
75
 
76
76
  ```bash
77
77
  npm run sync
@@ -79,6 +79,8 @@ npm run check
79
79
  npm pack --dry-run
80
80
  ```
81
81
 
82
+ The repository-level synchronizer is shared with Keel and fails on missing
83
+ configured reference directories, resource drift, or unexpected Harness files.
82
84
  Never edit `fabric/harness/internal/skills/preinstalled/mortar/` directly.
83
85
 
84
86
  ## License
package/SKILL.md CHANGED
@@ -1,11 +1,11 @@
1
1
  ---
2
2
  name: mortar
3
- description: Build or change Fabric applications that use Mortar for database, auth, storage, realtime, compute, or analytics. Use for Mortar schema and provisioning, SDK integration, RLS, transactions, or storage consistency; not for Fabric's own control plane or a different backend.
3
+ description: Build or change Fabric applications that use Mortar capabilities such as database, auth, storage, realtime, functions, analytics, automation, or messaging. Use for Mortar schema and provisioning, SDK integration, access policy, transactions, or backend consistency; not for Fabric's own control plane or a different backend.
4
4
  metadata:
5
5
  activation: auto
6
6
  priority: high
7
7
  triggers:
8
- - keywords: [database, 数据库, backend, 后端, auth, authentication, 登录, 认证, persistence, 持久化, transaction, 事务, ACID, storage, upload, 文件, realtime, 实时, collaboration, compute, cloud, 云端]
8
+ - keywords: [database, 数据库, backend, 后端, auth, authentication, 登录, 认证, persistence, 持久化, transaction, 事务, ACID, storage, upload, 文件, realtime, 实时, collaboration, function, compute, analytics, cron, queue, comms, secrets, billing, cloud, 云端]
9
9
  ---
10
10
 
11
11
  # Mortar for Fabric applications
@@ -52,257 +52,113 @@ below; if platform provisioning fails, report that platform failure.
52
52
 
53
53
  1. Call `mortar_schema` before designing or changing backend data. It returns
54
54
  the current live schema, including console changes.
55
- 2. For each schema change, call `mortar_migrate` once with one operation. It
56
- applies the live change first; only after success does Fabric append a
57
- migration record under `mortar/migrations/`.
58
- 3. `mortar/migrations/` is a platform-owned output journal. Never create or edit
59
- SQL there: a file in that directory does not execute or change the live
60
- tenant. Do not edit or create `mortar.config.json`. Do not edit generated
61
- `integrations/mortar/types.ts`; Fabric regenerates it from the live schema.
62
- 4. Run `npm install @mortar-ai/client` if the dependency is absent, then write app
63
- code using the injected singleton. Atomic batches, server-side filtered
64
- mutations, and `DatabaseTransactions` require `@mortar-ai/client` 0.8.0 or
65
- newer. If an existing project needs one of those capabilities and pins an
66
- older version, update that dependency with npm and let npm update its lockfile;
67
- do not rewrite unrelated dependencies.
68
- 5. Before writing a multi-step mutation, choose the supported consistency
69
- boundary: one-table batches and filtered mutations can use the bundle-safe
70
- singleton; cross-table row + queue transactions require trusted server code;
71
- object storage and external services never join a PostgreSQL transaction.
72
- 6. Type-check and build. Handle every `{ data, error }` result in UI code.
73
-
74
- Supported migration kinds are `create_table`, `drop_table`, `set_table_access`,
75
- `add_column`, `drop_column`, `create_index`, `set_bucket_access`, `deploy_function`, `delete_function`,
76
- `upsert_cron`, and `delete_cron`. Destructive and backend-changing operations
77
- must respect the workspace approval gate. This list is exhaustive: arbitrary
78
- PostgreSQL, data backfills, PL/pgSQL functions, transactions, and triggers are
79
- not migration inputs. If required behavior cannot be expressed by one of these
80
- operations, report the unsupported Mortar capability instead of authoring a SQL
81
- file that only looks executable.
82
-
83
- To change an existing table's access policy, use `set_table_access`. Access is
84
- metadata and must be updated in place. Never use `drop_table` followed by
85
- `create_table` for a permission change: dropping a table deletes all of its
86
- rows and columns.
87
-
88
- For a public form, event registration, evidence upload, or similar operator-
89
- reviewed intake, use `access: "public_insert"` on the destination table and
90
- `set_bucket_access` with `access: "public_insert"` on any upload bucket. This
91
- allows a public bundle client to create a new row/object while denying public
92
- list, read, update, delete, and overwrite. Never use `public` merely to make an
93
- anonymous form submit successfully; `public` exposes all rows/files for public
94
- read and mutation. Generate an unguessable object key for every upload.
95
-
96
- ## Database SDK
97
-
98
- The query surface is Supabase-shaped. Async row/auth/storage methods normally
99
- return `{ data, error }`; check `error` rather than assuming success.
100
-
101
- ```ts
102
- const { data: todos, error } = await mortar
103
- .from('todos')
104
- .select('*')
105
- .eq('done', false)
106
- .order('created_at', { ascending: false })
107
- .limit(20);
108
- if (error) throw new Error(error.message);
109
-
110
- const { data: created, error: insertError } = await mortar
111
- .from('todos')
112
- .insert({ title: 'Ship', done: false, user_id: userId })
113
- .select()
114
- .single();
55
+ 2. Author database schema as exact Mortar logical SQL and submit `{ name, sql }`
56
+ to `mortar_migrate`. Fabric asks Mortar to parse a language-neutral typed
57
+ plan, applies the workspace review policy, writes those exact bytes under
58
+ `mortar/migrations/` with source-revision CAS, then Mortar re-verifies the
59
+ checksum and expected schema revision and commits the whole plan atomically.
60
+ 3. Use `mortar_resource` for non-database desired state. Function source and its
61
+ manifest live under `mortar/functions/<name>/`; bucket policy lives under
62
+ `mortar/storage/buckets/`; cron schedules live under `mortar/cron/`. Fabric
63
+ writes the approved artifact before reconciling the live resource.
64
+ 4. These source directories are platform-protected because direct edits would
65
+ bypass plan/apply. Submit the exact source through the matching tool; do not
66
+ edit or create `mortar.config.json`. Do not edit generated
67
+ `integrations/mortar/types.ts`; Fabric regenerates it from the post-apply live
68
+ schema. The same schema can generate TypeScript, Swift, Kotlin, and Dart app
69
+ data types; those outputs are derived and never drive migration execution.
70
+ 5. Install or update `@mortar-ai/client` with npm when needed and let npm own the
71
+ lockfile. Atomic batches, server-side filtered mutations, and
72
+ `DatabaseTransactions` require version 0.8.0 or newer. Do not rewrite
73
+ unrelated dependencies.
74
+ 6. Before a multi-step mutation, choose the real consistency boundary. A
75
+ one-table batch or filtered mutation may use the public singleton;
76
+ cross-table row + queue transactions require trusted server code; object
77
+ storage and external services never join a PostgreSQL transaction.
78
+ 7. Type-check and build. Handle every `{ data, error }` SDK result in UI code.
79
+
80
+ Supported database DDL is `CREATE TABLE`, `DROP TABLE`, `ALTER TABLE … ADD/DROP
81
+ COLUMN`, `ALTER TABLE SET ACCESS`, and `CREATE INDEX`. Put
82
+ `-- mortar:access public_insert` (or another access mode) immediately before a
83
+ `CREATE TABLE` when the default `public_read` is wrong. Resource kinds are
84
+ `set_bucket_access`, `deploy_function`, `delete_function`, `upsert_cron`, and
85
+ `delete_cron`; they belong to `mortar_resource`, never SQL. Destructive and
86
+ backend-changing operations respect the workspace approval gate. Arbitrary
87
+ PostgreSQL, data backfills, PL/pgSQL, transactions, and triggers are not inputs.
88
+
89
+ ## Access and safety invariants
90
+
91
+ - Change an existing table's access with `set_table_access`; never drop and
92
+ recreate a table merely to change permissions.
93
+ - For a public form, registration, or evidence upload reviewed by an operator,
94
+ use table/bucket access `public_insert`. It permits new submissions while
95
+ denying anonymous list, read, update, delete, and overwrite.
96
+ - Never use `public` merely to make anonymous submission work: it exposes all
97
+ rows or files for public read and mutation.
98
+ - Generate an unguessable logical object key for each public upload.
99
+ - Client-side filters are UX, not authorization. Choose a server-enforced access
100
+ policy for private or owner-scoped rows.
101
+ - Never put `app` or `admin` keys, `DatabaseTransactions`, operator clients, or
102
+ other privileged capabilities in Web, Desktop renderer, Keel/React Native, or
103
+ WeChat bundles.
104
+ - Never call a database + storage/functions/external workflow "atomic". Only
105
+ work committed inside one tenant-scoped PostgreSQL transaction is ACID.
106
+
107
+ ## On-demand references
108
+
109
+ The following detailed references are bundled beside this core Skill. Read only
110
+ the documents needed for the current task; do not load all of them by default.
111
+ In Fabric they are materialized under `/tmp/skills/mortar/references/`; other
112
+ Agent Skills hosts resolve the same paths relative to this `SKILL.md`.
113
+
114
+ - Database CRUD, atomic row mutations, trusted cross-table transactions, and
115
+ exact ACID boundaries: `references/database.md`
116
+ - Versioned SQL syntax, checksums/revisions, resource artifact layout, type
117
+ generation, legacy migrations, and the complete capability-to-source matrix:
118
+ `references/schema-migrations.md`
119
+ - App-user signup/signin, OTP, session lifecycle, and private-user modeling:
120
+ `references/auth.md`
121
+ - Upload/download/list/signed URLs, immutable object versions, CORS, and
122
+ database-to-object consistency: `references/storage.md`
123
+ - Postgres changes, broadcast, presence, reconnect behavior, and cleanup:
124
+ `references/realtime.md`
125
+ - Function deployment/invocation, logs, public-key boundaries, and error
126
+ handling: `references/functions.md`
127
+ - The 9 infrastructure primitives, open capability registry, application
128
+ modules, usage accounting, and trust surfaces: `references/architecture.md`
129
+ - Product analytics, error capture, delivery guarantees, and operator summary:
130
+ `references/analytics.md`
131
+ - Cron schedules, queues, workers, retries, and transactional enqueue:
132
+ `references/automation.md`
133
+ - Transactional email/SMS and provider/idempotency boundaries:
134
+ `references/communications.md`
135
+ - Encrypted project secrets and function environment injection:
136
+ `references/secrets.md`
137
+ - Project usage, managed logs, billing attribution, and egress semantics:
138
+ `references/usage-billing.md`
139
+ - Accounts, projects, API keys, PATs, custom domains, top-up, and capacity:
140
+ `references/control-plane.md`
141
+
142
+ When using Fabric `bash`, read the exact materialized path shown by `load_skill`,
143
+ for example:
144
+
145
+ ```sh
146
+ sed -n '1,240p' /tmp/skills/mortar/references/storage.md
115
147
  ```
116
148
 
117
- Updates and deletes use the same filter chain. Useful operators include `eq`,
118
- `neq`, ranges, `like/ilike`, `in`, `is`, `order`, `limit`, `single`, and
119
- `maybeSingle`.
120
-
121
- ### Atomic row mutations
122
-
123
- Pass an array when every insert or upsert must succeed or fail together. Mortar
124
- sends at most 200 rows in one request and executes the batch in one
125
- tenant-scoped PostgreSQL transaction. For upsert, a row with an `id` replaces
126
- that row's data; a row without an `id` inserts. Do not use a loop or
127
- `Promise.all` when partial completion would break an invariant.
128
-
129
- ```ts
130
- const { data: createdTodos, error: batchError } = await mortar
131
- .from('todos')
132
- .insert([
133
- { title: 'Prepare', done: false, user_id: userId },
134
- { title: 'Ship', done: false, user_id: userId },
135
- ])
136
- .select();
137
- if (batchError) throw new Error(batchError.message);
138
-
139
- const { data: changedTodos, error: updateError } = await mortar
140
- .from('todos')
141
- .update({ done: true })
142
- .eq('user_id', userId)
143
- .eq('archived', false)
144
- .select();
145
- if (updateError) throw new Error(updateError.message);
146
- ```
147
-
148
- Filtered `update` and `delete` select and mutate all matching rows inside one
149
- server-side transaction. Do not pre-select IDs and then update/delete them one
150
- at a time: that creates a race and loses the all-or-nothing guarantee. Chain
151
- `.select()` only when the app needs the affected rows returned.
152
-
153
- ### Cross-table transactions are server-only
154
-
155
- `DatabaseTransactions` can atomically combine up to 200 cross-table row
156
- `insert` / by-ID `update` / by-ID `delete` operations and PostgreSQL-backed
157
- queue `enqueue` operations. It is intentionally not attached to the injected
158
- `mortar` singleton and requires an `app` or `admin` key. Its `execute` method
159
- throws on failure, so use `try`/`catch`, not a `{ data, error }` envelope.
160
-
161
- ```ts
162
- import { DatabaseTransactions } from '@mortar-ai/client';
163
-
164
- const transactions = new DatabaseTransactions(serverOnlyMortarOptions);
165
-
166
- try {
167
- const results = await transactions.execute([
168
- { op: 'insert', table: 'orders', data: { customer_id: customerId, total } },
169
- { op: 'enqueue', type: 'send_receipt', payload: { customer_id: customerId } },
170
- ]);
171
- console.log(results);
172
- } catch (error) {
173
- // Nothing committed. Report or retry the whole idempotent operation.
174
- throw error;
175
- }
176
- ```
177
-
178
- Never instantiate `DatabaseTransactions` in Web, Desktop renderer,
179
- Keel/React Native, or WeChat client code. Never add an app/admin key to a
180
- client-side environment variable or source file. Use this API only in an
181
- already-approved trusted server environment that holds the key. If a Fabric
182
- project has no such path, report cross-table ACID as unsupported instead of
183
- simulating it with several public-client calls. A transactional queue enqueue
184
- also requires Mortar's PostgreSQL queue backend; otherwise the whole request
185
- fails without committing row changes.
186
-
187
- ### Exact ACID boundary
188
-
189
- Mortar provides true ACID only for work executed in the same tenant-scoped
190
- PostgreSQL transaction: atomic row batches, filtered row mutations, and the
191
- trusted cross-table row + PostgreSQL queue API above. Object storage,
192
- compute/functions, communications providers, analytics delivery, and other
193
- external systems do not participate in that transaction. Never describe a
194
- workflow spanning them as one ACID transaction or claim two-phase commit.
195
-
196
- ## App-user authentication
197
-
198
- Auth sessions are persisted and attached to later database, storage, realtime,
199
- and compute calls by the SDK.
200
-
201
- ```ts
202
- const { data, error } = await mortar.auth.signUp({ email, password });
203
- const signedIn = await mortar.auth.signInWithPassword({ email, password });
204
-
205
- const sent = await mortar.auth.signInWithOtp({ email }); // or { phone }
206
- const verified = await mortar.auth.verifyOtp({ email, token });
207
-
208
- const { data: sessionData } = await mortar.auth.getSession();
209
- const { data: userData, error: userError } = await mortar.auth.getUser();
210
- await mortar.auth.signOut();
211
- ```
212
-
213
- Use `onAuthStateChange` when UI must react to session changes, and unsubscribe
214
- the returned subscription during cleanup.
215
-
216
- Password reset starts with `mortar.auth.resetPasswordForEmail(email)`; confirm via
217
- `passwordResetConfirm(email, token, newPassword)`. Never store or pass session
218
- tokens manually unless the user explicitly needs a server integration.
219
-
220
- For private user rows, include the authenticated user's ID in the schema/data
221
- model and choose the correct table access policy. Client-side filters improve
222
- UX but do not replace server-side access rules.
223
-
224
- ## File storage
225
-
226
- ```ts
227
- const bucket = mortar.storage.from('avatars');
228
-
229
- const { data: uploaded, error: uploadError } = await bucket.upload(path, file, {
230
- contentType: file.type,
231
- upsert: true,
232
- });
233
- const { data: blob, error: downloadError } = await bucket.download(path);
234
- const { data: files, error: listError } = await bucket.list('users/');
235
- const { data: signed, error: signError } = await bucket.createSignedUrl(path, 3600);
236
- const { error: removeError } = await bucket.remove([path]);
237
- ```
238
-
239
- Applications address files by their stable logical `path`. Mortar stores new
240
- uploads under immutable internal physical versions, then atomically switches
241
- its PostgreSQL metadata pointer and durably schedules orphan/replaced versions
242
- for retryable cleanup. Never construct, persist, or expose Mortar's internal
243
- physical object key. Existing logical paths remain compatible, and application
244
- projects do not run or author the platform storage migration.
245
-
246
- This protocol keeps Mortar's own file metadata from pointing at a partial
247
- upload, but it does not make a separate application row and an object upload
248
- one transaction. When a business row must reference a file, generate an
249
- unguessable logical key. For a simple workflow, upload first and write the
250
- business row only after upload succeeds. For recoverable workflows, create a
251
- row with a stable idempotency key and `pending` state, upload, then mark it
252
- `ready`; reconcile stale rows to `failed` or retry them. Do not issue a database
253
- write and `bucket.upload()` concurrently and call the result atomic.
254
-
255
- Downloads mint a short-lived URL and fetch the object directly from the storage
256
- backend. Reuse `signed.signedUrl` until it nears expiry when rendering media.
257
- Prefer the signed URL directly in `<img src>`, `<video src>`, or `<audio src>`;
258
- this avoids an unnecessary blob copy and does not require CORS merely to render
259
- the media. Signed-URL minting is excluded from the project's API-concurrency
260
- budget, but clients should still lazy-load visible media and cache/reuse each
261
- URL instead of signing again on every render. Signing authorizes a possible
262
- download; it does not prove bytes were served and must never be described or
263
- modeled as an object-egress charge. Only provider-observed access-log or billing
264
- reconciliation may record actual object egress. The optional third
265
- `createSignedUrl` argument (`{ signal, timeoutMs }`) bounds only the signing
266
- request when the UI needs a deadline.
267
-
268
- `bucket.download(path)` uses JavaScript `fetch()` against the object store and
269
- therefore needs a matching bucket CORS rule in browsers. Appunvs-managed Mortar
270
- storage provides that contract; self-hosted object storage must allow the app
271
- origin for credential-free `GET` / `HEAD`. Never attach Mortar Authorization or
272
- browser cookies to a signed object URL.
273
-
274
- ## Realtime, broadcast, and presence
275
-
276
- ```ts
277
- const room = mortar
278
- .channel('todos-room', { presence: { key: userId } })
279
- .on(
280
- 'postgres_changes',
281
- { event: '*', schema: 'public', table: 'todos' },
282
- (payload) => refreshFromChange(payload),
283
- )
284
- .on('broadcast', { event: 'cursor' }, ({ payload }) => drawCursor(payload))
285
- .on('presence', { event: 'sync' }, () => renderRoster(room.presenceState()))
286
- .subscribe((status) => {
287
- if (status === 'SUBSCRIBED') void room.track({ online: true });
288
- });
289
-
290
- await room.send({ type: 'broadcast', event: 'cursor', payload: { x, y } });
291
- // On component/page cleanup:
292
- mortar.removeChannel(room);
293
- ```
294
-
295
- Clean up channels with their page/component. Persist through `from(table)`;
296
- realtime only notifies peers.
297
-
298
- ## Compute and analytics
149
+ ## Completion checklist
299
150
 
300
- Invoke only functions already deployed through `mortar_migrate`; check
301
- `response.ok` and read the response body. Analytics provides `track`,
302
- `identify`, `captureError`, and `flush`; events are fire-and-forget except
303
- `flush`.
151
+ - Live schema was read before schema design.
152
+ - Every database schema change used reviewed exact SQL through `mortar_migrate`;
153
+ every function/bucket/cron change used `mortar_resource`.
154
+ - Application code imports only the platform-owned singleton.
155
+ - No secret/admin client or hand-written fallback configuration entered the
156
+ client bundle.
157
+ - Every SDK error path is handled.
158
+ - The relevant on-demand reference—not every reference—was consulted.
159
+ - Type-check and the surface's real build pass.
304
160
 
305
161
  ## Boundary for non-Fabric development
306
162
 
307
- For Mortar itself or a standalone non-Fabric integration, inspect the current SDK
308
- README. Never copy its standalone `createClient` setup into a Fabric project.
163
+ For Mortar itself or a standalone non-Fabric integration, inspect the current
164
+ SDK README. Never copy its standalone `createClient` setup into a Fabric project.
package/bin/install.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * mortar-skill-install — copies the canonical SKILL.md into the user's Claude
4
- * Code skills directory.
3
+ * mortar-skill-install — copies the canonical SKILL.md and its on-demand
4
+ * references into the user's Claude Code skills directory.
5
5
  *
6
6
  * Default destination: `~/.claude/skills/mortar/SKILL.md`. Override
7
7
  * via `--dest=<path>` for non-default Claude installs.
@@ -10,7 +10,7 @@
10
10
  */
11
11
  'use strict';
12
12
 
13
- const { copyFileSync, existsSync, mkdirSync } = require('node:fs');
13
+ const { copyFileSync, cpSync, existsSync, mkdirSync, rmSync } = require('node:fs');
14
14
  const { homedir } = require('node:os');
15
15
  const { dirname, resolve } = require('node:path');
16
16
 
@@ -27,6 +27,7 @@ function parseFlags(argv) {
27
27
  function main() {
28
28
  const flags = parseFlags(process.argv);
29
29
  const src = resolve(__dirname, '..', 'SKILL.md');
30
+ const referencesSrc = resolve(__dirname, '..', 'references');
30
31
  if (!existsSync(src)) {
31
32
  console.error(`mortar-skill: source missing at ${src}`);
32
33
  process.exit(1);
@@ -37,7 +38,11 @@ function main() {
37
38
 
38
39
  mkdirSync(dirname(dest), { recursive: true });
39
40
  copyFileSync(src, dest);
41
+ const referencesDest = resolve(dirname(dest), 'references');
42
+ rmSync(referencesDest, { recursive: true, force: true });
43
+ if (existsSync(referencesSrc)) cpSync(referencesSrc, referencesDest, { recursive: true });
40
44
  console.log(`✓ Installed Mortar skill → ${dest}`);
45
+ console.log(`✓ Installed on-demand references → ${referencesDest}`);
41
46
  console.log('');
42
47
  console.log('Restart Claude Code (or run `/skill reload`) to activate.');
43
48
  }
package/index.js CHANGED
@@ -10,6 +10,7 @@ const { readFileSync } = require('node:fs');
10
10
  const { resolve } = require('node:path');
11
11
 
12
12
  const SKILL_PATH = resolve(__dirname, 'SKILL.md');
13
+ const REFERENCES_PATH = resolve(__dirname, 'references');
13
14
  const MANIFEST_PATH = resolve(__dirname, 'manifest.json');
14
15
 
15
16
  module.exports = {
@@ -25,5 +26,6 @@ module.exports = {
25
26
  return JSON.parse(readFileSync(MANIFEST_PATH, 'utf8'));
26
27
  },
27
28
  SKILL_PATH,
29
+ REFERENCES_PATH,
28
30
  MANIFEST_PATH,
29
31
  };
package/manifest.json CHANGED
@@ -1,16 +1,16 @@
1
1
  {
2
2
  "schema_version": 1,
3
3
  "id": "mortar",
4
- "version": "0.3.0",
5
- "content_sha256": "sha256:c69dab74ad4a377086c0c883c4d400770600738de2741778fad21ec27e925f27",
4
+ "version": "0.6.0",
5
+ "content_sha256": "sha256:f3cf01640a48dca83ccfe49a744c33355a5009567a1706d5c59528d6ddd3ee92",
6
6
  "source_repo": "liamxujia/appunvs",
7
- "source_path": "mortar/skill/SKILL.md",
7
+ "source_path": "mortar/skill",
8
8
  "distributions": [
9
9
  "harness_preinstalled",
10
10
  "npm"
11
11
  ],
12
12
  "npm": {
13
13
  "package": "@mortar-ai/skill",
14
- "version": "0.3.0"
14
+ "version": "0.6.0"
15
15
  }
16
16
  }
package/package.json CHANGED
@@ -1,15 +1,15 @@
1
1
  {
2
2
  "name": "@mortar-ai/skill",
3
- "version": "0.3.0",
4
- "description": "Agent skill package for building Fabric applications on Mortar (BaaS), distributed from one versioned SKILL.md with a content-addressed manifest.",
3
+ "version": "0.6.0",
4
+ "description": "Agent skill package for building Fabric applications on Mortar (BaaS), with compact core guidance and on-demand references.",
5
5
  "main": "index.js",
6
- "files": ["SKILL.md", "manifest.json", "index.js", "README.md"],
6
+ "files": ["SKILL.md", "references", "manifest.json", "index.js", "README.md"],
7
7
  "bin": {
8
8
  "mortar-skill-install": "bin/install.js"
9
9
  },
10
10
  "scripts": {
11
- "sync": "node scripts/sync-distributions.js",
12
- "check": "node scripts/sync-distributions.js --check"
11
+ "sync": "node ../../shared/skill-distribution/sync.js --config distribution.json",
12
+ "check": "node scripts/check-coverage.js && node ../../shared/skill-distribution/sync.js --config distribution.json --check"
13
13
  },
14
14
  "publishConfig": { "access": "public" },
15
15
  "license": "Apache-2.0"
@@ -0,0 +1,29 @@
1
+ # Analytics and error-capture reference
2
+
3
+ Read this reference for product events, identity traits, error capture, flush
4
+ behavior, or operator summaries. Analytics composes the `warehouse`, `network`,
5
+ and operational logging paths; it is a capability, not a new cost primitive.
6
+
7
+ The public client batches events and exposes synchronous capture calls plus an
8
+ async flush:
9
+
10
+ ```ts
11
+ mortar.analytics.track('pageview', { path: location.pathname });
12
+ mortar.analytics.identify(user.id, { plan: 'pro' });
13
+
14
+ try {
15
+ await checkout();
16
+ } catch (error) {
17
+ mortar.analytics.captureError(error, { where: 'checkout' });
18
+ throw error;
19
+ }
20
+
21
+ await mortar.analytics.flush();
22
+ ```
23
+
24
+ Do not treat capture or flush as a durable business transaction and do not block
25
+ primary UX on analytics delivery. Flush at an explicit lifecycle boundary when
26
+ best-effort delivery should be attempted before navigation or shutdown. The
27
+ summary endpoint is a trusted project/operator read, not part of Fabric's public
28
+ bundle singleton. A deployment without a configured warehouse returns an
29
+ availability error; do not silently invent local analytics persistence.
@@ -0,0 +1,99 @@
1
+ # Architecture and capability taxonomy reference
2
+
3
+ Read this reference when deciding where a Mortar API belongs, which credential
4
+ may call it, which code implements it, which infrastructure it consumes, or
5
+ how its observed usage is priced.
6
+
7
+ Mortar keeps five concerns separate. Do not collapse them into one:
8
+
9
+ 1. A **capability** is an open-ended user or operator outcome exposed by API,
10
+ SDK, CLI, MCP, or control-plane tooling. There is deliberately no fixed
11
+ capability count.
12
+ 2. A **trust surface** says which credential and runtime may call a capability.
13
+ 3. An **application module** implements and composes use cases.
14
+ 4. A **primitive** is a provider-neutral resource, replacement, operational,
15
+ and cost-attribution boundary.
16
+ 5. **Usage accounting** observes selected operations, applies meters, quotas,
17
+ and pricing policy, and records the customer ledger.
18
+
19
+ An `internal/application/<name>` Go package is an application composition
20
+ module, not a closed product taxonomy. A capability may span multiple
21
+ application, primitive, account, or API packages, and one application module
22
+ may support several capabilities.
23
+
24
+ The current application modules are `auth`, `cron`, `data`, `files`,
25
+ `migration`, `queue`, `realtime`, and `secret`. Functions, comms, analytics,
26
+ accounts, and custom domains remain capabilities even when their implementation
27
+ spans primitive, API, account, or other packages rather than a same-named
28
+ application directory. `internal/usage` separately owns metering, quota,
29
+ pricing, and the credit ledger. `internal/subscription` separately owns account
30
+ subscription periods, fulfillment, settlement, and compute-grant allocation;
31
+ usage accounting consumes those grants without owning the subscription
32
+ lifecycle.
33
+
34
+ ## The 9 primitives
35
+
36
+ | Primitive | Role |
37
+ |---|---|
38
+ | `compute` | Baseline tenant share of pool compute |
39
+ | `database` | PostgreSQL baseline and durable relational state |
40
+ | `cache` | Redis/in-process cache, rate limits, pub/sub, and presence |
41
+ | `storage` | Object bytes and GB-month storage |
42
+ | `network` | Provider-observed or response-writer outbound bytes |
43
+ | `function` | User function execution through a configured runner |
44
+ | `comms` | Transactional email and SMS delivery |
45
+ | `warehouse` | High-cardinality analytics events and summaries |
46
+ | `log` | Managed provider-log ingestion, indexing, and retention |
47
+
48
+ These names are stable because they align resource ownership, cost attribution,
49
+ and swappable cloud backends. They do not predict the public SDK or customer
50
+ invoice shape. For example, `network` is a side effect rather than
51
+ `mortar.network`, while auth composes database, cache, comms, and network
52
+ without becoming another primitive.
53
+
54
+ Primitive use is not equivalent to a customer charge. The same `warehouse`
55
+ primitive backs both uncharged summary reads and metered event ingestion.
56
+ Provider cost, observed usage, included allowance, and customer price are four
57
+ different facts; `internal/usage` connects them without making primitive
58
+ interfaces depend on plans or billing.
59
+
60
+ ## Current capability registry
61
+
62
+ | Capability family | Public app bundle | Trusted project/operator | Account control plane |
63
+ |---|---|---|---|
64
+ | Auth | Signup/signin, OTP, session, reset | User administration | Account signup/signin and notification email |
65
+ | Data | Row CRUD under access policy | Schema/index/column changes and cross-table transactions | Project ownership and lifecycle |
66
+ | Files | Upload/list/delete/sign under bucket policy | Bucket policy and cleanup operations | Project lifecycle cleanup |
67
+ | Realtime | Row changes, broadcast, presence | Backend/channel configuration | — |
68
+ | Functions | Invoke deployed functions | List/deploy/delete/read logs | Read project function logs |
69
+ | Analytics | Capture events and errors | Read project summaries | — |
70
+ | Automation | — | Cron CRUD and queue enqueue/list/get | Read project cron schedules |
71
+ | Communications | — | Send configured email/SMS | Operational account email |
72
+ | Secrets | — | Encrypted project secret CRUD and function injection | — |
73
+ | Usage and logs | — | Project usage ledger and audit logs | Read project usage |
74
+ | Identity and access | — | Project API-key administration | Projects, keys, PATs, ownership |
75
+ | Domains and commerce | — | — | Domain claim/verification, plans, top-up, auto-top-up, capacity/enterprise intent |
76
+
77
+ The registry grows when a real API or workflow ships. Cron, queue, and custom
78
+ domains are implemented capabilities, not a future three-item extension list.
79
+ Their availability can still depend on configuration: cron may be disabled,
80
+ queue may use PostgreSQL or be off, analytics needs a warehouse, functions need
81
+ a runner, comms needs a provider, and domain TLS routing is completed by the
82
+ deployment edge after Mortar verifies ownership.
83
+
84
+ ## Trust surfaces
85
+
86
+ - **Public app bundle**: a PUBLIC bundle key plus optional app-user session.
87
+ Fabric's injected `MortarClient` intentionally contains only auth, row data,
88
+ storage, realtime, function invocation, and analytics capture.
89
+ - **Trusted project/operator**: app/admin project keys in server, CI, CLI, MCP,
90
+ or platform provisioning. Never ship these credentials or clients to a Web,
91
+ Desktop renderer, Keel/React Native, or WeChat bundle.
92
+ - **Account control plane**: account JWT or `mtr_pat_*` PAT against the apex
93
+ `_accounts` routes. These credentials manage projects and ownership; they are
94
+ not interchangeable with project or app-user credentials.
95
+
96
+ When documenting or implementing a capability, state its trust surface, its
97
+ application implementation, the primitives it consumes, and any metering and
98
+ pricing policy. No one field answers authorization, implementation, operations,
99
+ and billing at the same time.
@@ -0,0 +1,33 @@
1
+ # App-user authentication reference
2
+
3
+ Read this reference for signup/signin, OTP, password reset, session state, or
4
+ private-user data modeling.
5
+
6
+ Auth sessions are persisted and attached to later database, storage, realtime,
7
+ and compute calls by the platform-owned singleton.
8
+
9
+ ```ts
10
+ const { data, error } = await mortar.auth.signUp({ email, password });
11
+ const signedIn = await mortar.auth.signInWithPassword({ email, password });
12
+
13
+ const sent = await mortar.auth.signInWithOtp({ email }); // or { phone }
14
+ const verified = await mortar.auth.verifyOtp({ email, token });
15
+
16
+ const { data: sessionData } = await mortar.auth.getSession();
17
+ const { data: userData, error: userError } = await mortar.auth.getUser();
18
+ await mortar.auth.signOut();
19
+ ```
20
+
21
+ Check each returned `error`; do not infer authentication from the presence of UI
22
+ state. Use `onAuthStateChange` when the UI must react to session changes and
23
+ unsubscribe during component/page cleanup.
24
+
25
+ Password reset starts with `mortar.auth.resetPasswordForEmail(email)` and is
26
+ confirmed with `passwordResetConfirm(email, token, newPassword)`. Never store or
27
+ pass session tokens manually unless the user explicitly needs a trusted server
28
+ integration.
29
+
30
+ For private user rows, include the authenticated user ID in the schema/data
31
+ model and select a server-enforced private or owner-scoped table access policy.
32
+ Client-side filters improve UX but do not replace authorization. Anonymous
33
+ intake uses `public_insert`, not broad `public` access.
@@ -0,0 +1,23 @@
1
+ # Cron and queue automation reference
2
+
3
+ Read this reference for scheduled functions, background jobs, retries, workers,
4
+ or atomic row-plus-job workflows. Cron and queue are shipped capabilities whose
5
+ runtime availability depends on configuration; they are not “planned extension
6
+ slots.”
7
+
8
+ Fabric provisions schedules only through one `mortar_resource` operation:
9
+ `upsert_cron` creates or updates a schedule for an already deployed function,
10
+ and `delete_cron` removes it. Direct cron list/create/patch/delete endpoints are
11
+ trusted operator surfaces. Cron is normally enabled and can be disabled by the
12
+ Mortar deployment.
13
+
14
+ Direct queue enqueue/list/get is also trusted project/operator functionality;
15
+ it is intentionally absent from the public app-bundle `MortarClient`. Queue may
16
+ use the PostgreSQL backend or be disabled. Workers must make handlers
17
+ idempotent, bound retries, and distinguish a claim from successful completion.
18
+
19
+ When a database change and queue enqueue must commit together, use the trusted
20
+ `DatabaseTransactions` API described in `database.md`. That guarantee applies
21
+ only to the PostgreSQL queue backend and the row operations inside the same
22
+ tenant transaction. Function execution, communications, object storage, and
23
+ external providers remain outside that transaction.
@@ -0,0 +1,16 @@
1
+ # Transactional communications reference
2
+
3
+ Read this reference for email, SMS, OTP delivery, or provider behavior. Mortar's
4
+ `comms` primitive records email and SMS as separate ledger resources because
5
+ their provider prices differ.
6
+
7
+ Direct `/comms/send` is a trusted project/operator capability. It is not exposed
8
+ on Fabric's public app-bundle singleton; never place an app/admin key or provider
9
+ credential in client code to reach it. App-user auth may internally compose the
10
+ same primitive for phone OTP, email verification, and password reset.
11
+
12
+ Hosted Mortar can use configured Aliyun SMS and Aliyun DirectMail adapters.
13
+ Provider availability is deployment-specific, and an unconfigured channel must
14
+ fail visibly. Treat delivery as an external side effect: use an idempotency key
15
+ or persisted outbox state where duplicate messages matter, and never claim that
16
+ a database transaction atomically includes provider delivery.
@@ -0,0 +1,25 @@
1
+ # Account and project control-plane reference
2
+
3
+ Read this reference for Mortar accounts, projects, API keys, PATs, custom
4
+ domains, plans, top-up, auto-top-up, capacity, or enterprise placement.
5
+
6
+ The control plane uses the apex host and an account JWT or `mtr_pat_*` personal
7
+ access token. It manages account identity, project lifecycle, project API keys,
8
+ PATs, project usage, domain claims, plan/top-up settings, and enterprise intent.
9
+ These credentials are not app-user sessions and cannot be substituted for a
10
+ project key on tenant data-plane routes.
11
+
12
+ Project API keys have their own scopes. Public bundle keys are expected in app
13
+ code; app/admin keys belong only in trusted server, CI, CLI, MCP, or platform
14
+ provisioning. Revoke and rotate keys through the control plane rather than
15
+ editing generated client configuration.
16
+
17
+ Custom-domain creation records a claim and verification proves DNS ownership.
18
+ Mortar does not by itself finish edge routing or ACME issuance: the deployment
19
+ edge/operator consumes verified state and configures TLS. Do not claim a domain
20
+ is live merely because its TXT verification succeeded.
21
+
22
+ Top-up and capacity changes are commercial/control-plane operations, not schema
23
+ migrations. They may involve provider webhooks and asynchronous placement;
24
+ verify the resulting project state rather than assuming request acceptance is
25
+ final settlement or capacity activation.
@@ -0,0 +1,101 @@
1
+ # Database and transaction reference
2
+
3
+ Read this reference for row CRUD, batch mutations, cross-table transactions, or
4
+ questions about Mortar's exact ACID boundary.
5
+
6
+ ## Database SDK
7
+
8
+ The query surface is Supabase-shaped. Async row methods normally return
9
+ `{ data, error }`; check `error` rather than assuming success.
10
+
11
+ ```ts
12
+ const { data: todos, error } = await mortar
13
+ .from('todos')
14
+ .select('*')
15
+ .eq('done', false)
16
+ .order('created_at', { ascending: false })
17
+ .limit(20);
18
+ if (error) throw new Error(error.message);
19
+
20
+ const { data: created, error: insertError } = await mortar
21
+ .from('todos')
22
+ .insert({ title: 'Ship', done: false, user_id: userId })
23
+ .select()
24
+ .single();
25
+ if (insertError) throw new Error(insertError.message);
26
+ ```
27
+
28
+ Updates and deletes use the same filter chain. Useful operators include `eq`,
29
+ `neq`, ranges, `like/ilike`, `in`, `is`, `order`, `limit`, `single`, and
30
+ `maybeSingle`.
31
+
32
+ ## Atomic row mutations
33
+
34
+ Pass an array when every insert or upsert must succeed or fail together. Mortar
35
+ sends at most 200 rows in one request and executes the batch in one
36
+ tenant-scoped PostgreSQL transaction. For upsert, a row with an `id` replaces
37
+ that row's data; a row without an `id` inserts. Do not use a loop or
38
+ `Promise.all` when partial completion would break an invariant.
39
+
40
+ ```ts
41
+ const { data: createdTodos, error: batchError } = await mortar
42
+ .from('todos')
43
+ .insert([
44
+ { title: 'Prepare', done: false, user_id: userId },
45
+ { title: 'Ship', done: false, user_id: userId },
46
+ ])
47
+ .select();
48
+ if (batchError) throw new Error(batchError.message);
49
+
50
+ const { data: changedTodos, error: updateError } = await mortar
51
+ .from('todos')
52
+ .update({ done: true })
53
+ .eq('user_id', userId)
54
+ .eq('archived', false)
55
+ .select();
56
+ if (updateError) throw new Error(updateError.message);
57
+ ```
58
+
59
+ Filtered `update` and `delete` select and mutate all matching rows inside one
60
+ server-side transaction. Do not pre-select IDs and update/delete them one at a
61
+ time; that introduces a race and loses the all-or-nothing guarantee. Chain
62
+ `.select()` only when the app needs the affected rows returned.
63
+
64
+ ## Cross-table transactions are server-only
65
+
66
+ `DatabaseTransactions` can atomically combine up to 200 cross-table row
67
+ `insert`, by-ID `update`, by-ID `delete`, and PostgreSQL-backed queue `enqueue`
68
+ operations. It is intentionally absent from the injected `mortar` singleton and
69
+ requires an `app` or `admin` key. `execute` throws on failure, so use
70
+ `try`/`catch`, not a `{ data, error }` envelope.
71
+
72
+ ```ts
73
+ import { DatabaseTransactions } from '@mortar-ai/client';
74
+
75
+ const transactions = new DatabaseTransactions(serverOnlyMortarOptions);
76
+
77
+ try {
78
+ const results = await transactions.execute([
79
+ { op: 'insert', table: 'orders', data: { customer_id: customerId, total } },
80
+ { op: 'enqueue', type: 'send_receipt', payload: { customer_id: customerId } },
81
+ ]);
82
+ console.log(results);
83
+ } catch (error) {
84
+ // Nothing committed. Report or retry the whole idempotent operation.
85
+ throw error;
86
+ }
87
+ ```
88
+
89
+ Never instantiate `DatabaseTransactions` in Web, Desktop renderer,
90
+ Keel/React Native, or WeChat code. If a Fabric project has no approved trusted
91
+ server path, report cross-table ACID as unsupported instead of simulating it
92
+ with public-client calls. Transactional queue enqueue also requires Mortar's
93
+ PostgreSQL queue backend; otherwise no row operation commits.
94
+
95
+ ## Exact ACID boundary
96
+
97
+ Mortar provides ACID only for work executed in the same tenant-scoped
98
+ PostgreSQL transaction: atomic row batches, filtered row mutations, and the
99
+ trusted cross-table row + PostgreSQL queue API above. Object storage,
100
+ compute/functions, communications providers, analytics delivery, and other
101
+ external systems do not participate. Never claim two-phase commit across them.
@@ -0,0 +1,43 @@
1
+ # Functions reference
2
+
3
+ Read this reference for deploying or invoking compute functions and reading
4
+ function logs. Cron is in `automation.md`; analytics is in `analytics.md`.
5
+
6
+ ## Deploy and invoke functions
7
+
8
+ Deploy or delete functions only through one `mortar_resource` operation with
9
+ kind `deploy_function` or `delete_function`. Do not edit `mortar.config.json`,
10
+ directly write the protected function directory, or call operator/admin SDK methods from application
11
+ code. Function list/deploy/delete/logs are trusted project or account/operator
12
+ surfaces; the app-bundle client exposes invocation only.
13
+
14
+ The desired-state directory contains `function.json` plus the runtime entrypoint:
15
+ `index.js` for Node-compatible runtimes and `index.py` for Python-compatible
16
+ runtimes. Switching runtime removes the stale entrypoint; deleting a function
17
+ removes the manifest, both entrypoint forms, and the legacy flat `.js` artifact.
18
+
19
+ Client bundles may invoke a function that the platform already deployed:
20
+
21
+ ```ts
22
+ const response = await mortar.compute.invoke({
23
+ name: 'create-checkout',
24
+ method: 'POST',
25
+ body: JSON.stringify({ orderId }),
26
+ contentType: 'application/json',
27
+ });
28
+
29
+ if (!response.ok) {
30
+ throw new Error(`Function failed: ${response.status} ${await response.text()}`);
31
+ }
32
+ const result = await response.json();
33
+ ```
34
+
35
+ `compute.invoke` is the only public-key compute operation. Mortar infrastructure
36
+ errors such as unavailable compute or timeout throw; user-function status codes
37
+ are returned as the raw `Response`, so check `response.ok` and consume the body.
38
+ Function deployment/logs remain operator-side capabilities and must never be
39
+ recreated with an app/admin key in a browser or mobile bundle.
40
+
41
+ Functions and external providers do not join the caller's PostgreSQL
42
+ transaction. Make externally visible operations idempotent and persist explicit
43
+ pending/succeeded/failed state when a workflow must recover.
@@ -0,0 +1,33 @@
1
+ # Realtime, broadcast, and presence reference
2
+
3
+ Read this reference for row-change notifications, collaboration events, online
4
+ presence, reconnect behavior, or channel cleanup.
5
+
6
+ ```ts
7
+ const room = mortar
8
+ .channel('todos-room', { presence: { key: userId } })
9
+ .on(
10
+ 'postgres_changes',
11
+ { event: '*', schema: 'public', table: 'todos' },
12
+ (payload) => refreshFromChange(payload),
13
+ )
14
+ .on('broadcast', { event: 'cursor' }, ({ payload }) => drawCursor(payload))
15
+ .on('presence', { event: 'sync' }, () => renderRoster(room.presenceState()))
16
+ .subscribe((status) => {
17
+ if (status === 'SUBSCRIBED') void room.track({ online: true });
18
+ });
19
+
20
+ await room.send({ type: 'broadcast', event: 'cursor', payload: { x, y } });
21
+
22
+ // On component/page cleanup:
23
+ mortar.removeChannel(room);
24
+ ```
25
+
26
+ Persist durable state through `mortar.from(table)`. Realtime only notifies
27
+ peers; broadcast and presence are not a database and must not be the sole copy
28
+ of business data.
29
+
30
+ Use a stable presence key for the signed-in user, keep cursor/ephemeral payloads
31
+ small, and remove channels when their component/page unmounts. The SDK retains
32
+ the last tracked presence state and re-tracks it after reconnect, but the UI
33
+ must still tolerate disconnect/reconnect and duplicate change notifications.
@@ -0,0 +1,98 @@
1
+ # Schema migrations and resource artifacts
2
+
3
+ Read this reference when authoring backend source, reviewing a typed migration
4
+ plan, resolving revision/checksum conflicts, generating app-data types, or
5
+ deciding whether a Mortar capability belongs in source at all.
6
+
7
+ ## One canonical plan, four derived type targets
8
+
9
+ Mortar parses SQL into `mortar.migration-plan.v1`, a language-neutral JSON plan.
10
+ That plan—not TypeScript, Swift, Kotlin, or Dart code—is the execution contract.
11
+ After a successful apply, the canonical live schema may generate app-data models
12
+ for all four SDK languages. Generated models are downstream output; never edit
13
+ them to request a schema change.
14
+
15
+ ## Versioned database SQL
16
+
17
+ Submit exact SQL through `mortar_migrate`:
18
+
19
+ ```sql
20
+ -- mortar:access public_insert
21
+ CREATE TABLE submissions (
22
+ email text NOT NULL,
23
+ score integer,
24
+ metadata jsonb
25
+ );
26
+
27
+ ALTER TABLE submissions ADD COLUMN reviewed boolean NOT NULL;
28
+ CREATE INDEX submissions_reviewed ON submissions (reviewed, created_at);
29
+ ALTER TABLE submissions SET ACCESS app_only;
30
+ ```
31
+
32
+ The parser accepts common PostgreSQL type spellings and canonicalizes them to
33
+ Mortar's logical types: `text`, `number`, `bool`, `json`, `timestamp`, and
34
+ `uuid`. It rejects row data, backfills, triggers, stored procedures, transaction
35
+ control, and calls that pretend functions/storage/cron are SQL.
36
+
37
+ Plan/apply guarantees:
38
+
39
+ - SHA-256 covers the exact SQL bytes written to source.
40
+ - `migration_id + checksum` is idempotent; reusing an id with different bytes is
41
+ a conflict.
42
+ - Every tenant has a monotonic schema revision. Apply fails if the reviewed
43
+ `expected_revision` is stale.
44
+ - All logical schema operations and the revision ledger row commit in one
45
+ tenant-scoped PostgreSQL transaction.
46
+ - Source is written with Fabric's source-revision CAS before apply. If apply
47
+ fails, the file remains an honest pending migration that can be fixed or
48
+ retried; no live mutation is wrapped afterwards as fake SQL.
49
+
50
+ `DROP TABLE` removes its rows and `DROP COLUMN` removes the schema declaration;
51
+ both are destructive plan diagnostics. Changing access must use `ALTER TABLE …
52
+ SET ACCESS`, never drop/recreate.
53
+
54
+ ## Non-database desired state
55
+
56
+ Use `mortar_resource` so Fabric writes the exact artifact before reconciling the
57
+ live resource:
58
+
59
+ | Source path | Managed desired state |
60
+ |---|---|
61
+ | `mortar/functions/<name>/index.js` or `index.py` | Runtime-specific function source |
62
+ | `mortar/functions/<name>/function.json` | Runtime and authoritative entrypoint |
63
+ | `mortar/storage/buckets/<name>.json` | Bucket access policy |
64
+ | `mortar/cron/<name>.json` | Schedule, target function, enabled state |
65
+
66
+ Deleting a function or cron removes its current-state artifact. These resources
67
+ may call external systems and do not join a database migration transaction.
68
+
69
+ ## Complete capability-to-source matrix
70
+
71
+ “Mortar supports it” does not imply it should be a source artifact:
72
+
73
+ | Capability | Source authority | Why |
74
+ |---|---|---|
75
+ | Database tables/columns/access/index intent | Versioned SQL migration | Durable ordered schema with atomic revision ledger |
76
+ | Functions | Function directory | Executable source plus runtime metadata, not SQL |
77
+ | Bucket policy | Bucket JSON | Durable access policy; object bytes are runtime data |
78
+ | Cron schedules | Cron JSON | Durable desired schedule; executions are runtime state |
79
+ | Auth users/sessions/OTP/reset | None | Identity and credentials are runtime/security state |
80
+ | Database rows and row transactions | None | Application data, not schema |
81
+ | Stored files and signed URLs | None | Runtime objects and ephemeral authorization |
82
+ | Realtime row changes/broadcast/presence | None | Events and presence are runtime/ephemeral state |
83
+ | Queue jobs/claims/retries | None | Runtime work state; enqueue may join a row transaction |
84
+ | Analytics events/summaries | None | Runtime observations and derived reporting |
85
+ | Email/SMS sends | None | External side effects; make business workflows idempotent |
86
+ | Secret values | Encrypted Mortar secret store only | Values must never enter project source; source may document names in application-owned docs |
87
+ | Usage, logs, billing ledger | None | Platform observations and accounting authority |
88
+ | Projects, keys, PATs, domains, plans, capacity | None | Account/control-plane state |
89
+
90
+ ## Legacy projects
91
+
92
+ Do not automatically replay or rewrite an existing migration history merely
93
+ because this workflow shipped. Old files can describe already-live schema, and
94
+ some projects may keep explicit operator SQL under `mortar/operations/` for
95
+ capabilities outside Mortar logical DDL. Treat those files as legacy history;
96
+ new supported schema changes start at the next migration id. Arbitrary operator
97
+ SQL remains a separately reviewed operational path and is not silently claimed
98
+ as replayable Mortar migration source.
@@ -0,0 +1,17 @@
1
+ # Project secrets reference
2
+
3
+ Read this reference for function credentials, API tokens, encryption, or secret
4
+ rotation. Secret list/put/delete is a trusted project/operator capability and is
5
+ intentionally absent from the public app-bundle client.
6
+
7
+ Store provider credentials through Mortar's secret provisioning surface. Mortar
8
+ encrypts project secret values at rest and injects the resolved values into the
9
+ configured function environment. Do not copy a secret into Fabric source,
10
+ generated integration files, public environment variables, logs, analytics, or
11
+ function request payloads merely to make it reachable.
12
+
13
+ Application bundles should call a deployed function that uses the secret on the
14
+ server side. Rotation is a separate operation from function invocation; design
15
+ long-running workflows to tolerate a credential changing between attempts. If
16
+ the deployment has no encryption key or secret service configured, report the
17
+ platform failure instead of falling back to plaintext storage.
@@ -0,0 +1,53 @@
1
+ # File storage and consistency reference
2
+
3
+ Read this reference for buckets, uploads/downloads, signed URLs, media rendering,
4
+ CORS, or workflows that relate a database row to an object.
5
+
6
+ ```ts
7
+ const bucket = mortar.storage.from('avatars');
8
+
9
+ const { data: uploaded, error: uploadError } = await bucket.upload(path, file, {
10
+ contentType: file.type,
11
+ upsert: true,
12
+ });
13
+ const { data: blob, error: downloadError } = await bucket.download(path);
14
+ const { data: files, error: listError } = await bucket.list('users/');
15
+ const { data: signed, error: signError } = await bucket.createSignedUrl(path, 3600);
16
+ const { error: removeError } = await bucket.remove([path]);
17
+ ```
18
+
19
+ Applications address files by their stable logical `path`. Mortar stores new
20
+ uploads under immutable internal physical versions, atomically switches its
21
+ PostgreSQL metadata pointer, and schedules orphan/replaced versions for durable
22
+ retryable cleanup. Never construct, persist, or expose an internal physical
23
+ object key. Application projects do not run or author the platform storage
24
+ migration.
25
+
26
+ Set durable bucket access through `mortar_resource`; Fabric writes
27
+ `mortar/storage/buckets/<name>.json` before reconciling the live policy. Object
28
+ bytes, logical object keys, signed URLs, and cleanup jobs remain runtime state
29
+ and never enter this source artifact.
30
+
31
+ This protocol prevents Mortar metadata from pointing at a partial upload, but a
32
+ separate application row and an object upload are not one transaction. Generate
33
+ an unguessable logical key. For a simple workflow, upload first and write the
34
+ business row after success. For recoverable workflows, create a row with a
35
+ stable idempotency key and `pending` state, upload, then mark it `ready`; reconcile
36
+ stale rows to `failed` or retry them. Do not run a database write and
37
+ `bucket.upload()` concurrently and call the result atomic.
38
+
39
+ Downloads mint a short-lived URL and fetch the object directly from the storage
40
+ backend. Reuse `signed.signedUrl` until near expiry and prefer it directly in
41
+ `<img>`, `<video>`, or `<audio>` instead of making an unnecessary blob copy.
42
+ Signed-URL minting is excluded from the project's API-concurrency budget, but
43
+ clients should lazy-load media and cache/reuse each URL. Signing authorizes a
44
+ possible download; it does not prove bytes were served and is not an
45
+ object-egress charge. Only provider access logs or billing reconciliation may
46
+ record actual object egress.
47
+
48
+ The optional third `createSignedUrl` argument (`{ signal, timeoutMs }`) bounds
49
+ only the signing request. `bucket.download(path)` uses JavaScript `fetch()` and
50
+ therefore needs a matching bucket CORS rule in browsers. Appunvs-managed Mortar
51
+ provides that contract; self-hosted storage must allow credential-free `GET` and
52
+ `HEAD` from the app origin. Never attach Mortar Authorization or cookies to a
53
+ signed object URL.
@@ -0,0 +1,27 @@
1
+ # Usage, managed logs, and billing reference
2
+
3
+ Read this reference for quota, balance, overage, managed logs, or network/object
4
+ egress attribution. The 9 primitives are infrastructure and cost-attribution
5
+ boundaries; user-facing usage and billing are capabilities built over selected
6
+ observations, meters, allowances, pricing policy, and the credit ledger.
7
+
8
+ A primitive is never inherently free or billable. For example, warehouse event
9
+ ingestion is metered while a warehouse summary read is not. Keep four facts
10
+ separate: provider cost, observed units, included allowance, and customer
11
+ charge. Primitive interfaces remain unaware of those policies.
12
+
13
+ Project usage and audit-log reads are trusted project/operator surfaces, with an
14
+ account-owned project usage view on the account control plane. They are not part
15
+ of Fabric's public bundle singleton. A balance or quota read does not itself
16
+ create a metered observation or customer charge.
17
+
18
+ Do not estimate object egress from signed-URL minting or object size. Signing is
19
+ authorization and the URL may never be used or may be reused many times. Actual
20
+ direct OSS/COS/OBS bytes require provider access logs or billing reconciliation.
21
+ Mortar-served responses can be counted at the response writer, but direct object
22
+ downloads bypass it.
23
+
24
+ Managed-log billing uses provider-canonical ingestion records with durable IDs
25
+ for deduplication; query response bytes are not ingestion. Function logs retain
26
+ their function/source context. Never turn a read query or repeated reconciliation
27
+ scan into duplicate usage rows.