@happyvertical/smrt-core 0.51.10 → 0.51.14

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 (93) hide show
  1. package/agents/change-feed.md +82 -0
  2. package/agents/generators.md +3 -1
  3. package/agents/schema-paths.md +23 -6
  4. package/dist/change-feed-sensitivity.d.ts +155 -0
  5. package/dist/change-feed-sensitivity.d.ts.map +1 -0
  6. package/dist/change-feed-sensitivity.js +139 -0
  7. package/dist/change-feed-sensitivity.js.map +1 -0
  8. package/dist/change-feed.d.ts +37 -10
  9. package/dist/change-feed.d.ts.map +1 -1
  10. package/dist/change-feed.js +128 -12
  11. package/dist/change-feed.js.map +1 -1
  12. package/dist/change-signals.d.ts.map +1 -1
  13. package/dist/change-signals.js +3 -0
  14. package/dist/change-signals.js.map +1 -1
  15. package/dist/class.d.ts +1 -1
  16. package/dist/class.d.ts.map +1 -1
  17. package/dist/class.js +4 -2
  18. package/dist/class.js.map +1 -1
  19. package/dist/decorators/index.d.ts +19 -0
  20. package/dist/decorators/index.d.ts.map +1 -1
  21. package/dist/decorators/index.js.map +1 -1
  22. package/dist/index.d.ts +1 -0
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +3 -2
  25. package/dist/manifest/static-manifest.js +1 -1
  26. package/dist/manifest/static-manifest.js.map +1 -1
  27. package/dist/manifest/store.js +1 -1
  28. package/dist/manifest.json +1 -1
  29. package/dist/migrations/differ.d.ts +64 -5
  30. package/dist/migrations/differ.d.ts.map +1 -1
  31. package/dist/migrations/differ.js +274 -37
  32. package/dist/migrations/differ.js.map +1 -1
  33. package/dist/migrations/index.d.ts +1 -1
  34. package/dist/migrations/index.d.ts.map +1 -1
  35. package/dist/migrations/index.js +2 -2
  36. package/dist/migrations/sqlite-rebuild.d.ts +41 -0
  37. package/dist/migrations/sqlite-rebuild.d.ts.map +1 -1
  38. package/dist/migrations/sqlite-rebuild.js +68 -13
  39. package/dist/migrations/sqlite-rebuild.js.map +1 -1
  40. package/dist/migrations.js +2 -2
  41. package/dist/node_modules/.pnpm/{minimatch@10.2.5 → minimatch@10.2.6}/node_modules/minimatch/dist/esm/assert-valid-pattern.js +1 -1
  42. package/dist/node_modules/.pnpm/{minimatch@10.2.5 → minimatch@10.2.6}/node_modules/minimatch/dist/esm/assert-valid-pattern.js.map +1 -1
  43. package/dist/node_modules/.pnpm/{minimatch@10.2.5 → minimatch@10.2.6}/node_modules/minimatch/dist/esm/ast.js +1 -1
  44. package/dist/node_modules/.pnpm/{minimatch@10.2.5 → minimatch@10.2.6}/node_modules/minimatch/dist/esm/ast.js.map +1 -1
  45. package/dist/node_modules/.pnpm/{minimatch@10.2.5 → minimatch@10.2.6}/node_modules/minimatch/dist/esm/brace-expressions.js +1 -1
  46. package/dist/node_modules/.pnpm/{minimatch@10.2.5 → minimatch@10.2.6}/node_modules/minimatch/dist/esm/brace-expressions.js.map +1 -1
  47. package/dist/node_modules/.pnpm/{minimatch@10.2.5 → minimatch@10.2.6}/node_modules/minimatch/dist/esm/escape.js +1 -1
  48. package/dist/node_modules/.pnpm/{minimatch@10.2.5 → minimatch@10.2.6}/node_modules/minimatch/dist/esm/escape.js.map +1 -1
  49. package/dist/node_modules/.pnpm/{minimatch@10.2.5 → minimatch@10.2.6}/node_modules/minimatch/dist/esm/index.js +1 -1
  50. package/dist/node_modules/.pnpm/{minimatch@10.2.5 → minimatch@10.2.6}/node_modules/minimatch/dist/esm/index.js.map +1 -1
  51. package/dist/node_modules/.pnpm/{minimatch@10.2.5 → minimatch@10.2.6}/node_modules/minimatch/dist/esm/unescape.js +1 -1
  52. package/dist/node_modules/.pnpm/{minimatch@10.2.5 → minimatch@10.2.6}/node_modules/minimatch/dist/esm/unescape.js.map +1 -1
  53. package/dist/registry/class-registration.d.ts +1 -1
  54. package/dist/registry/class-registration.d.ts.map +1 -1
  55. package/dist/registry/class-registration.js +50 -4
  56. package/dist/registry/class-registration.js.map +1 -1
  57. package/dist/registry/db-cache-key.d.ts +47 -0
  58. package/dist/registry/db-cache-key.d.ts.map +1 -0
  59. package/dist/registry/db-cache-key.js +201 -0
  60. package/dist/registry/db-cache-key.js.map +1 -0
  61. package/dist/registry/schema-builder.d.ts.map +1 -1
  62. package/dist/registry/schema-builder.js +6 -1
  63. package/dist/registry/schema-builder.js.map +1 -1
  64. package/dist/registry/types.d.ts +43 -0
  65. package/dist/registry/types.d.ts.map +1 -1
  66. package/dist/registry.d.ts +17 -1
  67. package/dist/registry.d.ts.map +1 -1
  68. package/dist/registry.js +20 -8
  69. package/dist/registry.js.map +1 -1
  70. package/dist/scanner/types.d.ts +2 -0
  71. package/dist/scanner/types.d.ts.map +1 -1
  72. package/dist/scanner/types.js.map +1 -1
  73. package/dist/schema/backfill.d.ts +16 -0
  74. package/dist/schema/backfill.d.ts.map +1 -0
  75. package/dist/schema/backfill.js +26 -0
  76. package/dist/schema/backfill.js.map +1 -0
  77. package/dist/schema/generator.d.ts.map +1 -1
  78. package/dist/schema/generator.js +3 -0
  79. package/dist/schema/generator.js.map +1 -1
  80. package/dist/schema/types.d.ts +20 -1
  81. package/dist/schema/types.d.ts.map +1 -1
  82. package/dist/smrt-knowledge.json +21 -21
  83. package/dist/vite-plugin/dev-plane-route.js +1 -1
  84. package/dist/vite-plugin/dev-plane-route.js.map +1 -1
  85. package/dist/vite-plugin/index.js +1 -1
  86. package/dist/vite-plugin/sveltekit-generator.d.ts.map +1 -1
  87. package/dist/vite-plugin/sveltekit-generator.js +54 -7
  88. package/dist/vite-plugin/sveltekit-generator.js.map +1 -1
  89. package/dist/vite-plugin/sync-apply-route.d.ts +7 -0
  90. package/dist/vite-plugin/sync-apply-route.d.ts.map +1 -1
  91. package/dist/vite-plugin/sync-apply-route.js +50 -3
  92. package/dist/vite-plugin/sync-apply-route.js.map +1 -1
  93. package/package.json +23 -23
