@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/AGENTS.md CHANGED
@@ -36,9 +36,11 @@ per-field `{defaultValue, visibility, help, label, order, locked}` for any
36
36
  2. App rows — `scopeType: 'app'`, `tenantId`/`userId` null
37
37
  3. Tenant rows — hierarchy walk root → leaf via an injected
38
38
  `tenantHierarchyLoader` (smrt-features shape); the default loader
39
- dynamic-imports `@happyvertical/smrt-users` (missing-package failures
40
- `ERR_MODULE_NOT_FOUND` / "Cannot find package" across the cause chain
41
- fall back to a flat single-tenant chain). A node that breaks permission
39
+ uses `@happyvertical/smrt-users`' `TenantCollection`; an injected loader
40
+ may return no provider (or no chain for a tenant), which falls back to a
41
+ flat single-tenant chain. `smrt-users` itself is a required Fields runtime
42
+ dependency for policy authorization, not an optional hierarchy dependency.
43
+ A node that breaks permission
42
44
  inheritance discards every earlier tenant contribution (chain-structural),
43
45
  so only the suffix from the LAST break participates — in merging AND in
44
46
  the explained layers, which therefore replay to the merged result
@@ -75,9 +77,11 @@ per-field `{defaultValue, visibility, help, label, order, locked}` for any
75
77
  - **Write-time org checks reuse the resolver**: the required-demotion default
76
78
  and the user-write lock are computed by `resolveFieldPolicy` over the org
77
79
  tiers (default hierarchy loader), so cascading ancestor-tenant defaults and
78
- locks are honored at save time too. On updates the resolver sees the row's
79
- persisted version (no self-exclusion), so the resolver-side safety net
80
- remains the authoritative enforcement at read time.
80
+ locks are honored at save time too. On updates the persisted row is excluded
81
+ from projected lower-layer resolution, so clearing the row's only default
82
+ while demoting a required field is rejected before mutation; that projected
83
+ lookup bypasses the shared cache. The resolver-side safety net remains
84
+ authoritative when a different row is later deleted.
81
85
  - **Isolation — a MISSING identity component DENIES, it never skips.** This
82
86
  is the package rule; both the write guard
83
87
  (`FieldPolicy.assertScopeOwnedByAmbientContext`) and the read guard
@@ -88,12 +92,13 @@ per-field `{defaultValue, visibility, help, label, order, locked}` for any
88
92
  `withTenant({ tenantId })`) may not touch the user tier at all. Skipping
89
93
  that check instead of denying it was a live ownership bypass: user rows are
90
94
  `tenantId: null` by design, so nothing else contains such a write. App-scope
91
- writes inside a tenant context require super-admin bypass. With NO ambient
92
- identity at all (tenancy ALS never entered), only app-scope writes/deletes
93
- are accepted; context-LESS *reads* stay allowed because
94
- `resolveFieldPolicy` is a trusted server-side API. Residual: ALS-less
95
- deployments can still write APP rows with any authenticated principal until
96
- the #2049 permission layer adds `fields:policy:manage`. `save()`/`delete()`
95
+ writes inside a tenant context require super-admin bypass. `fields.policy.manage`
96
+ authorizes app/tenant policy mutations; `fields.policy.personalize` authorizes
97
+ only the caller's user tier (default-seeded for built-in roles). With NO ambient
98
+ identity at all (tenancy ALS never entered), policy writes/deletes fail closed;
99
+ trusted system and super-admin contexts retain their explicit bypasses.
100
+ Context-LESS *reads* stay allowed because `resolveFieldPolicy` is a trusted
101
+ server-side API. `save()`/`delete()`
97
102
  on an existing row additionally authorize against the row's PERSISTED
98
103
  scope, looked up by primary key AND — because a generated create always
99
104
  mints a fresh UUID while the `conflictColumns` upsert still replaces the
@@ -171,6 +176,47 @@ per-field `{defaultValue, visibility, help, label, order, locked}` for any
171
176
  (`_smrt_field_policies`), and both are pinned in
172
177
  `generated-surfaces.test.ts`.
173
178
 
