@agent-native/core 0.39.1 → 0.39.2

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.
Files changed (61) hide show
  1. package/dist/cli/index.js +1 -1
  2. package/dist/cli/index.js.map +1 -1
  3. package/dist/cli/skills.d.ts +5 -6
  4. package/dist/cli/skills.d.ts.map +1 -1
  5. package/dist/cli/skills.js +430 -723
  6. package/dist/cli/skills.js.map +1 -1
  7. package/dist/client/MultiTabAssistantChat.d.ts.map +1 -1
  8. package/dist/client/MultiTabAssistantChat.js +2 -5
  9. package/dist/client/MultiTabAssistantChat.js.map +1 -1
  10. package/dist/client/NewWorkspaceAppFlow.js +1 -1
  11. package/dist/client/NewWorkspaceAppFlow.js.map +1 -1
  12. package/dist/client/settings/SettingsPanel.d.ts.map +1 -1
  13. package/dist/client/settings/SettingsPanel.js +11 -19
  14. package/dist/client/settings/SettingsPanel.js.map +1 -1
  15. package/dist/client/use-chat-models.d.ts.map +1 -1
  16. package/dist/client/use-chat-models.js +2 -5
  17. package/dist/client/use-chat-models.js.map +1 -1
  18. package/dist/deploy/build.d.ts.map +1 -1
  19. package/dist/deploy/build.js +2 -1
  20. package/dist/deploy/build.js.map +1 -1
  21. package/dist/deploy/route-discovery.d.ts +29 -0
  22. package/dist/deploy/route-discovery.d.ts.map +1 -1
  23. package/dist/deploy/route-discovery.js +158 -11
  24. package/dist/deploy/route-discovery.js.map +1 -1
  25. package/dist/server/auth.d.ts +2 -0
  26. package/dist/server/auth.d.ts.map +1 -1
  27. package/dist/server/auth.js +9 -0
  28. package/dist/server/auth.js.map +1 -1
  29. package/dist/templates/default/.agents/skills/actions/SKILL.md +96 -11
  30. package/dist/templates/default/.agents/skills/adding-a-feature/SKILL.md +126 -26
  31. package/dist/templates/default/.agents/skills/capture-learnings/SKILL.md +56 -30
  32. package/dist/templates/default/.agents/skills/create-skill/SKILL.md +28 -0
  33. package/dist/templates/default/.agents/skills/delegate-to-agent/SKILL.md +75 -5
  34. package/dist/templates/default/.agents/skills/frontend-design/SKILL.md +17 -0
  35. package/dist/templates/default/.agents/skills/real-time-collab/SKILL.md +99 -124
  36. package/dist/templates/default/.agents/skills/real-time-sync/SKILL.md +43 -10
  37. package/dist/templates/default/.agents/skills/security/SKILL.md +162 -144
  38. package/dist/templates/default/.agents/skills/self-modifying-code/SKILL.md +5 -3
  39. package/dist/templates/default/.agents/skills/shadcn-ui/SKILL.md +15 -0
  40. package/dist/templates/default/.agents/skills/storing-data/SKILL.md +116 -83
  41. package/dist/templates/default/DEVELOPING.md +10 -13
  42. package/dist/templates/workspace-core/.agents/skills/client-methods/references/legacy-client-fetch-audit-2026-06-03.md +9 -0
  43. package/dist/templates/workspace-core/.agents/skills/writing-agent-instructions/SKILL.md +27 -0
  44. package/docs/content/template-plan.md +5 -3
  45. package/docs/content/visual-plans.md +5 -2
  46. package/package.json +1 -1
  47. package/src/templates/default/.agents/skills/actions/SKILL.md +96 -11
  48. package/src/templates/default/.agents/skills/adding-a-feature/SKILL.md +126 -26
  49. package/src/templates/default/.agents/skills/capture-learnings/SKILL.md +56 -30
  50. package/src/templates/default/.agents/skills/create-skill/SKILL.md +28 -0
  51. package/src/templates/default/.agents/skills/delegate-to-agent/SKILL.md +75 -5
  52. package/src/templates/default/.agents/skills/frontend-design/SKILL.md +17 -0
  53. package/src/templates/default/.agents/skills/real-time-collab/SKILL.md +99 -124
  54. package/src/templates/default/.agents/skills/real-time-sync/SKILL.md +43 -10
  55. package/src/templates/default/.agents/skills/security/SKILL.md +162 -144
  56. package/src/templates/default/.agents/skills/self-modifying-code/SKILL.md +5 -3
  57. package/src/templates/default/.agents/skills/shadcn-ui/SKILL.md +15 -0
  58. package/src/templates/default/.agents/skills/storing-data/SKILL.md +116 -83
  59. package/src/templates/default/DEVELOPING.md +10 -13
  60. package/src/templates/workspace-core/.agents/skills/client-methods/references/legacy-client-fetch-audit-2026-06-03.md +9 -0
  61. package/src/templates/workspace-core/.agents/skills/writing-agent-instructions/SKILL.md +27 -0
