@happyvertical/smrt-fields 0.40.59 → 0.40.61

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 (184) hide show
  1. package/AGENTS.md +58 -12
  2. package/README.md +90 -2
  3. package/dist/__smrt-register__.d.ts +2 -0
  4. package/dist/__smrt-register__.d.ts.map +1 -0
  5. package/dist/cache.d.ts +18 -0
  6. package/dist/cache.d.ts.map +1 -0
  7. package/dist/collections/FieldPolicyCollection.d.ts +95 -0
  8. package/dist/collections/FieldPolicyCollection.d.ts.map +1 -0
  9. package/dist/field-definitions.d.ts +77 -0
  10. package/dist/field-definitions.d.ts.map +1 -0
  11. package/dist/field-policy-resolver.d.ts +46 -0
  12. package/dist/field-policy-resolver.d.ts.map +1 -0
  13. package/dist/index.d.ts +9 -532
  14. package/dist/index.d.ts.map +1 -0
  15. package/dist/index.js +752 -55
  16. package/dist/index.js.map +1 -1
  17. package/dist/manifest.json +45 -3
  18. package/dist/models/FieldPolicy.d.ts +163 -0
  19. package/dist/models/FieldPolicy.d.ts.map +1 -0
  20. package/dist/permissions.d.ts +9 -0
  21. package/dist/permissions.d.ts.map +1 -0
  22. package/dist/playground.d.ts +2 -0
  23. package/dist/playground.d.ts.map +1 -0
  24. package/dist/playground.js +39 -0
  25. package/dist/playground.js.map +1 -0
  26. package/dist/settings-catalog.d.ts +73 -0
  27. package/dist/settings-catalog.d.ts.map +1 -0
  28. package/dist/smrt-knowledge.json +50 -7
  29. package/dist/svelte/__tests__/FieldPolicyControlPanel.test.js +223 -0
  30. package/dist/svelte/__tests__/FieldPolicyGear.test.js +503 -0
  31. package/dist/svelte/__tests__/FormHelp.test.js +138 -0
  32. package/dist/svelte/__tests__/ModeUX.test.js +109 -0
  33. package/dist/svelte/__tests__/ObjectForm.test.js +556 -0
  34. package/dist/svelte/__tests__/ObjectFormGeneratedApi.integration.test.js +436 -0
  35. package/dist/svelte/__tests__/PolicyField.test.js +373 -0
  36. package/dist/svelte/__tests__/fixtures/AdvancedFieldsFixture.svelte +30 -0
  37. package/dist/svelte/__tests__/fixtures/AdvancedFieldsFixture.svelte.d.ts +9 -0
  38. package/dist/svelte/__tests__/fixtures/AdvancedFieldsFixture.svelte.d.ts.map +1 -0
  39. package/dist/svelte/__tests__/fixtures/DomPrefillFixture.svelte +33 -0
  40. package/dist/svelte/__tests__/fixtures/DomPrefillFixture.svelte.d.ts +10 -0
  41. package/dist/svelte/__tests__/fixtures/DomPrefillFixture.svelte.d.ts.map +1 -0
  42. package/dist/svelte/__tests__/fixtures/FormHelpFixture.svelte +19 -0
  43. package/dist/svelte/__tests__/fixtures/FormHelpFixture.svelte.d.ts +10 -0
  44. package/dist/svelte/__tests__/fixtures/FormHelpFixture.svelte.d.ts.map +1 -0
  45. package/dist/svelte/__tests__/fixtures/GeneratedApiGearFixture.svelte +18 -0
  46. package/dist/svelte/__tests__/fixtures/GeneratedApiGearFixture.svelte.d.ts +11 -0
  47. package/dist/svelte/__tests__/fixtures/GeneratedApiGearFixture.svelte.d.ts.map +1 -0
  48. package/dist/svelte/__tests__/fixtures/GracefulDegradeFixture.svelte +11 -0
  49. package/dist/svelte/__tests__/fixtures/GracefulDegradeFixture.svelte.d.ts +19 -0
  50. package/dist/svelte/__tests__/fixtures/GracefulDegradeFixture.svelte.d.ts.map +1 -0
  51. package/dist/svelte/__tests__/fixtures/ModeSwitchFixture.svelte +18 -0
  52. package/dist/svelte/__tests__/fixtures/ModeSwitchFixture.svelte.d.ts +8 -0
  53. package/dist/svelte/__tests__/fixtures/ModeSwitchFixture.svelte.d.ts.map +1 -0
  54. package/dist/svelte/__tests__/fixtures/NoIdFixture.svelte +24 -0
  55. package/dist/svelte/__tests__/fixtures/NoIdFixture.svelte.d.ts +13 -0
  56. package/dist/svelte/__tests__/fixtures/NoIdFixture.svelte.d.ts.map +1 -0
  57. package/dist/svelte/__tests__/fixtures/ObjectFormActionsFixture.svelte +20 -0
  58. package/dist/svelte/__tests__/fixtures/ObjectFormActionsFixture.svelte.d.ts +11 -0
  59. package/dist/svelte/__tests__/fixtures/ObjectFormActionsFixture.svelte.d.ts.map +1 -0
  60. package/dist/svelte/__tests__/fixtures/ObjectFormFixture.svelte +17 -0
  61. package/dist/svelte/__tests__/fixtures/ObjectFormFixture.svelte.d.ts +10 -0
  62. package/dist/svelte/__tests__/fixtures/ObjectFormFixture.svelte.d.ts.map +1 -0
  63. package/dist/svelte/__tests__/fixtures/ObjectFormRegistryFixture.svelte +25 -0
  64. package/dist/svelte/__tests__/fixtures/ObjectFormRegistryFixture.svelte.d.ts +10 -0
  65. package/dist/svelte/__tests__/fixtures/ObjectFormRegistryFixture.svelte.d.ts.map +1 -0
  66. package/dist/svelte/__tests__/fixtures/ObjectFormSnippetFixture.svelte +34 -0
  67. package/dist/svelte/__tests__/fixtures/ObjectFormSnippetFixture.svelte.d.ts +10 -0
  68. package/dist/svelte/__tests__/fixtures/ObjectFormSnippetFixture.svelte.d.ts.map +1 -0
  69. package/dist/svelte/__tests__/fixtures/ObjectFormSourceFixture.svelte +29 -0
  70. package/dist/svelte/__tests__/fixtures/ObjectFormSourceFixture.svelte.d.ts +11 -0
  71. package/dist/svelte/__tests__/fixtures/ObjectFormSourceFixture.svelte.d.ts.map +1 -0
  72. package/dist/svelte/__tests__/fixtures/ObjectFormSourceSnippetFixture.svelte +33 -0
  73. package/dist/svelte/__tests__/fixtures/ObjectFormSourceSnippetFixture.svelte.d.ts +9 -0
  74. package/dist/svelte/__tests__/fixtures/ObjectFormSourceSnippetFixture.svelte.d.ts.map +1 -0
  75. package/dist/svelte/__tests__/fixtures/PolicyDataTableFixture.svelte +40 -0
  76. package/dist/svelte/__tests__/fixtures/PolicyDataTableFixture.svelte.d.ts +8 -0
  77. package/dist/svelte/__tests__/fixtures/PolicyDataTableFixture.svelte.d.ts.map +1 -0
  78. package/dist/svelte/__tests__/fixtures/PrefillFixture.svelte +26 -0
  79. package/dist/svelte/__tests__/fixtures/PrefillFixture.svelte.d.ts +13 -0
  80. package/dist/svelte/__tests__/fixtures/PrefillFixture.svelte.d.ts.map +1 -0
  81. package/dist/svelte/__tests__/fixtures/ProviderFieldFixture.svelte +39 -0
  82. package/dist/svelte/__tests__/fixtures/ProviderFieldFixture.svelte.d.ts +17 -0
  83. package/dist/svelte/__tests__/fixtures/ProviderFieldFixture.svelte.d.ts.map +1 -0
  84. package/dist/svelte/__tests__/fixtures/RegistryInput.svelte +15 -0
  85. package/dist/svelte/__tests__/fixtures/RegistryInput.svelte.d.ts +5 -0
  86. package/dist/svelte/__tests__/fixtures/RegistryInput.svelte.d.ts.map +1 -0
  87. package/dist/svelte/__tests__/fixtures/RevealPrefillFixture.svelte +29 -0
  88. package/dist/svelte/__tests__/fixtures/RevealPrefillFixture.svelte.d.ts +14 -0
  89. package/dist/svelte/__tests__/fixtures/RevealPrefillFixture.svelte.d.ts.map +1 -0
  90. package/dist/svelte/__tests__/fixtures/SettingsCatalogStub.svelte +24 -0
  91. package/dist/svelte/__tests__/fixtures/SettingsCatalogStub.svelte.d.ts +14 -0
  92. package/dist/svelte/__tests__/fixtures/SettingsCatalogStub.svelte.d.ts.map +1 -0
  93. package/dist/svelte/__tests__/fixtures/SnippetFixture.svelte +28 -0
  94. package/dist/svelte/__tests__/fixtures/SnippetFixture.svelte.d.ts +13 -0
  95. package/dist/svelte/__tests__/fixtures/SnippetFixture.svelte.d.ts.map +1 -0
  96. package/dist/svelte/__tests__/fixtures/ToggleModeButton.svelte +14 -0
  97. package/dist/svelte/__tests__/fixtures/ToggleModeButton.svelte.d.ts +19 -0
  98. package/dist/svelte/__tests__/fixtures/ToggleModeButton.svelte.d.ts.map +1 -0
  99. package/dist/svelte/__tests__/fixtures/TooltipFixture.svelte +25 -0
  100. package/dist/svelte/__tests__/fixtures/TooltipFixture.svelte.d.ts +14 -0
  101. package/dist/svelte/__tests__/fixtures/TooltipFixture.svelte.d.ts.map +1 -0
  102. package/dist/svelte/__tests__/settings-catalog.test.js +161 -0
  103. package/dist/svelte/components/AdvancedFields.svelte +49 -0
  104. package/dist/svelte/components/AdvancedFields.svelte.d.ts +17 -0
  105. package/dist/svelte/components/AdvancedFields.svelte.d.ts.map +1 -0
  106. package/dist/svelte/components/FieldInput.svelte +159 -0
  107. package/dist/svelte/components/FieldInput.svelte.d.ts +5 -0
  108. package/dist/svelte/components/FieldInput.svelte.d.ts.map +1 -0
  109. package/dist/svelte/components/FieldPolicyControlPanel.svelte +261 -0
  110. package/dist/svelte/components/FieldPolicyControlPanel.svelte.d.ts +26 -0
  111. package/dist/svelte/components/FieldPolicyControlPanel.svelte.d.ts.map +1 -0
  112. package/dist/svelte/components/FieldPolicyEditor.svelte +417 -0
  113. package/dist/svelte/components/FieldPolicyEditor.svelte.d.ts +18 -0
  114. package/dist/svelte/components/FieldPolicyEditor.svelte.d.ts.map +1 -0
  115. package/dist/svelte/components/FieldPolicyGearButton.svelte +17 -0
  116. package/dist/svelte/components/FieldPolicyGearButton.svelte.d.ts +8 -0
  117. package/dist/svelte/components/FieldPolicyGearButton.svelte.d.ts.map +1 -0
  118. package/dist/svelte/components/FieldPolicyGearProvider.svelte +135 -0
  119. package/dist/svelte/components/FieldPolicyGearProvider.svelte.d.ts +17 -0
  120. package/dist/svelte/components/FieldPolicyGearProvider.svelte.d.ts.map +1 -0
  121. package/dist/svelte/components/FieldPolicyProvider.svelte +89 -0
  122. package/dist/svelte/components/FieldPolicyProvider.svelte.d.ts +35 -0
  123. package/dist/svelte/components/FieldPolicyProvider.svelte.d.ts.map +1 -0
  124. package/dist/svelte/components/FormHelp.svelte +157 -0
  125. package/dist/svelte/components/FormHelp.svelte.d.ts +15 -0
  126. package/dist/svelte/components/FormHelp.svelte.d.ts.map +1 -0
  127. package/dist/svelte/components/ModeSwitch.svelte +47 -0
  128. package/dist/svelte/components/ModeSwitch.svelte.d.ts +10 -0
  129. package/dist/svelte/components/ModeSwitch.svelte.d.ts.map +1 -0
  130. package/dist/svelte/components/ObjectForm.svelte +351 -0
  131. package/dist/svelte/components/ObjectForm.svelte.d.ts +40 -0
  132. package/dist/svelte/components/ObjectForm.svelte.d.ts.map +1 -0
  133. package/dist/svelte/components/ObjectFormSourceProvider.svelte +18 -0
  134. package/dist/svelte/components/ObjectFormSourceProvider.svelte.d.ts +11 -0
  135. package/dist/svelte/components/ObjectFormSourceProvider.svelte.d.ts.map +1 -0
  136. package/dist/svelte/components/PolicyField.svelte +338 -0
  137. package/dist/svelte/components/PolicyField.svelte.d.ts +80 -0
  138. package/dist/svelte/components/PolicyField.svelte.d.ts.map +1 -0
  139. package/dist/svelte/context.svelte.d.ts +54 -0
  140. package/dist/svelte/context.svelte.d.ts.map +1 -0
  141. package/dist/svelte/context.svelte.js +55 -0
  142. package/dist/svelte/data-table.d.ts +17 -0
  143. package/dist/svelte/data-table.d.ts.map +1 -0
  144. package/dist/svelte/data-table.js +19 -0
  145. package/dist/svelte/field-policy-editor.d.ts +81 -0
  146. package/dist/svelte/field-policy-editor.d.ts.map +1 -0
  147. package/dist/svelte/field-policy-editor.js +120 -0
  148. package/dist/svelte/gear-context.svelte.d.ts +14 -0
  149. package/dist/svelte/gear-context.svelte.d.ts.map +1 -0
  150. package/dist/svelte/gear-context.svelte.js +15 -0
  151. package/dist/svelte/index.d.ts +58 -0
  152. package/dist/svelte/index.d.ts.map +1 -0
  153. package/dist/svelte/index.js +38 -0
  154. package/dist/svelte/input-registry.d.ts +21 -0
  155. package/dist/svelte/input-registry.d.ts.map +1 -0
  156. package/dist/svelte/input-registry.js +26 -0
  157. package/dist/svelte/object-form-source-context.svelte.d.ts +4 -0
  158. package/dist/svelte/object-form-source-context.svelte.d.ts.map +1 -0
  159. package/dist/svelte/object-form-source-context.svelte.js +8 -0
  160. package/dist/svelte/object-form-source.svelte.d.ts +18 -0
  161. package/dist/svelte/object-form-source.svelte.d.ts.map +1 -0
  162. package/dist/svelte/object-form-source.svelte.js +99 -0
  163. package/dist/svelte/object-form.d.ts +6 -0
  164. package/dist/svelte/object-form.d.ts.map +1 -0
  165. package/dist/svelte/object-form.js +38 -0
  166. package/dist/svelte/playground/FieldPolicyFormPreview.svelte +212 -0
  167. package/dist/svelte/playground/FieldPolicyFormPreview.svelte.d.ts +4 -0
  168. package/dist/svelte/playground/FieldPolicyFormPreview.svelte.d.ts.map +1 -0
  169. package/dist/svelte/playground/ObjectFormPreview.svelte +129 -0
  170. package/dist/svelte/playground/ObjectFormPreview.svelte.d.ts +4 -0
  171. package/dist/svelte/playground/ObjectFormPreview.svelte.d.ts.map +1 -0
  172. package/dist/svelte/playground.d.ts +20 -0
  173. package/dist/svelte/playground.d.ts.map +1 -0
  174. package/dist/svelte/playground.js +38 -0
  175. package/dist/svelte/settings-catalog.d.ts +54 -0
  176. package/dist/svelte/settings-catalog.d.ts.map +1 -0
  177. package/dist/svelte/settings-catalog.js +116 -0
  178. package/dist/svelte/types.d.ts +101 -0
  179. package/dist/svelte/types.d.ts.map +1 -0
  180. package/dist/svelte/types.js +4 -0
  181. package/dist/types.d.ts +276 -203
  182. package/dist/types.d.ts.map +1 -0
  183. package/dist/types.js.map +1 -1
  184. package/package.json +33 -8