179
+ ## Svelte generated forms (#2049)
180
+
181
+ - `ObjectForm` accepts direct generated `fields` plus a resolved `policy` for
182
+ SSR and explicit hosts, or accepts only `objectRef` beneath an
183
+ `ObjectFormSourceProvider`. `ObjectFormSourceRegistry` is per app and maps
184
+ canonical refs to generated web collection definitions before calling the
185
+ generated `resolveBatch` custom action. It uses structural types only: this
186
+ package must never import `smrt-web`.
187
+ - Generated custom-action clients resolve `Promise<any>`; validate their
188
+ `policies[objectRef]` response at the registry boundary and fail closed on a
189
+ missing/mismatched definition or policy. The component renders an accessible
190
+ loading state and alert rather than a partial form.
191
+ - Browser manifest types are `text`, `integer`, `decimal`, `boolean`,
192
+ `datetime`, `json`, `foreignKey`, and `crossPackageRef`; there is no `select`
193
+ wire type. Apps use the per-app `FieldInputRegistry.registerField` seam for
194
+ select-like widgets. Reference fields intentionally default to identifier
195
+ inputs unless an app supplies a chooser.
196
+ - `policyToVisibleColumnIds(policy, columns)` feeds smrt-ui `DataTable`'s
197
+ `visibleColumnIds`; it filters policy-hidden mapped fields, preserves unmapped
198
+ computed/action columns, and cannot reveal a static `column.hidden` column.
199
+
200
+ ## Defaults control panel (#2050)
201
+
202
+ - `buildFieldPolicySettingsCatalog()` is the server-side, URL/GET-driven
203
+ catalog builder. It structurally targets `SettingsCatalog`, but this package
204
+ must not import `smrt-svelte`; hosts inject `SettingsCatalog` into
205
+ `FieldPolicyControlPanel` and retain their own route and transport adapters.
206
+ - `policyAudit` is the only routed organization roll-up. It requires
207
+ `fields.policy.manage`, returns only the caller tenant's editable rows and
208
+ read-only app summaries, and represents other users strictly as per-field
209
+ counts. It resolves only requested page refs; never pre-resolve the catalog.
210
+ - Display code/app/org values by replaying the explained resolver layers, not
211
+ by independently calculating precedence. Reset and drift prune are ordinary
212
+ model deletes, so existing scope/permission validation remains authoritative.
213
+ The panel asks for an explicit confirmation before either destructive action;
214
+ SSR hosts can inject that confirmation decision instead of relying on
215
+ `window.confirm`.
216
+ - `fieldPolicyControlPanelNavItem()` is a structural AdminShell tenant-nav
217
+ seam. It never imports `smrt-svelte` or `smrt-web`; the host supplies the
218
+ returned entry and must enforce its real route permission server-side.
219
+
174
220
  ## Related
175
221
 
176
222
  - `@happyvertical/smrt-prompts` / `smrt-languages` / `smrt-features` — the
package/README.md CHANGED
@@ -1,52 +1,153 @@
1
1
  # @happyvertical/smrt-fields
2
2
 