@@ -1,222 +1,240 @@
1
1
  ---
2
2
  name: security
3
3
  description: >-
4
- Secure coding guide for agent-native apps. Covers input validation, SQL
5
- injection prevention, XSS, secrets management, auth patterns, data scoping,
6
- and A2A security. Read this when generating any code that handles user data.
4
+ Secure coding practices for agent-native apps: input validation, SQL
5
+ injection, XSS, secrets, data scoping, and auth. Use when writing any action,
6
+ route, or component that touches user data or external input.
7
+ metadata:
8
+ internal: true
7
9
  ---
8
10
 
9
11
  # Security
10
12
 
11
- The framework provides strong security primitives. Use them — don't reinvent security.
13
+ ## Rule
14
+
15
+ Use the framework's security primitives everywhere. Never bypass them.
16
+
17
+ ## Absolute Secrets Rule
18
+
19
+ Never hardcode secret values or real private data. This applies to source code,
20
+ docs, tests, fixtures, generated prompts, screenshots, seed data, and extension
21
+ HTML just as much as production code.
22
+
23
+ Do not paste or invent real-looking API keys, bearer tokens, OAuth refresh
24
+ tokens, webhook URLs, signing secrets, private Builder/internal data, or customer
25
+ data into the repo. Examples must use obvious placeholders such as
26
+ `<OPENAI_API_KEY>`, `${keys.SLACK_WEBHOOK}`, `sk-test-example`, or
27
+ `example.customer@example.com`. Test literals should be clearly fake and must
28
+ not match real provider token formats when an `example` token will do.
29
+
30
+ Credential values enter the system only through approved runtime channels:
31
+ deployment env vars for deploy-level secrets, the encrypted `app_secrets` vault
32
+ or `saveCredential` / `resolveCredential` for user/org/workspace API keys, and
33
+ `oauth_tokens` for OAuth. Code and instructions may name the credential key
34
+ (`OPENAI_API_KEY`), but must never contain the credential value.
12
35
 
13
36
  ## Input Validation
14
37
 
15
- **Always use `defineAction` with a Zod `schema:`** for every action that accepts user input. The framework validates automatically and returns clear error messages.
38
+ Use `defineAction` with a Zod `schema:` for every action. The framework validates input automatically and returns clear 400 errors for HTTP callers and structured error results for agent tool calls.
16
39
 
17
40
  ```ts
18
- // SECURE — framework validates before run() is called
19
41
  export default defineAction({
20
- description: "Create a note",
21
42
  schema: z.object({
22
- title: z.string().min(1).max(200),
23
- content: z.string().optional(),
43
+ email: z.string().email(),
44
+ role: z.enum(["admin", "member"]),
45
+ limit: z.coerce.number().int().min(1).max(100).default(25),
24
46
  }),
25
- run: async (args) => {
26
- // args is guaranteed valid — { title: string; content?: string }
27
- },
47
+ run: async (args) => { /* args is fully typed and validated */ },
28
48
  });
29
49
  ```
30
50
 
31
- The legacy `parameters:` format (plain JSON Schema) has **no runtime validation**the agent receives whatever the caller sends. Do not use it for new code.
32
-
33
- Actions without a `schema:` are unvalidated. This is acceptable for internal/dev scripts but never for user-facing operations.
51
+ The legacy `parameters:` field (plain JSON Schema) has no runtime validation — do not use it for new code.
34
52
 
35
- ## SQL Injection Prevention
53
+ ## SQL Injection
36
54
 
