@happyvertical/smrt-app-runtime 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 (41) hide show
  1. package/AGENTS.md +40 -0
  2. package/README.md +101 -0
  3. package/dist/application-identity.d.ts +43 -0
  4. package/dist/application-identity.d.ts.map +1 -0
  5. package/dist/chunks/src-DZGOVGsU.js +2258 -0
  6. package/dist/chunks/src-DZGOVGsU.js.map +1 -0
  7. package/dist/deployed-runtime.d.ts +11 -0
  8. package/dist/deployed-runtime.d.ts.map +1 -1
  9. package/dist/error-code.d.ts +3 -0
  10. package/dist/error-code.d.ts.map +1 -0
  11. package/dist/index.d.ts +6 -0
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js +2 -1831
  14. package/dist/manifest.json +1 -1
  15. package/dist/operation-lock.d.ts +28 -0
  16. package/dist/operation-lock.d.ts.map +1 -0
  17. package/dist/provider-readiness.d.ts +48 -0
  18. package/dist/provider-readiness.d.ts.map +1 -0
  19. package/dist/smrt-knowledge.json +9 -6
  20. package/dist/stale-reclaim.d.ts +33 -0
  21. package/dist/stale-reclaim.d.ts.map +1 -0
  22. package/dist/state-root.d.ts +42 -0
  23. package/dist/state-root.d.ts.map +1 -0
  24. package/dist/sveltekit/handlers.d.ts +83 -0
  25. package/dist/sveltekit/handlers.d.ts.map +1 -0
  26. package/dist/sveltekit/index.d.ts +20 -0
  27. package/dist/sveltekit/index.d.ts.map +1 -0
  28. package/dist/sveltekit/owner-setup.d.ts +102 -0
  29. package/dist/sveltekit/owner-setup.d.ts.map +1 -0
  30. package/dist/sveltekit/runtime.d.ts +277 -0
  31. package/dist/sveltekit/runtime.d.ts.map +1 -0
  32. package/dist/sveltekit/tenancy.d.ts +59 -0
  33. package/dist/sveltekit/tenancy.d.ts.map +1 -0
  34. package/dist/sveltekit.d.ts +2 -0
  35. package/dist/sveltekit.d.ts.map +1 -0
  36. package/dist/sveltekit.js +704 -0
  37. package/dist/sveltekit.js.map +1 -0
  38. package/dist/writer-lease.d.ts +36 -0
  39. package/dist/writer-lease.d.ts.map +1 -0
  40. package/package.json +22 -8
  41. package/dist/index.js.map +0 -1
package/AGENTS.md CHANGED
@@ -68,6 +68,46 @@ Application infrastructure composition for the validated runtime profiles in
68
68
  - Cloud must keep required tenant context and must never introduce a root or
69
69
  unscoped tenant fallback. RLS remains an explicit deployment/migration choice.
70
70
 