3
- Layered field policy for SMRT objects: personalize per-field defaults,
4
- visibility tiers (basic/advanced/hidden), help text, labels, ordering, and
5
- org locks at app, tenant, and user scope over the code-authored
6
- `@field({ ui })` seed.
3
+ `@happyvertical/smrt-fields` lets an application adjust an object's form
4
+ defaults, labels, help, order, visibility, and locks without changing the
5
+ object's source. Policies layer organization and personal choices over the
6
+ code-authored field definition.
7
+
8
+ For the complete application and operator guide, see the
9
+ [field policy guide](https://happyvertical.github.io/smrt/field-policies).
10
+
11
+ ## Install
12
+
13
+ ```bash
14
+ pnpm add @happyvertical/smrt-fields
15
+ ```
16
+
17
+ The package includes `@happyvertical/smrt-users` because policy writes and
18
+ operator actions use its permission and tenant context services.
19
+
20
+ ## Define a safe code seed
21
+
22
+ The decorated model remains the definition of a field. Use its description
23
+ and `ui` hints to supply a useful first form before any policy row exists.
7
24
 
8
25
  ```typescript
9
- import {
10
- FieldPolicy,
11
- FieldPolicyCollection,
12
- resolveFieldPolicy,
13
- resolveFieldPolicyExplained,
14
- } from '@happyvertical/smrt-fields';
15
-
16
- // An org (tenant) demotes an optional field and sets a default
17
- const policies = await FieldPolicyCollection.create({ db });
18
- await policies.create({
19
- objectRef: '@happyvertical/smrt-content:Article',
20
- fieldName: 'summary',
21
- scopeType: 'tenant',
22
- tenantId,
23
- visibility: 'advanced',
24
- defaultValue: JSON.stringify('TBD'),
25
- });
26
-
27
- // Resolve the effective policy for a user in that tenant
28
- const resolved = await resolveFieldPolicy(
29
- '@happyvertical/smrt-content:Article',
30
- { tenantId, userId, db },
31
- );
32
- resolved.fields.summary.visibility; // 'advanced'
26
+ import { field, SmrtObject, smrt } from '@happyvertical/smrt-core';
27
+
28
+ @smrt({ packageName: '@acme/billing' })
29
+ export class Invoice extends SmrtObject {
30
+ @field({
31
+ required: true,
32
+ description: 'Shown on the customer invoice.',
33
+ ui: { basic: true, group: 'billing', order: 1 },
34
+ })
35
+ title = '';
33
36
 
34
- // Explain variant: per-layer contributions for admin/gear UIs
35
- const explained = await resolveFieldPolicyExplained(
36
- '@happyvertical/smrt-content:Article',
37
- { tenantId, userId, db },
37
+ @field({ ui: { basic: false, group: 'billing', order: 2 } })
38
+ internalNotes = '';
39
+ }
40
+ ```
41
+
42
+ If no field has `ui.basic: true`, fields start in the basic view. Once at
43
+ least one field is marked basic, unmarked fields start advanced. `group` and
44
+ `order` are code-owned hints; `locked: true` seeds a lock that can prevent a
45
+ personal override.
46
+
47
+ ## Resolve on the server
48
+
49
+ Use the resolver in trusted server code when rendering SSR or applying a
50
+ server-owned workflow. It merges code → app → tenant ancestry → user.
51
+
52
+ ```typescript
53
+ import { resolveFieldPolicy } from '@happyvertical/smrt-fields';
54
+
55
+ const policy = await resolveFieldPolicy(
56
+ '@acme/billing:Invoice',
57
+ { tenantId: requestTenant.id, userId: session.user.id, db },
38
58
  );
59
+
60
+ const title = policy.fields.title;
39
61
  ```
40
62
 
41
- Resolution layers (low high): code seed app rows → tenant rows
42
- (hierarchy walk via an optional `tenantHierarchyLoader`; flat fallback
43
- without `@happyvertical/smrt-users`) user rows. A NULL column inherits
44
- from the lower layer; resetting a customization is a row delete.
63
+ The client-facing generated `resolveBatch` action derives the tenant and user
64
+ from the authenticated request context. Do not accept those identifiers from a
65
+ browser request. It returns only fields that are safe to expose; sensitive,
66
+ transient, and read-permission-gated fields are omitted.
67
+
68
+ ## Use policy-aware Svelte forms
69
+
70
+ For a custom form, provide a resolved policy with `FieldPolicyProvider` and
71
+ wrap each input in `PolicyField`. The wrapper applies visibility, labels, help,
72
+ and new-record defaults while preserving your markup and input component.
73
+
74
+ ```svelte
75
+ <script lang="ts">
76
+ import {
77
+ FieldPolicyProvider,
78
+ PolicyField,
79
+ } from '@happyvertical/smrt-fields/svelte';
80
+
81
+ let { policy, invoice = $bindable({}) } = $props();
82
+ </script>
83
+
84
+ <FieldPolicyProvider {policy}>
85
+ <PolicyField name="title">
86
+ <input id="title" bind:value={invoice.title} />
87
+ </PolicyField>
88
+ </FieldPolicyProvider>
89
+ ```
90
+
91
+ For generated forms, register the application's generated collection
92
+ definitions once in an `ObjectFormSourceRegistry`, place it in an
93
+ `ObjectFormSourceProvider`, then render an `ObjectForm` by canonical
94
+ `objectRef`. The registry validates both the generated field definition and
95
+ the `resolveBatch` response before rendering. See the guide for the complete
96
+ setup, including field-specific input renderers and policy-aware tables.
97
+
98
+ ## Administration and personal settings
99
+
100
+ Use `FieldPolicyGearProvider` with an adapter to the generated
101
+ `getEditorState`, create, update, and delete actions. It derives identity on
102
+ the server; adapter methods intentionally do not accept tenant or user ids.
103
+ Use `FieldPolicyGearButton` or `showPolicyGear` on `ObjectForm` to expose
104
+ the editor.
105
+
106
+ For a settings destination, the server builds data with
107
+ `buildFieldPolicySettingsCatalog()` and the browser renders it through
108
+ `FieldPolicyControlPanel`. Hosts supply route, transport, confirmation, and
109
+ AdminShell adapters. `fieldPolicyControlPanelNavItem()` and
110
+ `registerFieldPolicyFocusTool()` are structural seams, so Fields does not
111
+ take a dependency on a particular application shell.
112
+
113
+ `fields.policy.manage` permits app and tenant administration.
114
+ `fields.policy.personalize` permits only the current user's personal choices.
115
+ The host must still establish a trusted authenticated principal context and
116
+ enforce the route permission before rendering an operator destination.
117
+
118
+ ## Important behavior
119
+
120
+ - A policy row is sparse: `null` means inherit the lower layer. Delete a row
121
+ to reset that scope completely.
122
+ - `defaultValue` is the JSON-encoded wire channel. Server code with a plain
123
+ value should use `defaultValueRaw` or `setDefaultValue()`.
124
+ - Required fields cannot be hidden or moved to advanced without a usable
125
+ resolved default. Resolution forces a required, default-less field back to
126
+ basic as a safety net.
127
+ - App and tenant rows may lock a field. While the resolved organization policy
128
+ is locked, personal writes are rejected and old personal rows do not apply.
129
+ - Defaults are rejected for sensitive, transient, and read-permission-gated
130
+ fields. Reference defaults must use a valid UUID unless that reference
131
+ declares a text id type.
132
+
133
+ ## Usage learning
134
+
135
+ Optional usage capture records bounded, aggregated submissions from
136
+ authenticated tenant members and turns qualified patterns into
137
+ administrator-reviewed tenant-policy suggestions. It never auto-applies a
138
+ suggestion. `ObjectForm` reports only after its host confirms persistence;
139
+ browser values transit only for low-cardinality boolean and reference fields,
140
+ and telemetry failures never affect the saved submit.
141
+
142
+ Operators enable the dormant maintenance and suggestion schedules explicitly
143
+ with `ensureFieldUsageLearningSchedules({ db })`. The learning loop retains
144
+ aggregates and uses conservative thresholds; its accepted/dismissed suggestion
145
+ queue is restricted to `fields.policy.manage`. See the
146
+ [field policy guide](https://happyvertical.github.io/smrt/field-policies#usage-learning-and-suggestions)
147
+ for the capture, privacy, retention, and schedule contract.
45
148
 
46
- Writes are validated against the live `ObjectRegistry`: unknown
47
- objects/fields are rejected, defaults are type-checked, and defaults on
48
- `transient`/`sensitive`/`readPermission`-gated fields are refused. Required
49
- fields can only be demoted from `basic` when a usable default resolves —
50
- and the resolver re-enforces that invariant at read time.
149
+ ## Example application
51
150
 
52
- See `AGENTS.md` for the full architecture notes.
151
+ The [SMRT SaaS starter field-policy walkthrough](https://github.com/happyvertical/smrt-saas-starter/pull/51)
152
+ uses SMRT `0.40.61` and shows the owner/admin controls and the member-facing
153
+ personal form flow in a working application.
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=__smrt-register__.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"__smrt-register__.d.ts","sourceRoot":"","sources":["../src/__smrt-register__.ts"],"names":[],"mappings":""}
@@ -0,0 +1,18 @@
1
+ import { DatabaseInterface } from '@happyvertical/sql';
2
+ import { ExplainedObjectFieldPolicy } from './types.js';
3
+ export declare function getFieldPolicyCacheTtlMs(): number;
4
+ export declare function getCachedFieldPolicy(objectRef: string, tenantId: string | null | undefined, userId: string | null | undefined, db: DatabaseInterface | unknown, hierarchyLoader?: unknown): ExplainedObjectFieldPolicy | null;
5
+ export declare function setCachedFieldPolicy(objectRef: string, tenantId: string | null | undefined, userId: string | null | undefined, db: DatabaseInterface | unknown, value: ExplainedObjectFieldPolicy, hierarchyLoader?: unknown): void;
6
+ /**
7
+ * Drop every cached resolution for `(db, objectRef)`.
8
+ *
9
+ * Deliberately coarser than the prompts precedent (which deletes a single
10
+ * `(key, tenantId)` entry when the tenant is known): tenant HIERARCHY makes a
11
+ * parent-tenant row change affect every descendant tenant's resolution, and a
12
+ * lock or app-row change affects user-tier entries too, so precise
13
+ * invalidation would have to know the whole tenant tree. Per-objectRef prefix
14
+ * invalidation is always correct and the 30s TTL keeps the cost bounded.
15
+ */
16
+ export declare function invalidateFieldPolicyCache(objectRef: string, db: DatabaseInterface | unknown): void;
17
+ export declare function clearFieldPolicyCache(): void;
18
+ //# sourceMappingURL=cache.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../src/cache.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAC5D,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,YAAY,CAAC;AA2F7D,wBAAgB,wBAAwB,IAAI,MAAM,CAEjD;AAED,wBAAgB,oBAAoB,CAClC,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EACnC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EACjC,EAAE,EAAE,iBAAiB,GAAG,OAAO,EAC/B,eAAe,CAAC,EAAE,OAAO,GACxB,0BAA0B,GAAG,IAAI,CAoBnC;AAED,wBAAgB,oBAAoB,CAClC,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EACnC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EACjC,EAAE,EAAE,iBAAiB,GAAG,OAAO,EAC/B,KAAK,EAAE,0BAA0B,EACjC,eAAe,CAAC,EAAE,OAAO,GACxB,IAAI,CAQN;AAED;;;;;;;;;GASG;AACH,wBAAgB,0BAA0B,CACxC,SAAS,EAAE,MAAM,EACjB,EAAE,EAAE,iBAAiB,GAAG,OAAO,GAC9B,IAAI,CAON;AAED,wBAAgB,qBAAqB,IAAI,IAAI,CAE5C"}