37
- The framework's `db-query` and `db-exec` tools use **parameterized queries** (`?` placeholders). The database driver handles escaping user input never touches the SQL string.
55
+ Never concatenate user input into SQL strings. Use Drizzle ORM's query builder (always safe) or parameterized queries:
38
56
 
39
57
  ```ts
40
- // WRONGSQL injection vulnerability
41
- await exec(`INSERT INTO notes (title) VALUES ('${title}')`)
42
- await exec(`SELECT * FROM notes WHERE title LIKE '%${search}%'`)
58
+ // SafeDrizzle ORM
59
+ await db.select().from(users).where(eq(users.email, args.email));
43
60
 
44
- // RIGHT — parameterized queries (framework default)
45
- await exec({ sql: "INSERT INTO notes (title) VALUES (?)", args: [title] })
46
- await exec({ sql: "SELECT * FROM notes WHERE title LIKE ?", args: [`%${search}%`] })
61
+ // Safe — parameterized raw SQL
62
+ await client.execute({ sql: "SELECT * FROM users WHERE id = ?", args: [id] });
63
+
64
+ // NEVER do this
65
+ await client.execute(`SELECT * FROM users WHERE id = '${id}'`);
47
66
  ```
48
67
 
49
- **Drizzle ORM is always safe** — it generates parameterized queries automatically:
68
+ ## XSS
69
+
70
+ - React auto-escapes JSX content — trust it.
71
+ - Never use `dangerouslySetInnerHTML`, `innerHTML`, `eval()`, or `document.write()` with user-controlled content.
72
+ - For rich text editing, use TipTap (framework dependency).
73
+ - For rendering markdown, use `react-markdown`.
74
+
75
+ ## SSRF
76
+
77
+ Any server-side `fetch` of a user- or agent-controlled URL must go through the framework SSRF guard — a bare `fetch()` can be steered at cloud metadata (`169.254.169.254`), `localhost`, or internal services.
50
78
 
51
79
  ```ts
52
- const notes = await db.select().from(notesTable).where(eq(notesTable.title, title));
80
+ import { ssrfSafeFetch } from "@agent-native/core/extensions/url-safety";
81
+ // Blocks private/internal targets, re-checks the resolved IP at connect time
82
+ // (DNS rebinding), and re-validates every redirect hop.
83
+ const res = await ssrfSafeFetch(userProvidedUrl, {}, { maxRedirects: 3 });
53
84
  ```
54
85
 
55
- **When is SQL injection a risk?**
56
- - Only when writing raw SQL with string concatenation in server routes or actions
57
- - Never when using `db-query`/`db-exec` with `args` arrays
58
- - Never when using Drizzle ORM
86
+ For a pre-flight-only check (e.g. before a streaming or one-shot fetch), use `isBlockedExtensionUrlWithDns(url)` plus `createSsrfSafeDispatcher()` from the same module, and set `redirect: "manual"`. Never let the default `fetch` follow redirects for an untrusted URL — a public URL can 30x into the private network.
59
87
 
60
- ## XSS Prevention
88
+ ## Secrets
61
89
 
62
- React auto-escapes all JSX expressions by default. Trust it.
90
+ - OAuth tokens go in the `oauth_tokens` store via `saveOAuthTokens()`.
91
+ - Per-user / per-org API keys go through `saveCredential` / `resolveCredential` (`@agent-native/core/credentials`) or the `app_secrets` vault. Both encrypt values at rest with AES-256-GCM (keyed by `SECRETS_ENCRYPTION_KEY`, falling back to `BETTER_AUTH_SECRET`; production refuses to start without one).
92
+ - Never hand-roll secrets into `settings`, `application_state`, source code, or action responses sent to the client. The credential / vault APIs above are the only sanctioned stores.
93
+ - Never commit real keys, tokens, webhook URLs, signing secrets, or private
94
+ Builder/customer data in examples or fixtures. Use placeholders that cannot be
95
+ mistaken for working credentials.
63
96
 
