@happyvertical/smrt-fields 0.40.60 → 0.40.62

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 (218) hide show
  1. package/AGENTS.md +58 -12
  2. package/README.md +143 -42
  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/chunks/FieldPolicyCollection--RxukfCX.js +1218 -0
  8. package/dist/chunks/FieldPolicyCollection--RxukfCX.js.map +1 -0
  9. package/dist/collections/FieldPolicyCollection.d.ts +95 -0
  10. package/dist/collections/FieldPolicyCollection.d.ts.map +1 -0
  11. package/dist/collections/FieldPolicySuggestionCollection.d.ts +160 -0
  12. package/dist/collections/FieldPolicySuggestionCollection.d.ts.map +1 -0
  13. package/dist/collections/FieldUsageCounterCollection.d.ts +143 -0
  14. package/dist/collections/FieldUsageCounterCollection.d.ts.map +1 -0
  15. package/dist/deterministic-id.d.ts +18 -0
  16. package/dist/deterministic-id.d.ts.map +1 -0
  17. package/dist/field-definitions.d.ts +85 -0
  18. package/dist/field-definitions.d.ts.map +1 -0
  19. package/dist/field-policy-resolver.d.ts +46 -0
  20. package/dist/field-policy-resolver.d.ts.map +1 -0
  21. package/dist/index.d.ts +15 -532
  22. package/dist/index.d.ts.map +1 -0
  23. package/dist/index.js +1813 -628
  24. package/dist/index.js.map +1 -1
  25. package/dist/manifest.json +1487 -191
  26. package/dist/models/FieldPolicy.d.ts +163 -0
  27. package/dist/models/FieldPolicy.d.ts.map +1 -0
  28. package/dist/models/FieldPolicySuggestion.d.ts +132 -0
  29. package/dist/models/FieldPolicySuggestion.d.ts.map +1 -0
  30. package/dist/models/FieldUsageCounter.d.ts +178 -0
  31. package/dist/models/FieldUsageCounter.d.ts.map +1 -0
  32. package/dist/models/FieldUsageReportReceipt.d.ts +25 -0
  33. package/dist/models/FieldUsageReportReceipt.d.ts.map +1 -0
  34. package/dist/permissions.d.ts +9 -0
  35. package/dist/permissions.d.ts.map +1 -0
  36. package/dist/playground.d.ts +2 -0
  37. package/dist/playground.d.ts.map +1 -0
  38. package/dist/playground.js +39 -0
  39. package/dist/playground.js.map +1 -0
  40. package/dist/settings-catalog.d.ts +73 -0
  41. package/dist/settings-catalog.d.ts.map +1 -0
  42. package/dist/smrt-knowledge.json +449 -9
  43. package/dist/svelte/__tests__/FieldPolicyControlPanel.test.js +277 -0
  44. package/dist/svelte/__tests__/FieldPolicyGear.test.js +503 -0
  45. package/dist/svelte/__tests__/FormHelp.test.js +138 -0
  46. package/dist/svelte/__tests__/ModeUX.test.js +109 -0
  47. package/dist/svelte/__tests__/ObjectForm.test.js +687 -0
  48. package/dist/svelte/__tests__/ObjectFormGeneratedApi.integration.test.js +436 -0
  49. package/dist/svelte/__tests__/PolicyField.test.js +373 -0
  50. package/dist/svelte/__tests__/UsageLearning.test.js +140 -0
  51. package/dist/svelte/__tests__/fixtures/AdvancedFieldsFixture.svelte +30 -0
  52. package/dist/svelte/__tests__/fixtures/AdvancedFieldsFixture.svelte.d.ts +9 -0
  53. package/dist/svelte/__tests__/fixtures/AdvancedFieldsFixture.svelte.d.ts.map +1 -0
  54. package/dist/svelte/__tests__/fixtures/DomPrefillFixture.svelte +33 -0
  55. package/dist/svelte/__tests__/fixtures/DomPrefillFixture.svelte.d.ts +10 -0
  56. package/dist/svelte/__tests__/fixtures/DomPrefillFixture.svelte.d.ts.map +1 -0
  57. package/dist/svelte/__tests__/fixtures/FormHelpFixture.svelte +19 -0
  58. package/dist/svelte/__tests__/fixtures/FormHelpFixture.svelte.d.ts +10 -0
  59. package/dist/svelte/__tests__/fixtures/FormHelpFixture.svelte.d.ts.map +1 -0
  60. package/dist/svelte/__tests__/fixtures/GeneratedApiGearFixture.svelte +18 -0
  61. package/dist/svelte/__tests__/fixtures/GeneratedApiGearFixture.svelte.d.ts +11 -0
  62. package/dist/svelte/__tests__/fixtures/GeneratedApiGearFixture.svelte.d.ts.map +1 -0
  63. package/dist/svelte/__tests__/fixtures/GracefulDegradeFixture.svelte +11 -0
  64. package/dist/svelte/__tests__/fixtures/GracefulDegradeFixture.svelte.d.ts +19 -0
  65. package/dist/svelte/__tests__/fixtures/GracefulDegradeFixture.svelte.d.ts.map +1 -0
  66. package/dist/svelte/__tests__/fixtures/ModeSwitchFixture.svelte +18 -0
  67. package/dist/svelte/__tests__/fixtures/ModeSwitchFixture.svelte.d.ts +8 -0
  68. package/dist/svelte/__tests__/fixtures/ModeSwitchFixture.svelte.d.ts.map +1 -0
  69. package/dist/svelte/__tests__/fixtures/NoIdFixture.svelte +24 -0
  70. package/dist/svelte/__tests__/fixtures/NoIdFixture.svelte.d.ts +13 -0
  71. package/dist/svelte/__tests__/fixtures/NoIdFixture.svelte.d.ts.map +1 -0
  72. package/dist/svelte/__tests__/fixtures/ObjectFormActionsFixture.svelte +37 -0
  73. package/dist/svelte/__tests__/fixtures/ObjectFormActionsFixture.svelte.d.ts +15 -0
  74. package/dist/svelte/__tests__/fixtures/ObjectFormActionsFixture.svelte.d.ts.map +1 -0
  75. package/dist/svelte/__tests__/fixtures/ObjectFormFixture.svelte +17 -0
  76. package/dist/svelte/__tests__/fixtures/ObjectFormFixture.svelte.d.ts +10 -0
  77. package/dist/svelte/__tests__/fixtures/ObjectFormFixture.svelte.d.ts.map +1 -0
  78. package/dist/svelte/__tests__/fixtures/ObjectFormRegistryFixture.svelte +25 -0
  79. package/dist/svelte/__tests__/fixtures/ObjectFormRegistryFixture.svelte.d.ts +10 -0
  80. package/dist/svelte/__tests__/fixtures/ObjectFormRegistryFixture.svelte.d.ts.map +1 -0
  81. package/dist/svelte/__tests__/fixtures/ObjectFormSnippetFixture.svelte +34 -0
  82. package/dist/svelte/__tests__/fixtures/ObjectFormSnippetFixture.svelte.d.ts +10 -0
  83. package/dist/svelte/__tests__/fixtures/ObjectFormSnippetFixture.svelte.d.ts.map +1 -0
  84. package/dist/svelte/__tests__/fixtures/ObjectFormSourceFixture.svelte +29 -0
  85. package/dist/svelte/__tests__/fixtures/ObjectFormSourceFixture.svelte.d.ts +11 -0
  86. package/dist/svelte/__tests__/fixtures/ObjectFormSourceFixture.svelte.d.ts.map +1 -0
  87. package/dist/svelte/__tests__/fixtures/ObjectFormSourceSnippetFixture.svelte +33 -0
  88. package/dist/svelte/__tests__/fixtures/ObjectFormSourceSnippetFixture.svelte.d.ts +9 -0
  89. package/dist/svelte/__tests__/fixtures/ObjectFormSourceSnippetFixture.svelte.d.ts.map +1 -0
  90. package/dist/svelte/__tests__/fixtures/PolicyDataTableFixture.svelte +40 -0
  91. package/dist/svelte/__tests__/fixtures/PolicyDataTableFixture.svelte.d.ts +8 -0
  92. package/dist/svelte/__tests__/fixtures/PolicyDataTableFixture.svelte.d.ts.map +1 -0
  93. package/dist/svelte/__tests__/fixtures/PrefillFixture.svelte +26 -0
  94. package/dist/svelte/__tests__/fixtures/PrefillFixture.svelte.d.ts +13 -0
  95. package/dist/svelte/__tests__/fixtures/PrefillFixture.svelte.d.ts.map +1 -0
  96. package/dist/svelte/__tests__/fixtures/ProviderFieldFixture.svelte +39 -0
  97. package/dist/svelte/__tests__/fixtures/ProviderFieldFixture.svelte.d.ts +17 -0
  98. package/dist/svelte/__tests__/fixtures/ProviderFieldFixture.svelte.d.ts.map +1 -0
  99. package/dist/svelte/__tests__/fixtures/RegistryInput.svelte +15 -0
  100. package/dist/svelte/__tests__/fixtures/RegistryInput.svelte.d.ts +5 -0
  101. package/dist/svelte/__tests__/fixtures/RegistryInput.svelte.d.ts.map +1 -0
  102. package/dist/svelte/__tests__/fixtures/RevealPrefillFixture.svelte +29 -0
  103. package/dist/svelte/__tests__/fixtures/RevealPrefillFixture.svelte.d.ts +14 -0
  104. package/dist/svelte/__tests__/fixtures/RevealPrefillFixture.svelte.d.ts.map +1 -0
  105. package/dist/svelte/__tests__/fixtures/SettingsCatalogStub.svelte +24 -0
  106. package/dist/svelte/__tests__/fixtures/SettingsCatalogStub.svelte.d.ts +14 -0
  107. package/dist/svelte/__tests__/fixtures/SettingsCatalogStub.svelte.d.ts.map +1 -0
  108. package/dist/svelte/__tests__/fixtures/SnippetFixture.svelte +28 -0
  109. package/dist/svelte/__tests__/fixtures/SnippetFixture.svelte.d.ts +13 -0
  110. package/dist/svelte/__tests__/fixtures/SnippetFixture.svelte.d.ts.map +1 -0
  111. package/dist/svelte/__tests__/fixtures/SuggestionGearFixture.svelte +23 -0
  112. package/dist/svelte/__tests__/fixtures/SuggestionGearFixture.svelte.d.ts +10 -0
  113. package/dist/svelte/__tests__/fixtures/SuggestionGearFixture.svelte.d.ts.map +1 -0
  114. package/dist/svelte/__tests__/fixtures/ToggleModeButton.svelte +14 -0
  115. package/dist/svelte/__tests__/fixtures/ToggleModeButton.svelte.d.ts +19 -0
  116. package/dist/svelte/__tests__/fixtures/ToggleModeButton.svelte.d.ts.map +1 -0
  117. package/dist/svelte/__tests__/fixtures/TooltipFixture.svelte +25 -0
  118. package/dist/svelte/__tests__/fixtures/TooltipFixture.svelte.d.ts +14 -0
  119. package/dist/svelte/__tests__/fixtures/TooltipFixture.svelte.d.ts.map +1 -0
  120. package/dist/svelte/__tests__/settings-catalog.test.js +161 -0
  121. package/dist/svelte/components/AdvancedFields.svelte +49 -0
  122. package/dist/svelte/components/AdvancedFields.svelte.d.ts +17 -0
  123. package/dist/svelte/components/AdvancedFields.svelte.d.ts.map +1 -0
  124. package/dist/svelte/components/FieldInput.svelte +159 -0
  125. package/dist/svelte/components/FieldInput.svelte.d.ts +5 -0
  126. package/dist/svelte/components/FieldInput.svelte.d.ts.map +1 -0
  127. package/dist/svelte/components/FieldPolicyControlPanel.svelte +288 -0
  128. package/dist/svelte/components/FieldPolicyControlPanel.svelte.d.ts +29 -0
  129. package/dist/svelte/components/FieldPolicyControlPanel.svelte.d.ts.map +1 -0
  130. package/dist/svelte/components/FieldPolicyEditor.svelte +417 -0
  131. package/dist/svelte/components/FieldPolicyEditor.svelte.d.ts +18 -0
  132. package/dist/svelte/components/FieldPolicyEditor.svelte.d.ts.map +1 -0
  133. package/dist/svelte/components/FieldPolicyGearButton.svelte +24 -0
  134. package/dist/svelte/components/FieldPolicyGearButton.svelte.d.ts +8 -0
  135. package/dist/svelte/components/FieldPolicyGearButton.svelte.d.ts.map +1 -0
  136. package/dist/svelte/components/FieldPolicyGearProvider.svelte +169 -0
  137. package/dist/svelte/components/FieldPolicyGearProvider.svelte.d.ts +20 -0
  138. package/dist/svelte/components/FieldPolicyGearProvider.svelte.d.ts.map +1 -0
  139. package/dist/svelte/components/FieldPolicyProvider.svelte +89 -0
  140. package/dist/svelte/components/FieldPolicyProvider.svelte.d.ts +35 -0
  141. package/dist/svelte/components/FieldPolicyProvider.svelte.d.ts.map +1 -0
  142. package/dist/svelte/components/FieldPolicySuggestionQueue.svelte +135 -0
  143. package/dist/svelte/components/FieldPolicySuggestionQueue.svelte.d.ts +11 -0
  144. package/dist/svelte/components/FieldPolicySuggestionQueue.svelte.d.ts.map +1 -0
  145. package/dist/svelte/components/FormHelp.svelte +157 -0
  146. package/dist/svelte/components/FormHelp.svelte.d.ts +15 -0
  147. package/dist/svelte/components/FormHelp.svelte.d.ts.map +1 -0
  148. package/dist/svelte/components/ModeSwitch.svelte +47 -0
  149. package/dist/svelte/components/ModeSwitch.svelte.d.ts +10 -0
  150. package/dist/svelte/components/ModeSwitch.svelte.d.ts.map +1 -0
  151. package/dist/svelte/components/ObjectForm.svelte +440 -0
  152. package/dist/svelte/components/ObjectForm.svelte.d.ts +50 -0
  153. package/dist/svelte/components/ObjectForm.svelte.d.ts.map +1 -0
  154. package/dist/svelte/components/ObjectFormSourceProvider.svelte +18 -0
  155. package/dist/svelte/components/ObjectFormSourceProvider.svelte.d.ts +11 -0
  156. package/dist/svelte/components/ObjectFormSourceProvider.svelte.d.ts.map +1 -0
  157. package/dist/svelte/components/PolicyField.svelte +338 -0
  158. package/dist/svelte/components/PolicyField.svelte.d.ts +80 -0
  159. package/dist/svelte/components/PolicyField.svelte.d.ts.map +1 -0
  160. package/dist/svelte/context.svelte.d.ts +54 -0
  161. package/dist/svelte/context.svelte.d.ts.map +1 -0
  162. package/dist/svelte/context.svelte.js +55 -0
  163. package/dist/svelte/data-table.d.ts +17 -0
  164. package/dist/svelte/data-table.d.ts.map +1 -0
  165. package/dist/svelte/data-table.js +19 -0
  166. package/dist/svelte/field-policy-editor.d.ts +81 -0
  167. package/dist/svelte/field-policy-editor.d.ts.map +1 -0
  168. package/dist/svelte/field-policy-editor.js +120 -0
  169. package/dist/svelte/gear-context.svelte.d.ts +16 -0
  170. package/dist/svelte/gear-context.svelte.d.ts.map +1 -0
  171. package/dist/svelte/gear-context.svelte.js +15 -0
  172. package/dist/svelte/index.d.ts +63 -0
  173. package/dist/svelte/index.d.ts.map +1 -0
  174. package/dist/svelte/index.js +41 -0
  175. package/dist/svelte/input-registry.d.ts +21 -0
  176. package/dist/svelte/input-registry.d.ts.map +1 -0
  177. package/dist/svelte/input-registry.js +26 -0
  178. package/dist/svelte/object-form-source-context.svelte.d.ts +4 -0
  179. package/dist/svelte/object-form-source-context.svelte.d.ts.map +1 -0
  180. package/dist/svelte/object-form-source-context.svelte.js +8 -0
  181. package/dist/svelte/object-form-source.svelte.d.ts +18 -0
  182. package/dist/svelte/object-form-source.svelte.d.ts.map +1 -0
  183. package/dist/svelte/object-form-source.svelte.js +99 -0
  184. package/dist/svelte/object-form.d.ts +6 -0
  185. package/dist/svelte/object-form.d.ts.map +1 -0
  186. package/dist/svelte/object-form.js +38 -0
  187. package/dist/svelte/playground/FieldPolicyFormPreview.svelte +212 -0
  188. package/dist/svelte/playground/FieldPolicyFormPreview.svelte.d.ts +4 -0
  189. package/dist/svelte/playground/FieldPolicyFormPreview.svelte.d.ts.map +1 -0
  190. package/dist/svelte/playground/ObjectFormPreview.svelte +129 -0
  191. package/dist/svelte/playground/ObjectFormPreview.svelte.d.ts +4 -0
  192. package/dist/svelte/playground/ObjectFormPreview.svelte.d.ts.map +1 -0
  193. package/dist/svelte/playground.d.ts +20 -0
  194. package/dist/svelte/playground.d.ts.map +1 -0
  195. package/dist/svelte/playground.js +38 -0
  196. package/dist/svelte/settings-catalog.d.ts +54 -0
  197. package/dist/svelte/settings-catalog.d.ts.map +1 -0
  198. package/dist/svelte/settings-catalog.js +116 -0
  199. package/dist/svelte/suggestions.d.ts +38 -0
  200. package/dist/svelte/suggestions.d.ts.map +1 -0
  201. package/dist/svelte/suggestions.js +68 -0
  202. package/dist/svelte/types.d.ts +101 -0
  203. package/dist/svelte/types.d.ts.map +1 -0
  204. package/dist/svelte/types.js +4 -0
  205. package/dist/svelte/usage-capture.d.ts +63 -0
  206. package/dist/svelte/usage-capture.d.ts.map +1 -0
  207. package/dist/svelte/usage-capture.js +61 -0
  208. package/dist/types.d.ts +316 -203
  209. package/dist/types.d.ts.map +1 -0
  210. package/dist/types.js +7 -1
  211. package/dist/types.js.map +1 -1
  212. package/dist/usage-learning.d.ts +201 -0
  213. package/dist/usage-learning.d.ts.map +1 -0
  214. package/dist/usage-schedules.d.ts +110 -0
  215. package/dist/usage-schedules.d.ts.map +1 -0
  216. package/dist/users-module.d.ts +30 -0
  217. package/dist/users-module.d.ts.map +1 -0
  218. package/package.json +33 -8
package/dist/index.js CHANGED
@@ -1,737 +1,1210 @@
1
- import { APP_FIELD_POLICY_SCOPE_KEY, FIELD_POLICY_SCOPE_TYPES, FIELD_POLICY_VISIBILITIES } from "./types.js";
1
+ import { C as setCachedFieldPolicy, D as ensureFieldPolicyPermissionsRegistered, E as PERSONALIZE_FIELD_POLICY_PERMISSION, O as __exportAll, S as invalidateFieldPolicyCache, T as MANAGE_FIELD_POLICY_PERMISSION, _ as requireRegisteredObject, a as buildCodeSeedDelta, b as getCachedFieldPolicy, c as getCodeSeedGroup, d as isPolicyAddressableField, f as isRequiredField, g as isUsableRequiredDefault, h as isTransientField, i as assertDefaultValueMatchesFieldType, l as getFieldReadPermission, m as isStorableReferenceId, o as buildCodeSeedVisibility, p as isSensitiveField, r as FieldPolicy, s as getCodeDefault, t as FieldPolicyCollection, u as getObjectFieldMap, v as sanitizeFieldUIHints, w as FIELD_POLICY_PERMISSION_DEFINITIONS, x as getFieldPolicyCacheTtlMs, y as clearFieldPolicyCache } from "./chunks/FieldPolicyCollection--RxukfCX.js";
2
+ import { APP_FIELD_POLICY_SCOPE_KEY, FIELD_POLICY_SCOPE_TYPES, FIELD_POLICY_SUGGESTION_KINDS, FIELD_POLICY_SUGGESTION_STATUSES, FIELD_POLICY_VISIBILITIES } from "./types.js";
2
3
  import { ObjectRegistry, SmrtCollection, SmrtObject, crossPackageRef, field, smrt } from "@happyvertical/smrt-core";
4
+ import { assertOperationPermission } from "@happyvertical/smrt-users";
3
5
  import { TenantIsolationError, assertTenantReadAllowed, getCurrentTenant, isSuperAdminBypass, tenantId } from "@happyvertical/smrt-tenancy";
4
6
  import { importWorkspaceModule } from "@happyvertical/smrt-core/utils/import-workspace-module";
5
- //#region \0rolldown/runtime.js
6
- var __defProp$2 = Object.defineProperty;
7
- var __exportAll = (all, no_symbols) => {
8
- let target = {};
9
- for (var name in all) __defProp$2(target, name, {
10
- get: all[name],
11
- enumerable: true
12
- });
13
- if (!no_symbols) __defProp$2(target, Symbol.toStringTag, { value: "Module" });
14
- return target;
15
- };
16
- //#endregion
17
7
  //#region src/__smrt-register__.ts
