@happyvertical/smrt-template-sveltekit 0.53.2 → 0.53.4

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 (47) hide show
  1. package/AGENTS.md +53 -26
  2. package/README.md +23 -16
  3. package/mcp-apps-template/package.dependencies.json +2 -2
  4. package/mcp-apps-template/src/routes/.well-known/oauth-protected-resource/api/mcp/+server.ts +14 -6
  5. package/mcp-apps-template/src/routes/api/mcp/+server.ts +69 -12
  6. package/package.json +17 -15
  7. package/template/AGENTS.md +16 -5
  8. package/template/Dockerfile +0 -1
  9. package/template/INSTALL_PROMPT.md +2 -2
  10. package/template/README.md +52 -27
  11. package/template/compose.yaml +2 -2
  12. package/template/package.json +28 -27
  13. package/template/smrt.config.ts +17 -4
  14. package/template/src/app.d.ts +4 -11
  15. package/template/src/hooks.server.ts +10 -51
  16. package/template/src/lib/server/smrt.ts +70 -62
  17. package/template/src/routes/+layout.server.ts +4 -12
  18. package/template/src/routes/+layout.svelte +27 -99
  19. package/template/src/routes/api/_runtime/diagnostics/+server.ts +17 -3
  20. package/template/src/routes/api/_runtime/health/+server.ts +4 -28
  21. package/template/src/routes/settings/+page.svelte +3 -34
  22. package/template/src/routes/setup/+page.server.ts +15 -88
  23. package/template/src/routes/setup/+page.svelte +3 -23
  24. package/template/vite.config.ts +5 -43
  25. package/template.config.js +11 -11
  26. package/mcp-apps-template/src/lib/McpAppsBridge.svelte +0 -37
  27. package/mcp-apps-template/src/lib/server/mcp-hosted.ts +0 -28
  28. package/mcp-apps-template/src/lib/server/mcp.ts +0 -85
  29. package/template/scripts/smrt-app.mjs +0 -885
  30. package/template/scripts/smrt-mcp-apps.mjs +0 -18
  31. package/template/scripts/smrt-operation-lock.mjs +0 -102
  32. package/template/scripts/smrt-portability-assets.mjs +0 -707
  33. package/template/scripts/smrt-portability.mjs +0 -470
  34. package/template/scripts/smrt-prepare-migration.mjs +0 -93
  35. package/template/scripts/smrt-process.mjs +0 -82
  36. package/template/scripts/smrt-provider-readiness.mjs +0 -33
  37. package/template/scripts/smrt-runtime-identity.mjs +0 -245
  38. package/template/scripts/smrt-vite.mjs +0 -45
  39. package/template/scripts/smrt-web.mjs +0 -10
  40. package/template/scripts/smrt-worker.mjs +0 -100
  41. package/template/scripts/smrt-writer-lease.mjs +0 -199
  42. package/template/src/lib/RuntimeDiagnosticsWebMcp.svelte +0 -11
  43. package/template/src/lib/runtime-diagnostics-webmcp.ts +0 -108
  44. package/template/src/lib/server/application-runtime.ts +0 -262
  45. package/template/src/lib/server/runtime-diagnostics.ts +0 -114
  46. package/template/src/lib/server/tenancy.ts +0 -114
  47. package/template/src/smrt-webmcp.d.ts +0 -6
package/AGENTS.md CHANGED
@@ -17,6 +17,16 @@ It is the ground-up alternative to `smrt-saas-starter`.
17
17
  images ship no Corepack, so the Dockerfile installs a pinned copy.
18
18
  - `runtime.profile` is the canonical infrastructure selector. Generated apps
19
19
  expose deterministic `app:*` operations and keep runtime state outside source.
20
+ - Every lifecycle script is a `smrt app <operation>` one-liner
21
+ (`packages/cli/agents/app-commands.md`); the template ships no `scripts/`.
22
+ Never reintroduce copied operator scripts: fix the CLI command instead.
23
+ - Server composition is `createSmrtSvelteKitRuntime()` from
24
+ `@happyvertical/smrt-app-runtime/sveltekit`, built once in
25
+ `src/lib/server/smrt.ts` with the writer lease
26
+ (`acquireWriterLease(prepareApplicationStateRoot(...))`) and provider
27
+ readiness (`createProviderReadinessProbe`) from app-runtime's root entry, so
28
+ the web process never imports the CLI. Health, diagnostics, layout session,
29
+ and owner setup routes mount that package's handlers.
20
30
  - The production baseline uses adapter-node with separate web, task-worker, and
21
31
  schedule-worker processes. Workers import the build-compiled