64
- ```tsx
65
- // SAFE — React escapes the output
66
- <p>{userInput}</p>
67
- <span>{comment.text}</span>
97
+ ## User Credentials Are Per-User Data — Never `process.env`
68
98
 
69
- // DANGEROUS bypasses React's escaping
70
- <div dangerouslySetInnerHTML={{ __html: userInput }} /> // NEVER with user content
71
- element.innerHTML = userInput; // NEVER
72
- eval(userInput); // NEVER
73
- document.write(userInput); // NEVER
74
- new Function(userInput); // NEVER
75
- ```
99
+ User credentials (API keys, third-party tokens) are per-user (or per-org) data. They MUST live in SQL, scoped per-user (`u:<email>:credential:KEY`) or per-org (`o:<orgId>:credential:KEY`). Always read with the request context:
76
100
 
77
- **For rich text:** Use TipTap (framework dependency) with the Collaboration extension. TipTap sanitizes content through its schema — only allowed node types render.
101
+ ```ts
102
+ import { resolveCredential } from "@agent-native/core/credentials";
103
+ const apiKey = await resolveCredential("OPENAI_API_KEY", { userEmail, orgId });
104
+ ```
78
105
 
79
- **For markdown:** Use `react-markdown` (already used in the framework). It parses markdown to React elements without `dangerouslySetInnerHTML`.
106
+ Values are encrypted at rest (AES-256-GCM, shared `secrets/crypto.ts`): `saveCredential` encrypts on write and `resolveCredential` decrypts on read, with a transparent fallback for legacy plaintext rows. The agent's raw `db-query` / `db-exec` tools also cannot read credential rows — they are excluded from the scoped `settings` view. To encrypt pre-existing rows in place, run `pnpm action db-migrate-encrypt-credentials` (idempotent, non-destructive; needs the same `SECRETS_ENCRYPTION_KEY` / `BETTER_AUTH_SECRET` as the app).
80
107
 
81
- **For HTML from external sources:** If you absolutely must render external HTML, use a sanitization library like `dompurify`. But prefer structured data (markdown, TipTap JSON) over raw HTML.
108
+ On 2026-04-29 the previous one-arg `resolveCredential(key)` form fell back to `process.env[key]` and an unscoped global `settings` row, so every signed-in user inherited the deployment's credentials. Two guards now block this in CI (`pnpm prep`):
82
109
 
83
- ## Secrets Management
110
+ - `scripts/guard-no-env-credentials.mjs` — bans `process.env.<KEY>` reads in `packages/core/src/credentials/`, `secrets/`, `vault/`, and `templates/*/server/{lib,routes/api}/credential*` paths, except for an explicit allowlist of deploy-level vars (`DATABASE_URL`, `BETTER_AUTH_SECRET`, `NETLIFY_*`, etc.). Per-line opt-out: `// guard:allow-env-credential — <reason>`.
111
+ - `scripts/guard-no-unscoped-credentials.mjs` — bans one-arg calls to `resolveCredential` / `hasCredential` / `saveCredential` / `deleteCredential`. Per-line opt-out: `// guard:allow-unscoped-credential — <reason>`.
84
112
 
85
- Never hardcode credential values or real private data. Source, docs, tests,
86
- fixtures, prompts, screenshots, seed data, application state, action responses,
87
- and generated app/extension content may name credential keys, but must never
88
- contain API key values, tokens, webhook URLs, signing secrets, private
89
- Builder/internal data, or customer data.
113
+ If a deploy-level value genuinely needs an env var (CI-set token, host secret), it's not a user credential — keep it out of the credentials/ secrets/ vault/ paths and the env-credentials guard won't see it.
90
114
 
91
- | Secret type | Where to store | Why |
92
- |-------------|----------------|-----|
93
- | User/org/workspace API keys, service tokens, webhook secrets | Secrets registry / `app_secrets` vault or `saveCredential` / `resolveCredential` | Encrypted, scoped, never sent to the client |
94
- | Deploy-only infrastructure secrets | Deployment env vars (`.env` only for local dev) | Server-side runtime configuration |
95
- | OAuth tokens (Google, GitHub) | `oauth_tokens` store | Per-user, per-provider, server-side |
96
- | App configuration | `settings` store | OK for non-secret config (themes, preferences) |
97
- | Session tokens | Framework handles | Automatic via Better Auth |
115
+ ## Guards
98
116
 