18
- ObjectRegistry.registerPackageManifest(JSON.parse("{\"version\":\"1.0.0\",\"timestamp\":0,\"packageName\":\"@happyvertical/smrt-fields\",\"packageVersion\":\"0.40.60\",\"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\"]}"));
8
+ ObjectRegistry.registerPackageManifest(JSON.parse("{\"version\":\"1.0.0\",\"timestamp\":0,\"packageName\":\"@happyvertical/smrt-fields\",\"packageVersion\":\"0.40.62\",\"objects\":{\"@happyvertical/smrt-fields:FieldUsageLearningAgent\":{\"name\":\"fieldusagelearningagent\",\"className\":\"FieldUsageLearningAgent\",\"qualifiedName\":\"@happyvertical/smrt-fields:FieldUsageLearningAgent\",\"collection\":\"fieldusagelearningagents\",\"filePath\":\"/home/runner/_work/smrt/smrt/packages/fields/src/usage-learning.ts\",\"packageName\":\"@happyvertical/smrt-fields\",\"fields\":{},\"methods\":{\"runUsageMaintenance\":{\"name\":\"runUsageMaintenance\",\"async\":true,\"parameters\":[{\"name\":\"args\",\"type\":\"Record<string>\",\"optional\":true}],\"returnType\":\"Promise<FieldUsageMaintenanceSummary>\",\"isStatic\":false,\"isPublic\":true},\"runSuggestionGeneration\":{\"name\":\"runSuggestionGeneration\",\"async\":true,\"parameters\":[{\"name\":\"args\",\"type\":\"Record<string>\",\"optional\":true}],\"returnType\":\"Promise<FieldUsageSuggestionRunSummary>\",\"isStatic\":false,\"isPublic\":true}},\"decoratorConfig\":{\"tableName\":\"_smrt_field_usage_learning_agents\",\"api\":{\"include\":[]},\"cli\":false,\"mcp\":{\"include\":[]}},\"extends\":\"SmrtObject\",\"exportName\":\"FieldUsageLearningAgent\",\"collectionExportName\":\"FieldUsageLearningAgentCollection\",\"schema\":{\"tableName\":\"_smrt_field_usage_learning_agents\",\"ddl\":\"CREATE TABLE IF NOT EXISTS \\\"_smrt_field_usage_learning_agents\\\" (\\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_usage_learning_agents_id_idx\",\"columns\":[\"id\"]},{\"name\":\"_smrt_field_usage_learning_agents_slug_context_idx\",\"columns\":[\"slug\",\"context\"],\"unique\":true}],\"version\":\"183ee1d8\"}},\"@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:FieldPolicySuggestionCollection\":{\"name\":\"fieldpolicysuggestioncollection\",\"className\":\"FieldPolicySuggestionCollection\",\"qualifiedName\":\"@happyvertical/smrt-fields:FieldPolicySuggestionCollection\",\"collection\":\"fieldpolicysuggestions\",\"filePath\":\"/home/runner/_work/smrt/smrt/packages/fields/src/collections/FieldPolicySuggestionCollection.ts\",\"packageName\":\"@happyvertical/smrt-fields\",\"fields\":{},\"methods\":{\"pendingSuggestions\":{\"name\":\"pendingSuggestions\",\"async\":true,\"parameters\":[{\"name\":\"options\",\"type\":\"object\",\"optional\":true}],\"returnType\":\"Promise<PendingFieldPolicySuggestionsResult>\",\"isStatic\":false,\"isPublic\":true},\"acceptSuggestion\":{\"name\":\"acceptSuggestion\",\"async\":true,\"parameters\":[{\"name\":\"options\",\"type\":\"object\",\"optional\":true}],\"returnType\":\"Promise<AcceptFieldPolicySuggestionResult>\",\"isStatic\":false,\"isPublic\":true},\"dismissSuggestion\":{\"name\":\"dismissSuggestion\",\"async\":true,\"parameters\":[{\"name\":\"options\",\"type\":\"object\",\"optional\":true}],\"returnType\":\"Promise<DismissFieldPolicySuggestionResult>\",\"isStatic\":false,\"isPublic\":true}},\"decoratorConfig\":{\"conflictColumns\":[\"object_ref\",\"field_name\",\"tenant_id\",\"kind\",\"active_key\"],\"api\":{\"include\":[\"pendingSuggestions\",\"acceptSuggestion\",\"dismissSuggestion\"],\"principalContext\":true,\"routes\":{\"pendingSuggestions\":{\"scope\":\"collection\",\"method\":\"POST\",\"path\":\"pending\"},\"acceptSuggestion\":{\"scope\":\"collection\",\"method\":\"POST\",\"path\":\"accept\"},\"dismissSuggestion\":{\"scope\":\"collection\",\"method\":\"POST\",\"path\":\"dismiss\"}}},\"cli\":false,\"mcp\":false,\"tableName\":\"_smrt_field_policy_suggestions\"},\"extends\":\"SmrtCollection\",\"extendsTypeArg\":\"FieldPolicySuggestion\",\"exportName\":\"FieldPolicySuggestionCollection\",\"collectionExportName\":\"FieldPolicySuggestionCollectionCollection\",\"schema\":{\"tableName\":\"_smrt_field_policy_suggestions\",\"ddl\":\"CREATE TABLE IF NOT EXISTS \\\"_smrt_field_policy_suggestions\\\" (\\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_policy_suggestions_id_idx\",\"columns\":[\"id\"]},{\"name\":\"_smrt_field_policy_suggestions_object_ref_field_name_idx\",\"columns\":[\"object_ref\",\"field_name\",\"tenant_id\",\"kind\",\"active_key\"],\"unique\":true}],\"version\":\"853df86d\"}},\"@happyvertical/smrt-fields:FieldUsageCounterCollection\":{\"name\":\"fieldusagecountercollection\",\"className\":\"FieldUsageCounterCollection\",\"qualifiedName\":\"@happyvertical/smrt-fields:FieldUsageCounterCollection\",\"collection\":\"fieldusagecounters\",\"filePath\":\"/home/runner/_work/smrt/smrt/packages/fields/src/collections/FieldUsageCounterCollection.ts\",\"packageName\":\"@happyvertical/smrt-fields\",\"fields\":{},\"methods\":{\"reportUsage\":{\"name\":\"reportUsage\",\"async\":true,\"parameters\":[{\"name\":\"options\",\"type\":\"object\",\"optional\":true}],\"returnType\":\"Promise<FieldUsageReportResult>\",\"isStatic\":false,\"isPublic\":true},\"listWindow\":{\"name\":\"listWindow\",\"async\":true,\"parameters\":[{\"name\":\"options\",\"type\":\"object\",\"optional\":false}],\"returnType\":\"Promise<FieldUsageCounter[]>\",\"isStatic\":false,\"isPublic\":true}},\"decoratorConfig\":{\"conflictColumns\":[\"object_ref\",\"field_name\",\"tenant_id\",\"period\"],\"api\":{\"include\":[\"reportUsage\"],\"principalContext\":true,\"routes\":{\"reportUsage\":{\"scope\":\"collection\",\"method\":\"POST\",\"path\":\"report\"}}},\"cli\":false,\"mcp\":false,\"tableName\":\"_smrt_field_usage_counters\"},\"extends\":\"SmrtCollection\",\"extendsTypeArg\":\"FieldUsageCounter\",\"exportName\":\"FieldUsageCounterCollection\",\"collectionExportName\":\"FieldUsageCounterCollectionCollection\",\"schema\":{\"tableName\":\"_smrt_field_usage_counters\",\"ddl\":\"CREATE TABLE IF NOT EXISTS \\\"_smrt_field_usage_counters\\\" (\\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_usage_counters_id_idx\",\"columns\":[\"id\"]},{\"name\":\"_smrt_field_usage_counters_object_ref_field_name_idx\",\"columns\":[\"object_ref\",\"field_name\",\"tenant_id\",\"period\"],\"unique\":true}],\"version\":\"8bd9beae\"}},\"@happyvertical/smrt-fields:FieldUsageReportReceiptCollection\":{\"name\":\"fieldusagereportreceiptcollection\",\"className\":\"FieldUsageReportReceiptCollection\",\"qualifiedName\":\"@happyvertical/smrt-fields:FieldUsageReportReceiptCollection\",\"collection\":\"fieldusagereportreceipts\",\"filePath\":\"/home/runner/_work/smrt/smrt/packages/fields/src/collections/FieldUsageCounterCollection.ts\",\"packageName\":\"@happyvertical/smrt-fields\",\"fields\":{},\"methods\":{\"claim\":{\"name\":\"claim\",\"async\":true,\"parameters\":[{\"name\":\"options\",\"type\":\"object\",\"optional\":false}],\"returnType\":\"Promise<boolean>\",\"isStatic\":false,\"isPublic\":true}},\"decoratorConfig\":{\"conflictColumns\":[\"tenant_id\",\"user_id\",\"object_ref\",\"field_name\",\"period\"],\"api\":false,\"cli\":false,\"mcp\":false,\"tableName\":\"_smrt_field_usage_report_receipts\"},\"extends\":\"SmrtCollection\",\"extendsTypeArg\":\"FieldUsageReportReceipt\",\"exportName\":\"FieldUsageReportReceiptCollection\",\"collectionExportName\":\"FieldUsageReportReceiptCollectionCollection\",\"schema\":{\"tableName\":\"_smrt_field_usage_report_receipts\",\"ddl\":\"CREATE TABLE IF NOT EXISTS \\\"_smrt_field_usage_report_receipts\\\" (\\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_usage_report_receipts_id_idx\",\"columns\":[\"id\"]},{\"name\":\"_smrt_field_usage_report_receipts_tenant_id_user_id_idx\",\"columns\":[\"tenant_id\",\"user_id\",\"object_ref\",\"field_name\",\"period\"],\"unique\":true}],\"version\":\"2e77b775\"}},\"@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\"}},\"@happyvertical/smrt-fields:FieldPolicySuggestion\":{\"name\":\"fieldpolicysuggestion\",\"className\":\"FieldPolicySuggestion\",\"qualifiedName\":\"@happyvertical/smrt-fields:FieldPolicySuggestion\",\"collection\":\"fieldpolicysuggestions\",\"filePath\":\"/home/runner/_work/smrt/smrt/packages/fields/src/models/FieldPolicySuggestion.ts\",\"packageName\":\"@happyvertical/smrt-fields\",\"fields\":{\"objectRef\":{\"type\":\"text\",\"required\":true,\"default\":\"\",\"_meta\":{\"required\":true}},\"fieldName\":{\"type\":\"text\",\"required\":true,\"default\":\"\",\"_meta\":{\"required\":true}},\"tenantId\":{\"type\":\"text\",\"required\":true,\"_meta\":{\"sqlType\":\"UUID\",\"__tenancy\":{\"isTenantIdField\":true,\"autoFilter\":true,\"required\":true,\"autoPopulate\":true,\"nullable\":false}}},\"kind\":{\"type\":\"text\",\"required\":true,\"default\":\"promote\",\"_meta\":{\"required\":true}},\"proposedValue\":{\"type\":\"text\",\"required\":false,\"_meta\":{\"nullable\":true}},\"evidence\":{\"type\":\"text\",\"required\":false,\"_meta\":{}},\"status\":{\"type\":\"text\",\"required\":true,\"default\":\"pending\",\"_meta\":{\"required\":true}},\"activeKey\":{\"type\":\"text\",\"required\":true,\"_meta\":{\"required\":true}},\"cooldownUntil\":{\"type\":\"datetime\",\"required\":false,\"_meta\":{\"nullable\":true}},\"decidedBy\":{\"type\":\"crossPackageRef\",\"required\":false,\"related\":\"@happyvertical/smrt-users:User\",\"_meta\":{\"nullable\":true}},\"decidedAt\":{\"type\":\"datetime\",\"required\":false,\"_meta\":{\"nullable\":true}}},\"methods\":{\"getEvidence\":{\"name\":\"getEvidence\",\"async\":false,\"parameters\":[],\"returnType\":\"Record<string>\",\"isStatic\":false,\"isPublic\":true},\"setEvidence\":{\"name\":\"setEvidence\",\"async\":false,\"parameters\":[{\"name\":\"evidence\",\"type\":\"Record<string>\",\"optional\":false}],\"returnType\":\"void\",\"isStatic\":false,\"isPublic\":true},\"getProposedValue\":{\"name\":\"getProposedValue\",\"async\":false,\"parameters\":[],\"returnType\":\"any\",\"isStatic\":false,\"isPublic\":true},\"toSuggestionData\":{\"name\":\"toSuggestionData\",\"async\":false,\"parameters\":[],\"returnType\":\"FieldPolicySuggestionData\",\"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_policy_suggestions\",\"conflictColumns\":[\"object_ref\",\"field_name\",\"tenant_id\",\"kind\",\"active_key\"],\"api\":{\"include\":[]},\"cli\":false,\"mcp\":{\"include\":[]}},\"extends\":\"SmrtObject\",\"exportName\":\"FieldPolicySuggestion\",\"collectionExportName\":\"FieldPolicySuggestionCollection\",\"validationRules\":[{\"field\":\"objectRef\",\"rule\":\"required\",\"fieldType\":\"text\"},{\"field\":\"fieldName\",\"rule\":\"required\",\"fieldType\":\"text\"},{\"field\":\"tenantId\",\"rule\":\"required\",\"fieldType\":\"text\"},{\"field\":\"kind\",\"rule\":\"required\",\"fieldType\":\"text\"},{\"field\":\"status\",\"rule\":\"required\",\"fieldType\":\"text\"},{\"field\":\"activeKey\",\"rule\":\"required\",\"fieldType\":\"text\"}],\"schema\":{\"tableName\":\"_smrt_field_policy_suggestions\",\"ddl\":\"CREATE TABLE IF NOT EXISTS \\\"_smrt_field_policy_suggestions\\\" (\\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 \\\"tenant_id\\\" UUID NOT NULL,\\n \\\"kind\\\" TEXT NOT NULL DEFAULT 'promote',\\n \\\"proposed_value\\\" TEXT,\\n \\\"evidence\\\" TEXT,\\n \\\"status\\\" TEXT NOT NULL DEFAULT 'pending',\\n \\\"active_key\\\" TEXT NOT NULL,\\n \\\"cooldown_until\\\" TIMESTAMP,\\n \\\"decided_by\\\" UUID,\\n \\\"decided_at\\\" 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\"},\"object_ref\":{\"type\":\"TEXT\",\"notNull\":true,\"unique\":false,\"default\":\"\"},\"field_name\":{\"type\":\"TEXT\",\"notNull\":true,\"unique\":false,\"default\":\"\"},\"tenant_id\":{\"type\":\"UUID\",\"referenceKind\":\"tenantId\",\"notNull\":true,\"unique\":false},\"kind\":{\"type\":\"TEXT\",\"notNull\":true,\"unique\":false,\"default\":\"promote\"},\"proposed_value\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false},\"evidence\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false},\"status\":{\"type\":\"TEXT\",\"notNull\":true,\"unique\":false,\"default\":\"pending\"},\"active_key\":{\"type\":\"TEXT\",\"notNull\":true,\"unique\":false},\"cooldown_until\":{\"type\":\"TIMESTAMP\",\"notNull\":false,\"unique\":false},\"decided_by\":{\"type\":\"UUID\",\"referenceKind\":\"crossPackageRef\",\"notNull\":false,\"unique\":false},\"decided_at\":{\"type\":\"TIMESTAMP\",\"notNull\":false,\"unique\":false}},\"indexes\":[{\"name\":\"_smrt_field_policy_suggestions_id_idx\",\"columns\":[\"id\"]},{\"name\":\"_smrt_field_policy_suggestions_object_ref_field_name_idx\",\"columns\":[\"object_ref\",\"field_name\",\"tenant_id\",\"kind\",\"active_key\"],\"unique\":true}],\"version\":\"1bc8c6e5\"}},\"@happyvertical/smrt-fields:FieldUsageCounter\":{\"name\":\"fieldusagecounter\",\"className\":\"FieldUsageCounter\",\"qualifiedName\":\"@happyvertical/smrt-fields:FieldUsageCounter\",\"collection\":\"fieldusagecounters\",\"filePath\":\"/home/runner/_work/smrt/smrt/packages/fields/src/models/FieldUsageCounter.ts\",\"packageName\":\"@happyvertical/smrt-fields\",\"fields\":{\"objectRef\":{\"type\":\"text\",\"required\":true,\"default\":\"\",\"_meta\":{\"required\":true}},\"fieldName\":{\"type\":\"text\",\"required\":true,\"default\":\"\",\"_meta\":{\"required\":true}},\"tenantId\":{\"type\":\"text\",\"required\":true,\"_meta\":{\"sqlType\":\"UUID\",\"__tenancy\":{\"isTenantIdField\":true,\"autoFilter\":true,\"required\":true,\"autoPopulate\":true,\"nullable\":false}}},\"period\":{\"type\":\"text\",\"required\":true,\"default\":\"\",\"_meta\":{\"required\":true}},\"submissionCount\":{\"type\":\"integer\",\"required\":false,\"_meta\":{}},\"setCount\":{\"type\":\"integer\",\"required\":false,\"_meta\":{}},\"distinctUserCount\":{\"type\":\"integer\",\"required\":false,\"_meta\":{}},\"distinctUserIds\":{\"type\":\"text\",\"required\":false,\"_meta\":{}},\"distinctUsersOverflowed\":{\"type\":\"boolean\",\"required\":false,\"_meta\":{}},\"valueHistogram\":{\"type\":\"text\",\"required\":false,\"_meta\":{\"nullable\":true}},\"valueHistogramOverflowed\":{\"type\":\"boolean\",\"required\":false,\"_meta\":{}}},\"methods\":{\"getDistinctUserIds\":{\"name\":\"getDistinctUserIds\",\"async\":false,\"parameters\":[],\"returnType\":\"string[]\",\"isStatic\":false,\"isPublic\":true},\"addDistinctUser\":{\"name\":\"addDistinctUser\",\"async\":false,\"parameters\":[{\"name\":\"userId\",\"type\":\"string\",\"optional\":false}],\"returnType\":\"void\",\"isStatic\":false,\"isPublic\":true},\"getValueHistogram\":{\"name\":\"getValueHistogram\",\"async\":false,\"parameters\":[],\"returnType\":\"Record<string, number>\",\"isStatic\":false,\"isPublic\":true},\"recordHistogramSample\":{\"name\":\"recordHistogramSample\",\"async\":false,\"parameters\":[{\"name\":\"key\",\"type\":\"string\",\"optional\":false}],\"returnType\":\"void\",\"isStatic\":false,\"isPublic\":true},\"isLegacyBucket\":{\"name\":\"isLegacyBucket\",\"async\":false,\"parameters\":[],\"returnType\":\"boolean\",\"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_usage_counters\",\"conflictColumns\":[\"object_ref\",\"field_name\",\"tenant_id\",\"period\"],\"api\":{\"include\":[]},\"cli\":false,\"mcp\":{\"include\":[]}},\"extends\":\"SmrtObject\",\"exportName\":\"FieldUsageCounter\",\"collectionExportName\":\"FieldUsageCounterCollection\",\"validationRules\":[{\"field\":\"objectRef\",\"rule\":\"required\",\"fieldType\":\"text\"},{\"field\":\"fieldName\",\"rule\":\"required\",\"fieldType\":\"text\"},{\"field\":\"tenantId\",\"rule\":\"required\",\"fieldType\":\"text\"},{\"field\":\"period\",\"rule\":\"required\",\"fieldType\":\"text\"}],\"schema\":{\"tableName\":\"_smrt_field_usage_counters\",\"ddl\":\"CREATE TABLE IF NOT EXISTS \\\"_smrt_field_usage_counters\\\" (\\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 \\\"tenant_id\\\" UUID NOT NULL,\\n \\\"period\\\" TEXT NOT NULL DEFAULT '',\\n \\\"submission_count\\\" INTEGER,\\n \\\"set_count\\\" INTEGER,\\n \\\"distinct_user_count\\\" INTEGER,\\n \\\"distinct_user_ids\\\" TEXT,\\n \\\"distinct_users_overflowed\\\" BOOLEAN,\\n \\\"value_histogram\\\" TEXT,\\n \\\"value_histogram_overflowed\\\" BOOLEAN\\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\":\"\"},\"tenant_id\":{\"type\":\"UUID\",\"referenceKind\":\"tenantId\",\"notNull\":true,\"unique\":false},\"period\":{\"type\":\"TEXT\",\"notNull\":true,\"unique\":false,\"default\":\"\"},\"submission_count\":{\"type\":\"INTEGER\",\"notNull\":false,\"unique\":false},\"set_count\":{\"type\":\"INTEGER\",\"notNull\":false,\"unique\":false},\"distinct_user_count\":{\"type\":\"INTEGER\",\"notNull\":false,\"unique\":false},\"distinct_user_ids\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false},\"distinct_users_overflowed\":{\"type\":\"BOOLEAN\",\"notNull\":false,\"unique\":false},\"value_histogram\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false},\"value_histogram_overflowed\":{\"type\":\"BOOLEAN\",\"notNull\":false,\"unique\":false}},\"indexes\":[{\"name\":\"_smrt_field_usage_counters_id_idx\",\"columns\":[\"id\"]},{\"name\":\"_smrt_field_usage_counters_object_ref_field_name_idx\",\"columns\":[\"object_ref\",\"field_name\",\"tenant_id\",\"period\"],\"unique\":true}],\"version\":\"46fcebc6\"}},\"@happyvertical/smrt-fields:FieldUsageReportReceipt\":{\"name\":\"fieldusagereportreceipt\",\"className\":\"FieldUsageReportReceipt\",\"qualifiedName\":\"@happyvertical/smrt-fields:FieldUsageReportReceipt\",\"collection\":\"fieldusagereportreceipts\",\"filePath\":\"/home/runner/_work/smrt/smrt/packages/fields/src/models/FieldUsageReportReceipt.ts\",\"packageName\":\"@happyvertical/smrt-fields\",\"fields\":{\"tenantId\":{\"type\":\"text\",\"required\":true,\"_meta\":{\"sqlType\":\"UUID\",\"__tenancy\":{\"isTenantIdField\":true,\"autoFilter\":true,\"required\":true,\"autoPopulate\":true,\"nullable\":false}}},\"userId\":{\"type\":\"crossPackageRef\",\"required\":false,\"related\":\"@happyvertical/smrt-users:User\"},\"objectRef\":{\"type\":\"text\",\"required\":true,\"default\":\"\",\"_meta\":{\"required\":true}},\"fieldName\":{\"type\":\"text\",\"required\":true,\"default\":\"\",\"_meta\":{\"required\":true}},\"period\":{\"type\":\"text\",\"required\":true,\"default\":\"\",\"_meta\":{\"required\":true}}},\"methods\":{},\"decoratorConfig\":{\"tableName\":\"_smrt_field_usage_report_receipts\",\"conflictColumns\":[\"tenant_id\",\"user_id\",\"object_ref\",\"field_name\",\"period\"],\"api\":{\"include\":[]},\"cli\":false,\"mcp\":{\"include\":[]}},\"extends\":\"SmrtObject\",\"exportName\":\"FieldUsageReportReceipt\",\"collectionExportName\":\"FieldUsageReportReceiptCollection\",\"validationRules\":[{\"field\":\"tenantId\",\"rule\":\"required\",\"fieldType\":\"text\"},{\"field\":\"objectRef\",\"rule\":\"required\",\"fieldType\":\"text\"},{\"field\":\"fieldName\",\"rule\":\"required\",\"fieldType\":\"text\"},{\"field\":\"period\",\"rule\":\"required\",\"fieldType\":\"text\"}],\"schema\":{\"tableName\":\"_smrt_field_usage_report_receipts\",\"ddl\":\"CREATE TABLE IF NOT EXISTS \\\"_smrt_field_usage_report_receipts\\\" (\\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 \\\"tenant_id\\\" UUID NOT NULL,\\n \\\"user_id\\\" UUID,\\n \\\"object_ref\\\" TEXT NOT NULL DEFAULT '',\\n \\\"field_name\\\" TEXT NOT NULL DEFAULT '',\\n \\\"period\\\" TEXT NOT NULL DEFAULT ''\\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\"},\"tenant_id\":{\"type\":\"UUID\",\"referenceKind\":\"tenantId\",\"notNull\":true,\"unique\":false},\"user_id\":{\"type\":\"UUID\",\"referenceKind\":\"crossPackageRef\",\"notNull\":false,\"unique\":false},\"object_ref\":{\"type\":\"TEXT\",\"notNull\":true,\"unique\":false,\"default\":\"\"},\"field_name\":{\"type\":\"TEXT\",\"notNull\":true,\"unique\":false,\"default\":\"\"},\"period\":{\"type\":\"TEXT\",\"notNull\":true,\"unique\":false,\"default\":\"\"}},\"indexes\":[{\"name\":\"_smrt_field_usage_report_receipts_id_idx\",\"columns\":[\"id\"]},{\"name\":\"_smrt_field_usage_report_receipts_tenant_id_user_id_idx\",\"columns\":[\"tenant_id\",\"user_id\",\"object_ref\",\"field_name\",\"period\"],\"unique\":true}],\"version\":\"c3119047\"}}},\"moduleType\":\"smrt\",\"smrtDependencies\":[\"@happyvertical/smrt-core\",\"@happyvertical/smrt-tenancy\",\"@happyvertical/smrt-users\"]}"));
19
9
  //#endregion
