@mortar-ai/skill 0.2.0 → 0.5.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,22 +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
- - **CLI commands** — `mortar login / use / db / storage / compute /
39
- cron / queue / domains / comms / keys / tokens / topup / usage`
40
- - **MCP tools** — the 40 `mortar_*` tools and when an agent should use
41
- them vs the SDK
42
- - **Tenant isolation + RLS** — `auth.tenant_id()` (Mortar's analog of
43
- Supabase's `auth.uid()`), the `tenant_id = auth.tenant_id()` policy
44
- shape, and why every per-project table carries a `tenant_id`
45
- - **Architecture** — the 9 primitive × (5 core + 3 extension) feature
46
- model, and why the SDK shape the billing shape
47
- - **Common patterns** — auth (email+password / phone-OTP), data CRUD,
48
- file upload + signed URLs, realtime subscribe, queue + cron
49
- - **Anti-patterns** assuming Fabric runs on Mortar; using `namespace`
50
- 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.
51
56
 
52
57
  ## Custom install location
53
58
 
@@ -64,8 +69,9 @@ Idempotent — overwrites in place.
64
69
 
65
70
  ## Maintainer release
66
71
 
67
- `SKILL.md` is the only hand-edited instruction source. Regenerate the manifest
68
- 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:
69
75
 
70
76
  ```bash
71
77
  npm run sync
@@ -73,6 +79,8 @@ npm run check
73
79
  npm pack --dry-run
74
80
  ```
75
81
 
82
+ The repository-level synchronizer is shared with Keel and fails on missing
83
+ configured reference directories, resource drift, or unexpected Harness files.
76
84
  Never edit `fabric/harness/internal/skills/preinstalled/mortar/` directly.
77
85
 
78
86
  ## License
package/SKILL.md CHANGED
@@ -1,10 +1,11 @@
1
1
  ---
2
- name: Mortar
3
- description: Required guidance for Fabric apps that need a database, backend, authentication, persistence, file storage, realtime collaboration, cloud functions, or product analytics.
4
- activation: auto
5
- priority: high
6
- triggers:
7
- - keywords: [database, 数据库, backend, 后端, auth, authentication, 登录, 认证, persistence, 持久化, storage, upload, 文件, realtime, 实时, collaboration, compute, cloud, 云端]
2
+ name: mortar
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
+ metadata:
5
+ activation: auto
6
+ priority: high
7
+ triggers:
8
+ - keywords: [database, 数据库, backend, 后端, auth, authentication, 登录, 认证, persistence, 持久化, transaction, 事务, ACID, storage, upload, 文件, realtime, 实时, collaboration, function, compute, analytics, cron, queue, comms, secrets, billing, cloud, 云端]
8
9
  ---
9
10
 
10
11
  # Mortar for Fabric applications
@@ -58,152 +59,94 @@ below; if platform provisioning fails, report that platform failure.
58
59
  SQL there: a file in that directory does not execute or change the live
59
60
  tenant. Do not edit or create `mortar.config.json`. Do not edit generated
60
61
  `integrations/mortar/types.ts`; Fabric regenerates it from the live schema.
61
- 4. Run `npm install @mortar-ai/client` if the dependency is absent, then write app
62
- code using the injected singleton.
63
- 5. Type-check and build. Handle every `{ data, error }` result in UI code.
62
+ 4. Install or update `@mortar-ai/client` with npm when needed and let npm own the
63
+ lockfile. Atomic batches, server-side filtered mutations, and
64
+ `DatabaseTransactions` require version 0.8.0 or newer. Do not rewrite
65
+ unrelated dependencies.
66
+ 5. Before a multi-step mutation, choose the real consistency boundary. A
67
+ one-table batch or filtered mutation may use the public singleton;
68
+ cross-table row + queue transactions require trusted server code; object
69
+ storage and external services never join a PostgreSQL transaction.
70
+ 6. Type-check and build. Handle every `{ data, error }` SDK result in UI code.
64
71
 
65
72
  Supported migration kinds are `create_table`, `drop_table`, `set_table_access`,
66
- `add_column`, `drop_column`, `create_index`, `set_bucket_access`, `deploy_function`, `delete_function`,
67
- `upsert_cron`, and `delete_cron`. Destructive and backend-changing operations
68
- must respect the workspace approval gate. This list is exhaustive: arbitrary
69
- PostgreSQL, data backfills, PL/pgSQL functions, transactions, and triggers are
70
- not migration inputs. If required behavior cannot be expressed by one of these
71
- operations, report the unsupported Mortar capability instead of authoring a SQL
72
- file that only looks executable.
73
-
74
- To change an existing table's access policy, use `set_table_access`. Access is
75
- metadata and must be updated in place. Never use `drop_table` followed by
76
- `create_table` for a permission change: dropping a table deletes all of its
77
- rows and columns.
78
-
79
- For a public form, event registration, evidence upload, or similar operator-
80
- reviewed intake, use `access: "public_insert"` on the destination table and
81
- `set_bucket_access` with `access: "public_insert"` on any upload bucket. This
82
- allows a public bundle client to create a new row/object while denying public
83
- list, read, update, delete, and overwrite. Never use `public` merely to make an
84
- anonymous form submit successfully; `public` exposes all rows/files for public
85
- read and mutation. Generate an unguessable object key for every upload.
86
-
87
- ## Database SDK
88
-
89
- The query surface is Supabase-shaped. Async row/auth/storage methods normally
90
- return `{ data, error }`; check `error` rather than assuming success.
91
-
92
- ```ts
93
- const { data: todos, error } = await mortar
94
- .from('todos')
95
- .select('*')
96
- .eq('done', false)
97
- .order('created_at', { ascending: false })
98
- .limit(20);
99
- if (error) throw new Error(error.message);
100
-
101
- const { data: created, error: insertError } = await mortar
102
- .from('todos')
103
- .insert({ title: 'Ship', done: false, user_id: userId })
104
- .select()
105
- .single();
106
-
107
- ```
108
-
109
- Updates and deletes use the same filter chain. Useful operators include `eq`,
110
- `neq`, ranges, `like/ilike`, `in`, `is`, `order`, `limit`, `single`, and
111
- `maybeSingle`.
112
-
113
- ## App-user authentication
114
-
115
- Auth sessions are persisted and attached to later database, storage, realtime,
116
- and compute calls by the SDK.
117
-
118
- ```ts
119
- const { data, error } = await mortar.auth.signUp({ email, password });
120
- const signedIn = await mortar.auth.signInWithPassword({ email, password });
121
-
122
- const sent = await mortar.auth.signInWithOtp({ email }); // or { phone }
123
- const verified = await mortar.auth.verifyOtp({ email, token });
124
-
125
- const { data: sessionData } = await mortar.auth.getSession();
126
- const { data: userData, error: userError } = await mortar.auth.getUser();
127
- await mortar.auth.signOut();
128
- ```
129
-
130
- Use `onAuthStateChange` when UI must react to session changes, and unsubscribe
131
- the returned subscription during cleanup.
132
-
133
- Password reset starts with `mortar.auth.resetPasswordForEmail(email)`; confirm via
134
- `passwordResetConfirm(email, token, newPassword)`. Never store or pass session
135
- tokens manually unless the user explicitly needs a server integration.
136
-
137
- For private user rows, include the authenticated user's ID in the schema/data
138
- model and choose the correct table access policy. Client-side filters improve
139
- UX but do not replace server-side access rules.
140
-
141
- ## File storage
142
-
143
- ```ts
144
- const bucket = mortar.storage.from('avatars');
145
-
146
- const { data: uploaded, error: uploadError } = await bucket.upload(path, file, {
147
- contentType: file.type,
148
- upsert: true,
149
- });
150
- const { data: blob, error: downloadError } = await bucket.download(path);
151
- const { data: files, error: listError } = await bucket.list('users/');
152
- const { data: signed, error: signError } = await bucket.createSignedUrl(path, 3600);
153
- const { error: removeError } = await bucket.remove([path]);
154
- ```
155
-
156
- Downloads mint a short-lived URL and fetch the object directly from the storage
157
- backend. Reuse `signed.signedUrl` until it nears expiry when rendering media.
158
- Prefer the signed URL directly in `<img src>`, `<video src>`, or `<audio src>`;
159
- this avoids an unnecessary blob copy and does not require CORS merely to render
160
- the media. Signed-URL minting is excluded from the project's API-concurrency
161
- budget, but clients should still lazy-load visible media and cache/reuse each
162
- URL instead of signing again on every render. Signing authorizes a possible
163
- download; it does not prove bytes were served and must never be described or
164
- modeled as an object-egress charge. Only provider-observed access-log or billing
165
- reconciliation may record actual object egress. The optional third
166
- `createSignedUrl` argument (`{ signal, timeoutMs }`) bounds only the signing
167
- request when the UI needs a deadline.
168
-
169
- `bucket.download(path)` uses JavaScript `fetch()` against the object store and
170
- therefore needs a matching bucket CORS rule in browsers. Appunvs-managed Mortar
171
- storage provides that contract; self-hosted object storage must allow the app
172
- origin for credential-free `GET` / `HEAD`. Never attach Mortar Authorization or
173
- browser cookies to a signed object URL.
174
-
175
- ## Realtime, broadcast, and presence
176
-
177
- ```ts
178
- const room = mortar
179
- .channel('todos-room', { presence: { key: userId } })
180
- .on(
181
- 'postgres_changes',
182
- { event: '*', schema: 'public', table: 'todos' },
183
- (payload) => refreshFromChange(payload),
184
- )
185
- .on('broadcast', { event: 'cursor' }, ({ payload }) => drawCursor(payload))
186
- .on('presence', { event: 'sync' }, () => renderRoster(room.presenceState()))
187
- .subscribe((status) => {
188
- if (status === 'SUBSCRIBED') void room.track({ online: true });
189
- });
190
-
191
- await room.send({ type: 'broadcast', event: 'cursor', payload: { x, y } });
192
- // On component/page cleanup:
193
- mortar.removeChannel(room);
73
+ `add_column`, `drop_column`, `create_index`, `set_bucket_access`,
74
+ `deploy_function`, `delete_function`, `upsert_cron`, and `delete_cron`.
75
+ Destructive and backend-changing operations must respect the workspace approval
76
+ gate. This list is exhaustive: arbitrary PostgreSQL, data backfills, PL/pgSQL
77
+ functions, transactions, and triggers are not migration inputs. If required
78
+ behavior cannot be expressed by one of these operations, report the unsupported
79
+ Mortar capability instead of authoring a SQL file that only looks executable.
80
+
81
+ ## Access and safety invariants
82
+
83
+ - Change an existing table's access with `set_table_access`; never drop and
84
+ recreate a table merely to change permissions.
85
+ - For a public form, registration, or evidence upload reviewed by an operator,
86
+ use table/bucket access `public_insert`. It permits new submissions while
87
+ denying anonymous list, read, update, delete, and overwrite.
88
+ - Never use `public` merely to make anonymous submission work: it exposes all
89
+ rows or files for public read and mutation.
90
+ - Generate an unguessable logical object key for each public upload.
91
+ - Client-side filters are UX, not authorization. Choose a server-enforced access
92
+ policy for private or owner-scoped rows.
93
+ - Never put `app` or `admin` keys, `DatabaseTransactions`, operator clients, or
94
+ other privileged capabilities in Web, Desktop renderer, Keel/React Native, or
95
+ WeChat bundles.
96
+ - Never call a database + storage/functions/external workflow "atomic". Only
97
+ work committed inside one tenant-scoped PostgreSQL transaction is ACID.
98
+
99
+ ## On-demand references
100
+
101
+ The following detailed references are bundled beside this core Skill. Read only
102
+ the documents needed for the current task; do not load all of them by default.
103
+ In Fabric they are materialized under `/tmp/skills/mortar/references/`; other
104
+ Agent Skills hosts resolve the same paths relative to this `SKILL.md`.
105
+
106
+ - Database CRUD, atomic row mutations, trusted cross-table transactions, and
107
+ exact ACID boundaries: `references/database.md`
108
+ - App-user signup/signin, OTP, session lifecycle, and private-user modeling:
109
+ `references/auth.md`
110
+ - Upload/download/list/signed URLs, immutable object versions, CORS, and
111
+ database-to-object consistency: `references/storage.md`
112
+ - Postgres changes, broadcast, presence, reconnect behavior, and cleanup:
113
+ `references/realtime.md`
114
+ - Function deployment/invocation, logs, public-key boundaries, and error
115
+ handling: `references/functions.md`
116
+ - The 9 infrastructure primitives, open capability registry, application
117
+ modules, usage accounting, and trust surfaces: `references/architecture.md`
118
+ - Product analytics, error capture, delivery guarantees, and operator summary:
119
+ `references/analytics.md`
120
+ - Cron schedules, queues, workers, retries, and transactional enqueue:
121
+ `references/automation.md`
122
+ - Transactional email/SMS and provider/idempotency boundaries:
123
+ `references/communications.md`
124
+ - Encrypted project secrets and function environment injection:
125
+ `references/secrets.md`
126
+ - Project usage, managed logs, billing attribution, and egress semantics:
127
+ `references/usage-billing.md`
128
+ - Accounts, projects, API keys, PATs, custom domains, top-up, and capacity:
129
+ `references/control-plane.md`
130
+
131
+ When using Fabric `bash`, read the exact materialized path shown by `load_skill`,
132
+ for example:
133
+
134
+ ```sh
135
+ sed -n '1,240p' /tmp/skills/mortar/references/storage.md
194
136
  ```
195
137
 
196
- Clean up channels with their page/component. Persist through `from(table)`;
197
- realtime only notifies peers.
198
-
199
- ## Compute and analytics
138
+ ## Completion checklist
200
139
 
201
- Invoke only functions already deployed through `mortar_migrate`; check
202
- `response.ok` and read the response body. Analytics provides `track`,
203
- `identify`, `captureError`, and `flush`; events are fire-and-forget except
204
- `flush`.
140
+ - Live schema was read before schema design.
141
+ - Every live schema change went through one approved `mortar_migrate` operation.
142
+ - Application code imports only the platform-owned singleton.
143
+ - No secret/admin client or hand-written fallback configuration entered the
144
+ client bundle.
145
+ - Every SDK error path is handled.
146
+ - The relevant on-demand reference—not every reference—was consulted.
147
+ - Type-check and the surface's real build pass.
205
148
 
206
149
  ## Boundary for non-Fabric development
207
150
 
208
- For Mortar itself or a standalone non-Fabric integration, inspect the current SDK
209
- README. Never copy its standalone `createClient` setup into a Fabric project.
151
+ For Mortar itself or a standalone non-Fabric integration, inspect the current
152
+ 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.2.0",
5
- "content_sha256": "sha256:dd11d5e635f47e64303927fd4af21b0f8a9aef8836a17c3a592eecb69cc75325",
4
+ "version": "0.5.0",
5
+ "content_sha256": "sha256:cbfe4219fa15934c523c1c22fe9795489539d0083d199624d2d15ac50bfe7755",
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.2.0"
14
+ "version": "0.5.0"
15
15
  }
16
16
  }
package/package.json CHANGED
@@ -1,15 +1,15 @@
1
1
  {
2
2
  "name": "@mortar-ai/skill",
3
- "version": "0.2.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.5.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`, `queue`,
25
+ `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_migrate` 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,38 @@
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_migrate` operation with
9
+ kind `deploy_function` or `delete_function`. Do not edit `mortar.config.json`,
10
+ write migration files, 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
+ Client bundles may invoke a function that the platform already deployed:
15
+
16
+ ```ts
17
+ const response = await mortar.compute.invoke({
18
+ name: 'create-checkout',
19
+ method: 'POST',
20
+ body: JSON.stringify({ orderId }),
21
+ contentType: 'application/json',
22
+ });
23
+
24
+ if (!response.ok) {
25
+ throw new Error(`Function failed: ${response.status} ${await response.text()}`);
26
+ }
27
+ const result = await response.json();
28
+ ```
29
+
30
+ `compute.invoke` is the only public-key compute operation. Mortar infrastructure
31
+ errors such as unavailable compute or timeout throw; user-function status codes
32
+ are returned as the raw `Response`, so check `response.ok` and consume the body.
33
+ Function deployment/logs remain operator-side capabilities and must never be
34
+ recreated with an app/admin key in a browser or mobile bundle.
35
+
36
+ Functions and external providers do not join the caller's PostgreSQL
37
+ transaction. Make externally visible operations idempotent and persist explicit
38
+ 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,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,48 @@
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
+ This protocol prevents Mortar metadata from pointing at a partial upload, but a
27
+ separate application row and an object upload are not one transaction. Generate
28
+ an unguessable logical key. For a simple workflow, upload first and write the
29
+ business row after success. For recoverable workflows, create a row with a
30
+ stable idempotency key and `pending` state, upload, then mark it `ready`; reconcile
31
+ stale rows to `failed` or retry them. Do not run a database write and
32
+ `bucket.upload()` concurrently and call the result atomic.
33
+
34
+ Downloads mint a short-lived URL and fetch the object directly from the storage
35
+ backend. Reuse `signed.signedUrl` until near expiry and prefer it directly in
36
+ `<img>`, `<video>`, or `<audio>` instead of making an unnecessary blob copy.
37
+ Signed-URL minting is excluded from the project's API-concurrency budget, but
38
+ clients should lazy-load media and cache/reuse each URL. Signing authorizes a
39
+ possible download; it does not prove bytes were served and is not an
40
+ object-egress charge. Only provider access logs or billing reconciliation may
41
+ record actual object egress.
42
+
43
+ The optional third `createSignedUrl` argument (`{ signal, timeoutMs }`) bounds
44
+ only the signing request. `bucket.download(path)` uses JavaScript `fetch()` and
45
+ therefore needs a matching bucket CORS rule in browsers. Appunvs-managed Mortar
46
+ provides that contract; self-hosted storage must allow credential-free `GET` and
47
+ `HEAD` from the app origin. Never attach Mortar Authorization or cookies to a
48
+ 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.