@@ -237,20 +237,33 @@ function collectReadPermissionFields(objectDef, manifest) {
237
237
  return result;
238
238
  }
239
239
  /**
240
+ * The collection segment of generated operation permission slugs. Mirrors the
241
+ * smrt-users `PermissionCatalogService` derivation: an explicit
242
+ * `@smrt({ collection })` wins, then the manifest collection.
243
+ */
244
+ function resolvePermissionCollection(objectDef) {
245
+ const configured = objectDef.decoratorConfig?.collection;
246
+ return typeof configured === "string" && configured.length > 0 ? configured : objectDef.collection;
247
+ }
248
+ /**
240
249
  * Emit the fail-closed authorization guard injected into generated route files
241
250
  * (#1540, 2c). Every generated CRUD/action handler calls `requireRouteAuth`,
242
251
  * which throws 401 unless the route is `public` or `locals` carries an
243
252
  * authenticated principal. Mirrors the knowledge-route `isKnowledgeAdmin`
244
253
  * precedent. Mutating verbs require auth even when reads are public.
245
254
  */
246
- function generateAuthGuardHelper(objectDef, manifest) {
255
+ function generateAuthGuardHelper(objectDef, manifest, permissionObjectDef = objectDef) {
247
256
  const publicAccess = getApiPublicAccess(objectDef.decoratorConfig?.api);
248
257
  const readPermissionFields = collectReadPermissionFields(objectDef, manifest);
258
+ const permissionCollection = resolvePermissionCollection(permissionObjectDef);
249
259
  return `
250
260
  // Fail-closed authorization (#1540): generated routes require an authenticated
251
261
  // principal on \`locals\` unless explicitly marked \`@smrt({ api: { public } })\`.
252
262
  const PUBLIC_ACCESS: boolean | 'read' = ${JSON.stringify(publicAccess)};
253
263
  const READ_PERMISSION_FIELDS: Array<[string, string]> = ${JSON.stringify(readPermissionFields)};
264
+ // Operation permissions (#2977): mutating handlers require
265
+ // \`<collection>.<action>\`, matching the smrt-users permission catalog slug.
266
+ const PERMISSION_COLLECTION = ${JSON.stringify(permissionCollection)};
254
267
 
255
268
  interface PublicJsonOptions {
256
269
  permissions?: Iterable<string>;
@@ -282,6 +295,34 @@ function requireRouteAuth(locals: unknown, mutating: boolean): void {
282
295
  }
283
296
  }
284
297
 
298
+ function hasGrantedPermission(value: unknown, permission: string): boolean {
299
+ if (Array.isArray(value)) return value.includes(permission);
300
+ if (value instanceof Set) return value.has(permission);
301
+ return false;
302
+ }
303
+
304
+ // Fail-closed write authorization (#2977): a mutating handler requires the
305
+ // \`<collection>.<action>\` operation permission in the principal's session
306
+ // permission snapshot (\`locals.permissions\` from smrt-users, or the
307
+ // smrt-tenancy \`locals.tenantContext\`). A missing snapshot denies. Only an
308
+ // explicit tenancy super-admin bypass skips the check; \`public: true\` routes
309
+ // keep their unauthenticated semantics.
310
+ function requireRoutePermission(locals: unknown, action: string): void {
311
+ if (PUBLIC_ACCESS === true) return;
312
+ const l = readJsonRecord(locals);
313
+ const tenantContext = readJsonRecord(l.tenantContext);
314
+ if (tenantContext.superAdminBypass === true) return;
315
+ const permission = \`\${PERMISSION_COLLECTION}.\${action}\`;
316
+ const granted =
317
+ hasGrantedPermission(l.permissions, permission) ||
318
+ hasGrantedPermission(l.permissionSet, permission) ||
319
+ hasGrantedPermission(l.smrtPermissions, permission) ||
320
+ hasGrantedPermission(tenantContext.permissions, permission);
321
+ if (!granted) {
322
+ throw error(403, 'Permission denied');
323
+ }
324
+ }
325
+
285
326
  function getPublicJsonOptions(locals: unknown): PublicJsonOptions {
286
327
  const l = readJsonRecord(locals);
287
328
  const permissions = l.permissions ?? l.permissionSet ?? l.smrtPermissions;
@@ -560,8 +601,12 @@ function tenantReadOptionsScope(): { tenantId: string | null } | undefined {
560
601
  * The per-handler guard preamble: the fail-closed auth check (#1540 2c) plus, for
561
602
  * tenant-scoped objects, tenant-context establishment (#1540 facet 2).
562
603
  */
563
- function routeGuardPreamble(objectDef, mutating) {
604
+ function routeGuardPreamble(objectDef, mutating, permissionAction) {
564
605
  const lines = [` requireRouteAuth(locals, ${mutating});`];
606
+ if (mutating) {
607
+ if (!permissionAction) throw new Error("Mutating generated routes require a permission action");
608
+ lines.push(` requireRoutePermission(locals, ${JSON.stringify(permissionAction)});`);
609
+ }
565
610
  if (needsRouteTenantContext(objectDef)) lines.push(" establishTenantContext(locals);");
566
611
  return lines.join("\n");
567
612
  }
@@ -1713,7 +1758,7 @@ ${listAndCount}
1713
1758
  const postHandler = hasPost ? `
1714
1759
  // Create new ${className.toLowerCase()}
1715
1760
  export const POST: RequestHandler = async ({ locals, request }) => {
1716
- ${routeGuardPreamble(objectDef, true)}
1761
+ ${routeGuardPreamble(objectDef, true, "create")}
1717
1762
  const publicJsonOptions = getPublicJsonOptions(locals);
1718
1763
  const body: unknown = await request.json();
1719
1764
  const data = applyWritablePolicy(body);
@@ -1835,7 +1880,7 @@ ${generateNotFoundError(className)}
1835
1880
  const putHandler = hasPut ? `
1836
1881
  // Update ${simpleClassName.toLowerCase()}
1837
1882
  export const PUT: RequestHandler = async ({ locals, params, request }) => {
1838
- ${routeGuardPreamble(objectDef, true)}
1883
+ ${routeGuardPreamble(objectDef, true, "update")}
1839
1884
  const publicJsonOptions = getPublicJsonOptions(locals);
1840
1885
  ${generateCollectionLoad(className, { typeName: modelType.typeName })}
1841
1886
  const item = await collection.get(params.id);
@@ -1859,7 +1904,7 @@ ${serializers.itemSerializerName ? `
1859
1904
  const deleteHandler = hasDelete ? `
1860
1905
  // Delete ${simpleClassName.toLowerCase()}
1861
1906
  export const DELETE: RequestHandler = async ({ locals, params }) => {
1862
- ${routeGuardPreamble(objectDef, true)}
1907
+ ${routeGuardPreamble(objectDef, true, "delete")}
1863
1908
  ${generateCollectionLoad(className, { typeName: modelType.typeName })}
1864
1909
  const item = await collection.get(params.id);
1865
1910
  ${generateNotFoundError(className)}
@@ -1914,13 +1959,15 @@ function generateActionRouteTemplate(projectRoot, routeDir, routeSpecs, objectDe
1914
1959
  // DO NOT EDIT - changes will be overwritten
1915
1960
 
1916
1961
  ${importBlock}
1917
- ${generateAuthGuardHelper(objectDef, semanticManifest)}${needsTenantContext ? generateTenantContextHelper(principalContext, tenantScoped) : ""}
1962
+ ${generateAuthGuardHelper(objectDef, semanticManifest, hostType === "collection" && firstSpec.lookupObjectDef && isCollectionManifestClass(semanticManifest, objectDef) ? firstSpec.lookupObjectDef : objectDef)}${needsTenantContext ? generateTenantContextHelper(principalContext, tenantScoped) : ""}
1918
1963
  ${generateTypedRouteErrorHelper()}
1919
1964
  ${handlers}`;
1920
1965
  }
1921
1966
  function generateActionRouteHandler(lookupClassName, actionName, actionDef, routeConfig, hostType, tenantScoped, needsTenantContext, lookupTypeName, hostTypeName) {
1922
1967
  const handlerName = routeConfig.method;
1923
- const guardLines = [` requireRouteAuth(locals, ${routeConfig.method !== "GET"});`];
1968
+ const mutating = routeConfig.method !== "GET";
1969
+ const guardLines = [` requireRouteAuth(locals, ${mutating});`];
1970
+ if (mutating) guardLines.push(` requireRoutePermission(locals, ${JSON.stringify(actionName)});`);
1924
1971
  if (needsTenantContext) guardLines.push(" establishTenantContext(locals);");
1925
1972
  const authGuardLine = guardLines.join("\n");
1926
1973
  const needsRequest = actionDef.parameters.length > 0;