package/dist/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { APP_FIELD_POLICY_SCOPE_KEY, FIELD_POLICY_SCOPE_TYPES, FIELD_POLICY_VISIBILITIES } from "./types.js";
2
2
  import { ObjectRegistry, SmrtCollection, SmrtObject, crossPackageRef, field, smrt } from "@happyvertical/smrt-core";
3
+ import { MembershipCollection, assertOperationPermission, checkOperationPermission, getCurrentSessionPermissionContext, registerPermissionDefinitions } from "@happyvertical/smrt-users";
3
4
  import { TenantIsolationError, assertTenantReadAllowed, getCurrentTenant, isSuperAdminBypass, tenantId } from "@happyvertical/smrt-tenancy";
4
5
  import { importWorkspaceModule } from "@happyvertical/smrt-core/utils/import-workspace-module";
5
6
  //#region \0rolldown/runtime.js
@@ -15,7 +16,28 @@ var __exportAll = (all, no_symbols) => {
15
16
  };
16
17
  //#endregion
17
18
  //#region src/__smrt-register__.ts
18
- ObjectRegistry.registerPackageManifest(JSON.parse("{\"version\":\"1.0.0\",\"timestamp\":0,\"packageName\":\"@happyvertical/smrt-fields\",\"packageVersion\":\"0.40.59\",\"objects\":{\"@happyvertical/smrt-fields:FieldPolicyCollection\":{\"name\":\"fieldpolicycollection\",\"className\":\"FieldPolicyCollection\",\"qualifiedName\":\"@happyvertical/smrt-fields:FieldPolicyCollection\",\"collection\":\"fieldpolicies\",\"filePath\":\"/home/runner/_work/smrt/smrt/packages/fields/src/collections/FieldPolicyCollection.ts\",\"packageName\":\"@happyvertical/smrt-fields\",\"fields\":{},\"methods\":{\"getAppRows\":{\"name\":\"getAppRows\",\"async\":true,\"parameters\":[{\"name\":\"objectRef\",\"type\":\"string\",\"optional\":false}],\"returnType\":\"Promise<Map<string, FieldPolicy>>\",\"isStatic\":false,\"isPublic\":true},\"getTenantRows\":{\"name\":\"getTenantRows\",\"async\":true,\"parameters\":[{\"name\":\"objectRef\",\"type\":\"string\",\"optional\":false},{\"name\":\"tenantIds\",\"type\":\"string[]\",\"optional\":false}],\"returnType\":\"Promise<Map<string, Map<string, FieldPolicy>>>\",\"isStatic\":false,\"isPublic\":true},\"getUserRows\":{\"name\":\"getUserRows\",\"async\":true,\"parameters\":[{\"name\":\"objectRef\",\"type\":\"string\",\"optional\":false},{\"name\":\"userId\",\"type\":\"string\",\"optional\":false}],\"returnType\":\"Promise<Map<string, FieldPolicy>>\",\"isStatic\":false,\"isPublic\":true},\"resolveBatch\":{\"name\":\"resolveBatch\",\"async\":true,\"parameters\":[{\"name\":\"options\",\"type\":\"object\",\"optional\":true}],\"returnType\":\"Promise<FieldPolicyBatchResult>\",\"isStatic\":false,\"isPublic\":true}},\"decoratorConfig\":{\"conflictColumns\":[\"object_ref\",\"field_name\",\"scope_type\",\"scope_key\"],\"api\":{\"include\":[\"create\",\"update\",\"delete\",\"resolveBatch\"],\"routes\":{\"resolveBatch\":{\"scope\":\"collection\",\"method\":\"POST\",\"path\":\"resolve\"}}},\"cli\":false,\"mcp\":false,\"tableName\":\"_smrt_field_policies\"},\"extends\":\"SmrtCollection\",\"extendsTypeArg\":\"FieldPolicy\",\"exportName\":\"FieldPolicyCollection\",\"collectionExportName\":\"FieldPolicyCollectionCollection\",\"schema\":{\"tableName\":\"_smrt_field_policies\",\"ddl\":\"CREATE TABLE IF NOT EXISTS \\\"_smrt_field_policies\\\" (\\n \\\"id\\\" UUID PRIMARY KEY NOT NULL,\\n \\\"slug\\\" TEXT NOT NULL,\\n \\\"context\\\" TEXT NOT NULL DEFAULT '',\\n \\\"created_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"updated_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp\\n);\",\"columns\":{\"id\":{\"type\":\"UUID\",\"primaryKey\":true,\"referenceKind\":\"id\",\"notNull\":true},\"slug\":{\"type\":\"TEXT\",\"notNull\":true},\"context\":{\"type\":\"TEXT\",\"notNull\":true,\"default\":\"\"},\"created_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"updated_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"}},\"indexes\":[{\"name\":\"_smrt_field_policies_id_idx\",\"columns\":[\"id\"]},{\"name\":\"_smrt_field_policies_object_ref_field_name_idx\",\"columns\":[\"object_ref\",\"field_name\",\"scope_type\",\"scope_key\"],\"unique\":true}],\"version\":\"13aa748c\"}},\"@happyvertical/smrt-fields:FieldPolicy\":{\"name\":\"fieldpolicy\",\"className\":\"FieldPolicy\",\"qualifiedName\":\"@happyvertical/smrt-fields:FieldPolicy\",\"collection\":\"fieldpolicies\",\"filePath\":\"/home/runner/_work/smrt/smrt/packages/fields/src/models/FieldPolicy.ts\",\"packageName\":\"@happyvertical/smrt-fields\",\"fields\":{\"objectRef\":{\"type\":\"text\",\"required\":true,\"default\":\"\",\"_meta\":{\"required\":true}},\"fieldName\":{\"type\":\"text\",\"required\":true,\"default\":\"\",\"_meta\":{\"required\":true}},\"scopeType\":{\"type\":\"text\",\"required\":true,\"default\":\"app\",\"_meta\":{\"required\":true}},\"tenantId\":{\"type\":\"text\",\"required\":false,\"_meta\":{\"sqlType\":\"UUID\",\"nullable\":true,\"__tenancy\":{\"isTenantIdField\":true,\"autoFilter\":true,\"required\":false,\"autoPopulate\":true,\"nullable\":true}}},\"userId\":{\"type\":\"crossPackageRef\",\"required\":false,\"related\":\"@happyvertical/smrt-users:User\",\"_meta\":{\"nullable\":true}},\"scopeKey\":{\"type\":\"text\",\"required\":true,\"_meta\":{\"required\":true}},\"defaultValue\":{\"type\":\"text\",\"required\":false,\"_meta\":{\"nullable\":true}},\"visibility\":{\"type\":\"text\",\"required\":false,\"_meta\":{\"nullable\":true}},\"help\":{\"type\":\"text\",\"required\":false,\"_meta\":{\"nullable\":true}},\"label\":{\"type\":\"text\",\"required\":false,\"_meta\":{\"nullable\":true}},\"displayOrder\":{\"type\":\"integer\",\"required\":false,\"_meta\":{\"nullable\":true}},\"locked\":{\"type\":\"boolean\",\"required\":false,\"_meta\":{\"nullable\":true}},\"updatedBy\":{\"type\":\"crossPackageRef\",\"required\":false,\"related\":\"@happyvertical/smrt-users:User\",\"_meta\":{\"nullable\":true}}},\"methods\":{\"getDefaultValue\":{\"name\":\"getDefaultValue\",\"async\":false,\"parameters\":[],\"returnType\":\"any\",\"isStatic\":false,\"isPublic\":true},\"setDefaultValue\":{\"name\":\"setDefaultValue\",\"async\":false,\"parameters\":[{\"name\":\"value\",\"type\":\"any\",\"optional\":false}],\"returnType\":\"void\",\"isStatic\":false,\"isPublic\":true},\"save\":{\"name\":\"save\",\"async\":true,\"parameters\":[],\"returnType\":\"Promise\",\"isStatic\":false,\"isPublic\":true},\"delete\":{\"name\":\"delete\",\"async\":true,\"parameters\":[],\"returnType\":\"Promise<void>\",\"isStatic\":false,\"isPublic\":true}},\"decoratorConfig\":{\"tableName\":\"_smrt_field_policies\",\"conflictColumns\":[\"object_ref\",\"field_name\",\"scope_type\",\"scope_key\"],\"api\":{\"include\":[\"create\",\"update\",\"delete\"]},\"cli\":{\"include\":[\"create\",\"update\",\"delete\"],\"exclude\":[\"getDefaultValue\",\"setDefaultValue\"]},\"mcp\":{\"include\":[]}},\"extends\":\"SmrtObject\",\"exportName\":\"FieldPolicy\",\"collectionExportName\":\"FieldPolicyCollection\",\"validationRules\":[{\"field\":\"objectRef\",\"rule\":\"required\",\"fieldType\":\"text\"},{\"field\":\"fieldName\",\"rule\":\"required\",\"fieldType\":\"text\"},{\"field\":\"scopeType\",\"rule\":\"required\",\"fieldType\":\"text\"},{\"field\":\"scopeKey\",\"rule\":\"required\",\"fieldType\":\"text\"}],\"schema\":{\"tableName\":\"_smrt_field_policies\",\"ddl\":\"CREATE TABLE IF NOT EXISTS \\\"_smrt_field_policies\\\" (\\n \\\"id\\\" UUID PRIMARY KEY NOT NULL,\\n \\\"slug\\\" TEXT NOT NULL,\\n \\\"context\\\" TEXT NOT NULL DEFAULT '',\\n \\\"created_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"updated_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"object_ref\\\" TEXT NOT NULL DEFAULT '',\\n \\\"field_name\\\" TEXT NOT NULL DEFAULT '',\\n \\\"scope_type\\\" TEXT NOT NULL DEFAULT 'app',\\n \\\"tenant_id\\\" UUID,\\n \\\"user_id\\\" UUID,\\n \\\"scope_key\\\" TEXT NOT NULL,\\n \\\"default_value\\\" TEXT,\\n \\\"visibility\\\" TEXT,\\n \\\"help\\\" TEXT,\\n \\\"label\\\" TEXT,\\n \\\"display_order\\\" INTEGER,\\n \\\"locked\\\" BOOLEAN,\\n \\\"updated_by\\\" UUID\\n);\",\"columns\":{\"id\":{\"type\":\"UUID\",\"primaryKey\":true,\"referenceKind\":\"id\",\"notNull\":true},\"slug\":{\"type\":\"TEXT\",\"notNull\":true},\"context\":{\"type\":\"TEXT\",\"notNull\":true,\"default\":\"\"},\"created_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"updated_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"object_ref\":{\"type\":\"TEXT\",\"notNull\":true,\"unique\":false,\"default\":\"\"},\"field_name\":{\"type\":\"TEXT\",\"notNull\":true,\"unique\":false,\"default\":\"\"},\"scope_type\":{\"type\":\"TEXT\",\"notNull\":true,\"unique\":false,\"default\":\"app\"},\"tenant_id\":{\"type\":\"UUID\",\"referenceKind\":\"tenantId\",\"notNull\":false,\"unique\":false},\"user_id\":{\"type\":\"UUID\",\"referenceKind\":\"crossPackageRef\",\"notNull\":false,\"unique\":false},\"scope_key\":{\"type\":\"TEXT\",\"notNull\":true,\"unique\":false},\"default_value\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false},\"visibility\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false},\"help\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false},\"label\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false},\"display_order\":{\"type\":\"INTEGER\",\"notNull\":false,\"unique\":false},\"locked\":{\"type\":\"BOOLEAN\",\"notNull\":false,\"unique\":false},\"updated_by\":{\"type\":\"UUID\",\"referenceKind\":\"crossPackageRef\",\"notNull\":false,\"unique\":false}},\"indexes\":[{\"name\":\"_smrt_field_policies_id_idx\",\"columns\":[\"id\"]},{\"name\":\"_smrt_field_policies_object_ref_field_name_idx\",\"columns\":[\"object_ref\",\"field_name\",\"scope_type\",\"scope_key\"],\"unique\":true}],\"version\":\"34b355cc\"}}},\"moduleType\":\"smrt\",\"smrtDependencies\":[\"@happyvertical/smrt-core\",\"@happyvertical/smrt-tenancy\",\"@happyvertical/smrt-users\"]}"));
19
+ ObjectRegistry.registerPackageManifest(JSON.parse("{\"version\":\"1.0.0\",\"timestamp\":0,\"packageName\":\"@happyvertical/smrt-fields\",\"packageVersion\":\"0.40.61\",\"objects\":{\"@happyvertical/smrt-fields:FieldPolicyCollection\":{\"name\":\"fieldpolicycollection\",\"className\":\"FieldPolicyCollection\",\"qualifiedName\":\"@happyvertical/smrt-fields:FieldPolicyCollection\",\"collection\":\"fieldpolicies\",\"filePath\":\"/home/runner/_work/smrt/smrt/packages/fields/src/collections/FieldPolicyCollection.ts\",\"packageName\":\"@happyvertical/smrt-fields\",\"fields\":{},\"methods\":{\"policyAudit\":{\"name\":\"policyAudit\",\"async\":true,\"parameters\":[{\"name\":\"options\",\"type\":\"object\",\"optional\":true}],\"returnType\":\"Promise<FieldPolicyAuditSnapshot>\",\"isStatic\":false,\"isPublic\":true},\"getAppRows\":{\"name\":\"getAppRows\",\"async\":true,\"parameters\":[{\"name\":\"objectRef\",\"type\":\"string\",\"optional\":false}],\"returnType\":\"Promise<Map<string, FieldPolicy>>\",\"isStatic\":false,\"isPublic\":true},\"getTenantRows\":{\"name\":\"getTenantRows\",\"async\":true,\"parameters\":[{\"name\":\"objectRef\",\"type\":\"string\",\"optional\":false},{\"name\":\"tenantIds\",\"type\":\"string[]\",\"optional\":false}],\"returnType\":\"Promise<Map<string, Map<string, FieldPolicy>>>\",\"isStatic\":false,\"isPublic\":true},\"getUserRows\":{\"name\":\"getUserRows\",\"async\":true,\"parameters\":[{\"name\":\"objectRef\",\"type\":\"string\",\"optional\":false},{\"name\":\"userId\",\"type\":\"string\",\"optional\":false}],\"returnType\":\"Promise<Map<string, FieldPolicy>>\",\"isStatic\":false,\"isPublic\":true},\"resolveBatch\":{\"name\":\"resolveBatch\",\"async\":true,\"parameters\":[{\"name\":\"options\",\"type\":\"object\",\"optional\":true}],\"returnType\":\"Promise<FieldPolicyBatchResult>\",\"isStatic\":false,\"isPublic\":true},\"getEditorState\":{\"name\":\"getEditorState\",\"async\":true,\"parameters\":[{\"name\":\"options\",\"type\":\"object\",\"optional\":true}],\"returnType\":\"Promise<FieldPolicyEditorStateResult>\",\"isStatic\":false,\"isPublic\":true}},\"decoratorConfig\":{\"conflictColumns\":[\"object_ref\",\"field_name\",\"scope_type\",\"scope_key\"],\"api\":{\"include\":[\"create\",\"update\",\"delete\",\"resolveBatch\",\"getEditorState\",\"policyAudit\"],\"principalContext\":true,\"routes\":{\"resolveBatch\":{\"scope\":\"collection\",\"method\":\"POST\",\"path\":\"resolve\"},\"getEditorState\":{\"scope\":\"collection\",\"method\":\"POST\",\"path\":\"editor-state\"},\"policyAudit\":{\"scope\":\"collection\",\"method\":\"POST\",\"path\":\"policy-audit\"}}},\"cli\":false,\"mcp\":false,\"tableName\":\"_smrt_field_policies\"},\"extends\":\"SmrtCollection\",\"extendsTypeArg\":\"FieldPolicy\",\"exportName\":\"FieldPolicyCollection\",\"collectionExportName\":\"FieldPolicyCollectionCollection\",\"schema\":{\"tableName\":\"_smrt_field_policies\",\"ddl\":\"CREATE TABLE IF NOT EXISTS \\\"_smrt_field_policies\\\" (\\n \\\"id\\\" UUID PRIMARY KEY NOT NULL,\\n \\\"slug\\\" TEXT NOT NULL,\\n \\\"context\\\" TEXT NOT NULL DEFAULT '',\\n \\\"created_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"updated_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp\\n);\",\"columns\":{\"id\":{\"type\":\"UUID\",\"primaryKey\":true,\"referenceKind\":\"id\",\"notNull\":true},\"slug\":{\"type\":\"TEXT\",\"notNull\":true},\"context\":{\"type\":\"TEXT\",\"notNull\":true,\"default\":\"\"},\"created_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"updated_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"}},\"indexes\":[{\"name\":\"_smrt_field_policies_id_idx\",\"columns\":[\"id\"]},{\"name\":\"_smrt_field_policies_object_ref_field_name_idx\",\"columns\":[\"object_ref\",\"field_name\",\"scope_type\",\"scope_key\"],\"unique\":true}],\"version\":\"13aa748c\"}},\"@happyvertical/smrt-fields:FieldPolicy\":{\"name\":\"fieldpolicy\",\"className\":\"FieldPolicy\",\"qualifiedName\":\"@happyvertical/smrt-fields:FieldPolicy\",\"collection\":\"fieldpolicies\",\"filePath\":\"/home/runner/_work/smrt/smrt/packages/fields/src/models/FieldPolicy.ts\",\"packageName\":\"@happyvertical/smrt-fields\",\"fields\":{\"objectRef\":{\"type\":\"text\",\"required\":true,\"default\":\"\",\"_meta\":{\"required\":true}},\"fieldName\":{\"type\":\"text\",\"required\":true,\"default\":\"\",\"_meta\":{\"required\":true}},\"scopeType\":{\"type\":\"text\",\"required\":true,\"default\":\"app\",\"_meta\":{\"required\":true}},\"tenantId\":{\"type\":\"text\",\"required\":false,\"_meta\":{\"sqlType\":\"UUID\",\"nullable\":true,\"__tenancy\":{\"isTenantIdField\":true,\"autoFilter\":true,\"required\":false,\"autoPopulate\":true,\"nullable\":true}}},\"userId\":{\"type\":\"crossPackageRef\",\"required\":false,\"related\":\"@happyvertical/smrt-users:User\",\"_meta\":{\"nullable\":true}},\"scopeKey\":{\"type\":\"text\",\"required\":true,\"_meta\":{\"required\":true}},\"defaultValue\":{\"type\":\"text\",\"required\":false,\"_meta\":{\"nullable\":true}},\"visibility\":{\"type\":\"text\",\"required\":false,\"_meta\":{\"nullable\":true}},\"help\":{\"type\":\"text\",\"required\":false,\"_meta\":{\"nullable\":true}},\"label\":{\"type\":\"text\",\"required\":false,\"_meta\":{\"nullable\":true}},\"displayOrder\":{\"type\":\"integer\",\"required\":false,\"_meta\":{\"nullable\":true}},\"locked\":{\"type\":\"boolean\",\"required\":false,\"_meta\":{\"nullable\":true}},\"updatedBy\":{\"type\":\"crossPackageRef\",\"required\":false,\"related\":\"@happyvertical/smrt-users:User\",\"_meta\":{\"nullable\":true}}},\"methods\":{\"getDefaultValue\":{\"name\":\"getDefaultValue\",\"async\":false,\"parameters\":[],\"returnType\":\"any\",\"isStatic\":false,\"isPublic\":true},\"setDefaultValue\":{\"name\":\"setDefaultValue\",\"async\":false,\"parameters\":[{\"name\":\"value\",\"type\":\"any\",\"optional\":false}],\"returnType\":\"void\",\"isStatic\":false,\"isPublic\":true},\"save\":{\"name\":\"save\",\"async\":true,\"parameters\":[],\"returnType\":\"Promise\",\"isStatic\":false,\"isPublic\":true},\"delete\":{\"name\":\"delete\",\"async\":true,\"parameters\":[],\"returnType\":\"Promise<void>\",\"isStatic\":false,\"isPublic\":true}},\"decoratorConfig\":{\"tableName\":\"_smrt_field_policies\",\"conflictColumns\":[\"object_ref\",\"field_name\",\"scope_type\",\"scope_key\"],\"api\":{\"include\":[\"create\",\"update\",\"delete\"],\"principalContext\":true},\"cli\":{\"include\":[\"create\",\"update\",\"delete\"],\"exclude\":[\"getDefaultValue\",\"setDefaultValue\"]},\"mcp\":{\"include\":[]}},\"extends\":\"SmrtObject\",\"exportName\":\"FieldPolicy\",\"collectionExportName\":\"FieldPolicyCollection\",\"validationRules\":[{\"field\":\"objectRef\",\"rule\":\"required\",\"fieldType\":\"text\"},{\"field\":\"fieldName\",\"rule\":\"required\",\"fieldType\":\"text\"},{\"field\":\"scopeType\",\"rule\":\"required\",\"fieldType\":\"text\"},{\"field\":\"scopeKey\",\"rule\":\"required\",\"fieldType\":\"text\"}],\"schema\":{\"tableName\":\"_smrt_field_policies\",\"ddl\":\"CREATE TABLE IF NOT EXISTS \\\"_smrt_field_policies\\\" (\\n \\\"id\\\" UUID PRIMARY KEY NOT NULL,\\n \\\"slug\\\" TEXT NOT NULL,\\n \\\"context\\\" TEXT NOT NULL DEFAULT '',\\n \\\"created_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"updated_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"object_ref\\\" TEXT NOT NULL DEFAULT '',\\n \\\"field_name\\\" TEXT NOT NULL DEFAULT '',\\n \\\"scope_type\\\" TEXT NOT NULL DEFAULT 'app',\\n \\\"tenant_id\\\" UUID,\\n \\\"user_id\\\" UUID,\\n \\\"scope_key\\\" TEXT NOT NULL,\\n \\\"default_value\\\" TEXT,\\n \\\"visibility\\\" TEXT,\\n \\\"help\\\" TEXT,\\n \\\"label\\\" TEXT,\\n \\\"display_order\\\" INTEGER,\\n \\\"locked\\\" BOOLEAN,\\n \\\"updated_by\\\" UUID\\n);\",\"columns\":{\"id\":{\"type\":\"UUID\",\"primaryKey\":true,\"referenceKind\":\"id\",\"notNull\":true},\"slug\":{\"type\":\"TEXT\",\"notNull\":true},\"context\":{\"type\":\"TEXT\",\"notNull\":true,\"default\":\"\"},\"created_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"updated_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"object_ref\":{\"type\":\"TEXT\",\"notNull\":true,\"unique\":false,\"default\":\"\"},\"field_name\":{\"type\":\"TEXT\",\"notNull\":true,\"unique\":false,\"default\":\"\"},\"scope_type\":{\"type\":\"TEXT\",\"notNull\":true,\"unique\":false,\"default\":\"app\"},\"tenant_id\":{\"type\":\"UUID\",\"referenceKind\":\"tenantId\",\"notNull\":false,\"unique\":false},\"user_id\":{\"type\":\"UUID\",\"referenceKind\":\"crossPackageRef\",\"notNull\":false,\"unique\":false},\"scope_key\":{\"type\":\"TEXT\",\"notNull\":true,\"unique\":false},\"default_value\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false},\"visibility\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false},\"help\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false},\"label\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false},\"display_order\":{\"type\":\"INTEGER\",\"notNull\":false,\"unique\":false},\"locked\":{\"type\":\"BOOLEAN\",\"notNull\":false,\"unique\":false},\"updated_by\":{\"type\":\"UUID\",\"referenceKind\":\"crossPackageRef\",\"notNull\":false,\"unique\":false}},\"indexes\":[{\"name\":\"_smrt_field_policies_id_idx\",\"columns\":[\"id\"]},{\"name\":\"_smrt_field_policies_object_ref_field_name_idx\",\"columns\":[\"object_ref\",\"field_name\",\"scope_type\",\"scope_key\"],\"unique\":true}],\"version\":\"34b355cc\"}}},\"moduleType\":\"smrt\",\"smrtDependencies\":[\"@happyvertical/smrt-core\",\"@happyvertical/smrt-tenancy\",\"@happyvertical/smrt-users\"]}"));
20
+ //#endregion
21
+ //#region src/permissions.ts
22
+ var MANAGE_FIELD_POLICY_PERMISSION = "fields.policy.manage";
23
+ var PERSONALIZE_FIELD_POLICY_PERMISSION = "fields.policy.personalize";
24
+ var FIELD_POLICY_PERMISSION_DEFINITIONS = [{
25
+ slug: MANAGE_FIELD_POLICY_PERMISSION,
26
+ category: "fields",
27
+ name: "Manage Field Policies",
28
+ description: "Manage application and tenant field-policy overrides"
29
+ }, {
30
+ slug: PERSONALIZE_FIELD_POLICY_PERMISSION,
31
+ category: "fields",
32
+ name: "Personalize Field Policies",
33
+ description: "Manage the caller’s own field-policy overrides"
34
+ }];
35
+ var registered = false;
36
+ function ensureFieldPolicyPermissionsRegistered() {
37
+ if (registered) return;
38
+ registered = true;
39
+ registerPermissionDefinitions(FIELD_POLICY_PERMISSION_DEFINITIONS);
40
+ }
19
41
  //#endregion