22
32
  `.smrt/runtime/register.js` (#3117) before creating runners so app-defined
@@ -37,8 +47,9 @@ It is the ground-up alternative to `smrt-saas-starter`.
37
47
  different or stale server is never accepted as this app. Deployed health
38
48
  responses expose only generic status and profile.
39
49
  - Directly used `@happyvertical/smrt-*` packages share one current release range.
40
- - `@happyvertical/smrt-cli` is a direct dev dependency because scripts/docs use
41
- its binary. The template includes `@happyvertical/smrt-web` because the root
50
+ - `@happyvertical/smrt-cli` is a direct dev dependency because every
51
+ lifecycle script runs its binary; the runtime image keeps dev dependencies so
52
+ workers and operator commands can. The template includes `@happyvertical/smrt-web` because the root
42
53
  Provider wires the generated read-only WebMCP definitions for every page.
43
54
  - The generated MCP server resolves its imports from the scaffolded app, not
44
55
  from the CLI, so every specifier `generate-mcp` emits must be declared here.
@@ -49,9 +60,10 @@ It is the ground-up alternative to `smrt-saas-starter`.
49
60
  for the default worker and tenant surfaces. Do not assume pnpm exposes the
50
61
  CLI's or core's transitive dependencies to the app root — its strict layout
51
62
  does not (#2297).
52
- - `smrtConsumer()` explicitly consumes profiles, tenancy, and users manifests;
53
- `smrtPlugin()` scans `src/lib/objects`, generates Vite virtual definitions,
54
- SvelteKit routes, runtime registration, and knowledge artifacts.
63
+ - `vite.config.ts` is `[sveltekit(), smrt()]`. The preset consumes exactly the
64
+ packages in `smrt.config.ts` `consumer.packages` (profiles, tenancy, users),
65
+ scans `src/lib/objects`, and generates Vite virtual definitions, SvelteKit
66
+ routes, runtime registration, and knowledge artifacts.
55
67
  - `pnpm db:migrate` builds first to refresh generated artifacts, then the
56
68
  migration wrapper holds the shared operation/writer exclusion for the full
57
69
  manifest-driven migration command. Do not restore deprecated `smrt db:setup`
@@ -59,11 +71,13 @@ It is the ground-up alternative to `smrt-saas-starter`.
59
71
 
60
72
  ## Application patterns
61
73
 
62
- - `src/hooks.server.ts` stores a URL-selected tenant candidate separately, then
63
- lets `createSessionHandler({ enterTenantContext: true })` establish the only
64
- authorized tenant context. Never turn an untrusted header into authority.
74
+ - `src/hooks.server.ts` re-exports the runtime's `handle`/`init`: URL tenant
75
+ selection stays a separate candidate and only the verified session
76
+ establishes tenant context. Never turn an untrusted header into authority.
65
77
  - `src/lib/server/smrt.ts` imports generated local registrations, loads the
66
- generated manifest metadata, and uses the public users request-scoped DB API.
78
+ generated manifest metadata, creates the runtime, and exposes
79
+ `getCollection()`/`getSmrtConfig()` over `runtime.classOptions()`, which is
80
+ request-scoped (RLS transaction) and must never be retained.
67
81
  - `Item` is the single example object and demonstrates optional tenant scope,
68
82
  a REST writable allowlist, shared CRUD action metadata, and the explicit
69
83
  collection registration required by generated CLI/MCP runtime commands.
@@ -71,18 +85,25 @@ It is the ground-up alternative to `smrt-saas-starter`.
71
85
  `depends('smrt:items')`, mutations call `invalidate('smrt:items')`, and
72
86
  hand-written writes call `assertOperationPermission()` with the session's
73
87
  exact permission snapshot.
74
- - The root layout uses Provider, the current smrt-ui ThemeProvider, AdminShell,
75
- and a small explicit TenantNav. Generated REST routes do not imply page routes.
76
- - The root Provider registers generated WebMCP read tools with the authenticated
77
- page session. Write/destructive effects require an explicit page-owned policy.
88
+ - The root layout is `AppShell` from `@happyvertical/smrt-svelte/app` with a
89
+ small explicit `nav`; `/setup` and `/settings` mount that package's
90
+ `OwnerSetupForm` and `ShellSettingsPage`. Generated REST routes do not imply
91
+ page routes.
92
+ - AppShell's Provider registers generated WebMCP read tools with the
93
+ authenticated page session. Write/destructive effects require an explicit
94
+ page-owned policy.
78
95
  Live browser collections remain opt-in per page and must seed from SSR
79
96
  `initialData` to avoid a duplicate first request.
80
- - `RuntimeDiagnosticsWebMcp` owns exactly one additional read-only tool,
81
- `smrt.runtime.diagnostics.read`. It uses same-origin page-session fetch to the
82
- authored `/api/_runtime/diagnostics` route and aborts its registration on
83
- unmount. The route requires a direct active tenant membership plus either the
84
- owner role or the explicit `runtime_diagnostics.read` permission before any
85
- runtime projection/probe; it never calls principal-bound server tools.
97
+ - AppShell's `runtimeDiagnostics` (on for signed-in sessions) owns exactly one
98
+ additional read-only tool, `smrt.runtime.diagnostics.read`, which fetches the
99
+ mounted `/api/_runtime/diagnostics` route as the page user. That route
100
+ (`createRuntimeDiagnosticsHandler`) requires a direct active tenant membership
101
+ plus the owner role or `runtime_diagnostics.read` before any projection or
102
+ probe; it never calls principal-bound server tools.
103
+ - The opt-in `mcp-apps-template/` overlay is one `mountMcpAppRoute()` route,
104
+ the protected-resource metadata route, and the application-owned
105
+ `mcp-hosted-principal.ts` binding; `pnpm typecheck` checks it against the
106
+ template.
86
107
 
87
108
  ## Tests
88
109
 
@@ -126,11 +147,13 @@ own. Never add a mocked REST handler, an in-memory database, or DOM automation
126
147
  presented as WebMCP execution.
127
148
 
128
149
  - `@happyvertical/smrt-cli` is a devDependency **because the gate needs it**:
129
- the app's `app:setup` migration step shells out to `pnpm exec smrt db:migrate`,
130
- and the copied app deliberately never runs `pnpm install`, so `smrt` has to
131
- arrive through the linked `node_modules/.bin`. The harness pins the app's own
132
- `.bin` ahead of `PATH` and fails with a named error if that binary is missing,
133
- so a host-global `smrt` cannot make a local run pass where CI fails.
150
+ the harness runs the app's own `smrt app setup` and `smrt app dev` from the
151
+ linked CLI, and setup's `pnpm build` is `smrt app build`. The copied app
152
+ deliberately never runs `pnpm install`, so `smrt` has to arrive through the
153
+ linked `node_modules`. The harness pins the app's own `.bin` ahead of `PATH`
154
+ and fails with a named error if the binary is missing, so a host-global
155
+ `smrt` cannot make a local run pass where CI fails. Identity and state-root
156
+ values it checks come from `@happyvertical/smrt-app-runtime`.
134
157
  - `pnpm test:e2e` runs the browser half.
135
158
  - `pnpm test:m5` runs the whole aggregate gate through
136
159
  `e2e/support/gate.mjs`, which requires a PostgreSQL service and fails when a
@@ -145,8 +168,12 @@ presented as WebMCP execution.
145
168
  from `testInfo.project.outputDir` and asserts it is outside the repository.
146
169
  The harness gives the copied app a real `node_modules` directory of
147
170
  individually symlinked entries rather than one symlink to this package's, so
148
- the served app's `node_modules/.vite` cache cannot land in the checkout —
149
- never replace that loop with a whole-directory symlink.
171
+ the served app's `node_modules/.vite` cache cannot land in the checkout.
172
+ Scope directories (`@happyvertical/`) and `.bin/` are real directories of
173
+ per-entry links too: the copy ships `pnpm-workspace.yaml` and setup runs
174
+ `pnpm build` in it, so anything pnpm relinks must land in the copy, never in
175
+ this package's `node_modules`. Never replace either loop with a
176
+ whole-directory symlink.
150
177
  - `e2e/` is outside `biome.json`'s include globs, as every package's `e2e/` is,
151
178
  so a green `Lint` job says nothing about this tree. `pnpm typecheck` covers
152
179
  the `.ts` files (`tsconfig.fixture.json` includes `e2e/**/*.ts`) but NOT
package/README.md CHANGED
@@ -52,17 +52,21 @@ fixtures.
52
52
 
53
53
  ## 2. Understand the generated files
54
54
 
55
- The checked-in template contains authored objects, hooks, server helpers,
56
- pages, and configuration. Vite generates `.smrt/manifest.json`,
55
+ The checked-in template contains authored objects, one server module
56
+ (`src/lib/server/smrt.ts`, the runtime and collection access), thin hooks and
57
+ routes that mount package handlers, pages, and configuration. It ships no
58
+ operator scripts. Vite generates `.smrt/manifest.json`,
57
59
  `.smrt/smrt-knowledge.json`, `.smrt/register.js`,
58
60
  `src/lib/server/smrt-register.ts`, `src/lib/types/smrt-generated/`, and
59
61
  `src/routes/api/**/+server.ts`. All are ignored and excluded from scaffold
60
62
  fixtures.
61
63
 
62
- `smrtPlugin()` scans the local object directory and generates local routes and
63
- types. `smrtConsumer()` explicitly consumes the profiles, tenancy, and users
64
- package manifests. `@happyvertical/smrt-cli` is a direct dev dependency because
65
- the generated package scripts and README invoke its `smrt` binary.
64
+ `vite.config.ts` is `plugins: [sveltekit(), smrt()]`: the
65
+ `@happyvertical/smrt-core/vite` preset applies the decorator transform, scans
66
+ the local object directory, generates local routes and types, and consumes the
67
+ packages `smrt.config.ts` lists under `consumer.packages` (profiles, tenancy,
68
+ users). `@happyvertical/smrt-cli` is a direct dev dependency because every
69
+ lifecycle script is `smrt app <operation>`.
66
70
 
67
71
  ## 3. Define the first object
68
72
 
@@ -85,25 +89,28 @@ pnpm db:migrate
85
89
  ```
86
90
 
87
91
  The script builds first to refresh manifests, registrations, types, and API
88
- routes, then runs `smrt db:migrate`. The deprecated `smrt db:setup` command is
92
+ routes, then runs `smrt app migrate`, which holds the operation lock and runs
93
+ `smrt db:migrate`. The deprecated `smrt db:setup` command is
89
94
  not documented or shipped.
90
95
 
91
96
  ## 5. Understand tenant context
92
97
 
93
- The template resolves a URL tenant candidate into separate
94
- `selectedTenantId`/`selectedTenantSlug` locals. It does not establish query
95
- context from that selection. The session hook runs afterward and only its
96
- membership-authorized tenant enters the s-m-r-t tenancy context.
98
+ `src/hooks.server.ts` mounts `createSmrtSvelteKitRuntime()` from
99
+ `@happyvertical/smrt-app-runtime/sveltekit`. It resolves a URL tenant candidate
100
+ into separate `selectedTenantId`/`selectedTenantSlug` locals and does not
101
+ establish query context from that selection. The session step runs afterward
102
+ and only its membership-authorized tenant enters the s-m-r-t tenancy context.
97
103
 
98
- Untrusted tenant headers are ignored. Applications may replace
99
- `src/lib/server/tenancy.ts` with a path, signed-cookie, or trusted-gateway
100
- selector, but selection must remain separate from authorization and session
101
- switches must use `switchSessionTenant()`.
104
+ Untrusted tenant headers are ignored. Applications may pass `selectTenant` for
105
+ a path, signed-cookie, or trusted-gateway selector, but selection must remain
106
+ separate from authorization and session switches must use
107
+ `switchSessionTenant()`.
102
108
 
103
109
  ## 6. Understand users, profiles, memberships, roles, and permissions
104
110
 
105
111
  The generated project consumes the current profiles and users manifests and
106
- wires `createSessionHandler({ enterTenantContext: true })`. A User is the auth
112
+ loads the signed session through the runtime, which enters tenant context only
113
+ for a verified active membership. A User is the auth
107
114
  identity, Profile is person-facing metadata, Membership joins a User to a
108
115
  Tenant and Role, Role maps to Permission records, and Session publishes the
109
116
  resolved permission snapshot.
@@ -1,4 +1,4 @@
1
1
  {
2
- "@happyvertical/smrt-app-mcp": "^0.53.2",
3
- "@happyvertical/smrt-mcp-openai": "^0.53.2"
2
+ "@happyvertical/smrt-app-mcp": "^0.53.4",
3
+ "@happyvertical/smrt-mcp-openai": "^0.53.4"
4
4
  }
@@ -1,7 +1,15 @@
1
- import { hostedMcpAuth } from '$lib/server/mcp-hosted';
1
+ import { createHostedMcpResourceAuth } from '@happyvertical/smrt-app-mcp/auth';
2
+ import { mountMcpProtectedResourceMetadataRoute } from '@happyvertical/smrt-app-mcp/sveltekit';
2
3
 
3
- /** Serve exactly the RFC 9728 metadata URL advertised by hosted bearer challenges. */
4
- export function GET() {
5
- const auth = hostedMcpAuth();
6
- return auth?.metadataResponse() ?? new Response(null, { status: 404 });
7
- }
4
+ import { resolveHostedMcpPrincipal } from '$lib/server/mcp-hosted-principal';
5
+ import { runtime } from '$lib/server/smrt';
6
+
7
+ const { profile } = await runtime.resolvedRuntime();
8
+
9
+ /** RFC 9728 metadata at exactly the URL hosted bearer challenges advertise (404 locally). */
10
+ export const GET = mountMcpProtectedResourceMetadataRoute(
11
+ createHostedMcpResourceAuth({
12
+ profile,
13
+ resolvePrincipal: resolveHostedMcpPrincipal,
14
+ }),
15
+ );
@@ -1,15 +1,72 @@
1
- import { mountMcpRoute } from '@happyvertical/smrt-app-mcp/sveltekit';
1
+ import type { McpWorkflowToolDefinition } from '@happyvertical/smrt-app-mcp';
2
+ import { createHostedMcpResourceAuth } from '@happyvertical/smrt-app-mcp/auth';
3
+ import { mountMcpAppRoute } from '@happyvertical/smrt-app-mcp/sveltekit';
4
+ import { openAiDisplayMetadata, withOpenAiEntrypoints } from '@happyvertical/smrt-mcp-openai';
2
5
 
3
- import { mcpServer, resolveMcpPrincipal } from '$lib/server/mcp';
4
- import { hostedMcpAuth } from '$lib/server/mcp-hosted';
6
+ import { Item } from '$lib/objects/Item';
7
+ import { resolveHostedMcpPrincipal } from '$lib/server/mcp-hosted-principal';
8
+ import { runtime } from '$lib/server/smrt';
5
9
 
6
- /** Stateless SDK-v2 Streamable HTTP MCP endpoint. */
7
- const localRoute = mountMcpRoute(mcpServer, { resolvePrincipal: resolveMcpPrincipal });
10
+ const ITEM_RESOURCE_URI = 'ui://smrt-app/v1/items.html';
11
+ const itemResourceHtml = `<!doctype html><html lang="en"><head><title>Items</title></head><body><main tabindex="-1"><h1>Items</h1><p>Use the host controls to review your authorized items.</p></main></body></html>`;
8
12
 
9
- export async function POST(event: Parameters<typeof localRoute>[0]) {
10
- const auth = hostedMcpAuth();
11
- if (!auth) return localRoute(event);
12
- const checked = await auth.authenticate(event.request);
13
- if (!checked.ok) return checked.response;
14
- return mountMcpRoute(mcpServer, { resolvePrincipal: () => checked.principal })(event);
15
- }
13
+ const itemsOverview: McpWorkflowToolDefinition = withOpenAiEntrypoints(
14
+ {
15
+ name: 'items_overview',
16
+ description: 'Open the authorized item overview.',
17
+ title: 'Items',
18
+ inputSchema: { type: 'object', properties: {}, additionalProperties: false },
19
+ outputSchema: {
20
+ type: 'object',
21
+ properties: { state: { type: 'string' } },
22
+ required: ['state'],
23
+ additionalProperties: false,
24
+ },
25
+ effect: 'read',
26
+ idempotent: true,
27
+ openWorld: false,
28
+ ui: { resourceUri: ITEM_RESOURCE_URI },
29
+ async execute() {
30
+ return {
31
+ content: [{ type: 'text', text: 'Item overview is ready.' }],
32
+ structuredContent: { state: 'ready' },
33
+ };
34
+ },
35
+ },
36
+ ['global'],
37
+ );
38
+
39
+ const { profile } = await runtime.resolvedRuntime();
40
+
41
+ /**
42
+ * Stateless SDK-v2 Streamable HTTP MCP endpoint for this app's own `Item`.
43
+ * Locally the principal is the signed session (user, authorized tenant, and
44
+ * permission slugs); hosted profiles verify a bearer token and map it through
45
+ * the application-owned `resolveHostedMcpPrincipal`, and the verified bearer
46
+ * principal is bound into the request permission context (under
47
+ * `database-rls`, its own RLS transaction). Every principal needs
48
+ * `items.read`, so only read-only tools are published until per-operation
49
+ * authorization exists; browser requests from a foreign `Origin` are refused.
50
+ */
51
+ export const POST = mountMcpAppRoute({
52
+ models: [Item],
53
+ requiredScopes: ['items.read'],
54
+ effects: ['read'],
55
+ smrtOptions: () => ({ db: runtime.databaseConfig() }),
56
+ auth: createHostedMcpResourceAuth({
57
+ profile,
58
+ resolvePrincipal: resolveHostedMcpPrincipal,
59
+ }),
60
+ bindPrincipal: runtime.runAsPrincipal,
61
+ workflowTools: [itemsOverview],
62
+ resources: [
63
+ {
64
+ uri: ITEM_RESOURCE_URI,
65
+ version: 'v1',
66
+ name: 'Items',
67
+ html: itemResourceHtml,
68
+ csp: {},
69
+ metadata: openAiDisplayMetadata({ availableDisplayModes: ['inline'] }),
70
+ },
71
+ ],
72
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@happyvertical/smrt-template-sveltekit",
3
- "version": "0.53.2",
3
+ "version": "0.53.4",
4
4
  "description": "Minimal SvelteKit project template with s-m-r-t framework integration",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -32,22 +32,24 @@
32
32
  "directory": "packages/template-sveltekit"
33
33
  },
34
34
  "peerDependencies": {
35
- "@happyvertical/smrt-core": "0.53.2"
35
+ "@happyvertical/smrt-core": "0.53.4"
36
36
  },
37
37
  "devDependencies": {
38
- "@happyvertical/smrt-app-runtime": "0.53.2",
39
- "@happyvertical/smrt-assets": "0.53.2",
40
- "@happyvertical/smrt-cli": "0.53.2",
41
- "@happyvertical/smrt-config": "0.53.2",
42
- "@happyvertical/smrt-jobs": "0.53.2",
43
- "@happyvertical/smrt-playbooks": "0.53.2",
44
- "@happyvertical/smrt-profiles": "0.53.2",
45
- "@happyvertical/smrt-scanner": "0.53.2",
46
- "@happyvertical/smrt-svelte": "0.53.2",
47
- "@happyvertical/smrt-tenancy": "0.53.2",
48
- "@happyvertical/smrt-ui": "0.53.2",
49
- "@happyvertical/smrt-users": "0.53.2",
50
- "@happyvertical/smrt-web": "0.53.2",
38
+ "@happyvertical/smrt-app-mcp": "0.53.4",
39
+ "@happyvertical/smrt-app-runtime": "0.53.4",
40
+ "@happyvertical/smrt-assets": "0.53.4",
41
+ "@happyvertical/smrt-cli": "0.53.4",
42
+ "@happyvertical/smrt-config": "0.53.4",
43
+ "@happyvertical/smrt-jobs": "0.53.4",
44
+ "@happyvertical/smrt-mcp-openai": "0.53.4",
45
+ "@happyvertical/smrt-playbooks": "0.53.4",
46
+ "@happyvertical/smrt-profiles": "0.53.4",
47
+ "@happyvertical/smrt-scanner": "0.53.4",
48
+ "@happyvertical/smrt-svelte": "0.53.4",
49
+ "@happyvertical/smrt-tenancy": "0.53.4",
50
+ "@happyvertical/smrt-ui": "0.53.4",
51
+ "@happyvertical/smrt-users": "0.53.4",
52
+ "@happyvertical/smrt-web": "0.53.4",
51
53
  "@happyvertical/sql": "^0.89.12",
52
54
  "@modelcontextprotocol/server": "^2.0.0",
53
55
  "@playwright/test": "^1.63.0",
@@ -18,7 +18,16 @@ tools, WebMCP definitions, and agent/developer knowledge artifacts.
18
18
  return only the schema-versioned allowlist; never expose private runtime
19
19
  objects, identity fields, paths, URLs, secrets, PII, raw errors, or logs.
20
20
  - Never improvise setup internals. Run `pnpm app:install`; use individual
21
- `app:*` commands only for recovery or explicit operator control.
21
+ `app:*` commands only for recovery or explicit operator control. They call
22
+ `smrt app <operation>` from `@happyvertical/smrt-cli`; do not copy operator
23
+ scripts into this app.
24
+ - Server composition (profile, storage custody, owner bootstrap, tenant
25
+ selection, session, health, diagnostics) comes from
26
+ `@happyvertical/smrt-app-runtime/sveltekit`, configured once in
27
+ `src/lib/server/smrt.ts`. Pass app-specific behaviour as options
28
+ (`selectTenant`, `classOverrides`, `session`, `prepareDatabase`); do not
29
+ reimplement hooks. `getCollection()`/`getSmrtConfig()` are request-scoped:
30
+ never keep their result beyond the request.
22
31
  - Recover an interrupted unclaimed owner invitation with `pnpm app:stop`, then
23
32
  `pnpm app:recover`, `pnpm app:start`, and `pnpm app:open`; never print or copy
24
33
  its token.
@@ -48,8 +57,9 @@ tools, WebMCP definitions, and agent/developer knowledge artifacts.
48
57
  - Keep object relationship metadata close to `@smrt()`.
49
58
  - Add human-facing routes explicitly; generated REST routes do not create nav
50
59
  pages.
51
- - The root layout includes `@happyvertical/smrt-web` and registers generated
52
- WebMCP read tools through Provider when the browser exposes `modelContext`.
60
+ - The root layout is `AppShell` from `@happyvertical/smrt-svelte/app`; it
61
+ includes `@happyvertical/smrt-web` and registers generated WebMCP read tools
62
+ through its Provider when the browser exposes `modelContext`.
53
63
  Keep the guard in place for SSR and seed live collections with SSR
54
64
  `initialData`.
55
65
  - Do not enable knowledge HTTP routes in production without explicit admin auth.
@@ -57,8 +67,9 @@ tools, WebMCP definitions, and agent/developer knowledge artifacts.
57
67
  imports `.smrt/runtime/register.js` (compiled by `pnpm build`) before any
58
68
  runner starts, so jobs can target this app's own `@smrt()` objects; keep that
59
69
  import ahead of runner creation and keep domain objects free of SvelteKit
60
- virtual modules (`$env/*`, `$app/*`), which a worker cannot load. Extend the
61
- portability adapter instead of converting database files. Deployed provider
70
+ virtual modules (`$env/*`, `$app/*`), which a worker cannot load. Add a
71
+ `scripts/smrt-portability.mjs` adapter (it overrides the CLI's built-in one)
72
+ instead of converting database files. Deployed provider
62
73
  readiness must come from installed modules that probe the real backing
63
74
  services; environment booleans are not readiness evidence.
64
75
  - Every supported local web entry point (`app:start` or `pnpm dev`) holds the
@@ -17,7 +17,6 @@ ENV NODE_ENV=production
17
17
  ENV SMRT_RUNTIME_PROFILE=self-hosted
18
18
  COPY --from=build /app/build ./build
19
19
  COPY --from=build /app/.smrt ./.smrt
20
- COPY --from=build /app/scripts ./scripts
21
20
  COPY --from=build /app/smrt.config.ts ./smrt.config.ts
22
21
  RUN chown -R node:node /app
23
22
  USER node
@@ -7,5 +7,5 @@
7
7
  > `pnpm app:doctor`, show me its secret-free recovery findings, and ask before
8
8
  > changing provider configuration.
9
9
 
10
- The prompt is intentionally thin. `scripts/smrt-app.mjs` owns operations and
11
- recovery.
10
+ The prompt is intentionally thin. `smrt app` (from `@happyvertical/smrt-cli`)
11
+ owns operations and recovery.
@@ -22,7 +22,9 @@ re-running install, setup, or recovery; each operation is repeatable from that
22
22
  stopped state. `pnpm app:doctor`
23
23
  prints secret-free JSON diagnostics and recovery steps. Individual
24
24
  setup/start/doctor/open/stop/backup/export/import operations are available as
25
- `pnpm app:<operation>`.
25
+ `pnpm app:<operation>`. Every lifecycle script is a one-line call to
26
+ `smrt app <operation>` from `@happyvertical/smrt-cli`; the app ships no copied
27
+ operator scripts, so upgrading the CLI upgrades them.
26
28
 
27
29
  `app:start` defaults adapter-node `ORIGIN` to its loopback HTTP URL (including
28
30
  the selected `PORT`) and preserves an explicitly configured `ORIGIN`. Origin
@@ -58,9 +60,12 @@ The source of truth is `src/lib/objects`. Running `pnpm dev`, `pnpm build`, or
58
60
  | `src/lib/types/smrt-generated/` | Virtual-module and consumer declarations | No |
59
61
  | `src/routes/api/**/+server.ts` | Generated SvelteKit REST routes | No |
60
62
 
61
- Do not edit generated files. `smrtPlugin()` owns local scanning, manifests,
62
- types, and routes. `smrtConsumer()` explicitly consumes the profiles, tenancy,
63
- and users manifests so those models are available to setup and tooling.
63
+ Do not edit generated files. The `smrt()` plugin in `vite.config.ts` owns
64
+ local scanning, manifests, types, routes, and the decorator transform. It
65
+ consumes exactly the packages listed in `smrt.config.ts` under
66
+ `consumer.packages` (profiles, tenancy, and users) so those models are
67
+ available to setup and tooling; add a SMRT package there when the app uses its
68
+ objects.
64
69
 
65
70
  ## 3. Define the first object
66
71
 
@@ -130,22 +135,31 @@ migration command, not by adding schema creation to a request handler.
130
135
 
131
136
  ## 5. Understand tenant context
132
137
 
133
- `src/hooks.server.ts` keeps tenant selection separate from authorization:
138
+ `src/hooks.server.ts` mounts the runtime configured in `src/lib/server/smrt.ts`
139
+ (`createSmrtSvelteKitRuntime()` from `@happyvertical/smrt-app-runtime/sveltekit`).
140
+ It keeps tenant selection separate from authorization:
134
141
 
135
- 1. `src/lib/server/tenancy.ts` reads a subdomain slug and looks up an active
136
- Tenant UUID. It stores the candidate in `locals.selectedTenantId` and
137
- `locals.selectedTenantSlug`.
142
+ 1. A subdomain slug is looked up as an active Tenant UUID and stored only as a
143
+ candidate in `locals.selectedTenantId` and `locals.selectedTenantSlug`.
138
144
  2. That candidate does not enter AsyncLocalStorage and cannot scope queries.
139
- 3. `createSessionHandler({ enterTenantContext: true })` loads the signed session,
140
- resolves its membership and permissions, and establishes the authorized
141
- `locals.tenantId` context.
142
- 4. `enableTenancy()` makes `@TenantScoped` collections honor that context.
143
-
144
- The default resolver ignores `x-tenant-id`. If a gateway supplies a tenant
145
- header, validate the gateway identity/signature before mapping it to a tenant,
146
- and still use `switchSessionTenant()` for browser session changes. That helper
147
- checks active membership and rotates the session ID; never copy an untrusted
148
- header directly into `locals.tenantId` or `enterTenantContext()`.
145
+ 3. The signed session is loaded. Its tenant is accepted only for an active (or
146
+ legitimately inherited) membership, which establishes the authorized
147
+ `locals.tenantId`, the permission set, and the tenant context.
148
+ 4. The runtime enables tenancy, so `@TenantScoped` collections honor that
149
+ context.
150
+
151
+ The default selector ignores `x-tenant-id`. To select tenants from a path,
152
+ signed cookie, or trusted gateway, pass `selectTenant` to
153
+ `createSmrtSvelteKitRuntime()`; selection must still never establish
154
+ authorization. Validate a gateway's identity/signature before mapping its
155
+ header to a tenant, and use `switchSessionTenant()` for browser session
156
+ changes. That helper checks active membership and rotates the session ID; never
157
+ copy an untrusted header directly into `locals.tenantId` or
158
+ `enterTenantContext()`.
159
+
160
+ `getCollection()` and `getSmrtConfig()` in `$lib/server/smrt` return
161
+ request-scoped options (under `database-rls` isolation they carry the request
162
+ transaction): call them inside a request and never keep the result.
149
163
 
150
164
  Set `TENANT_BASE_DOMAIN` for deployed subdomain routing. The fallback parser is
151
165
  only for local shapes such as `acme.demo.local`.
@@ -234,10 +248,14 @@ non-object or malformed manifests, credential-shaped JSON fields and credential
234
248
  URL userinfo, non-portable schemas, and non-loopback HTTP server URLs. Diagnostics never
235
249
  include malformed manifest content.
236
250
 
237
- The option stages `src/routes/api/mcp/+server.ts`, a session-authorized `mcp`
238
- server, and a bounded static resource with a restrictive CSP. It also includes
239
- `McpAppsBridge.svelte` for an application-configured trusted host origin and
240
- the optional OpenAI display adapter; it stays inert until your UI mounts it.
251
+ The option stages `src/routes/api/mcp/+server.ts`, one `mountMcpAppRoute()`
252
+ call from `@happyvertical/smrt-app-mcp/sveltekit` that publishes `Item` to
253
+ principals holding `items.read` (the signed session's permissions locally),
254
+ refuses browser requests from a foreign `Origin`, and serves a bounded static
255
+ resource with a restrictive CSP and the optional OpenAI display metadata. It
256
+ also stages the protected-resource metadata route. For a view, mount
257
+ `McpAppsBridge` from `@happyvertical/smrt-svelte/mcp-apps` with an
258
+ application-configured trusted host origin.
241
259
  The scaffold does not enable remote MCP tasks, so a deployment that adds them
242
260
  must supply the durable worker's live authorization callback before publication.
243
261
 
@@ -300,7 +318,7 @@ narrower tool set.
300
318
  ```svelte
301
319
  <script lang="ts">
302
320
  import { webMcpToolDefinitions } from '@happyvertical/smrt-virt-web';
303
- import { Provider } from '@happyvertical/smrt-svelte';
321
+ import { AppShell } from '@happyvertical/smrt-svelte/app';
304
322
 
305
323
  const webmcp = $derived(
306
324
  typeof document !== 'undefined' && 'modelContext' in document
@@ -309,11 +327,17 @@ narrower tool set.
309
327
  );
310
328
  </script>
311
329
 
312
- <Provider {webmcp}>
330
+ <!-- AppShell passes `webmcp` to its Provider unchanged. -->
331
+ <AppShell title="s-m-r-t app" {webmcp} {nav}>
313
332
  {@render children()}
314
- </Provider>
333
+ </AppShell>
315
334
  ```
316
335
 
336
+ The root layout is `AppShell` from `@happyvertical/smrt-svelte/app` (Provider,
337
+ theme and its CSS, the admin shell, and navigation); the app supplies its
338
+ navigation and content. `/setup` and `/settings` use the same package's
339
+ `OwnerSetupForm` and `ShellSettingsPage`.
340
+
317
341
  `registerWebMcpTools()` feature-detects browser support and uses the current
318
342
  authenticated page session. Omitted policy exposes all `read`-effect tools:
319
343
  intrinsic `list`/`get` plus custom actions explicitly declared as reads. To
@@ -399,8 +423,9 @@ Every supported local web
399
423
  entry point (`app:start` or `pnpm dev`) holds a shared writer lease. Direct
400
424
  production startup must set an explicit loopback `HOST`, and `app:start` is the
401
425
  recommended entry point. Stop the app before backup/import. For deployed import, stop
402
- web/workers and set `SMRT_MAINTENANCE_MODE=true`. Extend
403
- `scripts/smrt-portability.mjs` for domain-specific transformations.
426
+ web/workers and set `SMRT_MAINTENANCE_MODE=true`. For domain-specific
427
+ transformations, add a `scripts/smrt-portability.mjs` adapter; when present it
428
+ replaces `smrt app export`/`import`'s built-in one.
404
429
 
405
430
  ### Hosted MCP authorization
406
431
 
@@ -16,7 +16,7 @@ services:
16
16
  build: .
17
17
  env_file: .env.self-hosted
18
18
  environment: *runtime-environment
19
- command: node scripts/smrt-worker.mjs task
19
+ command: pnpm exec smrt app worker task
20
20
  depends_on:
21
21
  migrate:
22
22
  condition: service_completed_successfully
@@ -24,7 +24,7 @@ services:
24
24
  build: .
25
25
  env_file: .env.self-hosted
26
26
  environment: *runtime-environment
27
- command: node scripts/smrt-worker.mjs schedule
27
+ command: pnpm exec smrt app worker schedule
28
28
  depends_on:
29
29
  migrate:
30
30
  condition: service_completed_successfully