99
- **Rules:**
100
- - Never store secrets in `settings`, `application_state`, or source code
101
- - Never return secrets in action responses — they may appear in agent chat or client UI
102
- - Never log secrets (tokens, keys, passwords)
103
- - Never commit `.env` files — they're gitignored by default
104
- - Use env vars only for deploy-level secrets. User/org/workspace credentials
105
- must use the encrypted secrets/credential/OAuth stores.
106
- - Access env vars via `process.env` in actions/server code, never send them to the client
117
+ Two more CI guards (also wired into `pnpm prep`) target the 2026-04 cross-tenant leak class — request-state escaping into shared process state, and dev-mode sentinel identities used as production fallbacks.
107
118
 
108
- ## Auth Patterns
119
+ - `scripts/guard-no-env-mutation.mjs` — bans `process.env.<KEY> = …` (and bracket / compound forms) anywhere in production code. On serverless, every warm container handles many concurrent requests in one Node process, so `process.env` mutation leaks across in-flight requests (the "restore" line at the end of a handler races and never helps — most recently the Zoom webhook). Use `runWithRequestContext({ userEmail, orgId, timezone }, fn)` from `@agent-native/core/server` instead — it's AsyncLocalStorage-backed and per-request safe. Allowlisted paths: `scripts/`, `*.spec.ts` / `*.test.ts`, `packages/core/src/dev**`, `templates/*/test/`, anything under `/cli/` or `/scaffold/`. Per-line opt-out: `process.env.X = y // guard:allow-env-mutation — <reason>`.
120
+ - `scripts/guard-no-localhost-fallback.mjs` — bans the literal `"local@localhost"` / `'local@localhost'` / `` `local@localhost` `` in production code. The bug class: `getRequestUserEmail() ?? "local@localhost"` silently pools every unauthenticated request into a single shared tenant, leaking credentials, tools, and `application_state` rows between accounts. The right behavior is to throw / 401 when there's no session. Allowlisted paths: the dev-mode auth shim (`packages/core/src/server/auth.ts`), `packages/core/src/dev**`, tests, `scripts/`, `seed/` / `seeds/`, plus a few framework helpers that intentionally inspect or migrate the dev identity. SQL DDL `DEFAULT 'local@localhost'` and the Drizzle helper `.default('local@localhost')` are skipped per-line — schema column defaults are intentional dev fixtures, not the dangerous fallback pattern. Per-line opt-out: `email ?? "local@localhost" // guard:allow-localhost-fallback — <reason>`.
109
121
 
110
- ### Use `defineAction` (recommended)
122
+ ## Auth
111
123
 
112
- Actions defined with `defineAction` are automatically protected by the auth guard. Unauthenticated requests get a 401 response. This is the safest pattern.
124
+ - All actions are protected by the auth guard automatically.
125
+ - Prefer actions for normal app data. Do not hand-write `/api/*` routes for
126
+ CRUD, data queries, or action re-exports just to add auth; action endpoints
127
+ already get auth and request context.
128
+ - If you must create custom `/api/` routes, always call `getSession(event)` and reject requests without a session:
113
129
 
114
130
  ```ts
115
- // Auto-protected auth guard runs before this code
116
- export default defineAction({
117
- description: "Delete a note",
118
- schema: z.object({ id: z.string() }),
119
- run: async (args) => {
120
- // Only authenticated users reach here
121
- },
131
+ import { getSession } from "@agent-native/core/server";
132
+
133
+ export default defineEventHandler(async (event) => {
134
+ const session = await getSession(event);
135
+ if (!session) throw createError({ statusCode: 401 });
136
+ // ...
122
137
  });
123
138
  ```
124
139
 
125
- ### Custom `/api/` routes (use sparingly)
140
+ - Never create unprotected routes that modify data.
141
+
142
+ ## Custom HTTP Routes Must Apply Access Control Themselves
126
143
 