20
42
  //#region src/cache.ts
21
43
  var FIELD_POLICY_CACHE_TTL_MS = 3e4;
@@ -110,6 +132,9 @@ function getFieldReadPermission(field) {
110
132
  function isTransientField(field) {
111
133
  return field.transient === true || field._meta?.transient === true;
112
134
  }
135
+ function isPolicyAddressableField(field) {
136
+ return field._meta?.__smrtSystemField !== true && field.type !== "oneToMany" && field.type !== "manyToMany" && field.type !== "meta";
137
+ }
113
138
  function isRequiredField(field) {
114
139
  if (field.nullable === true || field._meta?.nullable === true) return false;
115
140
  return field.required === true || field._meta?.required === true;
@@ -199,6 +224,55 @@ var __decorateClass$1 = (decorators, target, key, kind) => {
199
224
  if (kind && result) __defProp$1(target, key, result);
200
225
  return result;
201
226
  };
227
+ ensureFieldPolicyPermissionsRegistered();
228
+ var FIELD_POLICY_SCOPE_DENIAL_MESSAGE = "Field policy scope is not permitted for this principal.";
229
+ function getFieldPolicyAmbientContext() {
230
+ const tenantContext = getCurrentTenant();
231
+ if (tenantContext) return {
232
+ permissions: tenantContext.permissions,
233
+ superAdminBypass: isSuperAdminBypass(),
234
+ tenantId: tenantContext.tenantId,
235
+ userId: tenantContext.userId
236
+ };
237
+ const sessionContext = getCurrentSessionPermissionContext();
238
+ if (!sessionContext?.tenantId) return;
239
+ return {
240
+ permissions: sessionContext.permissionSet,
241
+ superAdminBypass: sessionContext.superAdminBypass,
242
+ tenantId: sessionContext.tenantId,
243
+ userId: sessionContext.userId ?? void 0
244
+ };
245
+ }
246
+ var FieldPolicyAuthorizationError = class extends Error {
247
+ code = "permission_denied";
248
+ status = 403;
249
+ constructor(message) {
250
+ super(message);
251
+ this.name = "FieldPolicyAuthorizationError";
252
+ }
253
+ };
254
+ var FieldPolicyTenantIsolationError = class extends TenantIsolationError {
255
+ publicMessage = FIELD_POLICY_SCOPE_DENIAL_MESSAGE;
256
+ status = 403;
257
+ constructor(diagnosticMessage, details) {
258
+ super(FIELD_POLICY_SCOPE_DENIAL_MESSAGE);
259
+ Object.defineProperty(this, "cause", {
260
+ configurable: true,
261
+ enumerable: false,
262
+ value: new Error(diagnosticMessage),
263
+ writable: false
264
+ });
265
+ Object.defineProperty(this, "diagnostic", {
266
+ configurable: true,
267
+ enumerable: false,
268
+ value: {
269
+ ...details,
270
+ message: diagnosticMessage
271
+ },
272
+ writable: false
273
+ });
274
+ }
275
+ };
202
276
  var FieldPolicy = class extends SmrtObject {
203
277
  objectRef = "";
204
278
  fieldName = "";
@@ -246,11 +320,16 @@ var FieldPolicy = class extends SmrtObject {
246
320
  async save() {
247
321
  this.attributeScopeToAmbientContext();
248
322
  const previousIdentity = await this.getPersistedIdentity();
249
- if (previousIdentity) this.assertScopeOwnedByAmbientContext(previousIdentity, "save");
250
- const ambientContext = getCurrentTenant();
323
+ if (previousIdentity) {
324
+ this.assertScopeOwnedByAmbientContext(previousIdentity, "save");
325
+ await this.assertScopePermission(previousIdentity);
326
+ }
327
+ this.assertScopeOwnedByAmbientContext(this, "save");
328
+ await this.assertScopePermission(this);
329
+ const ambientContext = getFieldPolicyAmbientContext();
251
330
  if (ambientContext) this.updatedBy = ambientContext.userId ?? null;
252
331
  this.normalizeDefaultValueForPersistence();
253
- await this.validateFieldPolicy();
332
+ await this.validateFieldPolicy(previousIdentity);
254
333
  this.scopeKey = this.computeScopeKey();
255
334
  const identityChanged = previousIdentity && (previousIdentity.objectRef !== this.objectRef || previousIdentity.fieldName !== this.fieldName || previousIdentity.scopeType !== this.scopeType || previousIdentity.scopeKey !== this.scopeKey);
256
335
  const result = identityChanged && previousIdentity ? await this.saveAfterIdentityChange() : await super.save();
@@ -306,12 +385,13 @@ var FieldPolicy = class extends SmrtObject {
306
385
  async delete() {
307
386
  const persisted = await this.getPersistedIdentity();
308
387
  if (persisted) this.assertScopeOwnedByAmbientContext(persisted, "delete");
388
+ await this.assertScopePermission(persisted ?? this);
309
389
  const objectRef = persisted?.objectRef ?? this.objectRef;
310
390
  await super.delete();
311
391
  invalidateFieldPolicyCache(objectRef, this.db);
312
392
  if (this.objectRef && this.objectRef !== objectRef) invalidateFieldPolicyCache(this.objectRef, this.db);
313
393
  }
314
- async validateFieldPolicy() {
394
+ async validateFieldPolicy(previousIdentity) {
315
395
  if (!this.objectRef || this.objectRef.trim() === "") throw new Error("FieldPolicy.objectRef is required");
316
396
  if (!this.fieldName || this.fieldName.trim() === "") throw new Error("FieldPolicy.fieldName is required");
317
397
  if (!FIELD_POLICY_SCOPE_TYPES.includes(this.scopeType)) throw new Error(`FieldPolicy.scopeType must be one of ${FIELD_POLICY_SCOPE_TYPES.join(", ")}; got "${this.scopeType}"`);
@@ -335,11 +415,11 @@ var FieldPolicy = class extends SmrtObject {
335
415
  const needsOrgDefault = demotesRequiredField && !isUsableRequiredDefault(ownDefault);
336
416
  const needsLockCheck = this.scopeType === "user";
337
417
  if (needsOrgDefault || needsLockCheck) {
338
- const orgPolicy = await this.resolveOrgTierFieldPolicy();
418
+ const orgPolicy = await this.resolveOrgTierFieldPolicy(previousIdentity);
339
419
  if (needsOrgDefault) {
340
420
  if (!isUsableRequiredDefault(orgPolicy?.hasDefault ? { value: orgPolicy.defaultValue } : void 0)) throw new Error(`Cannot set visibility "${this.visibility}" for required field "${this.objectRef}.${this.fieldName}": no resolved default exists at or below this layer`);
341
421
  }
342
- if (needsLockCheck && orgPolicy?.locked) throw new Error(`Field "${this.objectRef}.${this.fieldName}" is locked by org policy; user-scope overrides are not allowed`);
422
+ if (needsLockCheck && orgPolicy?.locked) throw new FieldPolicyAuthorizationError(`Field "${this.objectRef}.${this.fieldName}" is locked by org policy; user-scope overrides are not allowed`);
343
423
  }
344
424
  }
345
425
  /**
@@ -350,17 +430,18 @@ var FieldPolicy = class extends SmrtObject {
350
430
  * tenant-scope rows, the ambient context tenant for user-scope rows, and
351
431
  * none for app-scope rows (their only lower layer is the code seed).
352
432
  *
353
- * On updates the resolver sees this row's PERSISTED version (there is no
354
- * self-exclusion), so a save that removes the only default while demoting
355
- * can pass here; the resolver-side safety net stays authoritative at read
356
- * time.
433
+ * During an update, the persisted row is excluded from the lower-layer
434
+ * projection. That makes a clear-default + required-field demotion validate
435
+ * the post-write policy rather than accidentally counting the row being
436
+ * replaced.
357
437
  */
358
- async resolveOrgTierFieldPolicy() {
359
- const tenantIdForChain = this.scopeType === "tenant" ? this.tenantId : this.scopeType === "user" ? getCurrentTenant()?.tenantId ?? null : null;
438
+ async resolveOrgTierFieldPolicy(previousIdentity) {
439
+ const tenantIdForChain = this.scopeType === "tenant" ? this.tenantId : this.scopeType === "user" ? getFieldPolicyAmbientContext()?.tenantId ?? null : null;
360
440
  const { resolveFieldPolicy } = await Promise.resolve().then(() => field_policy_resolver_exports);
361
441
  return (await resolveFieldPolicy(this.objectRef, {
362
442
  tenantId: tenantIdForChain,
363
- db: this.options.db ?? this.options.persistence
443
+ db: this.options.db ?? this.options.persistence,
444
+ ...previousIdentity ? { excludePolicyIds: /* @__PURE__ */ new Set([previousIdentity.id]) } : {}
364
445
  })).fields[this.fieldName];
365
446
  }
366
447
  /**
@@ -398,14 +479,15 @@ var FieldPolicy = class extends SmrtObject {
398
479
  * a non-bypass caller's tenant/user rows to name exactly the ambient
399
480
  * tenant/user, so the derived value is the ONLY value that could ever have
400
481
  * been accepted. An explicit value is never overwritten (a super-admin
401
- * bypass caller keeps naming other scopes), and with no ambient context
402
- * nothing is derived — scope-shape validation rejects the row as before.
482
+ * bypass caller keeps naming other scopes), and with neither tenancy nor
483
+ * authenticated-session context nothing is derived — scope-shape validation
484
+ * rejects the row as before.
403
485
  * Consequence: a bypass caller writing ANOTHER tenant's row must pass
404
486
  * `tenantId` through a server-side model call, because the generated routes
405
487
  * still strip it.
406
488
  */
407
489
  attributeScopeToAmbientContext() {
408
- const context = getCurrentTenant();
490
+ const context = getFieldPolicyAmbientContext();
409
491
  if (!context) return;
410
492
  if (this.scopeType === "tenant" && this.tenantId === null) this.tenantId = context.tenantId;
411
493
  if (this.scopeType === "user" && this.userId === null) this.userId = context.userId ?? null;
@@ -415,33 +497,33 @@ var FieldPolicy = class extends SmrtObject {
415
497
  * the NEW scope on save and to the PERSISTED scope on save/delete of an
416
498
  * existing row.
417
499
  *
418
- * With NO ambient identity at all (no tenant context entered — e.g. a
419
- * runtime API deployment whose auth middleware never enters the tenancy
420
- * ALS), only app-scope rows are accepted: tenant- and user-scope rows are
421
- * unattributable without a context, so they are rejected outright rather
422
- * than allowed by default. Inside a non-bypass context, a caller may only
423
- * touch rows for its own tenant, and user rows only for its own user id
424
- * a context that carries NO user id may not touch the user tier at all.
500
+ * With NO ambient identity at all (neither tenant ALS nor authenticated
501
+ * session-permission ALS), only app-scope rows are accepted: tenant- and
502
+ * user-scope rows are unattributable without a context, so they are
503
+ * rejected outright rather than allowed by default. Inside a non-bypass
504
+ * context, a caller may only touch rows for its own tenant, and user rows
505
+ * only for its own user id a context that carries NO user id may not
506
+ * touch the user tier at all.
425
507
  * App-wide rows then require super-admin bypass (or a context-less/system
426
508
  * caller).
427
509
  *
428
510
  * Package rule: a missing identity component DENIES, it never skips.
429
511
  */
430
512
  assertScopeOwnedByAmbientContext(scope, operation) {
431
- const context = getCurrentTenant();
513
+ const context = getFieldPolicyAmbientContext();
432
514
  if (!context) {
433
- if (scope.scopeType === "tenant" || scope.scopeType === "user") throw new TenantIsolationError(`${scope.scopeType === "tenant" ? "Tenant" : "User"}-scope field policy ${operation}s require an ambient tenant context (or super-admin bypass); without one the caller identity is unattributable`);
515
+ if (scope.scopeType === "tenant" || scope.scopeType === "user") throw new FieldPolicyTenantIsolationError(`${scope.scopeType === "tenant" ? "Tenant" : "User"}-scope field policy ${operation}s require an ambient tenant context (or super-admin bypass); without one the caller identity is unattributable`);
434
516
  return;
435
517
  }
436
- if (isSuperAdminBypass()) return;
437
- if (scope.scopeType === "app") throw new TenantIsolationError(`App-scope field policy ${operation}s are not allowed inside a tenant context (use a system context or super-admin bypass)`, { tenantId: context.tenantId });
438
- if (scope.scopeType === "tenant" && scope.tenantId !== context.tenantId) throw new TenantIsolationError(`Tenant isolation violation in FieldPolicy.${operation}: context tenant is '${context.tenantId}' but the row belongs to '${scope.tenantId}'`, {
518
+ if (context.superAdminBypass) return;
519
+ if (scope.scopeType === "app") throw new FieldPolicyTenantIsolationError(`App-scope field policy ${operation}s are not allowed inside a tenant context (use a system context or super-admin bypass)`, { tenantId: context.tenantId });
520
+ if (scope.scopeType === "tenant" && scope.tenantId !== context.tenantId) throw new FieldPolicyTenantIsolationError(`Tenant isolation violation in FieldPolicy.${operation}: context tenant is '${context.tenantId}' but the row belongs to '${scope.tenantId}'`, {
439
521
  tenantId: context.tenantId,
440
522
  attemptedTenantId: scope.tenantId ?? void 0
441
523
  });
442
524
  if (scope.scopeType === "user") {
443
- if (context.userId === void 0) throw new TenantIsolationError(`User-scope field policy ${operation}s require an ambient context that carries a user id; this context cannot attribute a user-scope write`, { tenantId: context.tenantId });
444
- if (scope.userId !== context.userId) throw new TenantIsolationError(`Tenant isolation violation in FieldPolicy.${operation}: context user is '${context.userId}' but the row belongs to '${scope.userId}'`, { tenantId: context.tenantId });
525
+ if (context.userId === void 0) throw new FieldPolicyTenantIsolationError(`User-scope field policy ${operation}s require an ambient context that carries a user id; this context cannot attribute a user-scope write`, { tenantId: context.tenantId });
526
+ if (scope.userId !== context.userId) throw new FieldPolicyTenantIsolationError(`Tenant isolation violation in FieldPolicy.${operation}: context user is '${context.userId}' but the row belongs to '${scope.userId}'`, { tenantId: context.tenantId });
445
527
  }
446
528
  }
447
529
  validateTenantContextBoundary() {
@@ -451,6 +533,24 @@ var FieldPolicy = class extends SmrtObject {
451
533
  userId: this.userId
452
534
  }, "save");
453
535
  }
536
+ /**
537
+ * Permission boundary for policy mutations. Scope ownership is checked
538
+ * separately (and first) because permission never grants cross-tenant or
539
+ * cross-user access. Existing rows are checked against their persisted
540
+ * scope before an identity-changing save can mutate or delete them.
541
+ */
542
+ async assertScopePermission(scope) {
543
+ const context = getFieldPolicyAmbientContext();
544
+ const [, , operation] = (scope.scopeType === "user" ? PERSONALIZE_FIELD_POLICY_PERMISSION : MANAGE_FIELD_POLICY_PERMISSION).split(".");
545
+ await assertOperationPermission({
546
+ collection: "fields.policy",
547
+ action: operation ?? "manage",
548
+ db: this.options.db ?? this.options.persistence,
549
+ tenantId: context?.tenantId ?? null,
550
+ userId: context?.userId ?? null,
551
+ permissionSet: context?.permissions
552
+ });
553
+ }
454
554
  validateDefaultAgainstSecurityRail(fieldDef) {
455
555
  if (isTransientField(fieldDef)) throw new Error(`Cannot store a default for transient field "${this.objectRef}.${this.fieldName}"`);
456
556
  if (isSensitiveField(fieldDef)) throw new Error(`Cannot store a default for sensitive field "${this.objectRef}.${this.fieldName}"`);
@@ -490,6 +590,7 @@ var FieldPolicy = class extends SmrtObject {
490
590
  return value === void 0 || value === null ? null : String(value);
491
591
  };
492
592
  return {
593
+ id: read("id", "id", String(this.id ?? "")),
493
594
  objectRef: read("objectRef", "object_ref", this.objectRef),
494
595
  fieldName: read("fieldName", "field_name", this.fieldName),
495
596
  scopeType: read("scopeType", "scope_type", this.scopeType),
@@ -571,11 +672,14 @@ FieldPolicy = __decorateClass$1([smrt({
571
672
  "scope_type",
572
673
  "scope_key"
573
674
  ],
574
- api: { include: [
575
- "create",
576
- "update",
577
- "delete"
578
- ] },
675
+ api: {
676
+ include: [
677
+ "create",
678
+ "update",
679
+ "delete"
680
+ ],
681
+ principalContext: true
682
+ },
579
683
  cli: {
580
684
  include: [
581
685
  "create",
@@ -604,7 +708,196 @@ var __decorateClass = (decorators, target, key, kind) => {
604
708
  };
605
709
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
606
710
  var MAX_BATCH_OBJECT_REFS = 100;
711
+ MAX_BATCH_OBJECT_REFS * 2;
607
712
  var FieldPolicyCollection = class extends SmrtCollection {
713
+ /**
714
+ * Permission-gated, tenant-scoped audit read for the defaults control
715
+ * panel. It returns editable own-tenant rows, read-only app summaries,
716
+ * projected user override counts, and identity-only manifest drift rows.
717
+ */
718
+ async policyAudit(options = {}) {
719
+ const objectRefs = normalizeAuditRefs(options.objectRefs, "objectRefs", 100);
720
+ const countObjectRefs = normalizeAuditRefs(options.countObjectRefs ?? objectRefs, "countObjectRefs", 200);
721
+ const context = getCurrentTenant();
722
+ const tenantId = context?.tenantId ?? null;
723
+ const userId = context?.userId ?? null;
724
+ const permissionOptions = {
725
+ collection: "fields.policy",
726
+ db: this.db,
727
+ tenantId,
728
+ userId,
729
+ permissionSet: context?.permissions,
730
+ onDeny: "return"
731
+ };
732
+ const manage = tenantId ? await checkOperationPermission({
733
+ ...permissionOptions,
734
+ action: "fields.policy.manage".split(".").at(-1) ?? "manage"
735
+ }) : { allowed: false };
736
+ const personalize = userId ? await checkOperationPermission({
737
+ ...permissionOptions,
738
+ action: "fields.policy.personalize".split(".").at(-1) ?? "personalize"
739
+ }) : { allowed: false };
740
+ const caller = {
741
+ tenantId,
742
+ userId,
743
+ canManageOrg: manage.allowed,
744
+ canPersonalize: personalize.allowed && userId !== null
745
+ };
746
+ if (!caller.canManageOrg || tenantId === null) return emptyAudit(caller);
747
+ if (options.summaryOnly === true) return emptyAudit(caller);
748
+ const fieldMaps = /* @__PURE__ */ new Map();
749
+ const fieldMapFor = (objectRef) => {
750
+ let loaded = fieldMaps.get(objectRef);
751
+ if (!loaded) {
752
+ loaded = getObjectFieldMap(objectRef).catch(() => null);
753
+ fieldMaps.set(objectRef, loaded);
754
+ }
755
+ return loaded;
756
+ };
757
+ const classify = async (row) => {
758
+ const fields = await fieldMapFor(row.objectRef);
759
+ if (!fields) return "unknown-object";
760
+ const definition = fields.get(row.fieldName);
761
+ if (!definition) return "unknown-field";
762
+ if (isSensitiveField(definition) || getFieldReadPermission(definition) !== void 0 || isTransientField(definition)) return "gated";
763
+ return isPolicyAddressableField(definition) ? "ok" : "excluded-field";
764
+ };
765
+ const [appRows, orgRows, ownUserRows] = objectRefs.length ? await Promise.all([
766
+ this.list({ where: {
767
+ scopeType: "app",
768
+ "objectRef in": objectRefs
769
+ } }),
770
+ this.list({ where: {
771
+ scopeType: "tenant",
772
+ tenantId,
773
+ "objectRef in": objectRefs
774
+ } }),
775
+ userId ? this.list({ where: {
776
+ scopeType: "user",
777
+ userId,
778
+ "objectRef in": objectRefs
779
+ } }) : Promise.resolve([])
780
+ ]) : [
781
+ [],
782
+ [],
783
+ []
784
+ ];
785
+ const audit = {
786
+ ...emptyAudit(caller),
787
+ policies: options.countsOnly ? {} : await this.resolveAuditPolicies(objectRefs, tenantId, fieldMapFor)
788
+ };
789
+ const append = async (row, target, driftPrunable) => {
790
+ const state = await classify(row);
791
+ if (state === "ok") target.push(toAuditRow(row));
792
+ else if (state !== "gated") audit.driftRows.push({
793
+ ...toDriftIdentity(row),
794
+ reason: state,
795
+ prunable: driftPrunable
796
+ });
797
+ };
798
+ for (const row of orgRows) await append(row, audit.orgRows, true);
799
+ for (const row of appRows) await append(row, audit.appRows, isSuperAdminBypass());
800
+ for (const row of ownUserRows) {
801
+ const state = await classify(row);
802
+ if (state !== "ok" && state !== "gated") audit.driftRows.push({
803
+ ...toDriftIdentity(row),
804
+ reason: state,
805
+ prunable: caller.canPersonalize
806
+ });
807
+ }
808
+ if (options.includeDrift === true) {
809
+ const [driftAppRows, driftOrgRows, driftUserRows] = await Promise.all([
810
+ this.list({
811
+ where: { scopeType: "app" },
812
+ limit: 100
813
+ }),
814
+ this.list({
815
+ where: {
816
+ scopeType: "tenant",
817
+ tenantId
818
+ },
819
+ limit: 100
820
+ }),
821
+ userId ? this.list({
822
+ where: {
823
+ scopeType: "user",
824
+ userId
825
+ },
826
+ limit: 100
827
+ }) : Promise.resolve([])
828
+ ]);
829
+ const knownIds = new Set([
830
+ ...appRows,
831
+ ...orgRows,
832
+ ...ownUserRows
833
+ ].map((row) => String(row.id)));
834
+ for (const [rows, prunable] of [
835
+ [driftAppRows, isSuperAdminBypass()],
836
+ [driftOrgRows, true],
837
+ [driftUserRows, caller.canPersonalize]
838
+ ]) for (const row of rows) {
839
+ if (knownIds.has(String(row.id))) continue;
840
+ const state = await classify(row);
841
+ if (state !== "ok" && state !== "gated") audit.driftRows.push({
842
+ ...toDriftIdentity(row),
843
+ reason: state,
844
+ prunable
845
+ });
846
+ }
847
+ }
848
+ const memberships = await MembershipCollection.create({ db: this.db });
849
+ const memberIds = [...new Set((await memberships.findActiveByTenant(tenantId)).map((membership) => membership.userId?.trim()).filter((id) => Boolean(id)))];
850
+ const projected = countObjectRefs.length && memberIds.length ? await this.list({
851
+ where: {
852
+ scopeType: "user",
853
+ "objectRef in": countObjectRefs,
854
+ "userId in": memberIds
855
+ },
856
+ select: ["objectRef", "fieldName"]
857
+ }) : [];
858
+ for (const row of projected) {
859
+ if (await classify(row) !== "ok") continue;
860
+ const byField = audit.userOverrideCounts[row.objectRef] ?? {};
861
+ audit.userOverrideCounts[row.objectRef] = byField;
862
+ byField[row.fieldName] = (byField[row.fieldName] ?? 0) + 1;
863
+ }
864
+ const { resolveSurvivingTenantChainIds } = await Promise.resolve().then(() => field_policy_resolver_exports);
865
+ const ancestorIds = (await resolveSurvivingTenantChainIds(tenantId, { db: this.db })).filter((id) => id !== tenantId);
866
+ if (ancestorIds.length && objectRefs.length) {
867
+ const ancestors = await this.list({ where: {
868
+ scopeType: "tenant",
869
+ "tenantId in": ancestorIds,
870
+ "objectRef in": objectRefs
871
+ } });
872
+ for (const row of ancestors) {
873
+ if (await classify(row) !== "ok") continue;
874
+ const names = audit.inheritedOrgKeys[row.objectRef] ?? [];
875
+ audit.inheritedOrgKeys[row.objectRef] = names;
876
+ if (!names.includes(row.fieldName)) names.push(row.fieldName);
877
+ }
878
+ }
879
+ audit.orgRows.sort(sortAuditRows);
880
+ audit.appRows.sort(sortAuditRows);
881
+ audit.driftRows.sort(sortDriftRows);
882
+ for (const names of Object.values(audit.inheritedOrgKeys)) names.sort();
883
+ return audit;
884
+ }
885
+ async resolveAuditPolicies(objectRefs, tenantId, fieldMapFor) {
886
+ const { resolveFieldPolicyExplained } = await Promise.resolve().then(() => field_policy_resolver_exports);
887
+ const policies = {};
888
+ for (const objectRef of objectRefs) {
889
+ if (!await fieldMapFor(objectRef)) continue;
890
+ const resolved = await resolveFieldPolicyExplained(objectRef, {
891
+ tenantId,
892
+ db: this.db
893
+ });
894
+ policies[objectRef] = await this.gateExplainedForEditorResponse(resolved, {
895
+ manage: true,
896
+ personalize: false
897
+ }, true);
898
+ }
899
+ return policies;
900
+ }
608
901
  /** All app-scope rows for an object, keyed by field name. */
609
902
  async getAppRows(objectRef) {
610
903
  const rows = await this.list({ where: {
@@ -687,6 +980,86 @@ var FieldPolicyCollection = class extends SmrtCollection {
687
980
  }
688
981
  return { policies };
689
982
  }
983
+ /**
984
+ * Context-derived bootstrap data for the policy gear. This is deliberately
985
+ * a collection action instead of reopening list/get: a caller can inspect
986
+ * only their safe effective policy and rows/layers they are permitted to
987
+ * edit, never arbitrary tenant or user policy rows.
988
+ */
989
+ async getEditorState(options = {}) {
990
+ const objectRef = options.objectRef;
991
+ if (typeof objectRef !== "string" || objectRef.trim() === "") throw new Error("getEditorState requires a non-empty \"objectRef\" string");
992
+ const context = getCurrentTenant();
993
+ const permissionOptions = {
994
+ collection: "fields.policy",
995
+ db: this.db,
996
+ tenantId: context?.tenantId ?? null,
997
+ userId: context?.userId ?? null,
998
+ permissionSet: context?.permissions,
999
+ onDeny: "return"
1000
+ };
1001
+ const manage = await checkOperationPermission({
1002
+ ...permissionOptions,
1003
+ action: "fields.policy.manage".split(".").at(-1) ?? "manage"
1004
+ });
1005
+ const personalize = await checkOperationPermission({
1006
+ ...permissionOptions,
1007
+ action: "fields.policy.personalize".split(".").at(-1) ?? "personalize"
1008
+ });
1009
+ const personalizeAllowed = personalize.allowed && typeof context?.userId === "string" && context.userId.length > 0;
1010
+ if (!manage.allowed && !personalizeAllowed) return {
1011
+ code: "permission_denied",
1012
+ message: `Permission denied for '${personalize.permission ?? "fields.policy.manage"}'.`,
1013
+ ok: false,
1014
+ status: 403
1015
+ };
1016
+ const { resolveFieldPolicyExplained } = await Promise.resolve().then(() => field_policy_resolver_exports);
1017
+ const resolved = await resolveFieldPolicyExplained(objectRef, {
1018
+ tenantId: context?.tenantId ?? null,
1019
+ userId: context?.userId ?? null,
1020
+ db: this.db
1021
+ });
1022
+ const policy = await this.gateExplainedForEditorResponse(resolved, {
1023
+ manage: manage.allowed,
1024
+ personalize: personalizeAllowed
1025
+ });
1026
+ const fieldNames = new Set(Object.keys(policy.fields));
1027
+ const personalLowerDefaultUsable = personalizeAllowed ? await this.getPersonalLowerDefaultUsability(objectRef, context?.tenantId ?? null, fieldNames) : {};
1028
+ const appRows = manage.allowed ? this.toEditorRows(await this.getAppRows(objectRef), fieldNames) : [];
1029
+ const tenantRows = manage.allowed && context?.tenantId ? this.toEditorRows((await this.getTenantRows(objectRef, [context.tenantId])).get(context.tenantId) ?? /* @__PURE__ */ new Map(), fieldNames) : [];
1030
+ const userRows = personalizeAllowed && context?.userId ? this.toEditorRows(await this.getUserRows(objectRef, context.userId), fieldNames) : [];
1031
+ return {
1032
+ capabilities: {
1033
+ manage: manage.allowed,
1034
+ personalize: personalizeAllowed
1035
+ },
1036
+ personalLowerDefaultUsable,
1037
+ policy,
1038
+ rows: {
1039
+ app: appRows,
1040
+ tenant: tenantRows,
1041
+ user: userRows
1042
+ }
1043
+ };
1044
+ }
1045
+ /**
1046
+ * Compute the non-disclosing fallback signal used only by personal drafts.
1047
+ * Passing no user id resolves exactly code/app/tenant precedence, including
1048
+ * the real tenant hierarchy, while keeping every lower-layer value server
1049
+ * side. The public map is limited to fields already admitted by the editor
1050
+ * response's security rail.
1051
+ */
1052
+ async getPersonalLowerDefaultUsability(objectRef, tenantId, fieldNames) {
1053
+ const { resolveFieldPolicy } = await Promise.resolve().then(() => field_policy_resolver_exports);
1054
+ const lowerPolicy = await resolveFieldPolicy(objectRef, {
1055
+ tenantId,
1056
+ db: this.db
1057
+ });
1058
+ return Object.fromEntries(Array.from(fieldNames, (fieldName) => {
1059
+ const field = lowerPolicy.fields[fieldName];
1060
+ return [fieldName, isUsableRequiredDefault(field?.hasDefault ? { value: field.defaultValue } : void 0)];
1061
+ }));
1062
+ }
690
1063
  async gateResolvedForPublicResponse(resolved) {
691
1064
  const fieldMap = await getObjectFieldMap(resolved.objectRef);
692
1065
  const fields = {};
@@ -701,6 +1074,38 @@ var FieldPolicyCollection = class extends SmrtCollection {
701
1074
  fields
702
1075
  };
703
1076
  }
1077
+ async gateExplainedForEditorResponse(resolved, capabilities, includeCode = false) {
1078
+ const fieldMap = await getObjectFieldMap(resolved.objectRef);
1079
+ const fields = {};
1080
+ const layers = {};
1081
+ for (const [fieldName, policy] of Object.entries(resolved.fields)) {
1082
+ const fieldDef = fieldMap.get(fieldName);
1083
+ if (!fieldDef || isSensitiveField(fieldDef) || getFieldReadPermission(fieldDef) !== void 0 || isTransientField(fieldDef)) continue;
1084
+ fields[fieldName] = policy;
1085
+ layers[fieldName] = (resolved.layers[fieldName] ?? []).filter((layer) => includeCode && layer.layer === "code" || capabilities.manage && (layer.layer === "app" || layer.layer === "tenant") || capabilities.personalize && layer.layer === "user");
1086
+ }
1087
+ return {
1088
+ objectRef: resolved.objectRef,
1089
+ fields,
1090
+ layers
1091
+ };
1092
+ }
1093
+ toEditorRows(rows, allowedFieldNames) {
1094
+ return Array.from(rows.values()).filter((row) => allowedFieldNames.has(row.fieldName)).map((row) => ({
1095
+ defaultValue: row.defaultValue,
1096
+ displayOrder: row.displayOrder,
1097
+ fieldName: row.fieldName,
1098
+ help: row.help,
1099
+ id: String(row.id),
1100
+ label: row.label,
1101
+ locked: row.locked,
1102
+ scopeType: row.scopeType,
1103
+ tenantId: row.tenantId,
1104
+ updatedBy: row.updatedBy,
1105
+ userId: row.userId,
1106
+ visibility: row.visibility
1107
+ })).sort((left, right) => left.fieldName.localeCompare(right.fieldName));
1108
+ }
704
1109
  };
705
1110
  __publicField(FieldPolicyCollection, "_itemClass", FieldPolicy);
706
1111
  FieldPolicyCollection = __decorateClass([smrt({
@@ -715,23 +1120,100 @@ FieldPolicyCollection = __decorateClass([smrt({
715
1120
  "create",
716
1121
  "update",
717
1122
  "delete",
718
- "resolveBatch"
1123
+ "resolveBatch",
1124
+ "getEditorState",
1125
+ "policyAudit"
719
1126
  ],
720
- routes: { resolveBatch: {
721
- scope: "collection",
722
- method: "POST",
723
- path: "resolve"
724
- } }
1127
+ principalContext: true,
1128
+ routes: {
1129
+ resolveBatch: {
1130
+ scope: "collection",
1131
+ method: "POST",
1132
+ path: "resolve"
1133
+ },
1134
+ getEditorState: {
1135
+ scope: "collection",
1136
+ method: "POST",
1137
+ path: "editor-state"
1138
+ },
1139
+ policyAudit: {
1140
+ scope: "collection",
1141
+ method: "POST",
1142
+ path: "policy-audit"
1143
+ }
1144
+ }
725
1145
  },
726
1146
  cli: false,
727
1147
  mcp: false
728
1148
  })], FieldPolicyCollection);
1149
+ function normalizeAuditRefs(value, label, maximum) {
1150
+ if (value === void 0) return [];
1151
+ if (!Array.isArray(value)) throw new Error(`policyAudit ${label} must be a string array`);
1152
+ if (value.some((ref) => typeof ref !== "string" || ref.trim() === "")) throw new Error(`policyAudit ${label} must contain non-empty strings`);
1153
+ const refs = [...new Set(value)];
1154
+ if (refs.length > maximum) throw new Error(`policyAudit ${label} accepts at most ${maximum} objectRefs`);
1155
+ return refs;
1156
+ }
1157
+ function emptyAudit(caller) {
1158
+ return {
1159
+ orgRows: [],
1160
+ appRows: [],
1161
+ inheritedOrgKeys: {},
1162
+ userOverrideCounts: {},
1163
+ driftRows: [],
1164
+ policies: {},
1165
+ caller
1166
+ };
1167
+ }
1168
+ function dateText(value) {
1169
+ if (value instanceof Date) return value.toISOString();
1170
+ return typeof value === "string" ? value : null;
1171
+ }
1172
+ function toAuditRow(row) {
1173
+ return {
1174
+ objectRef: row.objectRef,
1175
+ defaultValue: row.defaultValue,
1176
+ displayOrder: row.displayOrder,
1177
+ fieldName: row.fieldName,
1178
+ help: row.help,
1179
+ id: String(row.id),
1180
+ label: row.label,
1181
+ locked: row.locked,
1182
+ scopeType: row.scopeType,
1183
+ tenantId: row.tenantId,
1184
+ updatedBy: row.updatedBy,
1185
+ userId: row.userId,
1186
+ visibility: row.visibility,
1187
+ createdAt: dateText(row.createdAt),
1188
+ updatedAt: dateText(row.updatedAt)
1189
+ };
1190
+ }
1191
+ function toDriftIdentity(row) {
1192
+ return {
1193
+ id: String(row.id),
1194
+ objectRef: row.objectRef,
1195
+ fieldName: row.fieldName,
1196
+ scopeType: row.scopeType,
1197
+ tenantId: row.tenantId,
1198
+ userId: row.userId,
1199
+ updatedBy: row.updatedBy,
1200
+ createdAt: dateText(row.createdAt),
1201
+ updatedAt: dateText(row.updatedAt)
1202
+ };
1203
+ }
1204
+ function sortAuditRows(left, right) {
1205
+ return left.objectRef.localeCompare(right.objectRef) || left.fieldName.localeCompare(right.fieldName);
1206
+ }
1207
+ function sortDriftRows(left, right) {
1208
+ return left.objectRef.localeCompare(right.objectRef) || left.fieldName.localeCompare(right.fieldName) || left.scopeType.localeCompare(right.scopeType);
1209
+ }
729
1210
  //#endregion
730
1211
  //#region src/field-policy-resolver.ts
731
1212
  var field_policy_resolver_exports = /* @__PURE__ */ __exportAll({
732
1213
  isMissingUsersDependency: () => isMissingUsersDependency,
733
1214
  resolveFieldPolicy: () => resolveFieldPolicy,
734
- resolveFieldPolicyExplained: () => resolveFieldPolicyExplained
1215
+ resolveFieldPolicyExplained: () => resolveFieldPolicyExplained,
1216
+ resolveSurvivingTenantChainIds: () => resolveSurvivingTenantChainIds
735
1217
  });
736
1218
  async function resolveFieldPolicy(objectRef, options = {}) {
737
1219
  const explained = await resolveFieldPolicyExplained(objectRef, options);
@@ -740,6 +1222,10 @@ async function resolveFieldPolicy(objectRef, options = {}) {
740
1222
  fields: explained.fields
741
1223
  };
742
1224
  }
1225
+ async function resolveSurvivingTenantChainIds(tenantId, options = {}) {
1226
+ assertResolutionAllowedInContext(tenantId, null);
1227
+ return selectSurvivingChainSuffix(await resolveTenantChain(tenantId, options)).map((node) => node.id);
1228
+ }
743
1229
  async function resolveFieldPolicyExplained(objectRef, options = {}) {
744
1230
  const tenantId = options.tenantId ?? null;
745
1231
  const userId = options.userId ?? null;
@@ -751,8 +1237,11 @@ async function resolveFieldPolicyExplained(objectRef, options = {}) {
751
1237
  cacheDb = collection.db;
752
1238
  }
753
1239
  const fieldMap = await getObjectFieldMap(objectRef);
754
- const cached = getCachedFieldPolicy(objectRef, tenantId, userId, cacheDb, options.tenantHierarchyLoader);
755
- if (cached) return cached;
1240
+ const hasExcludedRows = (options.excludePolicyIds?.size ?? 0) > 0;
1241
+ if (!hasExcludedRows) {
1242
+ const cached = getCachedFieldPolicy(objectRef, tenantId, userId, cacheDb, options.tenantHierarchyLoader);
1243
+ if (cached) return cached;
1244
+ }
756
1245
  const policyFields = selectPolicyAddressableFields(fieldMap);
757
1246
  const codeVisibility = buildCodeSeedVisibility(fieldMap);
758
1247
  const appRows = collection ? await collection.getAppRows(objectRef) : /* @__PURE__ */ new Map();
@@ -774,7 +1263,7 @@ async function resolveFieldPolicyExplained(objectRef, options = {}) {
774
1263
  });
775
1264
  let state = applyDelta({ visibility: "basic" }, codeDelta);
776
1265
  const appRow = appRows.get(fieldName);
777
- if (appRow) {
1266
+ if (appRow && !options.excludePolicyIds?.has(String(appRow.id))) {
778
1267
  const delta = rowToDelta(appRow);
779
1268
  contributions.push({
780
1269
  layer: "app",
@@ -784,7 +1273,7 @@ async function resolveFieldPolicyExplained(objectRef, options = {}) {
784
1273
  }
785
1274
  for (const node of survivingChain) {
786
1275
  const row = tenantRows.get(node.id)?.get(fieldName);
787
- if (row) {
1276
+ if (row && !options.excludePolicyIds?.has(String(row.id))) {
788
1277
  const delta = rowToDelta(row);
789
1278
  contributions.push({
790
1279
  layer: "tenant",
@@ -796,7 +1285,7 @@ async function resolveFieldPolicyExplained(objectRef, options = {}) {
796
1285
  }
797
1286
  const orgLocked = state.locked === true;
798
1287
  const userRow = userId ? userRows.get(fieldName) : void 0;
799
- if (userRow && !orgLocked) {
1288
+ if (userRow && !options.excludePolicyIds?.has(String(userRow.id)) && !orgLocked) {
800
1289
  const delta = rowToDelta(userRow);
801
1290
  contributions.push({
802
1291
  layer: "user",
@@ -834,7 +1323,7 @@ async function resolveFieldPolicyExplained(objectRef, options = {}) {
834
1323
  fields,
835
1324
  layers
836
1325
  };
837
- setCachedFieldPolicy(objectRef, tenantId, userId, cacheDb, explained, options.tenantHierarchyLoader);
1326
+ if (!hasExcludedRows) setCachedFieldPolicy(objectRef, tenantId, userId, cacheDb, explained, options.tenantHierarchyLoader);
838
1327
  return explained;
839
1328
  }
840
1329
  function assertResolutionAllowedInContext(tenantId, userId) {
@@ -847,11 +1336,7 @@ function assertResolutionAllowedInContext(tenantId, userId) {
847
1336
  }
848
1337
  function selectPolicyAddressableFields(fieldMap) {
849
1338
  const selected = /* @__PURE__ */ new Map();
850
- for (const [name, field] of fieldMap) {
851
- if (field._meta?.__smrtSystemField === true) continue;
852
- if (field.type === "oneToMany" || field.type === "manyToMany" || field.type === "meta") continue;
853
- selected.set(name, field);
854
- }
1339
+ for (const [name, field] of fieldMap) if (isPolicyAddressableField(field)) selected.set(name, field);
855
1340
  return selected;
856
1341
  }
857
1342
  function rowToDelta(row) {
@@ -931,6 +1416,218 @@ function isMissingUsersDependency(error) {
931
1416
  return false;
932
1417
  }
933
1418
  //#endregion
934
- export { APP_FIELD_POLICY_SCOPE_KEY, FIELD_POLICY_SCOPE_TYPES, FIELD_POLICY_VISIBILITIES, FieldPolicy, FieldPolicyCollection, assertDefaultValueMatchesFieldType, buildCodeSeedDelta, buildCodeSeedVisibility, clearFieldPolicyCache, getCodeDefault, getCodeSeedGroup, getFieldPolicyCacheTtlMs, getFieldReadPermission, getObjectFieldMap, invalidateFieldPolicyCache, isRequiredField, isSensitiveField, isTransientField, isUsableRequiredDefault, requireRegisteredObject, resolveFieldPolicy, resolveFieldPolicyExplained, sanitizeFieldUIHints };
1419
+ //#region src/settings-catalog.ts
1420
+ var DEFAULT_PAGE_SIZE = 50;
1421
+ var MAX_PAGE_SIZE = 100;
1422
+ var FORM_TYPES = /* @__PURE__ */ new Set([
1423
+ "text",
1424
+ "integer",
1425
+ "decimal",
1426
+ "boolean",
1427
+ "datetime",
1428
+ "json",
1429
+ "foreignKey",
1430
+ "crossPackageRef"
1431
+ ]);
1432
+ var SYSTEM_NAMES = /* @__PURE__ */ new Set([
1433
+ "id",
1434
+ "slug",
1435
+ "context",
1436
+ "createdAt",
1437
+ "created_at",
1438
+ "updatedAt",
1439
+ "updated_at",
1440
+ "deletedAt",
1441
+ "deleted_at",
1442
+ "tenantId",
1443
+ "tenant_id"
1444
+ ]);
1445
+ function fieldPolicyCatalogItemId(objectRef, fieldName) {
1446
+ return `${objectRef}::${fieldName}`;
1447
+ }
1448
+ function parseFieldPolicyCatalogQuery(params) {
1449
+ return {
1450
+ query: params.get("q"),
1451
+ page: integerParam(params.get("page")),
1452
+ pageSize: integerParam(params.get("pageSize")),
1453
+ selectedId: params.get("selected"),
1454
+ packageFilter: params.get("package"),
1455
+ objectFilter: params.get("object"),
1456
+ customizedOnly: params.get("customized") === "1"
1457
+ };
1458
+ }
1459
+ async function buildFieldPolicySettingsCatalog(options) {
1460
+ const collection = options.collection ?? (options.db ? await FieldPolicyCollection.create({ db: options.db }) : null);
1461
+ if (!collection) throw new Error("A db or FieldPolicyCollection is required");
1462
+ const filters = {
1463
+ packageFilter: stringFilter(options.packageFilter),
1464
+ objectFilter: stringFilter(options.objectFilter),
1465
+ customizedOnly: options.customizedOnly === true
1466
+ };
1467
+ const query = options.query?.trim() ?? "";
1468
+ const pageSize = clamp(options.pageSize, 1, MAX_PAGE_SIZE, DEFAULT_PAGE_SIZE);
1469
+ const baseAudit = await collection.policyAudit({ summaryOnly: true });
1470
+ if (!baseAudit.caller.canManageOrg) return {
1471
+ page: emptyPage(query, pageSize),
1472
+ audit: baseAudit,
1473
+ objects: [],
1474
+ packages: [],
1475
+ filters
1476
+ };
1477
+ const objects = await listCatalogObjects(options.objectRefs);
1478
+ const candidates = objects.filter((object) => (!filters.packageFilter || object.packageName === filters.packageFilter) && (!filters.objectFilter || object.objectRef === filters.objectFilter));
1479
+ const allCandidateRefs = candidates.map((object) => object.objectRef);
1480
+ const customized = customizedKeys(filters.customizedOnly && allCandidateRefs.length ? await loadAuditCounts(collection, allCandidateRefs, baseAudit) : baseAudit);
1481
+ const entries = candidates.flatMap((object) => Object.entries(object.fields).filter(([fieldName]) => !filters.customizedOnly || customized.has(fieldPolicyCatalogItemId(object.objectRef, fieldName))).map(([fieldName, field]) => ({
1482
+ item: {
1483
+ id: fieldPolicyCatalogItemId(object.objectRef, fieldName),
1484
+ label: fieldName,
1485
+ ...field.description ? { description: field.description } : {},
1486
+ eyebrow: `${object.className} \xB7 ${object.packageName}`,
1487
+ objectRef: object.objectRef,
1488
+ fieldName,
1489
+ className: object.className,
1490
+ packageName: object.packageName
1491
+ },
1492
+ search: `${fieldName} ${spaced(fieldName)} ${object.className} ${object.packageName} ${object.objectRef} ${field.description ?? ""}`.toLowerCase()
1493
+ })));
1494
+ const filtered = query ? entries.filter((entry) => entry.search.includes(query.toLowerCase())) : entries;
1495
+ const total = filtered.length;
1496
+ const page = clamp(options.page, 1, Math.max(1, Math.ceil(total / pageSize)), 1);
1497
+ const items = filtered.slice((page - 1) * pageSize, page * pageSize).map((entry) => entry.item);
1498
+ const selectedSummary = (options.selectedId ? filtered.find((entry) => entry.item.id === options.selectedId)?.item : void 0) ?? items[0];
1499
+ const selected = selectedSummary ? {
1500
+ ...selectedSummary,
1501
+ fields: objects.find((object) => object.objectRef === selectedSummary.objectRef)?.fields ?? {}
1502
+ } : null;
1503
+ const auditRefs = selected ? uniqueRefs([selected.objectRef, ...items.map((item) => item.objectRef)]) : [];
1504
+ const audit = auditRefs.length ? await collection.policyAudit({
1505
+ objectRefs: auditRefs.slice(0, 100),
1506
+ countObjectRefs: auditRefs,
1507
+ includeDrift: true
1508
+ }) : await collection.policyAudit({ includeDrift: true });
1509
+ return {
1510
+ page: {
1511
+ items,
1512
+ selected,
1513
+ query,
1514
+ page,
1515
+ pageSize,
1516
+ total
1517
+ },
1518
+ audit,
1519
+ objects: objects.map(({ objectRef, className, packageName, fields }) => ({
1520
+ objectRef,
1521
+ className,
1522
+ packageName,
1523
+ fieldCount: Object.keys(fields).length
1524
+ })),
1525
+ packages: [...new Set(objects.map((object) => object.packageName))].sort(),
1526
+ filters
1527
+ };
1528
+ }
1529
+ async function listCatalogObjects(objectRefs) {
1530
+ const refs = objectRefs ? [...objectRefs] : Array.from(ObjectRegistry.getPublicClasses().values()).reduce((result, registered) => {
1531
+ if (registered.qualifiedName) result.push(registered.qualifiedName);
1532
+ return result;
1533
+ }, []);
1534
+ const unique = [...new Set(refs)].sort();
1535
+ const objects = [];
1536
+ for (const objectRef of unique) {
1537
+ if (!ObjectRegistry.getClassByQualifiedName(objectRef) || ObjectRegistry.getTableName(objectRef)?.startsWith("_smrt_")) continue;
1538
+ const fields = await getObjectFieldMap(objectRef);
1539
+ const selected = {};
1540
+ for (const [name, definition] of fields) {
1541
+ if (SYSTEM_NAMES.has(name) || !isPolicyAddressableField(definition) || isSensitiveField(definition) || isTransientField(definition) || getFieldReadPermission(definition) !== void 0 || !FORM_TYPES.has(String(definition.type))) continue;
1542
+ selected[name] = {
1543
+ type: definition.type,
1544
+ ...definition.required === true ? { required: true } : {},
1545
+ ...definition.default !== void 0 ? { default: definition.default } : {},
1546
+ ...typeof definition.description === "string" ? { description: definition.description } : {}
1547
+ };
1548
+ }
1549
+ if (!Object.keys(selected).length) continue;
1550
+ const colon = objectRef.lastIndexOf(":");
1551
+ const packageName = colon === -1 ? "" : objectRef.slice(0, colon);
1552
+ const className = colon === -1 ? objectRef : objectRef.slice(colon + 1);
1553
+ objects.push({
1554
+ objectRef,
1555
+ className,
1556
+ packageName,
1557
+ fields: selected
1558
+ });
1559
+ }
1560
+ return objects;
1561
+ }
1562
+ async function loadAuditCounts(collection, refs, baseline) {
1563
+ const userOverrideCounts = {};
1564
+ const orgRows = [];
1565
+ const appRows = [];
1566
+ const inheritedOrgKeys = {};
1567
+ const chunkSize = 100;
1568
+ for (let offset = 0; offset < refs.length; offset += chunkSize) {
1569
+ const audit = await collection.policyAudit({
1570
+ objectRefs: refs.slice(offset, offset + chunkSize),
1571
+ countObjectRefs: refs.slice(offset, offset + chunkSize),
1572
+ countsOnly: true
1573
+ });
1574
+ for (const [objectRef, byField] of Object.entries(audit.userOverrideCounts)) userOverrideCounts[objectRef] = {
1575
+ ...userOverrideCounts[objectRef] ?? {},
1576
+ ...byField
1577
+ };
1578
+ orgRows.push(...audit.orgRows);
1579
+ appRows.push(...audit.appRows);
1580
+ for (const [objectRef, fieldNames] of Object.entries(audit.inheritedOrgKeys)) {
1581
+ const names = inheritedOrgKeys[objectRef] ?? [];
1582
+ inheritedOrgKeys[objectRef] = names;
1583
+ for (const fieldName of fieldNames) if (!names.includes(fieldName)) names.push(fieldName);
1584
+ }
1585
+ }
1586
+ return {
1587
+ ...baseline,
1588
+ orgRows,
1589
+ appRows,
1590
+ inheritedOrgKeys,
1591
+ userOverrideCounts
1592
+ };
1593
+ }
1594
+ function customizedKeys(audit) {
1595
+ const keys = /* @__PURE__ */ new Set();
1596
+ for (const row of [...audit.orgRows, ...audit.appRows]) keys.add(fieldPolicyCatalogItemId(row.objectRef, row.fieldName));
1597
+ for (const [objectRef, names] of Object.entries(audit.inheritedOrgKeys)) for (const name of names) keys.add(fieldPolicyCatalogItemId(objectRef, name));
1598
+ for (const [objectRef, fields] of Object.entries(audit.userOverrideCounts)) for (const [name, count] of Object.entries(fields)) if (count > 0) keys.add(fieldPolicyCatalogItemId(objectRef, name));
1599
+ return keys;
1600
+ }
1601
+ function emptyPage(query, pageSize) {
1602
+ return {
1603
+ items: [],
1604
+ selected: null,
1605
+ query,
1606
+ page: 1,
1607
+ pageSize,
1608
+ total: 0
1609
+ };
1610
+ }
1611
+ function uniqueRefs(refs) {
1612
+ return [...new Set(refs)];
1613
+ }
1614
+ function stringFilter(value) {
1615
+ const trimmed = value?.trim();
1616
+ return trimmed ? trimmed : null;
1617
+ }
1618
+ function integerParam(value) {
1619
+ return value && /^\d+$/.test(value) ? Number(value) : null;
1620
+ }
1621
+ function clamp(value, min, max, fallback) {
1622
+ return Number.isFinite(value) ? Math.min(max, Math.max(min, Math.trunc(value))) : fallback;
1623
+ }
1624
+ function spaced(value) {
1625
+ return value.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[_-]/g, " ");
1626
+ }
1627
+ //#endregion
1628
+ //#region src/index.ts
1629
+ ensureFieldPolicyPermissionsRegistered();
1630
+ //#endregion
1631
+ export { APP_FIELD_POLICY_SCOPE_KEY, FIELD_POLICY_PERMISSION_DEFINITIONS, FIELD_POLICY_SCOPE_TYPES, FIELD_POLICY_VISIBILITIES, FieldPolicy, FieldPolicyCollection, MANAGE_FIELD_POLICY_PERMISSION, PERSONALIZE_FIELD_POLICY_PERMISSION, assertDefaultValueMatchesFieldType, buildCodeSeedDelta, buildCodeSeedVisibility, buildFieldPolicySettingsCatalog, clearFieldPolicyCache, ensureFieldPolicyPermissionsRegistered, fieldPolicyCatalogItemId, getCodeDefault, getCodeSeedGroup, getFieldPolicyCacheTtlMs, getFieldReadPermission, getObjectFieldMap, invalidateFieldPolicyCache, isPolicyAddressableField, isRequiredField, isSensitiveField, isTransientField, isUsableRequiredDefault, parseFieldPolicyCatalogQuery, requireRegisteredObject, resolveFieldPolicy, resolveFieldPolicyExplained, resolveSurvivingTenantChainIds, sanitizeFieldUIHints };
935
1632
 
936
1633
  //# sourceMappingURL=index.js.map