@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
@@ -0,0 +1,163 @@
1
+ import { SmrtObject } from '@happyvertical/smrt-core';
2
+ import { FieldPolicyOptions, FieldPolicyScopeType, FieldPolicyVisibility } from '../types.js';
3
+ /**
4
+ * Sparse, layered field policy override (epic #2045, issue #2047).
5
+ *
6
+ * One row personalizes a subset of `{defaultValue, visibility, help, label,
7
+ * displayOrder, locked}` for a single `(objectRef, fieldName)` at one scope
8
+ * tier. A NULL column means "inherit from the lower layer" (code seed → app →
9
+ * tenant → user), so resetting a customization is a row DELETE — later
10
+ * lower-layer changes then flow through (sparse-delta rationale, #1770).
11
+ *
12
+ * Rows are validated at write time against the live `ObjectRegistry` (the
13
+ * manifest is the definition registry): unknown objects/fields are rejected,
14
+ * defaults are type-checked, and the security rail (`sensitive` /
15
+ * `readPermission` / `transient`) refuses stored defaults outright.
16
+ */
17
+ export declare class FieldPolicy extends SmrtObject {
18
+ /** Qualified class name of the target object (`@package/name:ClassName`). */
19
+ objectRef: string;
20
+ /** Field name on the target object (validated against the registry). */
21
+ fieldName: string;
22
+ /** Scope tier this row belongs to ('app' | 'tenant' | 'user'). */
23
+ scopeType: FieldPolicyScopeType;
24
+ /** Owning tenant for tenant-scope rows; NULL otherwise (native UUID on PG). */
25
+ tenantId: string | null;
26
+ /** Owning user for user-scope rows; NULL otherwise. */
27
+ userId: string | null;
28
+ /**
29
+ * Computed uniqueness key (`userId ?? tenantId ?? '__app__'`), set in
30
+ * `save()`. Used ONLY by `conflictColumns` so the unique index stays total
31
+ * while `tenantId`/`userId` are nullable — mirrors `PromptOverride.context`.
32
+ * Never read it for scoping logic; `scopeType` + the typed columns own that.
33
+ */
34
+ scopeKey: string;
35
+ /** JSON-encoded default value; NULL = inherit. JSON `null` = "default to null". */
36
+ defaultValue: string | null;
37
+ /** Visibility override ('basic' | 'advanced' | 'hidden'); NULL = inherit. */
38
+ visibility: FieldPolicyVisibility | null;
39
+ /** Help text override; NULL = inherit (code seed: field description). */
40
+ help: string | null;
41
+ /** Label override; NULL = inherit (consumers derive from the field name). */
42
+ label: string | null;
43
+ /**
44
+ * Sort-order override; NULL = inherit (code seed: `ui.order`). Named
45
+ * `displayOrder` because a column literally named `order` is an SQL keyword
46
+ * the runtime INSERT path does not quote; resolved output exposes `order`.
47
+ */
48
+ displayOrder: number | null;
49
+ /**
50
+ * Org lock (app/tenant rows only): when the effective lock is true, the
51
+ * user tier may not override this field. NULL = inherit (code seed:
52
+ * `ui.locked`); org rows may set `false` to explicitly unlock.
53
+ */
54
+ locked: boolean | null;
55
+ /** Audit attribution for #2050 ("who changed what"); not validated. */
56
+ updatedBy: string | null;
57
+ constructor(options?: FieldPolicyOptions);
58
+ /** Parse the stored JSON default. `undefined` = no stored default (inherit). */
59
+ getDefaultValue(): unknown;
60
+ /** Serialize a default value; `undefined` clears the override (inherit). */
61
+ setDefaultValue(value: unknown): void;
62
+ save(): Promise<this>;
63
+ private saveAfterIdentityChange;
64
+ private saveAfterIdentityChangeInTransaction;
65
+ private saveAfterIdentityChangeWithDeferredDelete;
66
+ delete(): Promise<void>;
67
+ private validateFieldPolicy;
68
+ /**
69
+ * Effective org-tier (code → app → tenant hierarchy) policy for this row's
70
+ * field, computed by the RESOLVER so write-time checks share the one
71
+ * precedence implementation — including ancestor-tenant cascades via the
72
+ * default hierarchy loader. The chain tenant is the row's own tenant for
73
+ * tenant-scope rows, the ambient context tenant for user-scope rows, and
74
+ * none for app-scope rows (their only lower layer is the code seed).
75
+ *
76
+ * During an update, the persisted row is excluded from the lower-layer
77
+ * projection. That makes a clear-default + required-field demotion validate
78
+ * the post-write policy rather than accidentally counting the row being
79
+ * replaced.
80
+ */
81
+ private resolveOrgTierFieldPolicy;
82
+ /**
83
+ * Exactly-one-owner scope shape: app rows carry neither id, tenant rows
84
+ * carry only `tenantId`, user rows carry only `userId` (the user tier is
85
+ * keyed by user alone so preferences follow the user across tenants).
86
+ */
87
+ private validateScopeConsistency;
88
+ /** `userId ?? tenantId ?? '__app__'` — the `conflictColumns` scope key. */
89
+ private computeScopeKey;
90
+ /**
91
+ * Derive a scope-owner column the transport could not carry (#2047).
92
+ *
93
+ * Core's mass-assignment guard treats `tenantId` as server-managed and
94
+ * strips it from EVERY generated create/update body, and `FieldPolicy` is
95
+ * deliberately not `@TenantScoped`, so the tenancy interceptor never
96
+ * repopulates it. A `POST {scopeType:'tenant', tenantId}` therefore always
97
+ * reached `validateScopeConsistency` with `tenantId === null`, making the
98
+ * org tier write-dead over every generated surface — the model, as the
99
+ * single validation authority for these rows, fills it in instead.
100
+ *
101
+ * This grants nothing: `assertScopeOwnedByAmbientContext` already requires
102
+ * a non-bypass caller's tenant/user rows to name exactly the ambient
103
+ * tenant/user, so the derived value is the ONLY value that could ever have
104
+ * been accepted. An explicit value is never overwritten (a super-admin
105
+ * bypass caller keeps naming other scopes), and with neither tenancy nor
106
+ * authenticated-session context nothing is derived — scope-shape validation
107
+ * rejects the row as before.
108
+ * Consequence: a bypass caller writing ANOTHER tenant's row must pass
109
+ * `tenantId` through a server-side model call, because the generated routes
110
+ * still strip it.
111
+ */
112
+ private attributeScopeToAmbientContext;
113
+ /**
114
+ * Fail-closed write boundary against the ambient tenant context, applied to
115
+ * the NEW scope on save and to the PERSISTED scope on save/delete of an
116
+ * existing row.
117
+ *
118
+ * With NO ambient identity at all (neither tenant ALS nor authenticated
119
+ * session-permission ALS), only app-scope rows are accepted: tenant- and
120
+ * user-scope rows are unattributable without a context, so they are
121
+ * rejected outright rather than allowed by default. Inside a non-bypass
122
+ * context, a caller may only touch rows for its own tenant, and user rows
123
+ * only for its own user id — a context that carries NO user id may not
124
+ * touch the user tier at all.
125
+ * App-wide rows then require super-admin bypass (or a context-less/system
126
+ * caller).
127
+ *
128
+ * Package rule: a missing identity component DENIES, it never skips.
129
+ */
130
+ private assertScopeOwnedByAmbientContext;
131
+ private validateTenantContextBoundary;
132
+ /**
133
+ * Permission boundary for policy mutations. Scope ownership is checked
134
+ * separately (and first) because permission never grants cross-tenant or
135
+ * cross-user access. Existing rows are checked against their persisted
136
+ * scope before an identity-changing save can mutate or delete them.
137
+ */
138
+ private assertScopePermission;
139
+ private validateDefaultAgainstSecurityRail;
140
+ private parseDefaultValueOrThrow;
141
+ /**
142
+ * The persisted row this save/delete would replace, looked up by primary
143
+ * key and — when that misses — by the NATURAL key.
144
+ *
145
+ * The natural-key fallback is load-bearing for authorization: every
146
+ * generated create arrives with a freshly minted UUID, so a primary-key
147
+ * lookup always misses, yet the `conflictColumns` upsert still overwrites
148
+ * whatever row already occupies `(objectRef, fieldName, scopeType,
149
+ * scopeKey)`. Authorizing on the primary key alone therefore skipped the
150
+ * persisted-scope guard on exactly the path that can replace an existing
151
+ * row's contents.
152
+ */
153
+ private getPersistedIdentity;
154
+ private getPersistedRowById;
155
+ /**
156
+ * The row occupying this row's `conflictColumns` tuple, if any. Column
157
+ * names are the physical snake_case ones: this reads the driver directly
158
+ * rather than through a collection (the model has no collection handle).
159
+ */
160
+ private getPersistedRowByNaturalKey;
161
+ private normalizeDefaultValueForPersistence;
162
+ }
163
+ //# sourceMappingURL=FieldPolicy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FieldPolicy.d.ts","sourceRoot":"","sources":["../../src/models/FieldPolicy.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,UAAU,EAEX,MAAM,0BAA0B,CAAC;AA4BlC,OAAO,EAIL,KAAK,kBAAkB,EACvB,KAAK,oBAAoB,EACzB,KAAK,qBAAqB,EAE3B,MAAM,aAAa,CAAC;AA6GrB;;;;;;;;;;;;;GAaG;AASH,qBAgBa,WAAY,SAAQ,UAAU;IACzC,6EAA6E;IAE7E,SAAS,EAAE,MAAM,CAAM;IAEvB,wEAAwE;IAExE,SAAS,EAAE,MAAM,CAAM;IAEvB,kEAAkE;IAElE,SAAS,EAAE,oBAAoB,CAAS;IAExC,+EAA+E;IAE/E,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAQ;IAE/B,uDAAuD;IAEvD,MAAM,EAAE,MAAM,GAAG,IAAI,CAAQ;IAE7B;;;;;OAKG;IAEH,QAAQ,EAAE,MAAM,CAAM;IAEtB,mFAAmF;IAEnF,YAAY,EAAE,MAAM,GAAG,IAAI,CAAQ;IAEnC,6EAA6E;IAE7E,UAAU,EAAE,qBAAqB,GAAG,IAAI,CAAQ;IAEhD,yEAAyE;IAEzE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAQ;IAE3B,6EAA6E;IAE7E,KAAK,EAAE,MAAM,GAAG,IAAI,CAAQ;IAE5B;;;;OAIG;IAEH,YAAY,EAAE,MAAM,GAAG,IAAI,CAAQ;IAEnC;;;;OAIG;IAEH,MAAM,EAAE,OAAO,GAAG,IAAI,CAAQ;IAE9B,uEAAuE;IAEvE,SAAS,EAAE,MAAM,GAAG,IAAI,CAAQ;gBAEpB,OAAO,GAAE,kBAAuB;IAsC5C,gFAAgF;IAChF,eAAe,IAAI,OAAO;IAW1B,4EAA4E;IAC5E,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI;IAItB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;YAkDtB,uBAAuB;YAQvB,oCAAoC;YA+BpC,yCAAyC;IA6BxC,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;YAiBxB,mBAAmB;IA8HjC;;;;;;;;;;;;OAYG;YACW,yBAAyB;IAuBvC;;;;OAIG;IACH,OAAO,CAAC,wBAAwB;IAwBhC,2EAA2E;IAC3E,OAAO,CAAC,eAAe;IAIvB;;;;;;;;;;;;;;;;;;;;;OAqBG;IACH,OAAO,CAAC,8BAA8B;IAatC;;;;;;;;;;;;;;;;OAgBG;IACH,OAAO,CAAC,gCAAgC;IA0ExC,OAAO,CAAC,6BAA6B;IAWrC;;;;;OAKG;YACW,qBAAqB;IAoBnC,OAAO,CAAC,kCAAkC;IAwB1C,OAAO,CAAC,wBAAwB;IAkBhC;;;;;;;;;;;OAWG;YACW,oBAAoB;YAkCpB,mBAAmB;IAQjC;;;;OAIG;YACW,2BAA2B;IAgBzC,OAAO,CAAC,mCAAmC;CAiB5C"}
@@ -0,0 +1,132 @@
1
+ import { SmrtObject, SmrtObjectOptions } from '@happyvertical/smrt-core';
2
+ import { FieldPolicySuggestionData, FieldPolicySuggestionKind, FieldPolicySuggestionStatus } from '../types.js';
3
+ /**
4
+ * `activeKey` sentinel for the single ACTIVE (pending) suggestion per
5
+ * `(objectRef, fieldName, tenantId, kind)`. Settled rows key themselves by id,
6
+ * so history never competes for the slot.
7
+ */
8
+ export declare const ACTIVE_SUGGESTION_KEY = "active";
9
+ export interface FieldPolicySuggestionOptions extends SmrtObjectOptions {
10
+ objectRef?: string;
11
+ fieldName?: string;
12
+ tenantId?: string;
13
+ kind?: FieldPolicySuggestionKind;
14
+ proposedValue?: string | null;
15
+ evidence?: string;
16
+ status?: FieldPolicySuggestionStatus;
17
+ cooldownUntil?: Date | null;
18
+ decidedBy?: string | null;
19
+ decidedAt?: Date | null;
20
+ }
21
+ /**
22
+ * A pending, human-reviewable field-policy improvement proposed from real
23
+ * usage (epic #2045, issue #2051): promote a field to the `basic` tier, or
24
+ * seed an org default with the dominant observed value.
25
+ *
26
+ * Suggestion-first by design — a row DOES NOTHING until a
27
+ * `fields.policy.manage` holder accepts it, and acceptance writes the
28
+ * org-scope {@link ../models/FieldPolicy.FieldPolicy} row through NORMAL
29
+ * validation (registry check, type check, security rail, required-field
30
+ * invariant, ownership + permission split). Dismissing sets a cool-down that
31
+ * suppresses regeneration of the same suggestion.
32
+ *
33
+ * **One ACTIVE suggestion per identity, structurally** (not merely by a
34
+ * check-then-insert): {@link activeKey} is a computed column holding the
35
+ * sentinel {@link ACTIVE_SUGGESTION_KEY} while the row is `pending` and the
36
+ * row's own id once it settles, and it participates in `conflictColumns`. The
37
+ * unique index therefore admits at most ONE pending row per
38
+ * `(objectRef, fieldName, tenantId, kind)` while every settled row keys
39
+ * itself — so two overlapping generation runs (e.g. a global and a
40
+ * tenant-specific schedule) UPSERT onto the same row instead of duplicating,
41
+ * with no transaction spanning their reads. It is the `FieldPolicy.scopeKey`
42
+ * trick applied to a lifecycle slot. On settle the column flips to the row's
43
+ * id, which frees the slot for a post-cool-down regeneration while keeping the
44
+ * dismissed/accepted history (and its id) intact — core conflicts a persisted
45
+ * row on its primary key (#1472), so the flip is a plain UPDATE.
46
+ *
47
+ * #1885 seam: this substrate is fully independent of personas'
48
+ * `DirectiveProposal` review queue (no shared producer discriminator —
49
+ * deliberately out of scope). Tenant learning agents MAY create
50
+ * FieldPolicySuggestion rows through this model's normal validation; the
51
+ * reviewed `fields.policy.manage` acceptance gate is unchanged by who
52
+ * proposed.
53
+ */
54
+ export declare class FieldPolicySuggestion extends SmrtObject {
55
+ /** Qualified class name of the target object (`@package/name:ClassName`). */
56
+ objectRef: string;
57
+ /** Field name on the target object (validated against the registry). */
58
+ fieldName: string;
59
+ /** Owning tenant (required — suggestions always target one org). */
60
+ tenantId?: string;
61
+ /** What the suggestion proposes ('promote' | 'default'). */
62
+ kind: FieldPolicySuggestionKind;
63
+ /**
64
+ * JSON-encoded proposed default (`kind: 'default'` only) — the exact
65
+ * encoding `FieldPolicy.defaultValue` stores, so acceptance passes it
66
+ * through unchanged. NULL for `promote`.
67
+ */
68
+ proposedValue: string | null;
69
+ /**
70
+ * Human-readable evidence as a JSON string: a `summary` sentence plus the
71
+ * structured window/threshold numbers behind it (see
72
+ * `buildFieldUsageEvidence`).
73
+ */
74
+ evidence: string;
75
+ /** Lifecycle status ('pending' | 'accepted' | 'dismissed'). */
76
+ status: FieldPolicySuggestionStatus;
77
+ /**
78
+ * Computed lifecycle-slot key, set in `save()`: {@link ACTIVE_SUGGESTION_KEY}
79
+ * while `pending`, else the row's own id. It exists ONLY to make the
80
+ * `conflictColumns` unique index express "at most one ACTIVE suggestion per
81
+ * identity, unlimited settled history" (the `FieldPolicy.scopeKey`
82
+ * precedent) — never read it for logic; `status` owns that.
83
+ */
84
+ activeKey: string;
85
+ /**
86
+ * Until this instant, a dismissed suggestion suppresses regeneration of the
87
+ * same `(objectRef, fieldName, tenantId, kind)` suggestion. NULL until
88
+ * dismissed.
89
+ */
90
+ cooldownUntil: Date | null;
91
+ /** Who accepted/dismissed (audit attribution, #2050); not validated. */
92
+ decidedBy: string | null;
93
+ /** When the suggestion was accepted/dismissed. */
94
+ decidedAt: Date | null;
95
+ constructor(options?: FieldPolicySuggestionOptions);
96
+ /** Parse the stored evidence object (guarded; junk parses as empty). */
97
+ getEvidence(): Record<string, unknown>;
98
+ /** Serialize an evidence object into the stored JSON string. */
99
+ setEvidence(evidence: Record<string, unknown>): void;
100
+ /** Parse the proposed value; `undefined` when none is stored. */
101
+ getProposedValue(): unknown;
102
+ /** Serialized row shape for the collection actions. */
103
+ toSuggestionData(): FieldPolicySuggestionData;
104
+ save(): Promise<this>;
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
+ private applyActiveKey;
113
+ delete(): Promise<void>;
114
+ /**
115
+ * The "normal validation" the #1885 seam promises producers: registry-known
116
+ * field, policy-addressable, never sensitive/read-permission-gated/transient
117
+ * (those fields are count-only in usage data and get no suggestions), valid
118
+ * kind/status, and for `default` suggestions a JSON proposed value that
119
+ * type-checks against the manifest field type.
120
+ */
121
+ private validateFieldPolicySuggestion;
122
+ /**
123
+ * Tenant write boundary (the FieldPolicy posture): inside a non-bypass
124
+ * tenant context a caller may only touch its own tenant's suggestions —
125
+ * checked against BOTH the in-memory scope and, for persisted rows, the
126
+ * PERSISTED tenant (a foreign row cannot be re-scoped into the caller's
127
+ * tenant). Trusted execution (no context / bypass) is exempt — that is what
128
+ * lets the scheduled generation job and platform flows operate.
129
+ */
130
+ private assertRowOwnedByAmbientContext;
131
+ }
132
+ //# sourceMappingURL=FieldPolicySuggestion.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FieldPolicySuggestion.d.ts","sourceRoot":"","sources":["../../src/models/FieldPolicySuggestion.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,UAAU,EACV,KAAK,iBAAiB,EAEvB,MAAM,0BAA0B,CAAC;AAclC,OAAO,EAGL,KAAK,yBAAyB,EAC9B,KAAK,yBAAyB,EAC9B,KAAK,2BAA2B,EACjC,MAAM,aAAa,CAAC;AAErB;;;;GAIG;AACH,eAAO,MAAM,qBAAqB,WAAW,CAAC;AAE9C,MAAM,WAAW,4BAA6B,SAAQ,iBAAiB;IACrE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,yBAAyB,CAAC;IACjC,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,2BAA2B,CAAC;IACrC,aAAa,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IAC5B,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,SAAS,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;CACzB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAOH,qBAaa,qBAAsB,SAAQ,UAAU;IACnD,6EAA6E;IAE7E,SAAS,EAAE,MAAM,CAAM;IAEvB,wEAAwE;IAExE,SAAS,EAAE,MAAM,CAAM;IAEvB,oEAAoE;IAEpE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB,4DAA4D;IAE5D,IAAI,EAAE,yBAAyB,CAAa;IAE5C;;;;OAIG;IAEH,aAAa,EAAE,MAAM,GAAG,IAAI,CAAQ;IAEpC;;;;OAIG;IAEH,QAAQ,EAAE,MAAM,CAAQ;IAExB,+DAA+D;IAE/D,MAAM,EAAE,2BAA2B,CAAa;IAEhD;;;;;;OAMG;IAEH,SAAS,EAAE,MAAM,CAAyB;IAE1C;;;;OAIG;IAEH,aAAa,EAAE,IAAI,GAAG,IAAI,CAAQ;IAElC,wEAAwE;IAExE,SAAS,EAAE,MAAM,GAAG,IAAI,CAAQ;IAEhC,kDAAkD;IAElD,SAAS,EAAE,IAAI,GAAG,IAAI,CAAQ;gBAElB,OAAO,GAAE,4BAAiC;IAkBtD,wEAAwE;IACxE,WAAW,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAWtC,gEAAgE;IAChE,WAAW,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;IAIpD,iEAAiE;IACjE,gBAAgB,IAAI,OAAO;IAW3B,uDAAuD;IACvD,gBAAgB,IAAI,yBAAyB;IAgB9B,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAOpC;;;;;;OAMG;IACH,OAAO,CAAC,cAAc;IAWP,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IAKtC;;;;;;OAMG;YACW,6BAA6B;IAiF3C;;;;;;;OAOG;YACW,8BAA8B;CAyC7C"}
@@ -0,0 +1,178 @@
1
+ import { SmrtObject, SmrtObjectOptions } from '@happyvertical/smrt-core';
2
+ /**
3
+ * Cap on distinct-user ids stored per bucket. For threshold questions
4
+ * ("did at least N distinct users set this field?") the capped set is EXACT up
5
+ * to the cap; once overflowed, {@link FieldUsageCounter.distinctUserCount} is
6
+ * an honest LOWER BOUND that trivially satisfies any threshold ≤ the cap.
7
+ */
8
+ export declare const MAX_DISTINCT_USERS_PER_BUCKET = 100;
9
+ /** Cap on histogram buckets per counter row (bounded storage). */
10
+ export declare const MAX_VALUE_HISTOGRAM_BUCKETS = 25;
11
+ /** Longest histogram key recorded; longer samples are skipped (count-only). */
12
+ export declare const MAX_VALUE_HISTOGRAM_KEY_LENGTH = 64;
13
+ /** `period` bucket format: UTC calendar day. */
14
+ export declare const FIELD_USAGE_PERIOD_PATTERN: RegExp;
15
+ /** The UTC day bucket for a timestamp (`YYYY-MM-DD`). */
16
+ export declare function fieldUsagePeriodForDate(date: Date): string;
17
+ /**
18
+ * A prototype-free histogram map.
19
+ *
20
+ * Histogram keys are user-supplied values (an `idType: 'text'` reference id may
21
+ * legitimately be `constructor`, `toString`, or `__proto__`). On a plain object
22
+ * those either resolve to inherited members — making an absent bucket look
23
+ * present and corrupting its count — or, for `__proto__`, invoke the prototype
24
+ * setter instead of creating an own key. A null-prototype object has no such
25
+ * members, so every key behaves like data. Use this everywhere histogram counts
26
+ * are accumulated (storage AND merge paths).
27
+ */
28
+ export declare function emptyHistogram(): Record<string, number>;
29
+ export interface FieldUsageCounterOptions extends SmrtObjectOptions {
30
+ objectRef?: string;
31
+ fieldName?: string;
32
+ tenantId?: string;
33
+ period?: string;
34
+ submissionCount?: number;
35
+ setCount?: number;
36
+ distinctUserCount?: number;
37
+ distinctUserIds?: string;
38
+ distinctUsersOverflowed?: boolean;
39
+ valueHistogram?: string | null;
40
+ valueHistogramOverflowed?: boolean;
41
+ }
42
+ /**
43
+ * Period-bucketed field usage counter (epic #2045, issue #2051).
44
+ *
45
+ * One row aggregates field submissions for a single
46
+ * `(objectRef, fieldName, tenantId, period)` — the substrate the
47
+ * suggestion-generation job reads. Counters are deliberately APPROXIMATE:
48
+ * ingestion is fire-and-forget and concurrent bucket merges may lose an
49
+ * increment (read-modify-write), which is acceptable for usage statistics and
50
+ * documented here rather than papered over.
51
+ *
52
+ * TWO counters, because they answer different questions:
53
+ * - {@link submissionCount} — EVERY observed submission of the field
54
+ * (default-matching or not). It is the denominator for value dominance:
55
+ * "N% of submissions used value V". Without it, a value seen only in
56
+ * deviations would look 100% dominant even against thousands of
57
+ * default-valued submissions.
58
+ * - {@link setCount} — submissions whose value DIFFERED from the resolved
59
+ * default (server-derived). It plus {@link distinctUserIds} is the
60
+ * promote signal ("real users are actively filling this in").
61
+ *
62
+ * Content rails (enforced by the ingestion action, which derives everything
63
+ * from the live registry and never trusts the client):
64
+ * - Sensitive and read-permission-gated fields are COUNT-ONLY: their raw
65
+ * values are never recorded anywhere in usage data — not even for
66
+ * default-matching submissions.
67
+ * - Value histograms exist only for low-cardinality field types (`boolean`,
68
+ * `foreignKey`, `crossPackageRef`) — never free text, even non-sensitive
69
+ * text (PII risk) — with a bounded bucket count and key length. They cover
70
+ * ALL submissions (not just deviations) so the dominance ratio is a true
71
+ * fraction of {@link submissionCount}.
72
+ * - `distinctUserIds` is a capped set with an overflow marker (see
73
+ * {@link MAX_DISTINCT_USERS_PER_BUCKET} for the honesty contract).
74
+ */
75
+ export declare class FieldUsageCounter extends SmrtObject {
76
+ /** Qualified class name of the target object (`@package/name:ClassName`). */
77
+ objectRef: string;
78
+ /** Field name on the target object. */
79
+ fieldName: string;
80
+ /**
81
+ * Owning tenant. REQUIRED: ingestion fails closed without an ambient tenant
82
+ * context, so every row is attributable (and the conflict-column tuple
83
+ * stays total). Native UUID on PostgreSQL/DuckDB.
84
+ */
85
+ tenantId?: string;
86
+ /** UTC day bucket (`YYYY-MM-DD`); lexicographic order is time order. */
87
+ period: string;
88
+ /**
89
+ * EVERY observed submission of this field in the bucket, whether or not the
90
+ * value matched the resolved default — the dominance denominator.
91
+ *
92
+ * Rows written before this column existed carry `0` while `setCount > 0`;
93
+ * {@link isLegacyBucket} detects that shape and the generation job then
94
+ * treats the total as UNKNOWN and skips `default` suggestions for the group
95
+ * (promote, which needs no denominator, still works).
96
+ */
97
+ submissionCount: number;
98
+ /**
99
+ * Submissions whose value DIFFERED from the server-resolved default (the
100
+ * promote signal). Always `<= submissionCount` on rows written by the
101
+ * current ingestion path.
102
+ */
103
+ setCount: number;
104
+ /**
105
+ * Size of the stored distinct-user set. When
106
+ * {@link distinctUsersOverflowed} is true this is a LOWER BOUND (the set is
107
+ * capped), never an estimate.
108
+ */
109
+ distinctUserCount: number;
110
+ /** JSON array of distinct user ids, capped (see the class doc). */
111
+ distinctUserIds: string;
112
+ /** True once a distinct user was NOT added because the set is at its cap. */
113
+ distinctUsersOverflowed: boolean;
114
+ /**
115
+ * JSON object `serializedValue -> count` for histogram-eligible fields;
116
+ * NULL when the field is count-only. Keys are bounded in number and length.
117
+ */
118
+ valueHistogram: string | null;
119
+ /** True once a sample was dropped because the bucket cap was reached. */
120
+ valueHistogramOverflowed: boolean;
121
+ constructor(options?: FieldUsageCounterOptions);
122
+ /** Parse the stored distinct-user set (guarded; junk parses as empty). */
123
+ getDistinctUserIds(): string[];
124
+ /**
125
+ * Add a user to the distinct set, honoring the cap. At the cap the id is
126
+ * NOT added and the overflow marker is set instead, keeping
127
+ * {@link distinctUserCount} an honest lower bound.
128
+ */
129
+ addDistinctUser(userId: string): void;
130
+ /**
131
+ * Parse the stored histogram (guarded; junk parses as empty).
132
+ *
133
+ * Returns a NULL-PROTOTYPE object. Histogram keys are user-supplied ids —
134
+ * an `idType: 'text'` reference may legitimately be `constructor`,
135
+ * `toString`, or `__proto__` — and on a plain object those inherit truthy
136
+ * prototype values (so a missing bucket reads as present) or, for
137
+ * `__proto__`, hit the prototype setter instead of creating an own key.
138
+ * Both would silently corrupt counts. See {@link emptyHistogram}.
139
+ */
140
+ getValueHistogram(): Record<string, number>;
141
+ /**
142
+ * Record one histogram sample under an already-serialized key, honoring the
143
+ * bucket cap (a NEW key past the cap is dropped and the overflow marker
144
+ * set; existing keys keep counting).
145
+ *
146
+ * Bucket presence is an OWN-key test, never a truthiness/`undefined` read,
147
+ * so prototype-shaped ids behave like any other key.
148
+ */
149
+ recordHistogramSample(key: string): void;
150
+ /**
151
+ * Whether this bucket predates the {@link submissionCount} column (or was
152
+ * corrupted): it records deviations without a total, so no honest dominance
153
+ * ratio can be computed from it. The generation job skips `default`
154
+ * suggestions for any group containing such a bucket.
155
+ */
156
+ isLegacyBucket(): boolean;
157
+ save(): Promise<this>;
158
+ delete(): Promise<void>;
159
+ private validateFieldUsageCounter;
160
+ /**
161
+ * Tenant write boundary (the FieldPolicy posture — no class-level
162
+ * `@TenantScoped`, because the learning jobs legitimately operate
163
+ * cross-tenant in trusted execution): inside a non-bypass tenant context a
164
+ * caller may only touch rows of its own tenant; without a context (system/
165
+ * job execution) writes are trusted.
166
+ *
167
+ * Checked against BOTH the in-memory tenant and — for a row that already
168
+ * exists — the PERSISTED one. The persisted check is what makes the boundary
169
+ * real: bucket ids are deterministic and the deriving helper is exported, so
170
+ * a foreign row is trivially addressable, and an in-memory-only check would
171
+ * let a caller load it, re-stamp `tenantId` with its own, and adopt or delete
172
+ * another tenant's counters (the #2047 FieldPolicy pattern).
173
+ */
174
+ private assertRowOwnedByAmbientContext;
175
+ /** The stored tenant for this row's id; `null` when it is not persisted. */
176
+ private getPersistedTenantId;
177
+ }
178
+ //# sourceMappingURL=FieldUsageCounter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FieldUsageCounter.d.ts","sourceRoot":"","sources":["../../src/models/FieldUsageCounter.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,UAAU,EACV,KAAK,iBAAiB,EAEvB,MAAM,0BAA0B,CAAC;AAQlC;;;;;GAKG;AACH,eAAO,MAAM,6BAA6B,MAAM,CAAC;AAEjD,kEAAkE;AAClE,eAAO,MAAM,2BAA2B,KAAK,CAAC;AAE9C,+EAA+E;AAC/E,eAAO,MAAM,8BAA8B,KAAK,CAAC;AAEjD,gDAAgD;AAChD,eAAO,MAAM,0BAA0B,QAAwB,CAAC;AAEhE,yDAAyD;AACzD,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,CAE1D;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,cAAc,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAEvD;AAED,MAAM,WAAW,wBAAyB,SAAQ,iBAAiB;IACjE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAClC,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,wBAAwB,CAAC,EAAE,OAAO,CAAC;CACpC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAMH,qBAOa,iBAAkB,SAAQ,UAAU;IAC/C,6EAA6E;IAE7E,SAAS,EAAE,MAAM,CAAM;IAEvB,uCAAuC;IAEvC,SAAS,EAAE,MAAM,CAAM;IAEvB;;;;OAIG;IAEH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB,wEAAwE;IAExE,MAAM,EAAE,MAAM,CAAM;IAEpB;;;;;;;;OAQG;IAEH,eAAe,EAAE,MAAM,CAAK;IAE5B;;;;OAIG;IAEH,QAAQ,EAAE,MAAM,CAAK;IAErB;;;;OAIG;IAEH,iBAAiB,EAAE,MAAM,CAAK;IAE9B,mEAAmE;IAEnE,eAAe,EAAE,MAAM,CAAQ;IAE/B,6EAA6E;IAE7E,uBAAuB,EAAE,OAAO,CAAS;IAEzC;;;OAGG;IAEH,cAAc,EAAE,MAAM,GAAG,IAAI,CAAQ;IAErC,yEAAyE;IAEzE,wBAAwB,EAAE,OAAO,CAAS;gBAE9B,OAAO,GAAE,wBAA6B;IA2BlD,0EAA0E;IAC1E,kBAAkB,IAAI,MAAM,EAAE;IAW9B;;;;OAIG;IACH,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAcrC;;;;;;;;;OASG;IACH,iBAAiB,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;IAwB3C;;;;;;;OAOG;IACH,qBAAqB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAiBxC;;;;;OAKG;IACH,cAAc,IAAI,OAAO;IAIV,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAMrB,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IAKtC,OAAO,CAAC,yBAAyB;IAoCjC;;;;;;;;;;;;;OAaG;YACW,8BAA8B;IAgC5C,4EAA4E;YAC9D,oBAAoB;CAYnC"}
@@ -0,0 +1,25 @@
1
+ import { SmrtObject, SmrtObjectOptions } from '@happyvertical/smrt-core';
2
+ /**
3
+ * Durable daily receipt for one member's contribution to one field.
4
+ *
5
+ * The counter action creates this before incrementing its aggregate. Its
6
+ * natural key makes the anti-inflation rule durable across requests and
7
+ * replicas: one `(tenant, user, object, field, UTC day)` sample may affect
8
+ * usage evidence. Receipts intentionally retain no submitted value.
9
+ */
10
+ export declare class FieldUsageReportReceipt extends SmrtObject {
11
+ tenantId?: string;
12
+ userId: string;
13
+ objectRef: string;
14
+ fieldName: string;
15
+ period: string;
16
+ constructor(options?: FieldUsageReportReceiptOptions);
17
+ }
18
+ export interface FieldUsageReportReceiptOptions extends SmrtObjectOptions {
19
+ tenantId?: string;
20
+ userId?: string;
21
+ objectRef?: string;
22
+ fieldName?: string;
23
+ period?: string;
24
+ }
25
+ //# sourceMappingURL=FieldUsageReportReceipt.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FieldUsageReportReceipt.d.ts","sourceRoot":"","sources":["../../src/models/FieldUsageReportReceipt.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,UAAU,EACV,KAAK,iBAAiB,EAEvB,MAAM,0BAA0B,CAAC;AAGlC;;;;;;;GAOG;AACH,qBAaa,uBAAwB,SAAQ,UAAU;IAErD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAGlB,MAAM,EAAE,MAAM,CAAM;IAGpB,SAAS,EAAE,MAAM,CAAM;IAGvB,SAAS,EAAE,MAAM,CAAM;IAGvB,MAAM,EAAE,MAAM,CAAM;gBAER,OAAO,GAAE,8BAAmC;CAQzD;AAED,MAAM,WAAW,8BAA+B,SAAQ,iBAAiB;IACvE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB"}
@@ -0,0 +1,9 @@
1
+ import { PermissionDefinition } from '@happyvertical/smrt-users';
2
+ /** Authorizes app- and tenant-scope field-policy administration. */
3
+ export declare const MANAGE_FIELD_POLICY_PERMISSION = "fields.policy.manage";
4
+ /** Authorizes a principal to maintain their own user-scope field policy. */
5
+ export declare const PERSONALIZE_FIELD_POLICY_PERMISSION = "fields.policy.personalize";
6
+ export declare const FIELD_POLICY_PERMISSION_DEFINITIONS: PermissionDefinition[];
7
+ /** Register field-policy permissions once when smrt-fields is loaded. */
8
+ export declare function ensureFieldPolicyPermissionsRegistered(): void;
9
+ //# sourceMappingURL=permissions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"permissions.d.ts","sourceRoot":"","sources":["../src/permissions.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,oBAAoB,EAE1B,MAAM,2BAA2B,CAAC;AAEnC,oEAAoE;AACpE,eAAO,MAAM,8BAA8B,yBAAyB,CAAC;AAErE,4EAA4E;AAC5E,eAAO,MAAM,mCAAmC,8BAA8B,CAAC;AAE/E,eAAO,MAAM,mCAAmC,EAAE,oBAAoB,EAarE,CAAC;AAIF,yEAAyE;AACzE,wBAAgB,sCAAsC,IAAI,IAAI,CAM7D"}
@@ -0,0 +1,2 @@
1
+ export { default } from './svelte/playground.js';
2
+ //# sourceMappingURL=playground.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"playground.d.ts","sourceRoot":"","sources":["../src/playground.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAC"}
@@ -0,0 +1,39 @@
1
+ //#region src/svelte/playground.ts
2
+ var loadFieldPolicyForm = () => import("./svelte/playground/FieldPolicyFormPreview.svelte");
3
+ var loadObjectForm = () => import("./svelte/playground/ObjectFormPreview.svelte");
4
+ var playground_default = {
5
+ packageName: "@happyvertical/smrt-fields",
6
+ displayName: "Field Policies",
7
+ description: "Headless form primitives: FieldPolicyProvider, PolicyField, basic/advanced modes, and manifest-sourced help.",
8
+ entries: [{
9
+ id: "policy-form",
10
+ title: "Policy-Driven Form",
11
+ description: "A hand-written product form adopting PolicyField on a subset of fields — visibility by mode, default prefill, help hints, and the form-level glossary.",
12
+ loadComponent: loadFieldPolicyForm,
13
+ order: 1,
14
+ tags: [
15
+ "forms",
16
+ "fields",
17
+ "policy",
18
+ "defaults"
19
+ ],
20
+ modes: { mock: { label: "Mock" } }
21
+ }, {
22
+ id: "object-form",
23
+ title: "Generated ObjectForm",
24
+ description: "A reusable create/edit form from generated web fields and resolved policy, including advanced groups and a per-field custom renderer.",
25
+ loadComponent: loadObjectForm,
26
+ order: 2,
27
+ tags: [
28
+ "forms",
29
+ "fields",
30
+ "object-form",
31
+ "registry"
32
+ ],
33
+ modes: { mock: { label: "Mock" } }
34
+ }]
35
+ };
36
+ //#endregion
37
+ export { playground_default as default };
38
+
39
+ //# sourceMappingURL=playground.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"playground.js","names":[],"sources":["../src/svelte/playground.ts"],"sourcesContent":["/**\n * Field-policy playground previews (#2048).\n *\n * Registered with the shared playground host via the auto-discovered\n * \"packages/STAR/src/svelte/playground.ts\" convention (smrt-playground vite\n * plugin).\n */\nconst loadFieldPolicyForm = () =>\n import('./playground/FieldPolicyFormPreview.svelte');\nconst loadObjectForm = () => import('./playground/ObjectFormPreview.svelte');\n\nexport default {\n packageName: '@happyvertical/smrt-fields',\n displayName: 'Field Policies',\n description:\n 'Headless form primitives: FieldPolicyProvider, PolicyField, basic/advanced modes, and manifest-sourced help.',\n entries: [\n {\n id: 'policy-form',\n title: 'Policy-Driven Form',\n description:\n 'A hand-written product form adopting PolicyField on a subset of fields — visibility by mode, default prefill, help hints, and the form-level glossary.',\n loadComponent: loadFieldPolicyForm,\n order: 1,\n tags: ['forms', 'fields', 'policy', 'defaults'],\n modes: {\n mock: {\n label: 'Mock',\n },\n },\n },\n {\n id: 'object-form',\n title: 'Generated ObjectForm',\n description:\n 'A reusable create/edit form from generated web fields and resolved policy, including advanced groups and a per-field custom renderer.',\n loadComponent: loadObjectForm,\n order: 2,\n tags: ['forms', 'fields', 'object-form', 'registry'],\n modes: { mock: { label: 'Mock' } },\n },\n ],\n};\n"],"mappings":";AAOA,IAAM,4BACJ,OAAO;AACT,IAAM,uBAAuB,OAAO;AAEpC,IAAA,qBAAe;CACb,aAAa;CACb,aAAa;CACb,aACE;CACF,SAAS,CACP;EACE,IAAI;EACJ,OAAO;EACP,aACE;EACF,eAAe;EACf,OAAO;EACP,MAAM;GAAC;GAAS;GAAU;GAAU;EAAU;EAC9C,OAAO,EACL,MAAM,EACJ,OAAO,OACT,EACF;CACF,GACA;EACE,IAAI;EACJ,OAAO;EACP,aACE;EACF,eAAe;EACf,OAAO;EACP,MAAM;GAAC;GAAS;GAAU;GAAe;EAAU;EACnD,OAAO,EAAE,MAAM,EAAE,OAAO,OAAO,EAAE;CACnC,CACF;AACF"}