71
+ ## SvelteKit entry (`./sveltekit`)
72
+
73
+ - Server-only subpath; `@sveltejs/kit` is an optional peer and is externalized
74
+ in `vite.config.ts` (kit recognizes `redirect`/`fail` by class identity).
75
+ The root entry must never import kit or Svelte.
76
+ - `createSmrtSvelteKitRuntime()` order is fixed: readiness gate → URL tenant
77
+ selection (`locals.selectedTenant*`, never tenant context, headers ignored) →
78
+ verified session (tenant context entered only when
79
+ `isSessionTenantAuthorized()` accepts it, else unauthenticated; never via
80
+ `createSessionHandler`, which re-runs `resolve` on downstream errors) →
81
+ publish `locals.tenantContext` only for that verified tenant.
82
+ - `database-rls` isolation forces `postgresRls: true`, including on
83
+ `skipPaths` (anonymous principal). `resolve` runs at most once;
84
+ session-layer failure before it is a 500 with cleared locals.
85
+ - In an RLS request `classOptions()`/`databaseConfig()` return the
86
+ transaction-bound request db (class `db` overrides win). Anything that
87
+ outlives a request (session service, tenant selector) must use the base
88
+ config, never these.
89
+ - The writer lease, provider readiness, and onboarding file cleanup are
90
+ injected hooks. Apps pass the root entry's `acquireWriterLease` (over
91
+ `prepareApplicationStateRoot`) and `createProviderReadinessProbe`; deployed
92
+ startup fails closed without `providerReadiness`.
93
+ - Owner setup re-checks loopback peer and loopback URL host per request and
94
+ returns only fixed `{ code, message }` failures.
95
+ - `resolveApplicationId()` / `runtimeConfigurationFingerprint()` must stay
96
+ byte-compatible with process managers (golden vectors in tests).
97
+
98
+ ## Operator state (root entry)
99
+
100
+ - One owner for the state shared by the web process and `smrt app`
101
+ (`@happyvertical/smrt-cli/app` re-exports these): `resolveApplicationStateRoot`
102
+ / `prepareApplicationStateRoot` (private app-bound mode-0700 root and
103
+ marker), `withOperationLock` (`operation.lock`), `acquireWriterLease` /
104
+ `readActiveWriterLease` (`writer.lease`), `reclaimStaleRecord`, and
105
+ `createProviderReadinessProbe` (`SMRT_*_READINESS_MODULE`, resolved from the
106
+ app root with Node's own ESM rules). File names, record formats, and
107
+ messages are a cross-process contract; never change them unilaterally.
108
+ - Tests that hook `node:fs` interleavings must live in this package: from a
109
+ consumer the package is an externalized dependency a mock cannot reach.
110
+
71
111
  ## Public runtime diagnostics
72
112
 
73
113
  - `projectRuntimeDiagnostics()` is the only public diagnostic projection. It
package/README.md CHANGED
@@ -102,6 +102,107 @@ explicitly enabled. With embedded job topology, `createEmbeddedJobRunner()`
102
102
  returns the normal s-m-r-t `TaskRunner`, so the application keeps one enqueue
103
103
  and execution contract without requiring a separate worker service.
104
104
 
105
+ ## SvelteKit entry
106
+
107
+ `@happyvertical/smrt-app-runtime/sveltekit` composes the profile runtime,
108
+ tenant selection, the signed session, and authorized-tenant locals so an app's
109
+ server hooks are a few lines. `@sveltejs/kit` is an optional peer dependency;
110
+ the root entry never imports it.
111
+
112
+ ```ts
113
+ // src/hooks.server.ts
114
+ import { createSmrtSvelteKitRuntime } from '@happyvertical/smrt-app-runtime/sveltekit';
115
+
116
+ export const runtime = createSmrtSvelteKitRuntime({
117
+ prepareDatabase: runApplicationMigrations, // optional, idempotent
118
+ });
119
+ export const { handle, init } = runtime;
120
+ ```
121
+
122
+ ```ts
123
+ // src/app.d.ts
124
+ import type { SmrtRuntimeLocals } from '@happyvertical/smrt-app-runtime/sveltekit';
125
+ declare global {
126
+ namespace App {
127
+ interface Locals extends SmrtRuntimeLocals {}
128
+ }
129
+ }
130
+ ```
131
+
132
+ `init` is the fail-closed startup gate (local runtime or deployed bindings) and
133
+ `handle` waits for it, then runs, in order: URL tenant selection into
134
+ `locals.selectedTenant*` (never tenant context; tenant headers are ignored),
135
+ the signed session, and publication of `locals.tenantContext` only when the
136
+ active context matches the verified session tenant. A session's tenant is
137
+ accepted only for an active direct membership or a legitimately inherited
138
+ one (`isSessionTenantAuthorized()`, the same rule deployed `restoreSession()`
139
+ applies); otherwise the request is unauthenticated (no user, permissions,
140
+ tenant, or tenant context). `database-rls` isolation always runs the request
141
+ RLS transaction (`session.postgresRls` cannot disable it); a
142
+ `session.skipPaths` prefix skips session loading only and still runs inside
143
+ that transaction as an anonymous principal. During such a request
144
+ `runtime.classOptions()` / `runtime.databaseConfig()` return the
145
+ transaction-bound request database (unless the class has its own `db`
146
+ override), so call them per request and never retain the result. Downstream code runs
147
+ at most once: its error propagates unchanged, and a session-layer failure
148
+ before it returns 500 with no authenticated locals. Defaults read `SMRT_APP_ID`, `SMRT_DATA_DIR`, `HOST`, `DATABASE_URL`,
149
+ `TENANT_BASE_DOMAIN`, and `SMRT_BACKGROUND_JOBS`; `smrt.config` `runtime`
150
+ selects the profile (local when absent). Deployed profiles additionally require
151
+ `providerReadiness` probes and fail closed without them. Optional hooks:
152
+ `acquireWriterLease` (local single-writer lease), `onBootstrapInvitation`
153
+ (present a newly issued setup token), `selectTenant`, `session`, and
154
+ `classOverrides`. `runtime.classOptions(className)` returns collection options
155
+ for application code.
156
+
157
+ Mountable routes:
158
+
159
+ ```ts
160
+ // src/routes/api/_runtime/health/+server.ts
161
+ export const GET = createRuntimeHealthHandler(runtime);
162
+ // src/routes/api/_runtime/diagnostics/+server.ts
163
+ export const GET = createRuntimeDiagnosticsHandler({ runtime, toolNames });
164
+ // src/routes/setup/+page.server.ts
165
+ export const { load, actions } = createOwnerSetupPage(runtime);
166
+ // src/routes/+layout.server.ts
167
+ export const load = createSessionLayoutLoad();
168
+ ```
169
+
170
+ Diagnostics authorize (owner role or `runtime_diagnostics.read` on an active,
171
+ session-matching membership) before reading the runtime and return only stable
172
+ `{ schemaVersion: 1, error: { code } }` failures. Owner setup is local-only and
173
+ re-checks on every request that both the peer address and the URL host are
174
+ loopback; its `default` action reads `token`, `name`, `email`, and optional
175
+ `tenantName`, sets the session cookie, and redirects 303, or returns
176
+ `fail(status, { code, message })` with `setup_disabled` (404),
177
+ `setup_unavailable` (403), `setup_invalid_input` (400), or `setup_invalid`
178
+ (400). Claim error text is never returned.
179
+
180
+ `resolveApplicationId()` and `runtimeConfigurationFingerprint()` (root entry)
181
+ are the canonical app ID and secret-free configuration fingerprint shared by
182
+ the web health route and process managers.
183
+
184
+ The root entry also owns the operator state shared with `smrt app`:
185
+ `prepareApplicationStateRoot()` (private, app-bound state directory),
186
+ `withOperationLock()`, `acquireWriterLease()` / `readActiveWriterLease()`,
187
+ and `createProviderReadinessProbe()`. A SvelteKit app wires the last two into
188
+ its runtime without depending on the CLI:
189
+
190
+ ```ts
191
+ import {
192
+ acquireWriterLease,
193
+ createProviderReadinessProbe,
194
+ prepareApplicationStateRoot,
195
+ } from '@happyvertical/smrt-app-runtime';
196
+
197
+ export const runtime = createSmrtSvelteKitRuntime({
198
+ acquireWriterLease: (context) =>
199
+ acquireWriterLease(prepareApplicationStateRoot(context), {
200
+ operationInstance: process.env.SMRT_OPERATION_INSTANCE,
201
+ }),
202
+ providerReadiness: createProviderReadinessProbe,
203
+ });
204
+ ```
205
+
105
206
  ## Self-hosted and cloud applications
106
207
 
107
208
  The deployed initializer validates the selected profile against concrete,
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Canonical application identity shared by the web process, operator commands,
3
+ * and generated operational surfaces.
4
+ *
5
+ * Every process that manages the same application must derive the same app ID
6
+ * and the same configuration fingerprint, so these helpers live next to
7
+ * `validateApplicationId()` / `encodeApplicationId()` rather than in copied
8
+ * template scripts.
9
+ */
10
+ export interface ResolveApplicationIdOptions {
11
+ /** Directory containing the application's `package.json`. Defaults to cwd. */
12
+ sourceRoot?: string;
13
+ /** Package name to encode instead of reading `package.json`. */
14
+ packageName?: string;
15
+ /** Explicit, strictly validated app ID (for example `SMRT_APP_ID`). */
16
+ explicitId?: string;
17
+ }
18
+ /**
19
+ * Resolve one stable application identity.
20
+ *
21
+ * An explicit ID is validated with {@link validateApplicationId}; otherwise
22
+ * the package name is encoded with {@link encodeApplicationId}.
23
+ */
24
+ export declare function resolveApplicationId(options?: ResolveApplicationIdOptions): string;
25
+ /**
26
+ * Runtime selection the fingerprint covers. Structural so operator tooling
27
+ * can fingerprint a resolved runtime it loaded itself; a
28
+ * {@link ResolvedApplicationRuntime} always satisfies it.
29
+ */
30
+ export interface FingerprintRuntime {
31
+ readonly profile: string;
32
+ readonly providers: unknown;
33
+ }
34
+ /**
35
+ * Secret-safe identity used to reject stale managed processes after profile,
36
+ * provider, database-target, or listener configuration changes.
37
+ *
38
+ * Credentials, query strings, and fragments of `DATABASE_URL` are excluded;
39
+ * only `sslmode` survives. The digest is stable for identical inputs so a
40
+ * process manager and the web process can compare it.
41
+ */
42
+ export declare function runtimeConfigurationFingerprint(runtime: FingerprintRuntime, environment?: Readonly<Record<string, string | undefined>>): string;
43
+ //# sourceMappingURL=application-identity.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"application-identity.d.ts","sourceRoot":"","sources":["../src/application-identity.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAQH,MAAM,WAAW,2BAA2B;IAC1C,8EAA8E;IAC9E,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,gEAAgE;IAChE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,uEAAuE;IACvE,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,GAAE,2BAAgC,GACxC,MAAM,CAeR;AAkBD;;;;GAIG;AACH,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;CAC7B;AAED;;;;;;;GAOG;AACH,wBAAgB,+BAA+B,CAC7C,OAAO,EAAE,kBAAkB,EAC3B,WAAW,GAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAe,GACtE,MAAM,CAyBR"}