127
- If you must create custom routes (file uploads, streaming, webhooks), always check auth:
144
+ This is the single most-failed rule in the codebase. Auto-mounted action routes (`/_agent-native/actions/...`) get a request context wired up automatically. **Hand-written `/api/*` Nitro routes do not.** If your handler queries an ownable resource (any table with `...ownableColumns()`), you MUST:
145
+
146
+ 1. Read the session: `const session = await getSession(event).catch(() => null)`.
147
+ 2. Run the work inside `runWithRequestContext({ userEmail: session?.email, orgId: session?.orgId }, fn)` from `@agent-native/core/server`.
148
+ 3. Inside `fn`, query through one of:
149
+ - `accessFilter(table, sharesTable)` in the WHERE clause for list/read-many.
150
+ - `resolveAccess("<type>", id)` for read-by-id (returns null if no access — return 404, not 403, so existence isn't leaked).
151
+ - `assertAccess("<type>", id, "viewer"|"editor"|"admin")` for write/delete-by-id.
128
152
 
129
153
  ```ts
130
- // server/routes/api/upload.ts
131
- import { getSession } from "@agent-native/core/server";
154
+ // Bad — Brent's signup leaked every other user's decks because of this exact shape.
155
+ export default defineEventHandler(async () => {
156
+ const db = getDb();
157
+ return db.select().from(schema.decks); // no access filter!
158
+ });
132
159
 
160
+ // Good
161
+ import { getSession, runWithRequestContext } from "@agent-native/core/server";
162
+ import { accessFilter } from "@agent-native/core/sharing";
133
163
  export default defineEventHandler(async (event) => {
134
- const session = await getSession(event);
135
- if (!session?.email) {
136
- setResponseStatus(event, 401);
137
- return { error: "Unauthorized" };
138
- }
139
- // ... handle upload with session.email
164
+ const session = await getSession(event).catch(() => null);
165
+ return runWithRequestContext(
166
+ { userEmail: session?.email, orgId: session?.orgId },
167
+ async () => {
168
+ const db = getDb();
169
+ return db
170
+ .select()
171
+ .from(schema.decks)
172
+ .where(accessFilter(schema.decks, schema.deckShares));
173
+ },
174
+ );
140
175
  });
141
176
  ```
142
177
 
143
- ### CSRF Protection
144
-
145
- The framework uses `SameSite=lax` cookies with `httpOnly` flag. This prevents most CSRF attacks. Additional rules:
146
- - State-changing actions should use POST (the default for `defineAction`)
147
- - GET actions (`http: { method: "GET" }`) should be read-only
148
- - Never perform writes in response to GET requests
178
+ `scripts/guard-no-unscoped-queries.mjs` runs in `pnpm prep` and fails the build if any file in `templates/*/server/`, `templates/*/actions/`, or `packages/*/src/` queries an ownable table without one of the access helpers. Last-resort opt-out is the marker comment `// guard:allow-unscoped — <reason>` — only use it for cases like the sharing primitives themselves or share-token-public viewer endpoints, and always include a reviewer-readable reason.
149
179
 
150
180
  ## Data Scoping
151
181
 
152
- In production, the framework enforces data isolation at the SQL level. Agents and users can only see and modify data they own. This is automaticyou don't write WHERE clauses yourself.
182
+ In production, the framework automatically restricts all agent SQL queries to the current user's data using temporary views. This is enforced at the SQL level the agent cannot bypass it.
153
183
 
154
- ### Per-User Scoping (`owner_email`)
184
+ The `db-query` / `db-exec` tools (and the extension SQL bridge, which shares the same path) reject schema-qualified table references like `public.<table>` or `main.<table>` — a qualified name resolves to the base table and would skip the temp view. Use bare table names; scoping is applied automatically.
155
185
 
156
- Every table with user-specific data **must** have an `owner_email` text column.
186
+ ### Per-User Scoping (`owner_email`)
157
187
 
158
- ```ts
159
- import { table, text, integer } from "@agent-native/core/db/schema";
188
+ Every template table with user data **must** have an `owner_email` text column:
160
189
 
161
- export const notes = table("notes", {
162
- id: text("id").primaryKey(),
163
- title: text("title").notNull(),
164
- content: text("content"),
165
- owner_email: text("owner_email").notNull(), // REQUIRED for user data
166
- });
167
- ```
190
+ 1. Framework detects `owner_email` via schema introspection
191
+ 2. Creates temp views `WHERE owner_email = <current user>` before each query
192
+ 3. Auto-injects `owner_email` into INSERT statements
168
193
 
169
- **What happens automatically:**
170
- - `db-query` creates temporary views with `WHERE owner_email = <current user>`
171
- - `db-exec` INSERT statements get `owner_email` auto-injected
172
- - `db-exec` UPDATE/DELETE statements are scoped to the current user's rows
173
- - The current user comes from `AGENT_USER_EMAIL` (set from the auth session)
194
+ The current user is resolved from `AGENT_USER_EMAIL` (set automatically from the session).
174
195
 
175
196
  ### Per-Org Scoping (`org_id`)
176
197
 
177
- For multi-user apps where teams share data, add an `org_id` column:
198
+ For multi-org apps, tables also need `org_id`:
199
+
200
+ 1. `WHERE org_id = <current org>` is added (in addition to `owner_email` if present)
201
+ 2. `org_id` is auto-injected into INSERT statements
202
+
203
+ Enable org scoping in the agent-chat plugin:
178
204
 
179
205
  ```ts
180
- export const projects = table("projects", {
181
- id: text("id").primaryKey(),
182
- name: text("name").notNull(),
183
- owner_email: text("owner_email").notNull(),
184
- org_id: text("org_id").notNull(),
206
+ createAgentChatPlugin({
207
+ resolveOrgId: async (event) => {
208
+ const ctx = await getOrgContext(event);
209
+ return ctx.orgId;
210
+ },
185
211
  });
186
212
  ```
187
213
 
188
- When both columns are present, queries are scoped by **both**: `WHERE owner_email = ? AND org_id = ?`.
214
+ ### Column Conventions
189
215
 
190
- ### Validation
216
+ | Column | Purpose | Required |
217
+ | ------------- | ----------------------- | ------------------------------- |
218
+ | `owner_email` | Per-user data isolation | Yes, for all user-facing tables |
219
+ | `org_id` | Per-org data isolation | Yes, for multi-org apps |
191
220
 
192
- Run `pnpm action db-check-scoping` to verify all tables have proper ownership columns. Use `--require-org` for multi-org apps.
221
+ Run `pnpm action db-check-scoping` to verify. Use `--require-org` for multi-org apps.
193
222
 
194
- ## A2A Security
223
+ ## Checklist
195
224
 
196
- ### Cross-App Identity
225
+ - [ ] New action uses `defineAction` with a Zod `schema:`
226
+ - [ ] No SQL string concatenation with user input
227
+ - [ ] No `dangerouslySetInnerHTML` with user content
228
+ - [ ] Server-side fetches of user/agent URLs use `ssrfSafeFetch`, not bare `fetch`
229
+ - [ ] Secrets stored via `saveCredential` / the vault (encrypted), never raw in `settings` or responses
230
+ - [ ] No hardcoded API keys, tokens, webhook URLs, signing secrets, real
231
+ credential-looking strings, private Builder/internal data, or customer data
232
+ - [ ] New env vars in `.env` only, not committed
233
+ - [ ] New user-data tables have `owner_email` column
234
+ - [ ] Custom routes call `getSession` and reject unauthenticated requests
197
235
 
198
- When apps call each other via A2A, they need to verify identity. Set the same `A2A_SECRET` on all apps that need to trust each other:
199
-
200
- ```bash
201
- A2A_SECRET=your-shared-secret-at-least-32-chars
202
- ```
236
+ ## Related Skills
203
237
 
204
- **How it works:**
205
- 1. App A signs a JWT with `A2A_SECRET` containing `sub: "user@example.com"`
206
- 2. App B receives the call, verifies the JWT signature
207
- 3. App B sets `AGENT_USER_EMAIL` from the verified `sub` claim
208
- 4. Data scoping applies — App B only shows steve's data
209
-
210
- Without `A2A_SECRET`, A2A calls are unauthenticated (fine for local dev, not production).
211
-
212
- ## Rules for Agents
213
-
214
- 1. **Every new table with user data must have `owner_email`.** No exceptions.
215
- 2. **Always use `defineAction` with a Zod `schema:`** for input validation on user-facing actions.
216
- 3. **Never concatenate user input into SQL** — use parameterized queries or Drizzle ORM.
217
- 4. **Never use `dangerouslySetInnerHTML`** or `innerHTML` with user-controlled content.
218
- 5. **Never hardcode or expose secrets** — no settings, application state, source code, fixtures, logs, or responses. Use the secrets registry / vault, scoped credentials, OAuth stores, or deploy-level env vars as appropriate.
219
- 6. **Never bypass scoping** — don't raw-query tables without going through `db-query`/`db-exec`.
220
- 7. **Never create unprotected routes that modify data** — use `defineAction` or check `getSession()`.
221
- 8. **Don't hardcode emails** — use `AGENT_USER_EMAIL` environment variable.
222
- 9. **Don't expose user data in application state** — it's per-session, not per-user. Use SQL tables with `owner_email`.
238
+ - `storing-data` — SQL patterns and the agent's db tools
239
+ - `actions` `defineAction` with Zod schema validation
240
+ - `authentication` Auth modes, sessions, and org context
@@ -4,6 +4,8 @@ description: >-
4
4
  How the agent can modify the app's own source code. Use when the agent needs
5
5
  to edit components, routes, styles, or scripts, when designing UI for agent
6
6
  editability, or when deciding what the agent should and shouldn't modify.
7
+ metadata:
8
+ internal: true
7
9
  ---
8
10
 
9
11
  # Self-Modifying Code
@@ -73,7 +75,7 @@ el.dataset.selectedId = selectedItem?.id || "";
73
75
 
74
76
  ## Related Skills
75
77
 
76
- - **storing-data** — Tier 1 modifications (database writes) are the safest and most common
77
- - **scripts** — The agent can create or modify scripts to add new capabilities
78
+ - **storing-data** — Tier 1 modifications (data files) are the safest and most common
79
+ - **actions** — The agent can create or modify actions to add new capabilities
78
80
  - **delegate-to-agent** — Self-modification requests come through the agent chat
79
- - **real-time-sync** — Source edits and database writes trigger SSE events to update the UI
81
+ - **real-time-sync** — Database writes trigger change events to update the UI
@@ -5,6 +5,8 @@ description: >-
5
5
  components, forms, dialogs, menus, charts, sidebars, themes, registries, or
6
6
  any project with a components.json file.
7
7
  source: https://ui.shadcn.com/docs/skills
8
+ metadata:
9
+ internal: true
8
10
  ---
9
11
 
10
12
  # shadcn/ui
@@ -57,6 +59,19 @@ This skill keeps shadcn/ui work project-aware. Components are source files in th
57
59
  - Do not add manual `z-index` to overlay primitives unless you are fixing a verified stacking bug.
58
60
  - Add custom colors as CSS variables in the existing Tailwind CSS file reported by shadcn info. For Tailwind v4, register variables with `@theme inline`.
59
61
 
62
+ ## Transitions And Motion
63
+
64
+ shadcn's built-in component animations are the right level of polish — keep them. The goal is a snappy, clean UI, not a motionless one. Match shadcn's motion vocabulary; don't strip it and don't pile on decorative custom animation.
65
+
66
+ - **Never remove or override a shadcn component's default animation.** `data-[state=open]:animate-in`, `data-[state=closed]:animate-out`, `fade-in/out`, `zoom-in/out`, `slide-in-from-*`, accordion height, the `tailwindcss-animate` utilities — these ship for a reason. Leave them as-is.
67
+ - **Custom transitions are fine when they communicate a state change and match shadcn's feel.** Reuse the same vocabulary: short durations (~120–200ms), `ease-out`, opacity/transform only, gated on `data-[state=...]`. Examples that are good and welcome:
68
+ - A portaled custom popover/tooltip/sheet that fades + scales/slides in on `data-[state=delayed-open]` / `data-[state=closed]`, mirroring Radix's own content animation.
69
+ - A list row or toast that fades/slides in on mount and out on dismiss.
70
+ - A chevron/caret `rotate` on expand, a subtle `opacity`/`color` hover on an icon button, skeleton shimmer, a progress/height transition on a collapsible.
71
+ - Continuous, product-defining motion where it _is_ the experience (e.g. a multi-stage booking flow's stage transitions) — fine, and framer-motion is acceptable there.
72
+ - **Avoid decorative, attention-seeking, or slow motion:** hand-rolled `duration-700` hero fade-ins, parallax, bouncing/spring entrances on ordinary content, animated gradients, staggered cascades on long lists, anything that delays the user seeing or acting on content. If an animation makes the UI feel slower, cut it.
73
+ - Rule of thumb: if the motion clarifies what just changed and is over in well under a quarter-second, it's polish; if it's there to look impressive, it's bloat.
74
+
60
75
  ## Icons
61
76
 
62
77
  - Agent-native apps use `@tabler/icons-react`. Do not add `lucide-react` because a registry example used it.