20
- //#region src/cache.ts
21
- var FIELD_POLICY_CACHE_TTL_MS = 3e4;
22
- var fieldPolicyCache = /* @__PURE__ */ new Map();
23
- var dbInstanceIds = /* @__PURE__ */ new WeakMap();
24
- var nextDbId = 1;
25
- function getDbNamespace(db) {
26
- if (!db) return "no-db";
27
- if (typeof db === "string") return `db:${db}`;
28
- if (typeof db === "object") {
29
- const dbObject = db;
30
- if (typeof dbObject.query === "function") {
31
- if (!dbInstanceIds.has(dbObject)) dbInstanceIds.set(dbObject, `db-instance:${nextDbId++}`);
32
- const namespace = dbInstanceIds.get(dbObject);
33
- if (namespace) return namespace;
34
- return "db-instance:unknown";
35
- }
36
- try {
37
- return `db-config:${JSON.stringify(dbObject)}`;
38
- } catch {
39
- return "db-config:opaque";
40
- }
41
- }
42
- return "db:unknown";
43
- }
44
- var hierarchyLoaderIds = /* @__PURE__ */ new WeakMap();
45
- var nextLoaderId = 1;
46
- function getHierarchyLoaderNamespace(loader) {
47
- if (typeof loader !== "function") return "loader:default";
48
- const key = loader;
49
- if (!hierarchyLoaderIds.has(key)) hierarchyLoaderIds.set(key, `loader:${nextLoaderId++}`);
50
- return hierarchyLoaderIds.get(key) ?? "loader:unknown";
51
- }
52
- function buildCacheKey(objectRef, tenantId, userId, db, hierarchyLoader) {
53
- return `${getDbNamespace(db)}::${objectRef}::${tenantId ?? "app"}::${userId ?? "user:none"}::${getHierarchyLoaderNamespace(hierarchyLoader)}`;
54
- }
55
- function getFieldPolicyCacheTtlMs() {
56
- return FIELD_POLICY_CACHE_TTL_MS;
57
- }
58
- function getCachedFieldPolicy(objectRef, tenantId, userId, db, hierarchyLoader) {
59
- const cacheKey = buildCacheKey(objectRef, tenantId, userId, db, hierarchyLoader);
60
- const cached = fieldPolicyCache.get(cacheKey);
61
- if (!cached) return null;
62
- if (cached.expiresAt <= Date.now()) {
63
- fieldPolicyCache.delete(cacheKey);
64
- return null;
65
- }
66
- return cached.value;
67
- }
68
- function setCachedFieldPolicy(objectRef, tenantId, userId, db, value, hierarchyLoader) {
69
- fieldPolicyCache.set(buildCacheKey(objectRef, tenantId, userId, db, hierarchyLoader), {
70
- expiresAt: Date.now() + FIELD_POLICY_CACHE_TTL_MS,
71
- value
72
- });
73
- }
74
- function invalidateFieldPolicyCache(objectRef, db) {
75
- const keyPrefix = `${getDbNamespace(db)}::${objectRef}::`;
76
- for (const cacheKey of fieldPolicyCache.keys()) if (cacheKey.startsWith(keyPrefix)) fieldPolicyCache.delete(cacheKey);
77
- }
78
- function clearFieldPolicyCache() {
79
- fieldPolicyCache.clear();
10
+ //#region src/deterministic-id.ts
11
+ async function deterministicFieldsUuid(parts) {
12
+ const bytes = new TextEncoder().encode(JSON.stringify(parts));
13
+ const uuid = new Uint8Array(await crypto.subtle.digest("SHA-256", bytes)).slice(0, 16);
14
+ uuid[6] = uuid[6] & 15 | 80;
15
+ uuid[8] = uuid[8] & 63 | 128;
16
+ const hex = Array.from(uuid, (byte) => byte.toString(16).padStart(2, "0")).join("");
17
+ return [
18
+ hex.slice(0, 8),
19
+ hex.slice(8, 12),
20
+ hex.slice(12, 16),
21
+ hex.slice(16, 20),
22
+ hex.slice(20)
23
+ ].join("-");
80
24
  }
81
25
  //#endregion
82
- //#region src/field-definitions.ts
83
- function requireRegisteredObject(objectRef) {
84
- if (!objectRef?.includes(":")) throw new Error(`Field policy objectRef must be a qualified class name ("@package/name:ClassName"), got "${objectRef}"`);
85
- if (!ObjectRegistry.getClassByQualifiedName(objectRef)) throw new Error(`Unknown field policy objectRef "${objectRef}": no class with that qualified name is registered`);
86
- }
87
- async function getObjectFieldMap(objectRef) {
88
- requireRegisteredObject(objectRef);
89
- return ObjectRegistry.getAllFields(objectRef);
90
- }
91
- function sanitizeFieldUIHints(value) {
92
- if (!value || typeof value !== "object" || Array.isArray(value)) return;
93
- const raw = value;
94
- const ui = {
95
- ...typeof raw.basic === "boolean" ? { basic: raw.basic } : {},
96
- ...typeof raw.group === "string" ? { group: raw.group } : {},
97
- ...typeof raw.order === "number" && Number.isFinite(raw.order) ? { order: raw.order } : {},
98
- ...typeof raw.locked === "boolean" ? { locked: raw.locked } : {}
99
- };
100
- return Object.keys(ui).length > 0 ? ui : void 0;
101
- }
102
- function isSensitiveField(field) {
103
- return field.sensitive === true || field._meta?.sensitive === true;
104
- }
105
- function getFieldReadPermission(field) {
106
- if (typeof field.readPermission === "string") return field.readPermission;
107
- const metaPermission = field._meta?.readPermission;
108
- return typeof metaPermission === "string" ? metaPermission : void 0;
109
- }
110
- function isTransientField(field) {
111
- return field.transient === true || field._meta?.transient === true;
112
- }
113
- function isRequiredField(field) {
114
- if (field.nullable === true || field._meta?.nullable === true) return false;
115
- return field.required === true || field._meta?.required === true;
116
- }
117
- function getCodeDefault(field) {
118
- if (field.default !== void 0) return { value: field.default };
119
- if (field._meta?.default !== void 0) return { value: field._meta.default };
120
- }
121
- function isUsableRequiredDefault(defaultBox) {
122
- if (!defaultBox) return false;
123
- return defaultBox.value !== null && defaultBox.value !== "";
124
- }
125
- function buildCodeSeedVisibility(fields) {
126
- let hasBasicMarkers = false;
127
- const hints = /* @__PURE__ */ new Map();
128
- for (const [name, field] of fields) {
129
- const ui = sanitizeFieldUIHints(field._meta?.ui);
130
- hints.set(name, ui);
131
- if (ui?.basic === true) hasBasicMarkers = true;
132
- }
133
- const visibility = /* @__PURE__ */ new Map();
134
- for (const [name] of fields) {
135
- const ui = hints.get(name);
136
- if (ui?.basic === true) visibility.set(name, "basic");
137
- else if (ui?.basic === false || hasBasicMarkers) visibility.set(name, "advanced");
138
- else visibility.set(name, "basic");
139
- }
140
- return visibility;
141
- }
142
- function buildCodeSeedDelta(field, visibility) {
143
- const ui = sanitizeFieldUIHints(field._meta?.ui);
144
- const help = typeof field.description === "string" ? field.description : typeof field._meta?.description === "string" ? field._meta.description : void 0;
145
- const codeDefault = getCodeDefault(field);
146
- return {
147
- ...codeDefault ? { default: codeDefault } : {},
148
- visibility,
149
- ...help !== void 0 ? { help } : {},
150
- ...ui?.order !== void 0 ? { order: ui.order } : {},
151
- ...ui?.locked === true ? { locked: true } : {}
152
- };
153
- }
154
- function getCodeSeedGroup(field) {
155
- return sanitizeFieldUIHints(field._meta?.ui)?.group ?? null;
156
- }
157
- var UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
158
- function usesTextIdStorage(field) {
159
- return field.idType === "text" || field._meta?.idType === "text";
160
- }
161
- function assertDefaultValueMatchesFieldType(objectRef, fieldName, field, value) {
162
- const label = `Field policy default for "${objectRef}.${fieldName}"`;
163
- if (value === null) {
164
- if (isRequiredField(field)) throw new Error(`${label} may not be null: the field is required, so a null default is never a usable default`);
165
- return;
166
- }
167
- switch (field.type) {
168
- case "text":
169
- if (typeof value !== "string") throw new Error(`${label} must be a string (field type "text")`);
170
- return;
171
- case "integer":
172
- if (typeof value !== "number" || !Number.isInteger(value)) throw new Error(`${label} must be an integer (field type "integer")`);
173
- return;
174
- case "decimal":
175
- if (typeof value !== "number" || !Number.isFinite(value)) throw new Error(`${label} must be a finite number (field type "decimal")`);
176
- return;
177
- case "boolean":
178
- if (typeof value !== "boolean") throw new Error(`${label} must be a boolean (field type "boolean")`);
179
- return;
180
- case "datetime":
181
- if (typeof value !== "string" || Number.isNaN(Date.parse(value))) throw new Error(`${label} must be a date-parseable string (field type "datetime")`);
182
- return;
183
- case "json": return;
184
- case "foreignKey":
185
- case "crossPackageRef":
186
- if (typeof value !== "string") throw new Error(`${label} must be a string id (field type "${field.type}")`);
187
- if (!usesTextIdStorage(field) && !UUID_PATTERN.test(value)) throw new Error(`${label} must be a UUID string (the reference column stores native UUIDs; declare idType 'text' on the field to store arbitrary ids)`);
188
- return;
189
- default: throw new Error(`${label} is not supported: defaults cannot target "${String(field.type)}" fields`);
190
- }
191
- }
192
- //#endregion
193
- //#region src/models/FieldPolicy.ts
194
- var __defProp$1 = Object.defineProperty;
195
- var __getOwnPropDesc$1 = Object.getOwnPropertyDescriptor;
196
- var __decorateClass$1 = (decorators, target, key, kind) => {
197
- var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$1(target, key) : target;
26
+ //#region src/models/FieldPolicySuggestion.ts
27
+ var __defProp$5 = Object.defineProperty;
28
+ var __getOwnPropDesc$5 = Object.getOwnPropertyDescriptor;
29
+ var __decorateClass$5 = (decorators, target, key, kind) => {
30
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$5(target, key) : target;
198
31
  for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
199
- if (kind && result) __defProp$1(target, key, result);
32
+ if (kind && result) __defProp$5(target, key, result);
200
33
  return result;
201
34
  };
202
- var FieldPolicy = class extends SmrtObject {
35
+ var ACTIVE_SUGGESTION_KEY = "active";
36
+ var FieldPolicySuggestion = class extends SmrtObject {
203
37
  objectRef = "";
204
38
  fieldName = "";
205
- scopeType = "app";
206
- tenantId = null;
207
- userId = null;
208
- scopeKey = "";
209
- defaultValue = null;
210
- visibility = null;
211
- help = null;
212
- label = null;
213
- displayOrder = null;
214
- locked = null;
215
- updatedBy = null;
39
+ tenantId;
40
+ kind = "promote";
41
+ proposedValue = null;
42
+ evidence = "{}";
43
+ status = "pending";
44
+ activeKey = ACTIVE_SUGGESTION_KEY;
45
+ cooldownUntil = null;
46
+ decidedBy = null;
47
+ decidedAt = null;
216
48
  constructor(options = {}) {
217
49
  super(options);
218
50
  if (options.objectRef !== void 0) this.objectRef = options.objectRef;
219
51
  if (options.fieldName !== void 0) this.fieldName = options.fieldName;
220
- if (options.scopeType !== void 0) this.scopeType = options.scopeType;
221
52
  if (options.tenantId !== void 0) this.tenantId = options.tenantId;
222
- if (options.userId !== void 0) this.userId = options.userId;
223
- if (options.defaultValue !== void 0 && options.defaultValueRaw !== void 0) throw new Error("FieldPolicy accepts either defaultValue (already JSON-encoded) or defaultValueRaw (a plain value to serialize), not both");
224
- if (options.defaultValueRaw !== void 0) this.setDefaultValue(options.defaultValueRaw);
225
- else if (options.defaultValue !== void 0) this.defaultValue = options.defaultValue;
226
- if (options.visibility !== void 0) this.visibility = options.visibility;
227
- if (options.help !== void 0) this.help = options.help;
228
- if (options.label !== void 0) this.label = options.label;
229
- if (options.displayOrder !== void 0) this.displayOrder = options.displayOrder;
230
- if (options.locked !== void 0) this.locked = options.locked;
231
- if (options.updatedBy !== void 0) this.updatedBy = options.updatedBy;
232
- }
233
- /** Parse the stored JSON default. `undefined` = no stored default (inherit). */
234
- getDefaultValue() {
235
- if (this.defaultValue === null || this.defaultValue === void 0) return;
53
+ if (options.kind !== void 0) this.kind = options.kind;
54
+ if (options.proposedValue !== void 0) this.proposedValue = options.proposedValue;
55
+ if (options.evidence !== void 0) this.evidence = options.evidence;
56
+ if (options.status !== void 0) this.status = options.status;
57
+ if (options.cooldownUntil !== void 0) this.cooldownUntil = options.cooldownUntil;
58
+ if (options.decidedBy !== void 0) this.decidedBy = options.decidedBy;
59
+ if (options.decidedAt !== void 0) this.decidedAt = options.decidedAt;
60
+ }
61
+ /** Parse the stored evidence object (guarded; junk parses as empty). */
62
+ getEvidence() {
236
63
  try {
237
- return JSON.parse(this.defaultValue);
64
+ const parsed = JSON.parse(this.evidence);
65
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
238
66
  } catch {
239
- return;
67
+ return {};
240
68
  }
241
69
  }
242
- /** Serialize a default value; `undefined` clears the override (inherit). */
243
- setDefaultValue(value) {
244
- this.defaultValue = value === void 0 ? null : JSON.stringify(value);
245
- }
246
- async save() {
247
- this.attributeScopeToAmbientContext();
248
- const previousIdentity = await this.getPersistedIdentity();
249
- if (previousIdentity) this.assertScopeOwnedByAmbientContext(previousIdentity, "save");
250
- const ambientContext = getCurrentTenant();
251
- if (ambientContext) this.updatedBy = ambientContext.userId ?? null;
252
- this.normalizeDefaultValueForPersistence();
253
- await this.validateFieldPolicy();
254
- this.scopeKey = this.computeScopeKey();
255
- const identityChanged = previousIdentity && (previousIdentity.objectRef !== this.objectRef || previousIdentity.fieldName !== this.fieldName || previousIdentity.scopeType !== this.scopeType || previousIdentity.scopeKey !== this.scopeKey);
256
- const result = identityChanged && previousIdentity ? await this.saveAfterIdentityChange() : await super.save();
257
- if (identityChanged && previousIdentity) invalidateFieldPolicyCache(previousIdentity.objectRef, this.db);
258
- invalidateFieldPolicyCache(this.objectRef, this.db);
259
- return result;
260
- }
261
- async saveAfterIdentityChange() {
262
- if (typeof this.db.beginTransaction === "function") return this.saveAfterIdentityChangeInTransaction();
263
- return this.saveAfterIdentityChangeWithDeferredDelete();
70
+ /** Serialize an evidence object into the stored JSON string. */
71
+ setEvidence(evidence) {
72
+ this.evidence = JSON.stringify(evidence);
264
73
  }
265
- async saveAfterIdentityChangeInTransaction() {
266
- const originalDb = this._db;
267
- const originalOptionsDb = this.options.db;
268
- const tx = await this.db.beginTransaction?.();
269
- if (!tx) return this.saveAfterIdentityChangeWithDeferredDelete();
74
+ /** Parse the proposed value; `undefined` when none is stored. */
75
+ getProposedValue() {
76
+ if (this.proposedValue === null || this.proposedValue === void 0) return;
270
77
  try {
271
- this._db = tx;
272
- this.options.db = tx;
273
- await super.delete();
274
- const result = await super.save();
275
- await tx.commit();
276
- return result;
277
- } catch (error) {
278
- try {
279
- await tx.rollback();
280
- } catch {}
281
- throw error;
282
- } finally {
283
- this._db = originalDb;
284
- this.options.db = originalOptionsDb;
78
+ return JSON.parse(this.proposedValue);
79
+ } catch {
80
+ return;
285
81
  }
286
82
  }
287
- async saveAfterIdentityChangeWithDeferredDelete() {
288
- const previousId = this.id;
289
- if (!previousId) return super.save();
290
- const replacementId = crypto.randomUUID();
291
- let replacementSaved = false;
292
- this.id = replacementId;
293
- try {
294
- const result = await super.save();
295
- replacementSaved = true;
296
- await this.db.delete(this.tableName, { id: previousId });
297
- return result;
298
- } catch (error) {
299
- if (replacementSaved) try {
300
- await this.db.delete(this.tableName, { id: replacementId });
301
- } catch {}
302
- this.id = previousId;
303
- throw error;
83
+ /** Serialized row shape for the collection actions. */
84
+ toSuggestionData() {
85
+ return {
86
+ id: String(this.id),
87
+ objectRef: this.objectRef,
88
+ fieldName: this.fieldName,
89
+ tenantId: String(this.tenantId ?? ""),
90
+ kind: this.kind,
91
+ proposedValue: this.proposedValue ?? null,
92
+ evidence: this.getEvidence(),
93
+ status: this.status,
94
+ cooldownUntil: toIsoOrNull(this.cooldownUntil),
95
+ decidedBy: this.decidedBy ?? null,
96
+ decidedAt: toIsoOrNull(this.decidedAt)
97
+ };
98
+ }
99
+ async save() {
100
+ await this.assertRowOwnedByAmbientContext("save");
101
+ await this.validateFieldPolicySuggestion();
102
+ this.applyActiveKey();
103
+ return super.save();
104
+ }
105
+ /**
106
+ * Recompute the lifecycle-slot key: the shared sentinel while pending (so
107
+ * the unique index admits exactly one), the row's own id once settled (so
108
+ * history never competes for the slot and the freed slot allows a
109
+ * post-cool-down regeneration). A settled row that has not been persisted
110
+ * yet is assigned its id here — the key must be unique from the first write.
111
+ */
112
+ applyActiveKey() {
113
+ if (this.status === "pending") {
114
+ this.activeKey = ACTIVE_SUGGESTION_KEY;
115
+ return;
304
116
  }
117
+ if (!this.id) this.id = crypto.randomUUID();
118
+ this.activeKey = String(this.id);
305
119
  }
306
120
  async delete() {
307
- const persisted = await this.getPersistedIdentity();
308
- if (persisted) this.assertScopeOwnedByAmbientContext(persisted, "delete");
309
- const objectRef = persisted?.objectRef ?? this.objectRef;
121
+ await this.assertRowOwnedByAmbientContext("delete");
310
122
  await super.delete();
311
- invalidateFieldPolicyCache(objectRef, this.db);
312
- if (this.objectRef && this.objectRef !== objectRef) invalidateFieldPolicyCache(this.objectRef, this.db);
313
- }
314
- async validateFieldPolicy() {
315
- if (!this.objectRef || this.objectRef.trim() === "") throw new Error("FieldPolicy.objectRef is required");
316
- if (!this.fieldName || this.fieldName.trim() === "") throw new Error("FieldPolicy.fieldName is required");
317
- 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}"`);
318
- if (this.visibility !== null && !FIELD_POLICY_VISIBILITIES.includes(this.visibility)) throw new Error(`FieldPolicy.visibility must be null or one of ${FIELD_POLICY_VISIBILITIES.join(", ")}; got "${this.visibility}"`);
319
- if (this.displayOrder !== null && (typeof this.displayOrder !== "number" || !Number.isInteger(this.displayOrder))) throw new Error("FieldPolicy.displayOrder must be null or an integer");
320
- this.validateScopeConsistency();
321
- this.validateTenantContextBoundary();
123
+ }
124
+ /**
125
+ * The "normal validation" the #1885 seam promises producers: registry-known
126
+ * field, policy-addressable, never sensitive/read-permission-gated/transient
127
+ * (those fields are count-only in usage data and get no suggestions), valid
128
+ * kind/status, and for `default` suggestions a JSON proposed value that
129
+ * type-checks against the manifest field type.
130
+ */
131
+ async validateFieldPolicySuggestion() {
132
+ if (!this.objectRef || this.objectRef.trim() === "") throw new Error("FieldPolicySuggestion.objectRef is required");
133
+ if (!this.fieldName || this.fieldName.trim() === "") throw new Error("FieldPolicySuggestion.fieldName is required");
134
+ if (!this.tenantId) throw new Error("FieldPolicySuggestion.tenantId is required");
135
+ if (!FIELD_POLICY_SUGGESTION_KINDS.includes(this.kind)) throw new Error(`FieldPolicySuggestion.kind must be one of ${FIELD_POLICY_SUGGESTION_KINDS.join(", ")}; got "${this.kind}"`);
136
+ if (!FIELD_POLICY_SUGGESTION_STATUSES.includes(this.status)) throw new Error(`FieldPolicySuggestion.status must be one of ${FIELD_POLICY_SUGGESTION_STATUSES.join(", ")}; got "${this.status}"`);
322
137
  const fieldDef = (await getObjectFieldMap(this.objectRef)).get(this.fieldName);
323
138
  if (!fieldDef) throw new Error(`Unknown field "${this.fieldName}" on "${this.objectRef}"`);
324
- if (fieldDef._meta?.__smrtSystemField === true) throw new Error(`Field "${this.fieldName}" on "${this.objectRef}" is a framework system field and is not policy-addressable`);
325
- if (fieldDef.type === "oneToMany" || fieldDef.type === "manyToMany") throw new Error(`Field "${this.fieldName}" on "${this.objectRef}" is a relationship pseudo-field and is not policy-addressable`);
326
- if (fieldDef.type === "meta") throw new Error(`Field "${this.fieldName}" on "${this.objectRef}" is STI meta storage and is not policy-addressable`);
327
- if (this.locked !== null && this.scopeType === "user") throw new Error("FieldPolicy.locked may only be set on org rows (app or tenant scope)");
328
- if (this.defaultValue !== null) {
329
- this.validateDefaultAgainstSecurityRail(fieldDef);
330
- const parsed = this.parseDefaultValueOrThrow();
139
+ if (fieldDef._meta?.__smrtSystemField === true || fieldDef.type === "oneToMany" || fieldDef.type === "manyToMany" || fieldDef.type === "meta") throw new Error(`Field "${this.fieldName}" on "${this.objectRef}" is not policy-addressable, so it cannot carry a suggestion`);
140
+ if (isSensitiveField(fieldDef) || getFieldReadPermission(fieldDef) !== void 0 || isTransientField(fieldDef)) throw new Error(`Field "${this.fieldName}" on "${this.objectRef}" is sensitive, read-permission-gated, or transient; usage data for it is count-only and it cannot carry a suggestion`);
141
+ if (this.kind === "default") {
142
+ if (this.proposedValue === null) throw new Error("FieldPolicySuggestion of kind 'default' requires a proposedValue");
143
+ let parsed;
144
+ try {
145
+ parsed = JSON.parse(this.proposedValue);
146
+ } catch (error) {
147
+ throw new Error(`FieldPolicySuggestion.proposedValue is not valid JSON: ${error instanceof Error ? error.message : String(error)}`);
148
+ }
331
149
  assertDefaultValueMatchesFieldType(this.objectRef, this.fieldName, fieldDef, parsed);
332
- }
333
- const demotesRequiredField = (this.visibility === "advanced" || this.visibility === "hidden") && isRequiredField(fieldDef);
334
- const ownDefault = demotesRequiredField && this.defaultValue !== null ? { value: this.parseDefaultValueOrThrow() } : void 0;
335
- const needsOrgDefault = demotesRequiredField && !isUsableRequiredDefault(ownDefault);
336
- const needsLockCheck = this.scopeType === "user";
337
- if (needsOrgDefault || needsLockCheck) {
338
- const orgPolicy = await this.resolveOrgTierFieldPolicy();
339
- if (needsOrgDefault) {
340
- 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`);
150
+ } else if (this.proposedValue !== null) throw new Error("FieldPolicySuggestion of kind 'promote' must not carry a proposedValue");
151
+ }
152
+ /**
153
+ * Tenant write boundary (the FieldPolicy posture): inside a non-bypass
154
+ * tenant context a caller may only touch its own tenant's suggestions —
155
+ * checked against BOTH the in-memory scope and, for persisted rows, the
156
+ * PERSISTED tenant (a foreign row cannot be re-scoped into the caller's
157
+ * tenant). Trusted execution (no context / bypass) is exempt — that is what
158
+ * lets the scheduled generation job and platform flows operate.
159
+ */
160
+ async assertRowOwnedByAmbientContext(operation) {
161
+ const context = getCurrentTenant();
162
+ if (!context || isSuperAdminBypass()) return;
163
+ if (this.tenantId !== context.tenantId) throw new TenantIsolationError(`Tenant isolation violation in FieldPolicySuggestion.${operation}: context tenant is '${context.tenantId}' but the row belongs to '${this.tenantId}'`, {
164
+ tenantId: context.tenantId,
165
+ attemptedTenantId: this.tenantId ?? void 0
166
+ });
167
+ if (this.id) {
168
+ const persisted = await this.db.get(this.tableName, { id: this.id });
169
+ if (persisted) {
170
+ const row = persisted;
171
+ const persistedTenant = row.tenantId ?? row.tenant_id ?? this.tenantId ?? null;
172
+ if (persistedTenant !== null && String(persistedTenant) !== context.tenantId) throw new TenantIsolationError(`Tenant isolation violation in FieldPolicySuggestion.${operation}: the persisted row belongs to '${String(persistedTenant)}'`, {
173
+ tenantId: context.tenantId,
174
+ attemptedTenantId: String(persistedTenant)
175
+ });
341
176
  }
342
- if (needsLockCheck && orgPolicy?.locked) throw new Error(`Field "${this.objectRef}.${this.fieldName}" is locked by org policy; user-scope overrides are not allowed`);
343
177
  }
344
178
  }
179
+ };
180
+ __decorateClass$5([field({ required: true })], FieldPolicySuggestion.prototype, "objectRef", 2);
181
+ __decorateClass$5([field({ required: true })], FieldPolicySuggestion.prototype, "fieldName", 2);
182
+ __decorateClass$5([tenantId()], FieldPolicySuggestion.prototype, "tenantId", 2);
183
+ __decorateClass$5([field({ required: true })], FieldPolicySuggestion.prototype, "kind", 2);
184
+ __decorateClass$5([field({
185
+ type: "text",
186
+ nullable: true
187
+ })], FieldPolicySuggestion.prototype, "proposedValue", 2);
188
+ __decorateClass$5([field({ type: "text" })], FieldPolicySuggestion.prototype, "evidence", 2);
189
+ __decorateClass$5([field({ required: true })], FieldPolicySuggestion.prototype, "status", 2);
190
+ __decorateClass$5([field({
191
+ type: "text",
192
+ required: true
193
+ })], FieldPolicySuggestion.prototype, "activeKey", 2);
194
+ __decorateClass$5([field({
195
+ type: "datetime",
196
+ nullable: true
197
+ })], FieldPolicySuggestion.prototype, "cooldownUntil", 2);
198
+ __decorateClass$5([crossPackageRef("@happyvertical/smrt-users:User", { nullable: true })], FieldPolicySuggestion.prototype, "decidedBy", 2);
199
+ __decorateClass$5([field({
200
+ type: "datetime",
201
+ nullable: true
202
+ })], FieldPolicySuggestion.prototype, "decidedAt", 2);
203
+ FieldPolicySuggestion = __decorateClass$5([smrt({
204
+ tableName: "_smrt_field_policy_suggestions",
205
+ conflictColumns: [
206
+ "object_ref",
207
+ "field_name",
208
+ "tenant_id",
209
+ "kind",
210
+ "active_key"
211
+ ],
212
+ api: { include: [] },
213
+ cli: false,
214
+ mcp: { include: [] }
215
+ })], FieldPolicySuggestion);
216
+ function toIsoOrNull(value) {
217
+ if (value === null || value === void 0) return null;
218
+ if (value instanceof Date) return value.toISOString();
219
+ const parsed = Date.parse(value);
220
+ return Number.isFinite(parsed) ? new Date(parsed).toISOString() : null;
221
+ }
222
+ //#endregion
223
+ //#region src/collections/FieldPolicySuggestionCollection.ts
224
+ var __defProp$4 = Object.defineProperty;
225
+ var __getOwnPropDesc$4 = Object.getOwnPropertyDescriptor;
226
+ var __defNormalProp$2 = (obj, key, value) => key in obj ? __defProp$4(obj, key, {
227
+ enumerable: true,
228
+ configurable: true,
229
+ writable: true,
230
+ value
231
+ }) : obj[key] = value;
232
+ var __decorateClass$4 = (decorators, target, key, kind) => {
233
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$4(target, key) : target;
234
+ for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
235
+ if (kind && result) __defProp$4(target, key, result);
236
+ return result;
237
+ };
238
+ var __publicField$2 = (obj, key, value) => __defNormalProp$2(obj, typeof key !== "symbol" ? key + "" : key, value);
239
+ var FieldPolicySuggestionConflictError = class extends Error {
240
+ /** Core's generated-REST status contract (own integer property). */
241
+ httpStatus = 409;
242
+ /** The users-style shape mirrored by this package's authorization errors. */
243
+ status = 409;
244
+ constructor(operation, suggestionId) {
245
+ super(`${operation}: suggestion "${suggestionId}" was already decided by another request (it is no longer pending)`);
246
+ this.name = "FieldPolicySuggestionConflictError";
247
+ }
248
+ };
249
+ var FieldPolicySuggestionRequestContextError = class extends TenantIsolationError {
250
+ httpStatus = 403;
251
+ status = 403;
252
+ constructor(message, details) {
253
+ super(message, details);
254
+ this.name = "FieldPolicySuggestionRequestContextError";
255
+ }
256
+ };
257
+ function transactionBoundOptions(db) {
258
+ return {
259
+ db,
260
+ _reuseInitializedDb: true,
261
+ _deferRuntimeInitialization: true
262
+ };
263
+ }
264
+ async function claimSuggestionTransition(db, suggestionId, patch) {
265
+ return ((await db.query(`UPDATE _smrt_field_policy_suggestions
266
+ SET status = ?,
267
+ active_key = ?,
268
+ decided_at = ?,
269
+ decided_by = ?,
270
+ cooldown_until = ?
271
+ WHERE id = ? AND status = 'pending'
272
+ RETURNING id`, patch.status, patch.activeKey, patch.decidedAt.toISOString(), patch.decidedBy, patch.cooldownUntil ? patch.cooldownUntil.toISOString() : null, suggestionId))?.rows?.length ?? 0) > 0;
273
+ }
274
+ async function revertSuggestionToPending(db, suggestionId) {
275
+ await db.query(`UPDATE _smrt_field_policy_suggestions
276
+ SET status = 'pending',
277
+ active_key = ?,
278
+ decided_at = NULL,
279
+ decided_by = NULL,
280
+ cooldown_until = NULL
281
+ WHERE id = ?`, ACTIVE_SUGGESTION_KEY, suggestionId);
282
+ }
283
+ async function settleSuggestionActiveKey(db, suggestionId) {
284
+ await db.query(`UPDATE _smrt_field_policy_suggestions
285
+ SET active_key = ?
286
+ WHERE id = ?`, suggestionId, suggestionId);
287
+ }
288
+ var FieldPolicySuggestionCompensationError = class extends Error {
289
+ httpStatus = 500;
290
+ status = 500;
291
+ revertCause;
292
+ constructor(suggestionId, cause, revertCause) {
293
+ super(`acceptSuggestion: the policy write for suggestion "${suggestionId}" failed AND reverting it to pending also failed; the suggestion may be left accepted without its policy row and needs reconciliation (policy error: ${cause instanceof Error ? cause.message : String(cause)}; revert error: ${revertCause instanceof Error ? revertCause.message : String(revertCause)})`, { cause });
294
+ this.name = "FieldPolicySuggestionCompensationError";
295
+ this.revertCause = revertCause;
296
+ }
297
+ };
298
+ function fieldPolicyRowId(tenantId, objectRef, fieldName) {
299
+ return deterministicFieldsUuid([
300
+ "field-policy-row",
301
+ "tenant",
302
+ tenantId,
303
+ objectRef,
304
+ fieldName
305
+ ]);
306
+ }
307
+ async function updatePolicyColumn(db, target) {
308
+ const id = ((await db.query(`UPDATE _smrt_field_policies
309
+ SET ${target.column} = ?,
310
+ updated_by = ?,
311
+ updated_at = ?
312
+ WHERE object_ref = ?
313
+ AND field_name = ?
314
+ AND scope_type = 'tenant'
315
+ AND tenant_id = ?
316
+ RETURNING id`, target.value, target.decidedBy, (/* @__PURE__ */ new Date()).toISOString(), target.objectRef, target.fieldName, target.tenantId))?.rows ?? [])[0]?.id;
317
+ return id === void 0 || id === null ? null : String(id);
318
+ }
319
+ async function assertAcceptedDefaultStillAllowed(suggestion) {
320
+ const fieldDef = await assertAcceptedPolicyTargetStillAllowed(suggestion);
321
+ if (suggestion.proposedValue === null) throw new Error("FieldPolicySuggestion of kind 'default' requires a proposedValue");
322
+ let parsed;
323
+ try {
324
+ parsed = JSON.parse(suggestion.proposedValue);
325
+ } catch (error) {
326
+ throw new Error(`FieldPolicySuggestion.proposedValue is not valid JSON: ${error instanceof Error ? error.message : String(error)}`);
327
+ }
328
+ assertDefaultValueMatchesFieldType(suggestion.objectRef, suggestion.fieldName, fieldDef, parsed);
329
+ }
330
+ async function assertAcceptedPolicyTargetStillAllowed(suggestion) {
331
+ const fieldDef = (await getObjectFieldMap(suggestion.objectRef)).get(suggestion.fieldName);
332
+ if (!fieldDef || !isPolicyAddressableField(fieldDef)) throw new Error(`Field "${suggestion.fieldName}" on "${suggestion.objectRef}" is not policy-addressable`);
333
+ if (isSensitiveField(fieldDef) || getFieldReadPermission(fieldDef) !== void 0 || isTransientField(fieldDef)) throw new Error(`Cannot apply a policy for "${suggestion.objectRef}.${suggestion.fieldName}": the field is sensitive, read-permission-gated, or transient`);
334
+ return fieldDef;
335
+ }
336
+ var DEFAULT_SUGGESTION_COOL_DOWN_MS = 720 * 60 * 60 * 1e3;
337
+ var MIN_SUGGESTION_COOL_DOWN_MS = 3600 * 1e3;
338
+ var MAX_SUGGESTION_COOL_DOWN_MS = 365 * 24 * 60 * 60 * 1e3;
339
+ var MAX_PENDING_OBJECT_REFS = 100;
340
+ var FieldPolicySuggestionCollection = class extends SmrtCollection {
345
341
  /**
346
- * Effective org-tier (code app tenant hierarchy) policy for this row's
347
- * field, computed by the RESOLVER so write-time checks share the one
348
- * precedence implementation — including ancestor-tenant cascades via the
349
- * default hierarchy loader. The chain tenant is the row's own tenant for
350
- * tenant-scope rows, the ambient context tenant for user-scope rows, and
351
- * none for app-scope rows (their only lower layer is the code seed).
352
- *
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.
342
+ * The caller's tenant's PENDING suggestions (optionally filtered to a set
343
+ * of objectRefs), newest first, plus the total for the gear badge.
357
344
  */
358
- async resolveOrgTierFieldPolicy() {
359
- const tenantIdForChain = this.scopeType === "tenant" ? this.tenantId : this.scopeType === "user" ? getCurrentTenant()?.tenantId ?? null : null;
360
- const { resolveFieldPolicy } = await Promise.resolve().then(() => field_policy_resolver_exports);
361
- return (await resolveFieldPolicy(this.objectRef, {
362
- tenantId: tenantIdForChain,
363
- db: this.options.db ?? this.options.persistence
364
- })).fields[this.fieldName];
345
+ async pendingSuggestions(options = {}) {
346
+ const tenantId = await this.requireManageContext("pendingSuggestions");
347
+ const objectRefs = normalizeObjectRefsFilter(options.objectRefs);
348
+ const where = {
349
+ tenantId,
350
+ status: "pending"
351
+ };
352
+ if (objectRefs) where["objectRef in"] = objectRefs;
353
+ const rows = await this.list({
354
+ where,
355
+ orderBy: "created_at DESC"
356
+ });
357
+ return {
358
+ suggestions: rows.map((row) => row.toSuggestionData()),
359
+ total: rows.length
360
+ };
365
361
  }
366
362
  /**
367
- * Exactly-one-owner scope shape: app rows carry neither id, tenant rows
368
- * carry only `tenantId`, user rows carry only `userId` (the user tier is
369
- * keyed by user alone so preferences follow the user across tenants).
363
+ * Accept a pending suggestion: CLAIM the pending→accepted transition, then
364
+ * write the corresponding org-scope `FieldPolicy` row THROUGH NORMAL
365
+ * VALIDATION (registry check, type check, security rail, required-field
366
+ * invariant, ownership boundary, and the #2049 permission split — the
367
+ * ambient caller must hold `fields.policy.manage`, which this action also
368
+ * asserts up front).
369
+ *
370
+ * An existing tenant row for the same `(objectRef, fieldName)` is UPDATED
371
+ * (read-modify-write), preserving its other sparse columns — never
372
+ * duplicated through the natural-key upsert.
373
+ *
374
+ * Concurrency: the claim is a compare-and-set on `status` (the jobs
375
+ * `writeOwnedJob` precedent — a guarded UPDATE with `RETURNING id`), so an
376
+ * overlapping accept/dismiss pair cannot both win; the loser throws
377
+ * {@link FieldPolicySuggestionConflictError} (409). Claim and policy write
378
+ * run in ONE transaction when the driver supports it, so a rejected policy
379
+ * write can never leave the suggestion settled without its policy (and vice
380
+ * versa). Drivers without transactions get an explicit compensating revert.
370
381
  */
371
- validateScopeConsistency() {
372
- if (this.scopeType === "app") {
373
- if (this.tenantId !== null || this.userId !== null) throw new Error("App-scope field policy rows must have tenantId and userId null");
374
- return;
382
+ async acceptSuggestion(options = {}) {
383
+ const tenantId = await this.requireManageContext("acceptSuggestion");
384
+ const suggestion = await this.loadOwnedPendingSuggestion("acceptSuggestion", options.id, tenantId);
385
+ const suggestionId = String(suggestion.id);
386
+ const decidedBy = getCurrentTenant()?.userId ?? null;
387
+ const decidedAt = /* @__PURE__ */ new Date();
388
+ const claim = {
389
+ status: "accepted",
390
+ activeKey: suggestionId,
391
+ decidedAt,
392
+ decidedBy,
393
+ cooldownUntil: null
394
+ };
395
+ const tx = await this.beginTransactionIfSupported();
396
+ if (tx) {
397
+ let policyRowId2;
398
+ try {
399
+ if (!await claimSuggestionTransition(tx, suggestionId, claim)) throw new FieldPolicySuggestionConflictError("acceptSuggestion", suggestionId);
400
+ policyRowId2 = await this.applyAcceptedPolicy(tx, suggestion, tenantId, decidedBy);
401
+ await tx.commit();
402
+ } catch (error) {
403
+ try {
404
+ await tx.rollback();
405
+ } catch {}
406
+ throw error;
407
+ }
408
+ invalidateFieldPolicyCache(suggestion.objectRef, this.db);
409
+ suggestion.status = "accepted";
410
+ suggestion.decidedAt = decidedAt;
411
+ suggestion.decidedBy = decidedBy;
412
+ return {
413
+ suggestion: suggestion.toSuggestionData(),
414
+ policyRowId: policyRowId2
415
+ };
375
416
  }
376
- if (this.scopeType === "tenant") {
377
- if (!this.tenantId || this.userId !== null) throw new Error("Tenant-scope field policy rows must set tenantId and leave userId null");
378
- return;
417
+ if (!await claimSuggestionTransition(this.db, suggestionId, {
418
+ ...claim,
419
+ activeKey: "active"
420
+ })) throw new FieldPolicySuggestionConflictError("acceptSuggestion", suggestionId);
421
+ let policyRowId;
422
+ try {
423
+ policyRowId = await this.applyAcceptedPolicy(this.db, suggestion, tenantId, decidedBy);
424
+ } catch (error) {
425
+ try {
426
+ await revertSuggestionToPending(this.db, suggestionId);
427
+ } catch (revertError) {
428
+ throw new FieldPolicySuggestionCompensationError(suggestionId, error, revertError);
429
+ }
430
+ throw error;
379
431
  }
380
- if (!this.userId || this.tenantId !== null) throw new Error("User-scope field policy rows must set userId and leave tenantId null");
381
- }
382
- /** `userId ?? tenantId ?? '__app__'` — the `conflictColumns` scope key. */
383
- computeScopeKey() {
384
- return this.userId ?? this.tenantId ?? "__app__";
432
+ await settleSuggestionActiveKey(this.db, suggestionId);
433
+ invalidateFieldPolicyCache(suggestion.objectRef, this.db);
434
+ suggestion.status = "accepted";
435
+ suggestion.decidedAt = decidedAt;
436
+ suggestion.decidedBy = decidedBy;
437
+ return {
438
+ suggestion: suggestion.toSuggestionData(),
439
+ policyRowId
440
+ };
385
441
  }
386
442
  /**
387
- * Derive a scope-owner column the transport could not carry (#2047).
388
- *
389
- * Core's mass-assignment guard treats `tenantId` as server-managed and
390
- * strips it from EVERY generated create/update body, and `FieldPolicy` is
391
- * deliberately not `@TenantScoped`, so the tenancy interceptor never
392
- * repopulates it. A `POST {scopeType:'tenant', tenantId}` therefore always
393
- * reached `validateScopeConsistency` with `tenantId === null`, making the
394
- * org tier write-dead over every generated surface — the model, as the
395
- * single validation authority for these rows, fills it in instead.
443
+ * Dismiss a pending suggestion: set status + a cool-down until which the
444
+ * generation job will NOT regenerate the same
445
+ * `(objectRef, fieldName, tenantId, kind)` suggestion.
396
446
  *
397
- * This grants nothing: `assertScopeOwnedByAmbientContext` already requires
398
- * a non-bypass caller's tenant/user rows to name exactly the ambient
399
- * tenant/user, so the derived value is the ONLY value that could ever have
400
- * 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.
403
- * Consequence: a bypass caller writing ANOTHER tenant's row must pass
404
- * `tenantId` through a server-side model call, because the generated routes
405
- * still strip it.
447
+ * Validates OWNERSHIP and PENDING STATUS ONLY — deliberately NOT continued
448
+ * proposal eligibility. A queued suggestion whose field has since been
449
+ * removed, turned sensitive/`readPermission`-gated/transient, or retyped is
450
+ * exactly the row an operator most needs to clear; re-running proposal
451
+ * validation on the way out would reject the dismissal and, because pending
452
+ * rows are never pruned, wedge it in the queue forever. The claim is the same
453
+ * compare-and-set as accept, so it also bypasses the model's proposal
454
+ * validation by construction.
406
455
  */
407
- attributeScopeToAmbientContext() {
408
- const context = getCurrentTenant();
409
- if (!context) return;
410
- if (this.scopeType === "tenant" && this.tenantId === null) this.tenantId = context.tenantId;
411
- if (this.scopeType === "user" && this.userId === null) this.userId = context.userId ?? null;
456
+ async dismissSuggestion(options = {}) {
457
+ const tenantId = await this.requireManageContext("dismissSuggestion");
458
+ const suggestion = await this.loadOwnedPendingSuggestion("dismissSuggestion", options.id, tenantId);
459
+ const suggestionId = String(suggestion.id);
460
+ const coolDownMs = normalizeCoolDownMs(options.coolDownMs);
461
+ const decidedAt = /* @__PURE__ */ new Date();
462
+ const decidedBy = getCurrentTenant()?.userId ?? null;
463
+ const cooldownUntil = new Date(decidedAt.getTime() + coolDownMs);
464
+ if (!await claimSuggestionTransition(this.db, suggestionId, {
465
+ status: "dismissed",
466
+ activeKey: suggestionId,
467
+ decidedAt,
468
+ decidedBy,
469
+ cooldownUntil
470
+ })) throw new FieldPolicySuggestionConflictError("dismissSuggestion", suggestionId);
471
+ suggestion.status = "dismissed";
472
+ suggestion.cooldownUntil = cooldownUntil;
473
+ suggestion.decidedAt = decidedAt;
474
+ suggestion.decidedBy = decidedBy;
475
+ return { suggestion: suggestion.toSuggestionData() };
412
476
  }
413
477
  /**
414
- * Fail-closed write boundary against the ambient tenant context, applied to
415
- * the NEW scope on save and to the PERSISTED scope on save/delete of an
416
- * existing row.
478
+ * Write the org-scope policy column an accepted suggestion implies, bound to
479
+ * `db` so it participates in the caller's transaction.
480
+ *
481
+ * A `promote` and a `default` suggestion for the SAME field share one sparse
482
+ * `FieldPolicy` row but own DIFFERENT columns, and both may be accepted
483
+ * concurrently. A read-modify-save of the whole row therefore loses updates:
484
+ * each acceptance reads the row (or its absence) before the other's save
485
+ * lands, and the later full-row write erases the sibling's column — and, via
486
+ * the natural-key upsert, can even replace the row id, dangling the earlier
487
+ * caller's `policyRowId`. (Distinct from the suggestion-row race fixed
488
+ * earlier: that one guards the pending→settled transition; this one is on the
489
+ * shared policy row.) So this NEVER writes a whole row over an existing one:
417
490
  *
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.
425
- * App-wide rows then require super-admin bypass (or a context-less/system
426
- * caller).
491
+ * 1. An ATOMIC partial UPDATE sets only this acceptance's column, keyed by
492
+ * the policy natural key and `RETURNING id` one statement, so no
493
+ * interleaving can drop the sibling's column, and the returned id is
494
+ * always the surviving row.
495
+ * 2. Only when no row exists yet does it CREATE through the model, so full
496
+ * validation (registry, type check, security rail, required-field
497
+ * invariant, ownership + permission split) runs on the row that is
498
+ * actually inserted. The row carries a DETERMINISTIC id under strict
499
+ * insert, so two concurrent creates cannot each mint a row: the loser
500
+ * collides and falls back to step 1, applying only its own column.
427
501
  *
428
- * Package rule: a missing identity component DENIES, it never skips.
502
+ * The update path skips the model, so BOTH kinds re-check live target
503
+ * addressability through the shared helpers (a field can turn sensitive,
504
+ * gated, transient, or disappear between queueing and acceptance). Defaults
505
+ * additionally re-check their value/type rail; a promotion to `basic` has no
506
+ * value payload, and the required-field invariant restricts only
507
+ * advanced/hidden. Org locks constrain the user tier, not org rows.
429
508
  */
430
- assertScopeOwnedByAmbientContext(scope, operation) {
431
- const context = getCurrentTenant();
432
- 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`);
434
- return;
509
+ async applyAcceptedPolicy(db, suggestion, tenantId, decidedBy) {
510
+ if (suggestion.kind === "default") await assertAcceptedDefaultStillAllowed(suggestion);
511
+ else await assertAcceptedPolicyTargetStillAllowed(suggestion);
512
+ const target = {
513
+ objectRef: suggestion.objectRef,
514
+ fieldName: suggestion.fieldName,
515
+ tenantId,
516
+ column: suggestion.kind === "promote" ? "visibility" : "default_value",
517
+ value: suggestion.kind === "promote" ? "basic" : suggestion.proposedValue,
518
+ decidedBy
519
+ };
520
+ const updatedId = await updatePolicyColumn(db, target);
521
+ if (updatedId) return updatedId;
522
+ const { FieldPolicyCollection } = await import("./chunks/FieldPolicyCollection--RxukfCX.js").then((n) => n.n);
523
+ const policies = await FieldPolicyCollection.create(transactionBoundOptions(db));
524
+ const deterministicId = await fieldPolicyRowId(tenantId, suggestion.objectRef, suggestion.fieldName);
525
+ try {
526
+ const created = await policies.create({
527
+ id: deterministicId,
528
+ objectRef: suggestion.objectRef,
529
+ fieldName: suggestion.fieldName,
530
+ scopeType: "tenant",
531
+ tenantId,
532
+ ...suggestion.kind === "promote" ? { visibility: "basic" } : { defaultValue: suggestion.proposedValue },
533
+ updatedBy: decidedBy,
534
+ _insertOnly: true
535
+ });
536
+ return String(created.id);
537
+ } catch (error) {
538
+ const racedId = await updatePolicyColumn(db, target);
539
+ if (racedId) return racedId;
540
+ throw error;
435
541
  }
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}'`, {
542
+ }
543
+ /**
544
+ * A driver transaction handle, or `null` when the driver has none (the
545
+ * `FieldPolicy.saveAfterIdentityChange` probe, same shape).
546
+ */
547
+ async beginTransactionIfSupported() {
548
+ if (typeof this.db.beginTransaction !== "function") return null;
549
+ return await this.db.beginTransaction() ?? null;
550
+ }
551
+ /**
552
+ * Ambient manage gate shared by the three actions: an ambient tenant
553
+ * context is required (no context ⇒ no identity ⇒ fail closed — the
554
+ * suggestion queue is never an anonymous surface), and the caller must hold
555
+ * `fields.policy.manage` unless running under super-admin bypass.
556
+ */
557
+ async requireManageContext(action) {
558
+ const context = getCurrentTenant();
559
+ if (!context?.tenantId) throw new FieldPolicySuggestionRequestContextError(`${action} requires an ambient tenant context (fail closed): the suggestion queue is scoped to the caller tenant`);
560
+ if (!isSuperAdminBypass()) await assertOperationPermission({
561
+ collection: "fields.policy",
562
+ action: "fields.policy.manage".split(".").at(-1) ?? "manage",
563
+ db: this.db,
439
564
  tenantId: context.tenantId,
440
- attemptedTenantId: scope.tenantId ?? void 0
565
+ userId: context.userId ?? null,
566
+ permissionSet: context.permissions
567
+ });
568
+ return context.tenantId;
569
+ }
570
+ async loadOwnedPendingSuggestion(action, id, tenantId) {
571
+ if (typeof id !== "string" || id.trim() === "") throw new Error(`${action} requires a suggestion "id" string`);
572
+ const suggestion = await this.get(id);
573
+ if (!suggestion) throw new Error(`${action}: no suggestion found for id "${id}"`);
574
+ if (suggestion.tenantId !== tenantId) throw new TenantIsolationError(`Tenant isolation violation in ${action}: the suggestion belongs to another tenant`, {
575
+ tenantId,
576
+ attemptedTenantId: suggestion.tenantId ?? void 0
441
577
  });
442
- 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 });
578
+ if (suggestion.status !== "pending") throw new FieldPolicySuggestionConflictError(action, id);
579
+ return suggestion;
580
+ }
581
+ };
582
+ __publicField$2(FieldPolicySuggestionCollection, "_itemClass", FieldPolicySuggestion);
583
+ FieldPolicySuggestionCollection = __decorateClass$4([smrt({
584
+ conflictColumns: [
585
+ "object_ref",
586
+ "field_name",
587
+ "tenant_id",
588
+ "kind",
589
+ "active_key"
590
+ ],
591
+ api: {
592
+ include: [
593
+ "pendingSuggestions",
594
+ "acceptSuggestion",
595
+ "dismissSuggestion"
596
+ ],
597
+ principalContext: true,
598
+ routes: {
599
+ pendingSuggestions: {
600
+ scope: "collection",
601
+ method: "POST",
602
+ path: "pending"
603
+ },
604
+ acceptSuggestion: {
605
+ scope: "collection",
606
+ method: "POST",
607
+ path: "accept"
608
+ },
609
+ dismissSuggestion: {
610
+ scope: "collection",
611
+ method: "POST",
612
+ path: "dismiss"
613
+ }
445
614
  }
615
+ },
616
+ cli: false,
617
+ mcp: false
618
+ })], FieldPolicySuggestionCollection);
619
+ function normalizeObjectRefsFilter(rawRefs) {
620
+ if (rawRefs === void 0) return null;
621
+ if (!Array.isArray(rawRefs) || rawRefs.length === 0) throw new Error("pendingSuggestions \"objectRefs\" must be a non-empty string array when provided");
622
+ if (rawRefs.some((ref) => typeof ref !== "string" || ref.trim() === "")) throw new Error("pendingSuggestions objectRefs must be non-empty strings");
623
+ const objectRefs = [...new Set(rawRefs)];
624
+ if (objectRefs.length > MAX_PENDING_OBJECT_REFS) throw new Error(`pendingSuggestions accepts at most ${MAX_PENDING_OBJECT_REFS} objectRefs per call (got ${objectRefs.length})`);
625
+ return objectRefs;
626
+ }
627
+ function normalizeCoolDownMs(raw) {
628
+ if (raw === void 0) return DEFAULT_SUGGESTION_COOL_DOWN_MS;
629
+ if (typeof raw !== "number" || !Number.isFinite(raw)) throw new Error("dismissSuggestion coolDownMs must be a finite number");
630
+ return Math.min(Math.max(raw, MIN_SUGGESTION_COOL_DOWN_MS), MAX_SUGGESTION_COOL_DOWN_MS);
631
+ }
632
+ //#endregion
633
+ //#region src/models/FieldUsageCounter.ts
634
+ var __defProp$3 = Object.defineProperty;
635
+ var __getOwnPropDesc$3 = Object.getOwnPropertyDescriptor;
636
+ var __decorateClass$3 = (decorators, target, key, kind) => {
637
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$3(target, key) : target;
638
+ for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
639
+ if (kind && result) __defProp$3(target, key, result);
640
+ return result;
641
+ };
642
+ var MAX_DISTINCT_USERS_PER_BUCKET = 100;
643
+ var MAX_VALUE_HISTOGRAM_BUCKETS = 25;
644
+ var FIELD_USAGE_PERIOD_PATTERN = /^\d{4}-\d{2}-\d{2}$/;
645
+ function fieldUsagePeriodForDate(date) {
646
+ return date.toISOString().slice(0, 10);
647
+ }
648
+ function emptyHistogram() {
649
+ return /* @__PURE__ */ Object.create(null);
650
+ }
651
+ var FieldUsageCounter = class extends SmrtObject {
652
+ objectRef = "";
653
+ fieldName = "";
654
+ tenantId;
655
+ period = "";
656
+ submissionCount = 0;
657
+ setCount = 0;
658
+ distinctUserCount = 0;
659
+ distinctUserIds = "[]";
660
+ distinctUsersOverflowed = false;
661
+ valueHistogram = null;
662
+ valueHistogramOverflowed = false;
663
+ constructor(options = {}) {
664
+ super(options);
665
+ if (options.objectRef !== void 0) this.objectRef = options.objectRef;
666
+ if (options.fieldName !== void 0) this.fieldName = options.fieldName;
667
+ if (options.tenantId !== void 0) this.tenantId = options.tenantId;
668
+ if (options.period !== void 0) this.period = options.period;
669
+ if (options.submissionCount !== void 0) this.submissionCount = options.submissionCount;
670
+ if (options.setCount !== void 0) this.setCount = options.setCount;
671
+ if (options.distinctUserCount !== void 0) this.distinctUserCount = options.distinctUserCount;
672
+ if (options.distinctUserIds !== void 0) this.distinctUserIds = options.distinctUserIds;
673
+ if (options.distinctUsersOverflowed !== void 0) this.distinctUsersOverflowed = options.distinctUsersOverflowed;
674
+ if (options.valueHistogram !== void 0) this.valueHistogram = options.valueHistogram;
675
+ if (options.valueHistogramOverflowed !== void 0) this.valueHistogramOverflowed = options.valueHistogramOverflowed;
446
676
  }
447
- validateTenantContextBoundary() {
448
- this.assertScopeOwnedByAmbientContext({
449
- scopeType: this.scopeType,
450
- tenantId: this.tenantId,
451
- userId: this.userId
452
- }, "save");
677
+ /** Parse the stored distinct-user set (guarded; junk parses as empty). */
678
+ getDistinctUserIds() {
679
+ try {
680
+ const parsed = JSON.parse(this.distinctUserIds);
681
+ return Array.isArray(parsed) ? parsed.filter((id) => typeof id === "string") : [];
682
+ } catch {
683
+ return [];
684
+ }
453
685
  }
454
- validateDefaultAgainstSecurityRail(fieldDef) {
455
- if (isTransientField(fieldDef)) throw new Error(`Cannot store a default for transient field "${this.objectRef}.${this.fieldName}"`);
456
- if (isSensitiveField(fieldDef)) throw new Error(`Cannot store a default for sensitive field "${this.objectRef}.${this.fieldName}"`);
457
- const readPermission = getFieldReadPermission(fieldDef);
458
- if (readPermission) throw new Error(`Cannot store a default for read-permission-gated field "${this.objectRef}.${this.fieldName}" (requires "${readPermission}")`);
686
+ /**
687
+ * Add a user to the distinct set, honoring the cap. At the cap the id is
688
+ * NOT added and the overflow marker is set instead, keeping
689
+ * {@link distinctUserCount} an honest lower bound.
690
+ */
691
+ addDistinctUser(userId) {
692
+ const ids = this.getDistinctUserIds();
693
+ if (ids.includes(userId)) return;
694
+ if (ids.length >= 100) {
695
+ this.distinctUsersOverflowed = true;
696
+ return;
697
+ }
698
+ ids.push(userId);
699
+ this.distinctUserIds = JSON.stringify(ids);
700
+ this.distinctUserCount = ids.length;
459
701
  }
460
- parseDefaultValueOrThrow() {
702
+ /**
703
+ * Parse the stored histogram (guarded; junk parses as empty).
704
+ *
705
+ * Returns a NULL-PROTOTYPE object. Histogram keys are user-supplied ids —
706
+ * an `idType: 'text'` reference may legitimately be `constructor`,
707
+ * `toString`, or `__proto__` — and on a plain object those inherit truthy
708
+ * prototype values (so a missing bucket reads as present) or, for
709
+ * `__proto__`, hit the prototype setter instead of creating an own key.
710
+ * Both would silently corrupt counts. See {@link emptyHistogram}.
711
+ */
712
+ getValueHistogram() {
713
+ const histogram = emptyHistogram();
714
+ if (!this.valueHistogram) return histogram;
461
715
  try {
462
- return JSON.parse(this.defaultValue);
463
- } catch (error) {
464
- throw new Error(`FieldPolicy default for "${this.objectRef}.${this.fieldName}" is not valid JSON: ${error instanceof Error ? error.message : String(error)}. The "defaultValue" option is the already-encoded channel; pass a plain value as "defaultValueRaw" (or call setDefaultValue()).`);
716
+ const parsed = JSON.parse(this.valueHistogram);
717
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return histogram;
718
+ for (const [key, count] of Object.entries(parsed)) if (typeof count === "number" && Number.isFinite(count) && count > 0) histogram[key] = count;
719
+ return histogram;
720
+ } catch {
721
+ return emptyHistogram();
465
722
  }
466
723
  }
467
724
  /**
468
- * The persisted row this save/delete would replace, looked up by primary
469
- * key and when that misses by the NATURAL key.
725
+ * Record one histogram sample under an already-serialized key, honoring the
726
+ * bucket cap (a NEW key past the cap is dropped and the overflow marker
727
+ * set; existing keys keep counting).
470
728
  *
471
- * The natural-key fallback is load-bearing for authorization: every
472
- * generated create arrives with a freshly minted UUID, so a primary-key
473
- * lookup always misses, yet the `conflictColumns` upsert still overwrites
474
- * whatever row already occupies `(objectRef, fieldName, scopeType,
475
- * scopeKey)`. Authorizing on the primary key alone therefore skipped the
476
- * persisted-scope guard on exactly the path that can replace an existing
477
- * row's contents.
729
+ * Bucket presence is an OWN-key test, never a truthiness/`undefined` read,
730
+ * so prototype-shaped ids behave like any other key.
478
731
  */
479
- async getPersistedIdentity() {
480
- const existing = await this.getPersistedRowById() ?? await this.getPersistedRowByNaturalKey();
481
- if (!existing) return null;
482
- const row = existing;
483
- const read = (camel, snake, fallback) => {
484
- if (row[camel] !== void 0 && row[camel] !== null) return String(row[camel]);
485
- if (row[snake] !== void 0 && row[snake] !== null) return String(row[snake]);
486
- return fallback;
487
- };
488
- const readNullable = (camel, snake) => {
489
- const value = row[camel] !== void 0 ? row[camel] : row[snake];
490
- return value === void 0 || value === null ? null : String(value);
491
- };
492
- return {
493
- objectRef: read("objectRef", "object_ref", this.objectRef),
494
- fieldName: read("fieldName", "field_name", this.fieldName),
495
- scopeType: read("scopeType", "scope_type", this.scopeType),
496
- scopeKey: read("scopeKey", "scope_key", this.scopeKey),
497
- tenantId: readNullable("tenantId", "tenant_id"),
498
- userId: readNullable("userId", "user_id")
499
- };
732
+ recordHistogramSample(key) {
733
+ if (key.length === 0 || key.length > 64) return;
734
+ const histogram = this.getValueHistogram();
735
+ if (!Object.hasOwn(histogram, key)) {
736
+ if (Object.keys(histogram).length >= 25) {
737
+ this.valueHistogramOverflowed = true;
738
+ return;
739
+ }
740
+ histogram[key] = 1;
741
+ } else histogram[key] += 1;
742
+ this.valueHistogram = JSON.stringify(histogram);
500
743
  }
501
- async getPersistedRowById() {
502
- if (!this.id) return null;
503
- return await this.db.get(this.tableName, { id: this.id }) ?? null;
744
+ /**
745
+ * Whether this bucket predates the {@link submissionCount} column (or was
746
+ * corrupted): it records deviations without a total, so no honest dominance
747
+ * ratio can be computed from it. The generation job skips `default`
748
+ * suggestions for any group containing such a bucket.
749
+ */
750
+ isLegacyBucket() {
751
+ return this.submissionCount < this.setCount;
752
+ }
753
+ async save() {
754
+ await this.assertRowOwnedByAmbientContext("save");
755
+ this.validateFieldUsageCounter();
756
+ return super.save();
757
+ }
758
+ async delete() {
759
+ await this.assertRowOwnedByAmbientContext("delete");
760
+ await super.delete();
761
+ }
762
+ validateFieldUsageCounter() {
763
+ if (!this.objectRef || this.objectRef.trim() === "") throw new Error("FieldUsageCounter.objectRef is required");
764
+ if (!this.fieldName || this.fieldName.trim() === "") throw new Error("FieldUsageCounter.fieldName is required");
765
+ if (!this.tenantId) throw new Error("FieldUsageCounter.tenantId is required");
766
+ if (!FIELD_USAGE_PERIOD_PATTERN.test(this.period)) throw new Error(`FieldUsageCounter.period must be a UTC day bucket (YYYY-MM-DD); got "${this.period}"`);
767
+ if (!Number.isInteger(this.submissionCount) || this.submissionCount < 0) throw new Error("FieldUsageCounter.submissionCount must be a non-negative integer");
768
+ if (!Number.isInteger(this.setCount) || this.setCount < 0) throw new Error("FieldUsageCounter.setCount must be a non-negative integer");
769
+ if (!Number.isInteger(this.distinctUserCount) || this.distinctUserCount < 0) throw new Error("FieldUsageCounter.distinctUserCount must be a non-negative integer");
504
770
  }
505
771
  /**
506
- * The row occupying this row's `conflictColumns` tuple, if any. Column
507
- * names are the physical snake_case ones: this reads the driver directly
508
- * rather than through a collection (the model has no collection handle).
772
+ * Tenant write boundary (the FieldPolicy posture no class-level
773
+ * `@TenantScoped`, because the learning jobs legitimately operate
774
+ * cross-tenant in trusted execution): inside a non-bypass tenant context a
775
+ * caller may only touch rows of its own tenant; without a context (system/
776
+ * job execution) writes are trusted.
777
+ *
778
+ * Checked against BOTH the in-memory tenant and — for a row that already
779
+ * exists — the PERSISTED one. The persisted check is what makes the boundary
780
+ * real: bucket ids are deterministic and the deriving helper is exported, so
781
+ * a foreign row is trivially addressable, and an in-memory-only check would
782
+ * let a caller load it, re-stamp `tenantId` with its own, and adopt or delete
783
+ * another tenant's counters (the #2047 FieldPolicy pattern).
509
784
  */
510
- async getPersistedRowByNaturalKey() {
511
- if (!this.objectRef || !this.fieldName) return null;
512
- return await this.db.get(this.tableName, {
513
- object_ref: this.objectRef,
514
- field_name: this.fieldName,
515
- scope_type: this.scopeType,
516
- scope_key: this.computeScopeKey()
517
- }) ?? null;
518
- }
519
- normalizeDefaultValueForPersistence() {
520
- const raw = this.defaultValue;
521
- if (raw === null || raw === void 0) {
522
- this.defaultValue = null;
523
- return;
524
- }
525
- if (typeof raw === "string") {
526
- this.defaultValue = raw;
527
- return;
528
- }
529
- this.defaultValue = JSON.stringify(raw);
785
+ async assertRowOwnedByAmbientContext(operation) {
786
+ const context = getCurrentTenant();
787
+ if (!context || isSuperAdminBypass()) return;
788
+ if (this.tenantId !== context.tenantId) throw new TenantIsolationError(`Tenant isolation violation in FieldUsageCounter.${operation}: context tenant is '${context.tenantId}' but the row belongs to '${this.tenantId}'`, {
789
+ tenantId: context.tenantId,
790
+ attemptedTenantId: this.tenantId ?? void 0
791
+ });
792
+ const persistedTenantId = await this.getPersistedTenantId();
793
+ if (persistedTenantId !== null && persistedTenantId !== context.tenantId) throw new TenantIsolationError(`Tenant isolation violation in FieldUsageCounter.${operation}: the persisted row belongs to '${persistedTenantId}'`, {
794
+ tenantId: context.tenantId,
795
+ attemptedTenantId: persistedTenantId
796
+ });
797
+ }
798
+ /** The stored tenant for this row's id; `null` when it is not persisted. */
799
+ async getPersistedTenantId() {
800
+ if (!this.id) return null;
801
+ const existing = await this.db.get(this.tableName, { id: this.id });
802
+ if (!existing) return null;
803
+ const row = existing;
804
+ const value = row.tenantId ?? row.tenant_id;
805
+ return value === void 0 || value === null ? null : String(value);
530
806
  }
531
807
  };
532
- __decorateClass$1([field({ required: true })], FieldPolicy.prototype, "objectRef", 2);
533
- __decorateClass$1([field({ required: true })], FieldPolicy.prototype, "fieldName", 2);
534
- __decorateClass$1([field({ required: true })], FieldPolicy.prototype, "scopeType", 2);
535
- __decorateClass$1([tenantId({ nullable: true })], FieldPolicy.prototype, "tenantId", 2);
536
- __decorateClass$1([crossPackageRef("@happyvertical/smrt-users:User", { nullable: true })], FieldPolicy.prototype, "userId", 2);
537
- __decorateClass$1([field({
538
- type: "text",
539
- required: true
540
- })], FieldPolicy.prototype, "scopeKey", 2);
541
- __decorateClass$1([field({
542
- type: "text",
543
- nullable: true
544
- })], FieldPolicy.prototype, "defaultValue", 2);
545
- __decorateClass$1([field({
808
+ __decorateClass$3([field({ required: true })], FieldUsageCounter.prototype, "objectRef", 2);
809
+ __decorateClass$3([field({ required: true })], FieldUsageCounter.prototype, "fieldName", 2);
810
+ __decorateClass$3([tenantId()], FieldUsageCounter.prototype, "tenantId", 2);
811
+ __decorateClass$3([field({ required: true })], FieldUsageCounter.prototype, "period", 2);
812
+ __decorateClass$3([field({ type: "integer" })], FieldUsageCounter.prototype, "submissionCount", 2);
813
+ __decorateClass$3([field({ type: "integer" })], FieldUsageCounter.prototype, "setCount", 2);
814
+ __decorateClass$3([field({ type: "integer" })], FieldUsageCounter.prototype, "distinctUserCount", 2);
815
+ __decorateClass$3([field({ type: "text" })], FieldUsageCounter.prototype, "distinctUserIds", 2);
816
+ __decorateClass$3([field({ type: "boolean" })], FieldUsageCounter.prototype, "distinctUsersOverflowed", 2);
817
+ __decorateClass$3([field({
546
818
  type: "text",
547
819
  nullable: true
548
- })], FieldPolicy.prototype, "visibility", 2);
549
- __decorateClass$1([field({
550
- type: "text",
551
- nullable: true
552
- })], FieldPolicy.prototype, "help", 2);
553
- __decorateClass$1([field({
554
- type: "text",
555
- nullable: true
556
- })], FieldPolicy.prototype, "label", 2);
557
- __decorateClass$1([field({
558
- type: "integer",
559
- nullable: true
560
- })], FieldPolicy.prototype, "displayOrder", 2);
561
- __decorateClass$1([field({
562
- type: "boolean",
563
- nullable: true
564
- })], FieldPolicy.prototype, "locked", 2);
565
- __decorateClass$1([crossPackageRef("@happyvertical/smrt-users:User", { nullable: true })], FieldPolicy.prototype, "updatedBy", 2);
566
- FieldPolicy = __decorateClass$1([smrt({
567
- tableName: "_smrt_field_policies",
820
+ })], FieldUsageCounter.prototype, "valueHistogram", 2);
821
+ __decorateClass$3([field({ type: "boolean" })], FieldUsageCounter.prototype, "valueHistogramOverflowed", 2);
822
+ FieldUsageCounter = __decorateClass$3([smrt({
823
+ tableName: "_smrt_field_usage_counters",
568
824
  conflictColumns: [
569
825
  "object_ref",
570
826
  "field_name",
571
- "scope_type",
572
- "scope_key"
827
+ "tenant_id",
828
+ "period"
573
829
  ],
574
- api: { include: [
575
- "create",
576
- "update",
577
- "delete"
578
- ] },
579
- cli: {
580
- include: [
581
- "create",
582
- "update",
583
- "delete"
584
- ],
585
- exclude: ["getDefaultValue", "setDefaultValue"]
586
- },
830
+ api: { include: [] },
831
+ cli: false,
587
832
  mcp: { include: [] }
588
- })], FieldPolicy);
833
+ })], FieldUsageCounter);
589
834
  //#endregion
590
- //#region src/collections/FieldPolicyCollection.ts
591
- var __defProp = Object.defineProperty;
592
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
593
- var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, {
835
+ //#region src/models/FieldUsageReportReceipt.ts
836
+ var __defProp$2 = Object.defineProperty;
837
+ var __getOwnPropDesc$2 = Object.getOwnPropertyDescriptor;
838
+ var __decorateClass$2 = (decorators, target, key, kind) => {
839
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$2(target, key) : target;
840
+ for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
841
+ if (kind && result) __defProp$2(target, key, result);
842
+ return result;
843
+ };
844
+ var FieldUsageReportReceipt = class extends SmrtObject {
845
+ tenantId;
846
+ userId = "";
847
+ objectRef = "";
848
+ fieldName = "";
849
+ period = "";
850
+ constructor(options = {}) {
851
+ super(options);
852
+ if (options.tenantId !== void 0) this.tenantId = options.tenantId;
853
+ if (options.userId !== void 0) this.userId = options.userId;
854
+ if (options.objectRef !== void 0) this.objectRef = options.objectRef;
855
+ if (options.fieldName !== void 0) this.fieldName = options.fieldName;
856
+ if (options.period !== void 0) this.period = options.period;
857
+ }
858
+ };
859
+ __decorateClass$2([tenantId()], FieldUsageReportReceipt.prototype, "tenantId", 2);
860
+ __decorateClass$2([crossPackageRef("@happyvertical/smrt-users:User")], FieldUsageReportReceipt.prototype, "userId", 2);
861
+ __decorateClass$2([field({ required: true })], FieldUsageReportReceipt.prototype, "objectRef", 2);
862
+ __decorateClass$2([field({ required: true })], FieldUsageReportReceipt.prototype, "fieldName", 2);
863
+ __decorateClass$2([field({ required: true })], FieldUsageReportReceipt.prototype, "period", 2);
864
+ FieldUsageReportReceipt = __decorateClass$2([smrt({
865
+ tableName: "_smrt_field_usage_report_receipts",
866
+ conflictColumns: [
867
+ "tenant_id",
868
+ "user_id",
869
+ "object_ref",
870
+ "field_name",
871
+ "period"
872
+ ],
873
+ api: { include: [] },
874
+ cli: false,
875
+ mcp: { include: [] }
876
+ })], FieldUsageReportReceipt);
877
+ //#endregion
878
+ //#region src/collections/FieldUsageCounterCollection.ts
879
+ var __defProp$1 = Object.defineProperty;
880
+ var __getOwnPropDesc$1 = Object.getOwnPropertyDescriptor;
881
+ var __defNormalProp$1 = (obj, key, value) => key in obj ? __defProp$1(obj, key, {
594
882
  enumerable: true,
595
883
  configurable: true,
596
884
  writable: true,
597
885
  value
598
886
  }) : obj[key] = value;
599
- var __decorateClass = (decorators, target, key, kind) => {
600
- var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
887
+ var __decorateClass$1 = (decorators, target, key, kind) => {
888
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$1(target, key) : target;
601
889
  for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
602
- if (kind && result) __defProp(target, key, result);
890
+ if (kind && result) __defProp$1(target, key, result);
603
891
  return result;
604
892
  };
605
- var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
606
- var MAX_BATCH_OBJECT_REFS = 100;
607
- var FieldPolicyCollection = class extends SmrtCollection {
608
- /** All app-scope rows for an object, keyed by field name. */
609
- async getAppRows(objectRef) {
610
- const rows = await this.list({ where: {
611
- objectRef,
612
- scopeType: "app"
613
- } });
614
- const byField = /* @__PURE__ */ new Map();
615
- for (const row of rows) byField.set(row.fieldName, row);
616
- return byField;
893
+ var __publicField$1 = (obj, key, value) => __defNormalProp$1(obj, typeof key !== "symbol" ? key + "" : key, value);
894
+ function usageValuesEqual(a, b) {
895
+ if (a === b) return true;
896
+ try {
897
+ return JSON.stringify(a) === JSON.stringify(b);
898
+ } catch {
899
+ return false;
900
+ }
901
+ }
902
+ var MAX_USAGE_REPORT_ENTRIES = 100;
903
+ var FieldUsageRequestContextError = class extends TenantIsolationError {
904
+ httpStatus = 403;
905
+ status = 403;
906
+ constructor(message, details) {
907
+ super(message, details);
908
+ this.name = "FieldUsageRequestContextError";
617
909
  }
910
+ };
911
+ var FieldUsageCounterCollection = class extends SmrtCollection {
618
912
  /**
619
- * All tenant-scope rows for an object across a tenant chain, keyed
620
- * `tenantId → fieldName → row`.
913
+ * Record a batch of field submissions into the current UTC-day counters.
914
+ *
915
+ * Identity is AMBIENT-ONLY (the resolveBatch posture): the tenant AND the
916
+ * user come exclusively from the tenant context established by the app's
917
+ * auth hook — the request body cannot attribute usage to another tenant or
918
+ * user. Both are REQUIRED and the call fails closed without them (#2047
919
+ * round-3 posture):
920
+ *
921
+ * - No ambient tenant ⇒ the report is unattributable.
922
+ * - No ambient USER ⇒ the caller is an unauthenticated principal (a
923
+ * deployment that resolves the tenant from host/header alone). Such
924
+ * callers could otherwise inflate counts and histograms without bound
925
+ * (the batch cap is per call), and `distinctUsers` — the promote signal —
926
+ * is meaningless without a principal. Consequence, accepted deliberately:
927
+ * ANONYMOUS/PUBLIC FORMS DO NOT CONTRIBUTE USAGE. The learning loop is
928
+ * for authenticated org users.
929
+ *
930
+ * Any authenticated in-tenant principal may report (no manage slug needed).
931
+ * A durable receipt admits at most one `(tenant, user, object, field, UTC
932
+ * day)` contribution, and the batch stays bounded per call
933
+ * ({@link MAX_USAGE_REPORT_ENTRIES}).
934
+ *
935
+ * Server-side derivation (the client is never the authority):
936
+ * - Fields are validated against the live `ObjectRegistry`; unknown or
937
+ * non-usage-addressable entries (system, relationship pseudo-fields, STI
938
+ * meta storage, transient) are DROPPED and counted, never failing the
939
+ * batch (stale clients after a redeploy are expected).
940
+ * - Sensitivity comes from BOTH `field.sensitive` and `field._meta.sensitive`
941
+ * (and `readPermission` in both places): such fields are recorded
942
+ * COUNT-ONLY — their values are never persisted anywhere in usage data,
943
+ * default-matching or not.
944
+ * - DEVIATION (`setCount`, distinct users) is decided by comparing the
945
+ * submitted value against the default RESOLVED HERE for the calling
946
+ * identity — never against a client claim. Every accepted entry also
947
+ * increments `submissionCount`, the dominance denominator.
948
+ * - Values are histogrammed only for low-cardinality field types
949
+ * (`boolean`, `foreignKey`, `crossPackageRef`) with type-checked samples;
950
+ * free text is never histogrammed.
951
+ *
952
+ * Bounded trust in `matchedDefault`: an entry may omit `value` entirely
953
+ * (`collectFieldUsageEntries({ includeValues: false })` — apps that prefer
954
+ * no value transit). Only then is the entry's `matchedDefault` flag read, to
955
+ * decide the deviation bit alone. It can never create a histogram entry,
956
+ * never bypass the count-only rail, and never manufacture distinct users
957
+ * beyond the caller's own id. The once-per-field/day receipt means a lying
958
+ * client gets only one such contribution, so repeated POSTs cannot inflate
959
+ * thresholds or steer a `default` suggestion.
621
960
  */
622
- async getTenantRows(objectRef, tenantIds) {
623
- const byTenant = /* @__PURE__ */ new Map();
624
- if (tenantIds.length === 0) return byTenant;
625
- const rows = await this.list({ where: {
626
- objectRef,
627
- scopeType: "tenant",
628
- "tenantId in": tenantIds
629
- } });
630
- for (const row of rows) {
631
- if (!row.tenantId) continue;
632
- let byField = byTenant.get(row.tenantId);
961
+ async reportUsage(options = {}) {
962
+ const entries = validateEntriesInput(options.entries);
963
+ const context = getCurrentTenant();
964
+ const tenantId = context?.tenantId;
965
+ if (!tenantId) throw new FieldUsageRequestContextError("reportUsage requires an ambient tenant context: usage is unattributable without one, so the call fails closed");
966
+ const userId = context.userId;
967
+ if (!userId) throw new FieldUsageRequestContextError("reportUsage requires an ambient AUTHENTICATED user: an unauthenticated caller could inflate counters without bound and distinct-user thresholds are meaningless without a principal, so the call fails closed (anonymous forms do not contribute usage)", { tenantId });
968
+ const period = fieldUsagePeriodForDate(/* @__PURE__ */ new Date());
969
+ const receipts = await FieldUsageReportReceiptCollection.create({ db: this.db });
970
+ const groups = /* @__PURE__ */ new Map();
971
+ for (const entry of entries) {
972
+ let byField = groups.get(entry.objectRef);
633
973
  if (!byField) {
634
974
  byField = /* @__PURE__ */ new Map();
635
- byTenant.set(row.tenantId, byField);
975
+ groups.set(entry.objectRef, byField);
636
976
  }
637
- byField.set(row.fieldName, row);
977
+ const samples = byField.get(entry.fieldName);
978
+ if (samples) samples.push(entry);
979
+ else byField.set(entry.fieldName, [entry]);
638
980
  }
639
- return byTenant;
640
- }
641
- /** All user-scope rows for an object and user, keyed by field name. */
642
- async getUserRows(objectRef, userId) {
643
- const rows = await this.list({ where: {
644
- objectRef,
645
- scopeType: "user",
646
- userId
647
- } });
648
- const byField = /* @__PURE__ */ new Map();
649
- for (const row of rows) byField.set(row.fieldName, row);
650
- return byField;
981
+ let accepted = 0;
982
+ let dropped = 0;
983
+ for (const [objectRef, byField] of groups) {
984
+ let fieldMap;
985
+ try {
986
+ fieldMap = await getObjectFieldMap(objectRef);
987
+ } catch {
988
+ for (const samples of byField.values()) dropped += samples.length;
989
+ continue;
990
+ }
991
+ const resolvedFields = await this.resolveDefaultsForCaller(objectRef, tenantId, userId);
992
+ if (!resolvedFields) {
993
+ for (const samples of byField.values()) dropped += samples.length;
994
+ continue;
995
+ }
996
+ for (const [fieldName, samples] of byField) {
997
+ const fieldDef = fieldMap.get(fieldName);
998
+ if (!fieldDef || !isUsageAddressableField(fieldDef)) {
999
+ dropped += samples.length;
1000
+ continue;
1001
+ }
1002
+ if (!await receipts.claim({
1003
+ tenantId,
1004
+ userId,
1005
+ objectRef,
1006
+ fieldName,
1007
+ period
1008
+ })) continue;
1009
+ const sample = samples[0];
1010
+ const histogramEligible = !(isSensitiveField(fieldDef) || getFieldReadPermission(fieldDef) !== void 0) && isHistogramEligibleField(fieldDef);
1011
+ const resolved = resolvedFields[fieldName];
1012
+ const hasDefault = resolved?.hasDefault === true;
1013
+ const defaultValue = hasDefault ? resolved?.defaultValue : void 0;
1014
+ let deviations = 0;
1015
+ const histogramKeys = [];
1016
+ if ("value" in sample) {
1017
+ if (!hasDefault || !usageValuesEqual(sample.value, defaultValue)) deviations += 1;
1018
+ if (histogramEligible) {
1019
+ const key = serializeHistogramSample(fieldDef, sample.value);
1020
+ if (key !== null) histogramKeys.push(key);
1021
+ }
1022
+ } else if (sample.matchedDefault !== true) deviations += 1;
1023
+ await this.mergeIntoBucket({
1024
+ objectRef,
1025
+ fieldName,
1026
+ tenantId,
1027
+ period,
1028
+ userId,
1029
+ submissionCount: 1,
1030
+ deviationCount: deviations,
1031
+ histogramKeys
1032
+ });
1033
+ accepted += 1;
1034
+ }
1035
+ }
1036
+ return {
1037
+ accepted,
1038
+ dropped
1039
+ };
651
1040
  }
652
1041
  /**
653
- * Resolve merged field policy for a set of objectRefs for the CURRENT
654
- * caller, gated for public consumption.
655
- *
656
- * Caller identity comes exclusively from the ambient tenant context
657
- * (established by the app's auth hook, e.g. smrt-users' session context) —
658
- * the request body cannot select another tenant or user (fail closed).
659
- * Server-side consumers wanting explicit identities call
660
- * `resolveFieldPolicy()` directly instead.
1042
+ * The defaults the CALLER's forms would have prefilled the deviation
1043
+ * authority. Resolved for the ambient `(tenant, user)` identity (the
1044
+ * resolver's own TTL cache keeps repeat batches cheap).
661
1045
  *
662
- * Field gating mirrors the REST serializer's derivation (`sensitive` /
663
- * `readPermission` read from both the top level and `_meta`): sensitive and
664
- * read-permission-gated fields are ABSENT from the response for every
665
- * caller (the generated action route cannot convey per-caller grants to
666
- * this method, so gated fields fail closed), and transient fields are
667
- * stripped for parity with generated client field definitions.
1046
+ * Ingestion is fire-and-forget, so a stored-layer read failure degrades to
1047
+ * the CODE-SEED-only resolution (no db) rather than failing the request;
1048
+ * `null` (both attempts failed) makes the caller drop the group.
668
1049
  */
669
- async resolveBatch(options = {}) {
670
- const rawRefs = options.objectRefs;
671
- if (!Array.isArray(rawRefs) || rawRefs.length === 0) throw new Error("resolveBatch requires a non-empty \"objectRefs\" string array");
672
- if (rawRefs.some((ref) => typeof ref !== "string" || ref.trim() === "")) throw new Error("resolveBatch objectRefs must be non-empty strings");
673
- const objectRefs = [...new Set(rawRefs)];
674
- if (objectRefs.length > MAX_BATCH_OBJECT_REFS) throw new Error(`resolveBatch accepts at most ${MAX_BATCH_OBJECT_REFS} objectRefs per call (got ${objectRefs.length})`);
675
- const context = getCurrentTenant();
676
- const tenantId = context?.tenantId ?? null;
677
- const userId = context?.userId ?? null;
1050
+ async resolveDefaultsForCaller(objectRef, tenantId, userId) {
678
1051
  const { resolveFieldPolicy } = await Promise.resolve().then(() => field_policy_resolver_exports);
679
- const policies = {};
680
- for (const objectRef of objectRefs) {
681
- const resolved = await resolveFieldPolicy(objectRef, {
1052
+ try {
1053
+ return (await resolveFieldPolicy(objectRef, {
682
1054
  tenantId,
683
1055
  userId,
684
1056
  db: this.db
685
- });
686
- policies[objectRef] = await this.gateResolvedForPublicResponse(resolved);
687
- }
688
- return { policies };
689
- }
690
- async gateResolvedForPublicResponse(resolved) {
691
- const fieldMap = await getObjectFieldMap(resolved.objectRef);
692
- const fields = {};
693
- for (const [fieldName, policy] of Object.entries(resolved.fields)) {
694
- const fieldDef = fieldMap.get(fieldName);
695
- if (!fieldDef) continue;
696
- if (isSensitiveField(fieldDef) || getFieldReadPermission(fieldDef) !== void 0 || isTransientField(fieldDef)) continue;
697
- fields[fieldName] = policy;
1057
+ })).fields;
1058
+ } catch {
1059
+ try {
1060
+ return (await resolveFieldPolicy(objectRef, {
1061
+ tenantId,
1062
+ userId
1063
+ })).fields;
1064
+ } catch {
1065
+ return null;
1066
+ }
698
1067
  }
699
- return {
700
- objectRef: resolved.objectRef,
701
- fields
702
- };
1068
+ }
1069
+ /**
1070
+ * Counter rows within a period window (inclusive bounds, lexicographic ISO
1071
+ * day comparison), optionally restricted to one tenant — the read the
1072
+ * suggestion-generation job consumes.
1073
+ */
1074
+ async listWindow(options) {
1075
+ const where = { "period >=": options.fromPeriod };
1076
+ if (options.toPeriod) where["period <="] = options.toPeriod;
1077
+ if (options.tenantId) where.tenantId = options.tenantId;
1078
+ return this.list({
1079
+ where,
1080
+ orderBy: "period ASC"
1081
+ });
1082
+ }
1083
+ /**
1084
+ * Idempotent read-modify-write merge into the deterministic day bucket.
1085
+ * Counters are approximate by design: concurrent cross-process merges may
1086
+ * lose an increment (documented on the model), while the deterministic id
1087
+ * plus the natural-key unique index keep concurrent creates converging on
1088
+ * one row.
1089
+ */
1090
+ async mergeIntoBucket(options) {
1091
+ const id = await fieldUsageCounterId(options.tenantId, options.objectRef, options.fieldName, options.period);
1092
+ const existing = await this.get(id);
1093
+ const counter = existing ?? new FieldUsageCounter({
1094
+ db: this.db,
1095
+ id,
1096
+ objectRef: options.objectRef,
1097
+ fieldName: options.fieldName,
1098
+ tenantId: options.tenantId,
1099
+ period: options.period
1100
+ });
1101
+ counter.submissionCount += options.submissionCount;
1102
+ counter.setCount += options.deviationCount;
1103
+ if (options.deviationCount > 0) counter.addDistinctUser(options.userId);
1104
+ for (const key of options.histogramKeys) counter.recordHistogramSample(key);
1105
+ if (!existing) await counter.initialize();
1106
+ await counter.save();
703
1107
  }
704
1108
  };
705
- __publicField(FieldPolicyCollection, "_itemClass", FieldPolicy);
706
- FieldPolicyCollection = __decorateClass([smrt({
1109
+ __publicField$1(FieldUsageCounterCollection, "_itemClass", FieldUsageCounter);
1110
+ FieldUsageCounterCollection = __decorateClass$1([smrt({
707
1111
  conflictColumns: [
708
1112
  "object_ref",
709
1113
  "field_name",
710
- "scope_type",
711
- "scope_key"
1114
+ "tenant_id",
1115
+ "period"
712
1116
  ],
713
1117
  api: {
714
- include: [
715
- "create",
716
- "update",
717
- "delete",
718
- "resolveBatch"
719
- ],
720
- routes: { resolveBatch: {
1118
+ include: ["reportUsage"],
1119
+ principalContext: true,
1120
+ routes: { reportUsage: {
721
1121
  scope: "collection",
722
1122
  method: "POST",
723
- path: "resolve"
1123
+ path: "report"
724
1124
  } }
725
1125
  },
726
1126
  cli: false,
727
1127
  mcp: false
728
- })], FieldPolicyCollection);
1128
+ })], FieldUsageCounterCollection);
1129
+ var FieldUsageReportReceiptCollection = class extends SmrtCollection {
1130
+ async claim(options) {
1131
+ const id = await fieldUsageReportReceiptId(options.tenantId, options.userId, options.objectRef, options.fieldName, options.period);
1132
+ if (await this.get(id)) return false;
1133
+ try {
1134
+ await this.create({
1135
+ ...options,
1136
+ id,
1137
+ _insertOnly: true
1138
+ });
1139
+ return true;
1140
+ } catch (error) {
1141
+ if (await this.get(id)) return false;
1142
+ throw error;
1143
+ }
1144
+ }
1145
+ };
1146
+ __publicField$1(FieldUsageReportReceiptCollection, "_itemClass", FieldUsageReportReceipt);
1147
+ FieldUsageReportReceiptCollection = __decorateClass$1([smrt({
1148
+ conflictColumns: [
1149
+ "tenant_id",
1150
+ "user_id",
1151
+ "object_ref",
1152
+ "field_name",
1153
+ "period"
1154
+ ],
1155
+ api: false,
1156
+ cli: false,
1157
+ mcp: false
1158
+ })], FieldUsageReportReceiptCollection);
1159
+ async function fieldUsageCounterId(tenantId, objectRef, fieldName, period) {
1160
+ return deterministicFieldsUuid([
1161
+ "field-usage-counter",
1162
+ tenantId,
1163
+ objectRef,
1164
+ fieldName,
1165
+ period
1166
+ ]);
1167
+ }
1168
+ async function fieldUsageReportReceiptId(tenantId, userId, objectRef, fieldName, period) {
1169
+ return deterministicFieldsUuid([
1170
+ "field-usage-report-receipt",
1171
+ tenantId,
1172
+ userId,
1173
+ objectRef,
1174
+ fieldName,
1175
+ period
1176
+ ]);
1177
+ }
1178
+ function isUsageAddressableField(field) {
1179
+ if (field._meta?.__smrtSystemField === true) return false;
1180
+ if (field.type === "oneToMany" || field.type === "manyToMany" || field.type === "meta") return false;
1181
+ return !isTransientField(field);
1182
+ }
1183
+ function isHistogramEligibleField(field) {
1184
+ return field.type === "boolean" || field.type === "foreignKey" || field.type === "crossPackageRef";
1185
+ }
1186
+ function serializeHistogramSample(field, value) {
1187
+ if (field.type === "boolean") return typeof value === "boolean" ? String(value) : null;
1188
+ if (isStorableReferenceId(field, value) && value.length <= 64) return value;
1189
+ return null;
1190
+ }
1191
+ function decodeHistogramKey(field, key) {
1192
+ if (field.type === "boolean") return key === "true";
1193
+ return key;
1194
+ }
1195
+ function validateEntriesInput(rawEntries) {
1196
+ if (!Array.isArray(rawEntries) || rawEntries.length === 0) throw new Error("reportUsage requires a non-empty \"entries\" array of { objectRef, fieldName, value?, matchedDefault? } samples");
1197
+ if (rawEntries.length > 100) throw new Error(`reportUsage accepts at most 100 entries per call (got ${rawEntries.length})`);
1198
+ for (const entry of rawEntries) if (!entry || typeof entry !== "object" || typeof entry.objectRef !== "string" || entry.objectRef.trim() === "" || typeof entry.fieldName !== "string" || entry.fieldName.trim() === "") throw new Error("reportUsage entries must carry non-empty objectRef and fieldName strings");
1199
+ return rawEntries;
1200
+ }
729
1201
  //#endregion
730
1202
  //#region src/field-policy-resolver.ts
731
1203
  var field_policy_resolver_exports = /* @__PURE__ */ __exportAll({
732
1204
  isMissingUsersDependency: () => isMissingUsersDependency,
733
1205
  resolveFieldPolicy: () => resolveFieldPolicy,
734
- resolveFieldPolicyExplained: () => resolveFieldPolicyExplained
1206
+ resolveFieldPolicyExplained: () => resolveFieldPolicyExplained,
1207
+ resolveSurvivingTenantChainIds: () => resolveSurvivingTenantChainIds
735
1208
  });
736
1209
  async function resolveFieldPolicy(objectRef, options = {}) {
737
1210
  const explained = await resolveFieldPolicyExplained(objectRef, options);
@@ -740,6 +1213,10 @@ async function resolveFieldPolicy(objectRef, options = {}) {
740
1213
  fields: explained.fields
741
1214
  };
742
1215
  }
1216
+ async function resolveSurvivingTenantChainIds(tenantId, options = {}) {
1217
+ assertResolutionAllowedInContext(tenantId, null);
1218
+ return selectSurvivingChainSuffix(await resolveTenantChain(tenantId, options)).map((node) => node.id);
1219
+ }
743
1220
  async function resolveFieldPolicyExplained(objectRef, options = {}) {
744
1221
  const tenantId = options.tenantId ?? null;
745
1222
  const userId = options.userId ?? null;
@@ -751,8 +1228,11 @@ async function resolveFieldPolicyExplained(objectRef, options = {}) {
751
1228
  cacheDb = collection.db;
752
1229
  }
753
1230
  const fieldMap = await getObjectFieldMap(objectRef);
754
- const cached = getCachedFieldPolicy(objectRef, tenantId, userId, cacheDb, options.tenantHierarchyLoader);
755
- if (cached) return cached;
1231
+ const hasExcludedRows = (options.excludePolicyIds?.size ?? 0) > 0;
1232
+ if (!hasExcludedRows) {
1233
+ const cached = getCachedFieldPolicy(objectRef, tenantId, userId, cacheDb, options.tenantHierarchyLoader);
1234
+ if (cached) return cached;
1235
+ }
756
1236
  const policyFields = selectPolicyAddressableFields(fieldMap);
757
1237
  const codeVisibility = buildCodeSeedVisibility(fieldMap);
758
1238
  const appRows = collection ? await collection.getAppRows(objectRef) : /* @__PURE__ */ new Map();
@@ -774,7 +1254,7 @@ async function resolveFieldPolicyExplained(objectRef, options = {}) {
774
1254
  });
775
1255
  let state = applyDelta({ visibility: "basic" }, codeDelta);
776
1256
  const appRow = appRows.get(fieldName);
777
- if (appRow) {
1257
+ if (appRow && !options.excludePolicyIds?.has(String(appRow.id))) {
778
1258
  const delta = rowToDelta(appRow);
779
1259
  contributions.push({
780
1260
  layer: "app",
@@ -784,7 +1264,7 @@ async function resolveFieldPolicyExplained(objectRef, options = {}) {
784
1264
  }
785
1265
  for (const node of survivingChain) {
786
1266
  const row = tenantRows.get(node.id)?.get(fieldName);
787
- if (row) {
1267
+ if (row && !options.excludePolicyIds?.has(String(row.id))) {
788
1268
  const delta = rowToDelta(row);
789
1269
  contributions.push({
790
1270
  layer: "tenant",
@@ -796,7 +1276,7 @@ async function resolveFieldPolicyExplained(objectRef, options = {}) {
796
1276
  }
797
1277
  const orgLocked = state.locked === true;
798
1278
  const userRow = userId ? userRows.get(fieldName) : void 0;
799
- if (userRow && !orgLocked) {
1279
+ if (userRow && !options.excludePolicyIds?.has(String(userRow.id)) && !orgLocked) {
800
1280
  const delta = rowToDelta(userRow);
801
1281
  contributions.push({
802
1282
  layer: "user",
@@ -834,7 +1314,7 @@ async function resolveFieldPolicyExplained(objectRef, options = {}) {
834
1314
  fields,
835
1315
  layers
836
1316
  };
837
- setCachedFieldPolicy(objectRef, tenantId, userId, cacheDb, explained, options.tenantHierarchyLoader);
1317
+ if (!hasExcludedRows) setCachedFieldPolicy(objectRef, tenantId, userId, cacheDb, explained, options.tenantHierarchyLoader);
838
1318
  return explained;
839
1319
  }
840
1320
  function assertResolutionAllowedInContext(tenantId, userId) {
@@ -847,11 +1327,7 @@ function assertResolutionAllowedInContext(tenantId, userId) {
847
1327
  }
848
1328
  function selectPolicyAddressableFields(fieldMap) {
849
1329
  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
- }
1330
+ for (const [name, field] of fieldMap) if (isPolicyAddressableField(field)) selected.set(name, field);
855
1331
  return selected;
856
1332
  }
857
1333
  function rowToDelta(row) {
@@ -914,7 +1390,7 @@ async function defaultTenantHierarchyLoader(options) {
914
1390
  throw error;
915
1391
  }
916
1392
  }
917
- var MISSING_MODULE_TARGET_PATTERN = /Cannot find (?:package|module) '([^']+)'/;
1393
+ var MISSING_MODULE_TARGET_PATTERN$1 = /Cannot find (?:package|module) '([^']+)'/;
918
1394
  function isUsersSpecifier(target) {
919
1395
  return target === "@happyvertical/smrt-users" || target.startsWith("@happyvertical/smrt-users/");
920
1396
  }
@@ -923,7 +1399,7 @@ function isMissingUsersDependency(error) {
923
1399
  const seen = /* @__PURE__ */ new Set();
924
1400
  while (current instanceof Error && !seen.has(current)) {
925
1401
  seen.add(current);
926
- const match = current.message.match(MISSING_MODULE_TARGET_PATTERN);
1402
+ const match = current.message.match(MISSING_MODULE_TARGET_PATTERN$1);
927
1403
  if (match && isUsersSpecifier(match[1])) return true;
928
1404
  if (current.message.includes("Failed to load @happyvertical/smrt-users for")) return true;
929
1405
  current = current.cause;
@@ -931,6 +1407,715 @@ function isMissingUsersDependency(error) {
931
1407
  return false;
932
1408
  }
933
1409
  //#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 };
1410
+ //#region src/settings-catalog.ts
1411
+ var DEFAULT_PAGE_SIZE = 50;
1412
+ var MAX_PAGE_SIZE = 100;
1413
+ var FORM_TYPES = /* @__PURE__ */ new Set([
1414
+ "text",
1415
+ "integer",
1416
+ "decimal",
1417
+ "boolean",
1418
+ "datetime",
1419
+ "json",
1420
+ "foreignKey",
1421
+ "crossPackageRef"
1422
+ ]);
1423
+ var SYSTEM_NAMES = /* @__PURE__ */ new Set([
1424
+ "id",
1425
+ "slug",
1426
+ "context",
1427
+ "createdAt",
1428
+ "created_at",
1429
+ "updatedAt",
1430
+ "updated_at",
1431
+ "deletedAt",
1432
+ "deleted_at",
1433
+ "tenantId",
1434
+ "tenant_id"
1435
+ ]);
1436
+ function fieldPolicyCatalogItemId(objectRef, fieldName) {
1437
+ return `${objectRef}::${fieldName}`;
1438
+ }
1439
+ function parseFieldPolicyCatalogQuery(params) {
1440
+ return {
1441
+ query: params.get("q"),
1442
+ page: integerParam(params.get("page")),
1443
+ pageSize: integerParam(params.get("pageSize")),
1444
+ selectedId: params.get("selected"),
1445
+ packageFilter: params.get("package"),
1446
+ objectFilter: params.get("object"),
1447
+ customizedOnly: params.get("customized") === "1"
1448
+ };
1449
+ }
1450
+ async function buildFieldPolicySettingsCatalog(options) {
1451
+ const collection = options.collection ?? (options.db ? await FieldPolicyCollection.create({ db: options.db }) : null);
1452
+ if (!collection) throw new Error("A db or FieldPolicyCollection is required");
1453
+ const filters = {
1454
+ packageFilter: stringFilter(options.packageFilter),
1455
+ objectFilter: stringFilter(options.objectFilter),
1456
+ customizedOnly: options.customizedOnly === true
1457
+ };
1458
+ const query = options.query?.trim() ?? "";
1459
+ const pageSize = clamp(options.pageSize, 1, MAX_PAGE_SIZE, DEFAULT_PAGE_SIZE);
1460
+ const baseAudit = await collection.policyAudit({ summaryOnly: true });
1461
+ if (!baseAudit.caller.canManageOrg) return {
1462
+ page: emptyPage(query, pageSize),
1463
+ audit: baseAudit,
1464
+ objects: [],
1465
+ packages: [],
1466
+ filters
1467
+ };
1468
+ const objects = await listCatalogObjects(options.objectRefs);
1469
+ const candidates = objects.filter((object) => (!filters.packageFilter || object.packageName === filters.packageFilter) && (!filters.objectFilter || object.objectRef === filters.objectFilter));
1470
+ const allCandidateRefs = candidates.map((object) => object.objectRef);
1471
+ const customized = customizedKeys(filters.customizedOnly && allCandidateRefs.length ? await loadAuditCounts(collection, allCandidateRefs, baseAudit) : baseAudit);
1472
+ const entries = candidates.flatMap((object) => Object.entries(object.fields).filter(([fieldName]) => !filters.customizedOnly || customized.has(fieldPolicyCatalogItemId(object.objectRef, fieldName))).map(([fieldName, field]) => ({
1473
+ item: {
1474
+ id: fieldPolicyCatalogItemId(object.objectRef, fieldName),
1475
+ label: fieldName,
1476
+ ...field.description ? { description: field.description } : {},
1477
+ eyebrow: `${object.className} \xB7 ${object.packageName}`,
1478
+ objectRef: object.objectRef,
1479
+ fieldName,
1480
+ className: object.className,
1481
+ packageName: object.packageName
1482
+ },
1483
+ search: `${fieldName} ${spaced(fieldName)} ${object.className} ${object.packageName} ${object.objectRef} ${field.description ?? ""}`.toLowerCase()
1484
+ })));
1485
+ const filtered = query ? entries.filter((entry) => entry.search.includes(query.toLowerCase())) : entries;
1486
+ const total = filtered.length;
1487
+ const page = clamp(options.page, 1, Math.max(1, Math.ceil(total / pageSize)), 1);
1488
+ const items = filtered.slice((page - 1) * pageSize, page * pageSize).map((entry) => entry.item);
1489
+ const selectedSummary = (options.selectedId ? filtered.find((entry) => entry.item.id === options.selectedId)?.item : void 0) ?? items[0];
1490
+ const selected = selectedSummary ? {
1491
+ ...selectedSummary,
1492
+ fields: objects.find((object) => object.objectRef === selectedSummary.objectRef)?.fields ?? {}
1493
+ } : null;
1494
+ const auditRefs = selected ? uniqueRefs([selected.objectRef, ...items.map((item) => item.objectRef)]) : [];
1495
+ const audit = auditRefs.length ? await collection.policyAudit({
1496
+ objectRefs: auditRefs.slice(0, 100),
1497
+ countObjectRefs: auditRefs,
1498
+ includeDrift: true
1499
+ }) : await collection.policyAudit({ includeDrift: true });
1500
+ return {
1501
+ page: {
1502
+ items,
1503
+ selected,
1504
+ query,
1505
+ page,
1506
+ pageSize,
1507
+ total
1508
+ },
1509
+ audit,
1510
+ objects: objects.map(({ objectRef, className, packageName, fields }) => ({
1511
+ objectRef,
1512
+ className,
1513
+ packageName,
1514
+ fieldCount: Object.keys(fields).length
1515
+ })),
1516
+ packages: [...new Set(objects.map((object) => object.packageName))].sort(),
1517
+ filters
1518
+ };
1519
+ }
1520
+ async function listCatalogObjects(objectRefs) {
1521
+ const refs = objectRefs ? [...objectRefs] : Array.from(ObjectRegistry.getPublicClasses().values()).reduce((result, registered) => {
1522
+ if (registered.qualifiedName) result.push(registered.qualifiedName);
1523
+ return result;
1524
+ }, []);
1525
+ const unique = [...new Set(refs)].sort();
1526
+ const objects = [];
1527
+ for (const objectRef of unique) {
1528
+ if (!ObjectRegistry.getClassByQualifiedName(objectRef) || ObjectRegistry.getTableName(objectRef)?.startsWith("_smrt_")) continue;
1529
+ const fields = await getObjectFieldMap(objectRef);
1530
+ const selected = {};
1531
+ for (const [name, definition] of fields) {
1532
+ if (SYSTEM_NAMES.has(name) || !isPolicyAddressableField(definition) || isSensitiveField(definition) || isTransientField(definition) || getFieldReadPermission(definition) !== void 0 || !FORM_TYPES.has(String(definition.type))) continue;
1533
+ selected[name] = {
1534
+ type: definition.type,
1535
+ ...definition.required === true ? { required: true } : {},
1536
+ ...definition.default !== void 0 ? { default: definition.default } : {},
1537
+ ...typeof definition.description === "string" ? { description: definition.description } : {}
1538
+ };
1539
+ }
1540
+ if (!Object.keys(selected).length) continue;
1541
+ const colon = objectRef.lastIndexOf(":");
1542
+ const packageName = colon === -1 ? "" : objectRef.slice(0, colon);
1543
+ const className = colon === -1 ? objectRef : objectRef.slice(colon + 1);
1544
+ objects.push({
1545
+ objectRef,
1546
+ className,
1547
+ packageName,
1548
+ fields: selected
1549
+ });
1550
+ }
1551
+ return objects;
1552
+ }
1553
+ async function loadAuditCounts(collection, refs, baseline) {
1554
+ const userOverrideCounts = {};
1555
+ const orgRows = [];
1556
+ const appRows = [];
1557
+ const inheritedOrgKeys = {};
1558
+ const chunkSize = 100;
1559
+ for (let offset = 0; offset < refs.length; offset += chunkSize) {
1560
+ const audit = await collection.policyAudit({
1561
+ objectRefs: refs.slice(offset, offset + chunkSize),
1562
+ countObjectRefs: refs.slice(offset, offset + chunkSize),
1563
+ countsOnly: true
1564
+ });
1565
+ for (const [objectRef, byField] of Object.entries(audit.userOverrideCounts)) userOverrideCounts[objectRef] = {
1566
+ ...userOverrideCounts[objectRef] ?? {},
1567
+ ...byField
1568
+ };
1569
+ orgRows.push(...audit.orgRows);
1570
+ appRows.push(...audit.appRows);
1571
+ for (const [objectRef, fieldNames] of Object.entries(audit.inheritedOrgKeys)) {
1572
+ const names = inheritedOrgKeys[objectRef] ?? [];
1573
+ inheritedOrgKeys[objectRef] = names;
1574
+ for (const fieldName of fieldNames) if (!names.includes(fieldName)) names.push(fieldName);
1575
+ }
1576
+ }
1577
+ return {
1578
+ ...baseline,
1579
+ orgRows,
1580
+ appRows,
1581
+ inheritedOrgKeys,
1582
+ userOverrideCounts
1583
+ };
1584
+ }
1585
+ function customizedKeys(audit) {
1586
+ const keys = /* @__PURE__ */ new Set();
1587
+ for (const row of [...audit.orgRows, ...audit.appRows]) keys.add(fieldPolicyCatalogItemId(row.objectRef, row.fieldName));
1588
+ for (const [objectRef, names] of Object.entries(audit.inheritedOrgKeys)) for (const name of names) keys.add(fieldPolicyCatalogItemId(objectRef, name));
1589
+ 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));
1590
+ return keys;
1591
+ }
1592
+ function emptyPage(query, pageSize) {
1593
+ return {
1594
+ items: [],
1595
+ selected: null,
1596
+ query,
1597
+ page: 1,
1598
+ pageSize,
1599
+ total: 0
1600
+ };
1601
+ }
1602
+ function uniqueRefs(refs) {
1603
+ return [...new Set(refs)];
1604
+ }
1605
+ function stringFilter(value) {
1606
+ const trimmed = value?.trim();
1607
+ return trimmed ? trimmed : null;
1608
+ }
1609
+ function integerParam(value) {
1610
+ return value && /^\d+$/.test(value) ? Number(value) : null;
1611
+ }
1612
+ function clamp(value, min, max, fallback) {
1613
+ return Number.isFinite(value) ? Math.min(max, Math.max(min, Math.trunc(value))) : fallback;
1614
+ }
1615
+ function spaced(value) {
1616
+ return value.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[_-]/g, " ");
1617
+ }
1618
+ //#endregion
1619
+ //#region src/usage-learning.ts
1620
+ var __defProp = Object.defineProperty;
1621
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
1622
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, {
1623
+ enumerable: true,
1624
+ configurable: true,
1625
+ writable: true,
1626
+ value
1627
+ }) : obj[key] = value;
1628
+ var __decorateClass = (decorators, target, key, kind) => {
1629
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
1630
+ for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
1631
+ if (kind && result) __defProp(target, key, result);
1632
+ return result;
1633
+ };
1634
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
1635
+ var DAY_MS = 1440 * 60 * 1e3;
1636
+ var FIELD_USAGE_MAINTENANCE_DEFAULTS = {
1637
+ counterMaxAgeMs: 90 * DAY_MS,
1638
+ counterMaxRows: 1e5,
1639
+ suggestionAcceptedMaxAgeMs: 180 * DAY_MS
1640
+ };
1641
+ var FIELD_USAGE_SUGGESTION_DEFAULTS = {
1642
+ windowDays: 30,
1643
+ minDistinctUsers: 5,
1644
+ minSetCount: 10,
1645
+ defaultDominanceRatio: .8
1646
+ };
1647
+ async function pruneFieldUsageCounters(db, retention) {
1648
+ const { maxAgeMs, maxRows } = retention;
1649
+ if (maxAgeMs == null && maxRows == null) throw new Error("pruneFieldUsageCounters requires maxAgeMs and/or maxRows");
1650
+ if (maxAgeMs != null && (!Number.isFinite(maxAgeMs) || maxAgeMs < 0)) throw new Error(`pruneFieldUsageCounters maxAgeMs must be >= 0, got ${maxAgeMs}`);
1651
+ if (maxRows != null && (!Number.isFinite(maxRows) || maxRows < 0)) throw new Error(`pruneFieldUsageCounters maxRows must be >= 0, got ${maxRows}`);
1652
+ const tenantCondition = retention.tenantId ? " AND tenant_id = ?" : "";
1653
+ const tenantParams = retention.tenantId ? [retention.tenantId] : [];
1654
+ let pruned = 0;
1655
+ if (maxAgeMs != null) {
1656
+ const cutoffDay = fieldUsagePeriodForDate(new Date(Date.now() - maxAgeMs));
1657
+ pruned += await deleteCounted(db, `period < ?${tenantCondition}`, [cutoffDay, ...tenantParams]);
1658
+ }
1659
+ if (maxRows != null) {
1660
+ const excess = numberFromRow(getQueryRows(await db.query(`SELECT COUNT(*) AS total FROM _smrt_field_usage_counters${retention.tenantId ? " WHERE tenant_id = ?" : ""}`, ...tenantParams))[0] ?? {}, "total") - Math.floor(maxRows);
1661
+ if (excess > 0) {
1662
+ await db.query(`DELETE FROM _smrt_field_usage_counters
1663
+ WHERE id IN (
1664
+ SELECT id FROM _smrt_field_usage_counters
1665
+ ${retention.tenantId ? "WHERE tenant_id = ?" : ""}
1666
+ ORDER BY period ASC, id ASC
1667
+ LIMIT ${excess}
1668
+ )`, ...tenantParams);
1669
+ pruned += excess;
1670
+ }
1671
+ }
1672
+ return { pruned };
1673
+ }
1674
+ async function pruneFieldUsageReportReceipts(db, options) {
1675
+ if (!Number.isFinite(options.maxAgeMs) || options.maxAgeMs < 0) throw new Error(`pruneFieldUsageReportReceipts maxAgeMs must be >= 0, got ${options.maxAgeMs}`);
1676
+ const cutoffDay = fieldUsagePeriodForDate(new Date(Date.now() - options.maxAgeMs));
1677
+ const tenantCondition = options.tenantId ? " AND tenant_id = ?" : "";
1678
+ const tenantParams = options.tenantId ? [options.tenantId] : [];
1679
+ return { pruned: await deleteCounted(db, `period < ?${tenantCondition}`, [cutoffDay, ...tenantParams], "_smrt_field_usage_report_receipts") };
1680
+ }
1681
+ async function pruneFieldPolicySuggestions(db, retention) {
1682
+ const { acceptedMaxAgeMs } = retention;
1683
+ if (!Number.isFinite(acceptedMaxAgeMs) || acceptedMaxAgeMs < 0) throw new Error(`pruneFieldPolicySuggestions acceptedMaxAgeMs must be >= 0, got ${acceptedMaxAgeMs}`);
1684
+ const now = retention.now ?? /* @__PURE__ */ new Date();
1685
+ const acceptedCutoff = new Date(now.getTime() - acceptedMaxAgeMs);
1686
+ const tenantCondition = retention.tenantId ? " AND tenant_id = ?" : "";
1687
+ const tenantParams = retention.tenantId ? [retention.tenantId] : [];
1688
+ return { pruned: await deleteCounted(db, `((status = 'dismissed' AND cooldown_until IS NOT NULL AND cooldown_until <= ?) OR (status = 'accepted' AND decided_at IS NOT NULL AND decided_at <= ?))${tenantCondition}`, [
1689
+ now.toISOString(),
1690
+ acceptedCutoff.toISOString(),
1691
+ ...tenantParams
1692
+ ], "_smrt_field_policy_suggestions") };
1693
+ }
1694
+ async function runFieldUsageMaintenance(options) {
1695
+ const config = normalizeMaintenanceConfig(options);
1696
+ const ambientTenantId = restrictingTenantId();
1697
+ const counters = await pruneFieldUsageCounters(options.db, {
1698
+ maxAgeMs: config.counterMaxAgeMs,
1699
+ maxRows: config.counterMaxRows,
1700
+ tenantId: ambientTenantId
1701
+ });
1702
+ const receipts = await pruneFieldUsageReportReceipts(options.db, {
1703
+ maxAgeMs: config.counterMaxAgeMs,
1704
+ tenantId: ambientTenantId
1705
+ });
1706
+ const suggestions = await pruneFieldPolicySuggestions(options.db, {
1707
+ acceptedMaxAgeMs: config.suggestionAcceptedMaxAgeMs,
1708
+ tenantId: ambientTenantId
1709
+ });
1710
+ return {
1711
+ countersPruned: counters.pruned,
1712
+ receiptsPruned: receipts.pruned,
1713
+ suggestionsPruned: suggestions.pruned
1714
+ };
1715
+ }
1716
+ async function runFieldPolicySuggestionGeneration(options) {
1717
+ const config = normalizeSuggestionConfig(options);
1718
+ const now = options.now ?? /* @__PURE__ */ new Date();
1719
+ const ambientTenantId = restrictingTenantId();
1720
+ const counters = await FieldUsageCounterCollection.create({ db: options.db });
1721
+ const suggestions = await FieldPolicySuggestionCollection.create({ db: options.db });
1722
+ const fromPeriod = fieldUsagePeriodForDate(/* @__PURE__ */ new Date(now.getTime() - (config.windowDays - 1) * DAY_MS));
1723
+ const toPeriod = fieldUsagePeriodForDate(now);
1724
+ const groups = groupCounters(await counters.listWindow({
1725
+ fromPeriod,
1726
+ toPeriod,
1727
+ tenantId: ambientTenantId
1728
+ }));
1729
+ const fieldMaps = /* @__PURE__ */ new Map();
1730
+ const resolvedPolicies = /* @__PURE__ */ new Map();
1731
+ const summary = {
1732
+ created: 0,
1733
+ suppressed: 0,
1734
+ groupsConsidered: 0,
1735
+ groupsFailed: 0,
1736
+ failures: []
1737
+ };
1738
+ for (const group of groups) {
1739
+ summary.groupsConsidered += 1;
1740
+ try {
1741
+ await evaluateGroup(group);
1742
+ } catch (error) {
1743
+ summary.groupsFailed += 1;
1744
+ if (summary.failures.length < 5) summary.failures.push(`${group.tenantId} ${group.objectRef}.${group.fieldName}: ${error instanceof Error ? error.message : String(error)}`);
1745
+ }
1746
+ }
1747
+ return summary;
1748
+ async function evaluateGroup(group) {
1749
+ let fieldMap = fieldMaps.get(group.objectRef);
1750
+ if (fieldMap === void 0) {
1751
+ try {
1752
+ fieldMap = await getObjectFieldMap(group.objectRef);
1753
+ } catch {
1754
+ fieldMap = null;
1755
+ }
1756
+ fieldMaps.set(group.objectRef, fieldMap);
1757
+ }
1758
+ if (!fieldMap) return;
1759
+ const fieldDef = fieldMap.get(group.fieldName);
1760
+ if (!fieldDef || isSensitiveField(fieldDef) || getFieldReadPermission(fieldDef) !== void 0 || isTransientField(fieldDef)) return;
1761
+ const stats = mergeGroupStats(group.buckets);
1762
+ const policyKey = `${group.tenantId}\0${group.objectRef}`;
1763
+ let orgFields = resolvedPolicies.get(policyKey);
1764
+ if (!orgFields) {
1765
+ const { resolveFieldPolicy } = await Promise.resolve().then(() => field_policy_resolver_exports);
1766
+ orgFields = (await resolveFieldPolicy(group.objectRef, {
1767
+ tenantId: group.tenantId,
1768
+ db: options.db
1769
+ })).fields;
1770
+ resolvedPolicies.set(policyKey, orgFields);
1771
+ }
1772
+ const fieldPolicy = orgFields[group.fieldName];
1773
+ if (!fieldPolicy) return;
1774
+ if (stats.distinctUsers >= config.minDistinctUsers && fieldPolicy.visibility !== "basic") if (await createUnlessSuppressed(suggestions, {
1775
+ tenantId: group.tenantId,
1776
+ objectRef: group.objectRef,
1777
+ fieldName: group.fieldName,
1778
+ kind: "promote",
1779
+ proposedValue: null,
1780
+ evidence: buildFieldUsageEvidence({
1781
+ kind: "promote",
1782
+ fieldName: group.fieldName,
1783
+ objectRef: group.objectRef,
1784
+ windowStart: fromPeriod,
1785
+ windowEnd: toPeriod,
1786
+ distinctUsers: stats.distinctUsers,
1787
+ distinctUsersAtLeast: stats.distinctUsersOverflowed,
1788
+ setCount: stats.setCount,
1789
+ submissionCount: stats.submissionTotalKnown ? stats.submissionCount : void 0,
1790
+ threshold: config.minDistinctUsers
1791
+ }),
1792
+ now
1793
+ })) summary.created += 1;
1794
+ else summary.suppressed += 1;
1795
+ if (isHistogramEligibleField(fieldDef) && stats.submissionTotalKnown && stats.submissionCount >= config.minSetCount) {
1796
+ const top = topHistogramEntry(stats.histogram);
1797
+ if (top) {
1798
+ const share = top.count / stats.submissionCount;
1799
+ const proposed = decodeHistogramKey(fieldDef, top.key);
1800
+ const currentDefault = fieldPolicy.hasDefault ? fieldPolicy.defaultValue : void 0;
1801
+ if (share >= config.defaultDominanceRatio && !sameProposedValue(proposed, currentDefault)) if (await createUnlessSuppressed(suggestions, {
1802
+ tenantId: group.tenantId,
1803
+ objectRef: group.objectRef,
1804
+ fieldName: group.fieldName,
1805
+ kind: "default",
1806
+ proposedValue: JSON.stringify(proposed),
1807
+ evidence: buildFieldUsageEvidence({
1808
+ kind: "default",
1809
+ fieldName: group.fieldName,
1810
+ objectRef: group.objectRef,
1811
+ windowStart: fromPeriod,
1812
+ windowEnd: toPeriod,
1813
+ distinctUsers: stats.distinctUsers,
1814
+ distinctUsersAtLeast: stats.distinctUsersOverflowed,
1815
+ setCount: stats.setCount,
1816
+ submissionCount: stats.submissionCount,
1817
+ threshold: config.minSetCount,
1818
+ topValue: proposed,
1819
+ topValueShare: share
1820
+ }),
1821
+ now
1822
+ })) summary.created += 1;
1823
+ else summary.suppressed += 1;
1824
+ }
1825
+ }
1826
+ }
1827
+ }
1828
+ var FieldUsageLearningAgent = class extends SmrtObject {
1829
+ constructor(options = {}) {
1830
+ super(options);
1831
+ }
1832
+ /** Schedule entry point for {@link runFieldUsageMaintenance}. */
1833
+ async runUsageMaintenance(args = {}) {
1834
+ return runFieldUsageMaintenance({
1835
+ db: this.db,
1836
+ ...pickFiniteNumbers(args, [
1837
+ "counterMaxAgeMs",
1838
+ "counterMaxRows",
1839
+ "suggestionAcceptedMaxAgeMs"
1840
+ ])
1841
+ });
1842
+ }
1843
+ /** Schedule entry point for {@link runFieldPolicySuggestionGeneration}. */
1844
+ async runSuggestionGeneration(args = {}) {
1845
+ return runFieldPolicySuggestionGeneration({
1846
+ db: this.db,
1847
+ ...pickFiniteNumbers(args, [
1848
+ "windowDays",
1849
+ "minDistinctUsers",
1850
+ "minSetCount",
1851
+ "defaultDominanceRatio"
1852
+ ])
1853
+ });
1854
+ }
1855
+ };
1856
+ /**
1857
+ * The smrt-jobs opt-in background allowlist (S5 contract): ONLY these two
1858
+ * methods are reachable from a persisted job/schedule row.
1859
+ */
1860
+ __publicField(FieldUsageLearningAgent, "backgroundEligibleMethods", ["runUsageMaintenance", "runSuggestionGeneration"]);
1861
+ FieldUsageLearningAgent = __decorateClass([smrt({
1862
+ tableName: "_smrt_field_usage_learning_agents",
1863
+ api: { include: [] },
1864
+ cli: false,
1865
+ mcp: { include: [] }
1866
+ })], FieldUsageLearningAgent);
1867
+ function buildFieldUsageEvidence(input) {
1868
+ const users = `${input.distinctUsersAtLeast ? "at least " : ""}${input.distinctUsers} user${input.distinctUsers === 1 && !input.distinctUsersAtLeast ? "" : "s"}`;
1869
+ const deviations = `${input.setCount} of ` + (input.submissionCount === void 0 ? "an unrecorded number of submissions" : `${input.submissionCount} submission${input.submissionCount === 1 ? "" : "s"}`);
1870
+ const base = `${users} set "${input.fieldName}" to a non-default value in ${deviations} between ${input.windowStart} and ${input.windowEnd}.`;
1871
+ return {
1872
+ summary: input.kind === "default" ? `${base} ${Math.round((input.topValueShare ?? 0) * 100)}% of all ${input.submissionCount ?? 0} submissions used the value ${JSON.stringify(input.topValue)}.` : base,
1873
+ objectRef: input.objectRef,
1874
+ fieldName: input.fieldName,
1875
+ windowStart: input.windowStart,
1876
+ windowEnd: input.windowEnd,
1877
+ distinctUsers: input.distinctUsers,
1878
+ ...input.distinctUsersAtLeast ? { distinctUsersAtLeast: true } : {},
1879
+ setCount: input.setCount,
1880
+ ...input.submissionCount !== void 0 ? { submissionCount: input.submissionCount } : { submissionCountUnknown: true },
1881
+ threshold: input.threshold,
1882
+ ...input.topValue !== void 0 ? { topValue: input.topValue } : {},
1883
+ ...input.topValueShare !== void 0 ? { topValueShare: Number(input.topValueShare.toFixed(4)) } : {}
1884
+ };
1885
+ }
1886
+ function groupCounters(rows) {
1887
+ const byKey = /* @__PURE__ */ new Map();
1888
+ for (const row of rows) {
1889
+ if (!row.tenantId) continue;
1890
+ const key = `${row.tenantId}\0${row.objectRef}\0${row.fieldName}`;
1891
+ let group = byKey.get(key);
1892
+ if (!group) {
1893
+ group = {
1894
+ tenantId: row.tenantId,
1895
+ objectRef: row.objectRef,
1896
+ fieldName: row.fieldName,
1897
+ buckets: []
1898
+ };
1899
+ byKey.set(key, group);
1900
+ }
1901
+ group.buckets.push(row);
1902
+ }
1903
+ return [...byKey.values()];
1904
+ }
1905
+ function mergeGroupStats(buckets) {
1906
+ let submissionCount = 0;
1907
+ let submissionTotalKnown = true;
1908
+ let setCount = 0;
1909
+ let overflowed = false;
1910
+ const users = /* @__PURE__ */ new Set();
1911
+ const histogram = emptyHistogram();
1912
+ for (const bucket of buckets) {
1913
+ submissionCount += bucket.submissionCount;
1914
+ if (bucket.isLegacyBucket()) submissionTotalKnown = false;
1915
+ setCount += bucket.setCount;
1916
+ overflowed = overflowed || bucket.distinctUsersOverflowed;
1917
+ for (const id of bucket.getDistinctUserIds()) users.add(id);
1918
+ for (const [key, count] of Object.entries(bucket.getValueHistogram())) histogram[key] = (Object.hasOwn(histogram, key) ? histogram[key] : 0) + count;
1919
+ }
1920
+ return {
1921
+ submissionCount,
1922
+ submissionTotalKnown,
1923
+ setCount,
1924
+ distinctUsers: users.size,
1925
+ distinctUsersOverflowed: overflowed,
1926
+ histogram
1927
+ };
1928
+ }
1929
+ function topHistogramEntry(histogram) {
1930
+ let top = null;
1931
+ for (const [key, count] of Object.entries(histogram)) if (!top || count > top.count) top = {
1932
+ key,
1933
+ count
1934
+ };
1935
+ return top;
1936
+ }
1937
+ function sameProposedValue(a, b) {
1938
+ if (a === b) return true;
1939
+ try {
1940
+ return JSON.stringify(a) === JSON.stringify(b);
1941
+ } catch {
1942
+ return false;
1943
+ }
1944
+ }
1945
+ async function createUnlessSuppressed(suggestions, candidate) {
1946
+ if ((await suggestions.list({ where: {
1947
+ tenantId: candidate.tenantId,
1948
+ objectRef: candidate.objectRef,
1949
+ fieldName: candidate.fieldName,
1950
+ kind: candidate.kind
1951
+ } })).some((row) => row.activeKey === "active" || row.status === "dismissed" && isWithinCoolDown(row, candidate.now))) return false;
1952
+ await suggestions.create({
1953
+ tenantId: candidate.tenantId,
1954
+ objectRef: candidate.objectRef,
1955
+ fieldName: candidate.fieldName,
1956
+ kind: candidate.kind,
1957
+ proposedValue: candidate.proposedValue,
1958
+ evidence: JSON.stringify(candidate.evidence),
1959
+ status: "pending"
1960
+ });
1961
+ return true;
1962
+ }
1963
+ function isWithinCoolDown(row, now) {
1964
+ const raw = row.cooldownUntil;
1965
+ if (raw === null || raw === void 0) return false;
1966
+ const until = raw instanceof Date ? raw.getTime() : Date.parse(String(raw));
1967
+ return Number.isFinite(until) && until > now.getTime();
1968
+ }
1969
+ function restrictingTenantId() {
1970
+ const context = getCurrentTenant();
1971
+ if (!context || isSuperAdminBypass()) return null;
1972
+ return context.tenantId;
1973
+ }
1974
+ function normalizeMaintenanceConfig(options) {
1975
+ return {
1976
+ counterMaxAgeMs: nonNegativeOrDefault(options.counterMaxAgeMs, FIELD_USAGE_MAINTENANCE_DEFAULTS.counterMaxAgeMs),
1977
+ counterMaxRows: nonNegativeOrDefault(options.counterMaxRows, FIELD_USAGE_MAINTENANCE_DEFAULTS.counterMaxRows),
1978
+ suggestionAcceptedMaxAgeMs: nonNegativeOrDefault(options.suggestionAcceptedMaxAgeMs, FIELD_USAGE_MAINTENANCE_DEFAULTS.suggestionAcceptedMaxAgeMs)
1979
+ };
1980
+ }
1981
+ function normalizeSuggestionConfig(options) {
1982
+ const ratio = positiveOrDefault(options.defaultDominanceRatio, FIELD_USAGE_SUGGESTION_DEFAULTS.defaultDominanceRatio);
1983
+ return {
1984
+ windowDays: Math.max(1, Math.floor(positiveOrDefault(options.windowDays, FIELD_USAGE_SUGGESTION_DEFAULTS.windowDays))),
1985
+ minDistinctUsers: Math.max(1, Math.floor(positiveOrDefault(options.minDistinctUsers, FIELD_USAGE_SUGGESTION_DEFAULTS.minDistinctUsers))),
1986
+ minSetCount: Math.max(1, Math.floor(positiveOrDefault(options.minSetCount, FIELD_USAGE_SUGGESTION_DEFAULTS.minSetCount))),
1987
+ defaultDominanceRatio: Math.min(Math.max(ratio, .01), 1)
1988
+ };
1989
+ }
1990
+ function positiveOrDefault(value, fallback) {
1991
+ return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : fallback;
1992
+ }
1993
+ function nonNegativeOrDefault(value, fallback) {
1994
+ return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : fallback;
1995
+ }
1996
+ function pickFiniteNumbers(args, keys) {
1997
+ const picked = {};
1998
+ if (!args || typeof args !== "object") return picked;
1999
+ for (const key of keys) {
2000
+ const value = args[key];
2001
+ if (typeof value === "number" && Number.isFinite(value)) picked[key] = value;
2002
+ }
2003
+ return picked;
2004
+ }
2005
+ async function deleteCounted(db, condition, params, table = "_smrt_field_usage_counters") {
2006
+ const total = numberFromRow(getQueryRows(await db.query(`SELECT COUNT(*) AS total FROM ${table} WHERE ${condition}`, ...params))[0] ?? {}, "total");
2007
+ if (total > 0) await db.query(`DELETE FROM ${table} WHERE ${condition}`, ...params);
2008
+ return total;
2009
+ }
2010
+ function getQueryRows(result) {
2011
+ return Array.isArray(result) ? result : result?.rows ?? [];
2012
+ }
2013
+ function numberFromRow(row, key) {
2014
+ const value = row[key];
2015
+ if (typeof value === "number") return value;
2016
+ if (typeof value === "bigint") return Number(value);
2017
+ if (typeof value === "string") return Number.parseFloat(value) || 0;
2018
+ return 0;
2019
+ }
2020
+ //#endregion
2021
+ //#region src/users-module.ts
2022
+ var MISSING_MODULE_TARGET_PATTERN = /Cannot find (?:package|module) '([^']+)'/;
2023
+ function isPackageSpecifier(target, packageName) {
2024
+ return target === packageName || target.startsWith(`${packageName}/`);
2025
+ }
2026
+ function isMissingWorkspaceDependency(error, packageName) {
2027
+ let current = error;
2028
+ const seen = /* @__PURE__ */ new Set();
2029
+ while (current instanceof Error && !seen.has(current)) {
2030
+ seen.add(current);
2031
+ const match = current.message.match(MISSING_MODULE_TARGET_PATTERN);
2032
+ if (match && isPackageSpecifier(match[1], packageName)) return true;
2033
+ if (current.message.includes(`Failed to load ${packageName} for`)) return true;
2034
+ current = current.cause;
2035
+ }
2036
+ return false;
2037
+ }
2038
+ //#endregion
2039
+ //#region src/usage-schedules.ts
2040
+ var FIELD_USAGE_LEARNING_AGENT_TYPE = "@happyvertical/smrt-fields:FieldUsageLearningAgent";
2041
+ var FIELD_USAGE_MAINTENANCE_METHOD = "runUsageMaintenance";
2042
+ var FIELD_USAGE_SUGGESTION_METHOD = "runSuggestionGeneration";
2043
+ function fieldUsageScheduleId(method) {
2044
+ return deterministicFieldsUuid([
2045
+ "field-usage-learning-schedule",
2046
+ FIELD_USAGE_LEARNING_AGENT_TYPE,
2047
+ method
2048
+ ]);
2049
+ }
2050
+ async function ensureFieldUsageLearningSchedules(options) {
2051
+ const context = getCurrentTenant();
2052
+ if (context && !isSuperAdminBypass()) throw new TenantIsolationError("ensureFieldUsageLearningSchedules installs GLOBAL schedules and must run from trusted execution (no ambient tenant context, or super-admin bypass)", { tenantId: context.tenantId });
2053
+ let agentsModule = options.agentsModule;
2054
+ if (!agentsModule) try {
2055
+ agentsModule = await importWorkspaceModule({
2056
+ packageName: "@happyvertical/smrt-agents",
2057
+ sourceEntry: "packages/agents/src/index.ts",
2058
+ purpose: "field usage learning schedule installation"
2059
+ });
2060
+ } catch (error) {
2061
+ if (isMissingWorkspaceDependency(error, "@happyvertical/smrt-agents")) return {
2062
+ installed: false,
2063
+ created: 0
2064
+ };
2065
+ throw error;
2066
+ }
2067
+ const schedules = await agentsModule.AgentScheduleCollection.create({ db: options.db });
2068
+ const enabled = options.enabled ?? false;
2069
+ let created = 0;
2070
+ const definitions = [{
2071
+ method: FIELD_USAGE_MAINTENANCE_METHOD,
2072
+ cron: options.maintenanceCron ?? "30 2 * * *",
2073
+ methodArgs: options.maintenanceArgs ?? {}
2074
+ }, {
2075
+ method: FIELD_USAGE_SUGGESTION_METHOD,
2076
+ cron: options.suggestionCron ?? "0 3 * * 1",
2077
+ methodArgs: options.suggestionArgs ?? {}
2078
+ }];
2079
+ for (const definition of definitions) {
2080
+ if ((await schedules.list({ where: {
2081
+ agentType: "@happyvertical/smrt-fields:FieldUsageLearningAgent",
2082
+ method: definition.method
2083
+ } })).some(isGlobalScheduleRow)) continue;
2084
+ const id = await fieldUsageScheduleId(definition.method);
2085
+ let row;
2086
+ try {
2087
+ row = await schedules.create({
2088
+ id,
2089
+ tenantId: null,
2090
+ agentType: FIELD_USAGE_LEARNING_AGENT_TYPE,
2091
+ agentId: null,
2092
+ cron: definition.cron,
2093
+ method: definition.method,
2094
+ agentConfig: {},
2095
+ methodArgs: definition.methodArgs,
2096
+ enabled,
2097
+ status: enabled ? "active" : "disabled",
2098
+ _insertOnly: true
2099
+ });
2100
+ } catch (error) {
2101
+ if ((await schedules.list({ where: { id } })).length === 0) throw error;
2102
+ continue;
2103
+ }
2104
+ await row.save?.();
2105
+ created += 1;
2106
+ }
2107
+ return {
2108
+ installed: true,
2109
+ created
2110
+ };
2111
+ }
2112
+ function isGlobalScheduleRow(row) {
2113
+ return row.tenantId === null || row.tenantId === void 0;
2114
+ }
2115
+ //#endregion
2116
+ //#region src/index.ts
2117
+ ensureFieldPolicyPermissionsRegistered();
2118
+ //#endregion
2119
+ export { ACTIVE_SUGGESTION_KEY, APP_FIELD_POLICY_SCOPE_KEY, FIELD_POLICY_PERMISSION_DEFINITIONS, FIELD_POLICY_SCOPE_TYPES, FIELD_POLICY_VISIBILITIES, FIELD_USAGE_LEARNING_AGENT_TYPE, FieldPolicy, FieldPolicyCollection, FieldPolicySuggestion, FieldPolicySuggestionCollection, FieldPolicySuggestionConflictError, FieldUsageCounter, FieldUsageCounterCollection, FieldUsageLearningAgent, MANAGE_FIELD_POLICY_PERMISSION, MAX_DISTINCT_USERS_PER_BUCKET, MAX_USAGE_REPORT_ENTRIES, MAX_VALUE_HISTOGRAM_BUCKETS, PERSONALIZE_FIELD_POLICY_PERMISSION, assertDefaultValueMatchesFieldType, buildCodeSeedDelta, buildCodeSeedVisibility, buildFieldPolicySettingsCatalog, clearFieldPolicyCache, decodeHistogramKey, ensureFieldPolicyPermissionsRegistered, ensureFieldUsageLearningSchedules, fieldPolicyCatalogItemId, fieldUsagePeriodForDate, getCodeDefault, getCodeSeedGroup, getFieldPolicyCacheTtlMs, getFieldReadPermission, getObjectFieldMap, invalidateFieldPolicyCache, isHistogramEligibleField, isPolicyAddressableField, isRequiredField, isSensitiveField, isStorableReferenceId, isTransientField, isUsableRequiredDefault, parseFieldPolicyCatalogQuery, pruneFieldPolicySuggestions, pruneFieldUsageCounters, requireRegisteredObject, resolveFieldPolicy, resolveFieldPolicyExplained, resolveSurvivingTenantChainIds, runFieldPolicySuggestionGeneration, runFieldUsageMaintenance, sanitizeFieldUIHints, serializeHistogramSample, field_policy_resolver_exports as t };
935
2120
 
936
2121
  //# sourceMappingURL=index.js.map