@happyvertical/smrt-fields 0.40.60 → 0.40.61

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (184) hide show
  1. package/AGENTS.md +58 -12
  2. package/README.md +90 -2
  3. package/dist/__smrt-register__.d.ts +2 -0
  4. package/dist/__smrt-register__.d.ts.map +1 -0
  5. package/dist/cache.d.ts +18 -0
  6. package/dist/cache.d.ts.map +1 -0
  7. package/dist/collections/FieldPolicyCollection.d.ts +95 -0
  8. package/dist/collections/FieldPolicyCollection.d.ts.map +1 -0
  9. package/dist/field-definitions.d.ts +77 -0
  10. package/dist/field-definitions.d.ts.map +1 -0
  11. package/dist/field-policy-resolver.d.ts +46 -0
  12. package/dist/field-policy-resolver.d.ts.map +1 -0
  13. package/dist/index.d.ts +9 -532
  14. package/dist/index.d.ts.map +1 -0
  15. package/dist/index.js +752 -55
  16. package/dist/index.js.map +1 -1
  17. package/dist/manifest.json +45 -3
  18. package/dist/models/FieldPolicy.d.ts +163 -0
  19. package/dist/models/FieldPolicy.d.ts.map +1 -0
  20. package/dist/permissions.d.ts +9 -0
  21. package/dist/permissions.d.ts.map +1 -0
  22. package/dist/playground.d.ts +2 -0
  23. package/dist/playground.d.ts.map +1 -0
  24. package/dist/playground.js +39 -0
  25. package/dist/playground.js.map +1 -0
  26. package/dist/settings-catalog.d.ts +73 -0
  27. package/dist/settings-catalog.d.ts.map +1 -0
  28. package/dist/smrt-knowledge.json +50 -7
  29. package/dist/svelte/__tests__/FieldPolicyControlPanel.test.js +223 -0
  30. package/dist/svelte/__tests__/FieldPolicyGear.test.js +503 -0
  31. package/dist/svelte/__tests__/FormHelp.test.js +138 -0
  32. package/dist/svelte/__tests__/ModeUX.test.js +109 -0
  33. package/dist/svelte/__tests__/ObjectForm.test.js +556 -0
  34. package/dist/svelte/__tests__/ObjectFormGeneratedApi.integration.test.js +436 -0
  35. package/dist/svelte/__tests__/PolicyField.test.js +373 -0
  36. package/dist/svelte/__tests__/fixtures/AdvancedFieldsFixture.svelte +30 -0
  37. package/dist/svelte/__tests__/fixtures/AdvancedFieldsFixture.svelte.d.ts +9 -0
  38. package/dist/svelte/__tests__/fixtures/AdvancedFieldsFixture.svelte.d.ts.map +1 -0
  39. package/dist/svelte/__tests__/fixtures/DomPrefillFixture.svelte +33 -0
  40. package/dist/svelte/__tests__/fixtures/DomPrefillFixture.svelte.d.ts +10 -0
  41. package/dist/svelte/__tests__/fixtures/DomPrefillFixture.svelte.d.ts.map +1 -0
  42. package/dist/svelte/__tests__/fixtures/FormHelpFixture.svelte +19 -0
  43. package/dist/svelte/__tests__/fixtures/FormHelpFixture.svelte.d.ts +10 -0
  44. package/dist/svelte/__tests__/fixtures/FormHelpFixture.svelte.d.ts.map +1 -0
  45. package/dist/svelte/__tests__/fixtures/GeneratedApiGearFixture.svelte +18 -0
  46. package/dist/svelte/__tests__/fixtures/GeneratedApiGearFixture.svelte.d.ts +11 -0
  47. package/dist/svelte/__tests__/fixtures/GeneratedApiGearFixture.svelte.d.ts.map +1 -0
  48. package/dist/svelte/__tests__/fixtures/GracefulDegradeFixture.svelte +11 -0
  49. package/dist/svelte/__tests__/fixtures/GracefulDegradeFixture.svelte.d.ts +19 -0
  50. package/dist/svelte/__tests__/fixtures/GracefulDegradeFixture.svelte.d.ts.map +1 -0
  51. package/dist/svelte/__tests__/fixtures/ModeSwitchFixture.svelte +18 -0
  52. package/dist/svelte/__tests__/fixtures/ModeSwitchFixture.svelte.d.ts +8 -0
  53. package/dist/svelte/__tests__/fixtures/ModeSwitchFixture.svelte.d.ts.map +1 -0
  54. package/dist/svelte/__tests__/fixtures/NoIdFixture.svelte +24 -0
  55. package/dist/svelte/__tests__/fixtures/NoIdFixture.svelte.d.ts +13 -0
  56. package/dist/svelte/__tests__/fixtures/NoIdFixture.svelte.d.ts.map +1 -0
  57. package/dist/svelte/__tests__/fixtures/ObjectFormActionsFixture.svelte +20 -0
  58. package/dist/svelte/__tests__/fixtures/ObjectFormActionsFixture.svelte.d.ts +11 -0
  59. package/dist/svelte/__tests__/fixtures/ObjectFormActionsFixture.svelte.d.ts.map +1 -0
  60. package/dist/svelte/__tests__/fixtures/ObjectFormFixture.svelte +17 -0
  61. package/dist/svelte/__tests__/fixtures/ObjectFormFixture.svelte.d.ts +10 -0
  62. package/dist/svelte/__tests__/fixtures/ObjectFormFixture.svelte.d.ts.map +1 -0
  63. package/dist/svelte/__tests__/fixtures/ObjectFormRegistryFixture.svelte +25 -0
  64. package/dist/svelte/__tests__/fixtures/ObjectFormRegistryFixture.svelte.d.ts +10 -0
  65. package/dist/svelte/__tests__/fixtures/ObjectFormRegistryFixture.svelte.d.ts.map +1 -0
  66. package/dist/svelte/__tests__/fixtures/ObjectFormSnippetFixture.svelte +34 -0
  67. package/dist/svelte/__tests__/fixtures/ObjectFormSnippetFixture.svelte.d.ts +10 -0
  68. package/dist/svelte/__tests__/fixtures/ObjectFormSnippetFixture.svelte.d.ts.map +1 -0
  69. package/dist/svelte/__tests__/fixtures/ObjectFormSourceFixture.svelte +29 -0
  70. package/dist/svelte/__tests__/fixtures/ObjectFormSourceFixture.svelte.d.ts +11 -0
  71. package/dist/svelte/__tests__/fixtures/ObjectFormSourceFixture.svelte.d.ts.map +1 -0
  72. package/dist/svelte/__tests__/fixtures/ObjectFormSourceSnippetFixture.svelte +33 -0
  73. package/dist/svelte/__tests__/fixtures/ObjectFormSourceSnippetFixture.svelte.d.ts +9 -0
  74. package/dist/svelte/__tests__/fixtures/ObjectFormSourceSnippetFixture.svelte.d.ts.map +1 -0
  75. package/dist/svelte/__tests__/fixtures/PolicyDataTableFixture.svelte +40 -0
  76. package/dist/svelte/__tests__/fixtures/PolicyDataTableFixture.svelte.d.ts +8 -0
  77. package/dist/svelte/__tests__/fixtures/PolicyDataTableFixture.svelte.d.ts.map +1 -0
  78. package/dist/svelte/__tests__/fixtures/PrefillFixture.svelte +26 -0
  79. package/dist/svelte/__tests__/fixtures/PrefillFixture.svelte.d.ts +13 -0
  80. package/dist/svelte/__tests__/fixtures/PrefillFixture.svelte.d.ts.map +1 -0
  81. package/dist/svelte/__tests__/fixtures/ProviderFieldFixture.svelte +39 -0
  82. package/dist/svelte/__tests__/fixtures/ProviderFieldFixture.svelte.d.ts +17 -0
  83. package/dist/svelte/__tests__/fixtures/ProviderFieldFixture.svelte.d.ts.map +1 -0
  84. package/dist/svelte/__tests__/fixtures/RegistryInput.svelte +15 -0
  85. package/dist/svelte/__tests__/fixtures/RegistryInput.svelte.d.ts +5 -0
  86. package/dist/svelte/__tests__/fixtures/RegistryInput.svelte.d.ts.map +1 -0
  87. package/dist/svelte/__tests__/fixtures/RevealPrefillFixture.svelte +29 -0
  88. package/dist/svelte/__tests__/fixtures/RevealPrefillFixture.svelte.d.ts +14 -0
  89. package/dist/svelte/__tests__/fixtures/RevealPrefillFixture.svelte.d.ts.map +1 -0
  90. package/dist/svelte/__tests__/fixtures/SettingsCatalogStub.svelte +24 -0
  91. package/dist/svelte/__tests__/fixtures/SettingsCatalogStub.svelte.d.ts +14 -0
  92. package/dist/svelte/__tests__/fixtures/SettingsCatalogStub.svelte.d.ts.map +1 -0
  93. package/dist/svelte/__tests__/fixtures/SnippetFixture.svelte +28 -0
  94. package/dist/svelte/__tests__/fixtures/SnippetFixture.svelte.d.ts +13 -0
  95. package/dist/svelte/__tests__/fixtures/SnippetFixture.svelte.d.ts.map +1 -0
  96. package/dist/svelte/__tests__/fixtures/ToggleModeButton.svelte +14 -0
  97. package/dist/svelte/__tests__/fixtures/ToggleModeButton.svelte.d.ts +19 -0
  98. package/dist/svelte/__tests__/fixtures/ToggleModeButton.svelte.d.ts.map +1 -0
  99. package/dist/svelte/__tests__/fixtures/TooltipFixture.svelte +25 -0
  100. package/dist/svelte/__tests__/fixtures/TooltipFixture.svelte.d.ts +14 -0
  101. package/dist/svelte/__tests__/fixtures/TooltipFixture.svelte.d.ts.map +1 -0
  102. package/dist/svelte/__tests__/settings-catalog.test.js +161 -0
  103. package/dist/svelte/components/AdvancedFields.svelte +49 -0
  104. package/dist/svelte/components/AdvancedFields.svelte.d.ts +17 -0
  105. package/dist/svelte/components/AdvancedFields.svelte.d.ts.map +1 -0
  106. package/dist/svelte/components/FieldInput.svelte +159 -0
  107. package/dist/svelte/components/FieldInput.svelte.d.ts +5 -0
  108. package/dist/svelte/components/FieldInput.svelte.d.ts.map +1 -0
  109. package/dist/svelte/components/FieldPolicyControlPanel.svelte +261 -0
  110. package/dist/svelte/components/FieldPolicyControlPanel.svelte.d.ts +26 -0
  111. package/dist/svelte/components/FieldPolicyControlPanel.svelte.d.ts.map +1 -0
  112. package/dist/svelte/components/FieldPolicyEditor.svelte +417 -0
  113. package/dist/svelte/components/FieldPolicyEditor.svelte.d.ts +18 -0
  114. package/dist/svelte/components/FieldPolicyEditor.svelte.d.ts.map +1 -0
  115. package/dist/svelte/components/FieldPolicyGearButton.svelte +17 -0
  116. package/dist/svelte/components/FieldPolicyGearButton.svelte.d.ts +8 -0
  117. package/dist/svelte/components/FieldPolicyGearButton.svelte.d.ts.map +1 -0
  118. package/dist/svelte/components/FieldPolicyGearProvider.svelte +135 -0
  119. package/dist/svelte/components/FieldPolicyGearProvider.svelte.d.ts +17 -0
  120. package/dist/svelte/components/FieldPolicyGearProvider.svelte.d.ts.map +1 -0
  121. package/dist/svelte/components/FieldPolicyProvider.svelte +89 -0
  122. package/dist/svelte/components/FieldPolicyProvider.svelte.d.ts +35 -0
  123. package/dist/svelte/components/FieldPolicyProvider.svelte.d.ts.map +1 -0
  124. package/dist/svelte/components/FormHelp.svelte +157 -0
  125. package/dist/svelte/components/FormHelp.svelte.d.ts +15 -0
  126. package/dist/svelte/components/FormHelp.svelte.d.ts.map +1 -0
  127. package/dist/svelte/components/ModeSwitch.svelte +47 -0
  128. package/dist/svelte/components/ModeSwitch.svelte.d.ts +10 -0
  129. package/dist/svelte/components/ModeSwitch.svelte.d.ts.map +1 -0
  130. package/dist/svelte/components/ObjectForm.svelte +351 -0
  131. package/dist/svelte/components/ObjectForm.svelte.d.ts +40 -0
  132. package/dist/svelte/components/ObjectForm.svelte.d.ts.map +1 -0
  133. package/dist/svelte/components/ObjectFormSourceProvider.svelte +18 -0
  134. package/dist/svelte/components/ObjectFormSourceProvider.svelte.d.ts +11 -0
  135. package/dist/svelte/components/ObjectFormSourceProvider.svelte.d.ts.map +1 -0
  136. package/dist/svelte/components/PolicyField.svelte +338 -0
  137. package/dist/svelte/components/PolicyField.svelte.d.ts +80 -0
  138. package/dist/svelte/components/PolicyField.svelte.d.ts.map +1 -0
  139. package/dist/svelte/context.svelte.d.ts +54 -0
  140. package/dist/svelte/context.svelte.d.ts.map +1 -0
  141. package/dist/svelte/context.svelte.js +55 -0
  142. package/dist/svelte/data-table.d.ts +17 -0
  143. package/dist/svelte/data-table.d.ts.map +1 -0
  144. package/dist/svelte/data-table.js +19 -0
  145. package/dist/svelte/field-policy-editor.d.ts +81 -0
  146. package/dist/svelte/field-policy-editor.d.ts.map +1 -0
  147. package/dist/svelte/field-policy-editor.js +120 -0
  148. package/dist/svelte/gear-context.svelte.d.ts +14 -0
  149. package/dist/svelte/gear-context.svelte.d.ts.map +1 -0
  150. package/dist/svelte/gear-context.svelte.js +15 -0
  151. package/dist/svelte/index.d.ts +58 -0
  152. package/dist/svelte/index.d.ts.map +1 -0
  153. package/dist/svelte/index.js +38 -0
  154. package/dist/svelte/input-registry.d.ts +21 -0
  155. package/dist/svelte/input-registry.d.ts.map +1 -0
  156. package/dist/svelte/input-registry.js +26 -0
  157. package/dist/svelte/object-form-source-context.svelte.d.ts +4 -0
  158. package/dist/svelte/object-form-source-context.svelte.d.ts.map +1 -0
  159. package/dist/svelte/object-form-source-context.svelte.js +8 -0
  160. package/dist/svelte/object-form-source.svelte.d.ts +18 -0
  161. package/dist/svelte/object-form-source.svelte.d.ts.map +1 -0
  162. package/dist/svelte/object-form-source.svelte.js +99 -0
  163. package/dist/svelte/object-form.d.ts +6 -0
  164. package/dist/svelte/object-form.d.ts.map +1 -0
  165. package/dist/svelte/object-form.js +38 -0
  166. package/dist/svelte/playground/FieldPolicyFormPreview.svelte +212 -0
  167. package/dist/svelte/playground/FieldPolicyFormPreview.svelte.d.ts +4 -0
  168. package/dist/svelte/playground/FieldPolicyFormPreview.svelte.d.ts.map +1 -0
  169. package/dist/svelte/playground/ObjectFormPreview.svelte +129 -0
  170. package/dist/svelte/playground/ObjectFormPreview.svelte.d.ts +4 -0
  171. package/dist/svelte/playground/ObjectFormPreview.svelte.d.ts.map +1 -0
  172. package/dist/svelte/playground.d.ts +20 -0
  173. package/dist/svelte/playground.d.ts.map +1 -0
  174. package/dist/svelte/playground.js +38 -0
  175. package/dist/svelte/settings-catalog.d.ts +54 -0
  176. package/dist/svelte/settings-catalog.d.ts.map +1 -0
  177. package/dist/svelte/settings-catalog.js +116 -0
  178. package/dist/svelte/types.d.ts +101 -0
  179. package/dist/svelte/types.d.ts.map +1 -0
  180. package/dist/svelte/types.js +4 -0
  181. package/dist/types.d.ts +276 -203
  182. package/dist/types.d.ts.map +1 -0
  183. package/dist/types.js.map +1 -1
  184. package/package.json +33 -8
package/dist/index.d.ts CHANGED
@@ -1,532 +1,9 @@
1
- import { DatabaseInterface } from '@happyvertical/sql';
2
- import { FieldUIHints } from '@happyvertical/smrt-core';
3
- import { ObjectRegistry } from '@happyvertical/smrt-core';
4
- import { SmrtClassOptions } from '@happyvertical/smrt-core';
5
- import { SmrtCollection } from '@happyvertical/smrt-core';
6
- import { SmrtObject } from '@happyvertical/smrt-core';
7
- import { SmrtObjectOptions } from '@happyvertical/smrt-core';
8
-
9
- /**
10
- * `scopeKey` value for app-scope rows. `scopeKey` exists ONLY so the
11
- * `conflictColumns` unique index stays total while `tenantId`/`userId` are
12
- * nullable (nullable columns would allow duplicate NULL rows) — the same
13
- * trick as `PromptOverride.context`.
14
- */
15
- export declare const APP_FIELD_POLICY_SCOPE_KEY = "__app__";
16
-
17
- /**
18
- * Type-check a parsed default value against the manifest field type.
19
- *
20
- * Explicit `null` is allowed only for optional fields (a required field with a
21
- * null default has no usable default). Unsupported field types reject defaults
22
- * outright (fail closed).
23
- */
24
- export declare function assertDefaultValueMatchesFieldType(objectRef: string, fieldName: string, field: RegisteredFieldInfo, value: unknown): void;
25
-
26
- /** Code-seed delta for one field (visibility supplied by the cold-start pass). */
27
- export declare function buildCodeSeedDelta(field: RegisteredFieldInfo, visibility: FieldPolicyVisibility): FieldPolicyDelta;
28
-
29
- /**
30
- * Compute the code-seed visibility for every field of an object using the
31
- * cold-start rule (#2046): with no `ui.basic` markers anywhere on the object,
32
- * every field is `basic`; once any field is marked `basic: true`, unmarked
33
- * fields default to `advanced` (and an explicit `basic: false` is always
34
- * `advanced`).
35
- */
36
- export declare function buildCodeSeedVisibility(fields: FieldDefinitionMap): Map<string, FieldPolicyVisibility>;
37
-
38
- export declare function clearFieldPolicyCache(): void;
39
-
40
- /**
41
- * Explain variant: merged result plus the ordered per-layer contributions for
42
- * each field, so admin/gear UIs (#2049/#2050) never re-derive precedence.
43
- * Layers are listed in application order (code, app, tenant chain root → leaf,
44
- * user) and contain ONLY contributions that survive into the merged result:
45
- * tenant ancestors discarded by a permission-inheritance break and user rows
46
- * suppressed by an effective org lock are omitted, so sequentially replaying
47
- * the listed deltas reproduces the merged policy.
48
- */
49
- export declare interface ExplainedObjectFieldPolicy extends ResolvedObjectFieldPolicy {
50
- layers: Record<string, FieldPolicyLayerContribution[]>;
51
- }
52
-
53
- export declare const FIELD_POLICY_SCOPE_TYPES: readonly FieldPolicyScopeType[];
54
-
55
- export declare const FIELD_POLICY_VISIBILITIES: readonly FieldPolicyVisibility[];
56
-
57
- /**
58
- * Field map for one object, keyed by field name (includes inherited fields).
59
- * Derived structurally from the PUBLIC `ObjectRegistry.getAllFields` return
60
- * type — core does not export its internal `RegisteredField` name.
61
- */
62
- export declare type FieldDefinitionMap = Awaited<ReturnType<typeof ObjectRegistry.getAllFields>>;
63
-
64
- /**
65
- * Sparse, layered field policy override (epic #2045, issue #2047).
66
- *
67
- * One row personalizes a subset of `{defaultValue, visibility, help, label,
68
- * displayOrder, locked}` for a single `(objectRef, fieldName)` at one scope
69
- * tier. A NULL column means "inherit from the lower layer" (code seed → app →
70
- * tenant → user), so resetting a customization is a row DELETE — later
71
- * lower-layer changes then flow through (sparse-delta rationale, #1770).
72
- *
73
- * Rows are validated at write time against the live `ObjectRegistry` (the
74
- * manifest is the definition registry): unknown objects/fields are rejected,
75
- * defaults are type-checked, and the security rail (`sensitive` /
76
- * `readPermission` / `transient`) refuses stored defaults outright.
77
- */
78
- export declare class FieldPolicy extends SmrtObject {
79
- /** Qualified class name of the target object (`@package/name:ClassName`). */
80
- objectRef: string;
81
- /** Field name on the target object (validated against the registry). */
82
- fieldName: string;
83
- /** Scope tier this row belongs to ('app' | 'tenant' | 'user'). */
84
- scopeType: FieldPolicyScopeType;
85
- /** Owning tenant for tenant-scope rows; NULL otherwise (native UUID on PG). */
86
- tenantId: string | null;
87
- /** Owning user for user-scope rows; NULL otherwise. */
88
- userId: string | null;
89
- /**
90
- * Computed uniqueness key (`userId ?? tenantId ?? '__app__'`), set in
91
- * `save()`. Used ONLY by `conflictColumns` so the unique index stays total
92
- * while `tenantId`/`userId` are nullable — mirrors `PromptOverride.context`.
93
- * Never read it for scoping logic; `scopeType` + the typed columns own that.
94
- */
95
- scopeKey: string;
96
- /** JSON-encoded default value; NULL = inherit. JSON `null` = "default to null". */
97
- defaultValue: string | null;
98
- /** Visibility override ('basic' | 'advanced' | 'hidden'); NULL = inherit. */
99
- visibility: FieldPolicyVisibility | null;
100
- /** Help text override; NULL = inherit (code seed: field description). */
101
- help: string | null;
102
- /** Label override; NULL = inherit (consumers derive from the field name). */
103
- label: string | null;
104
- /**
105
- * Sort-order override; NULL = inherit (code seed: `ui.order`). Named
106
- * `displayOrder` because a column literally named `order` is an SQL keyword
107
- * the runtime INSERT path does not quote; resolved output exposes `order`.
108
- */
109
- displayOrder: number | null;
110
- /**
111
- * Org lock (app/tenant rows only): when the effective lock is true, the
112
- * user tier may not override this field. NULL = inherit (code seed:
113
- * `ui.locked`); org rows may set `false` to explicitly unlock.
114
- */
115
- locked: boolean | null;
116
- /** Audit attribution for #2050 ("who changed what"); not validated. */
117
- updatedBy: string | null;
118
- constructor(options?: FieldPolicyOptions);
119
- /** Parse the stored JSON default. `undefined` = no stored default (inherit). */
120
- getDefaultValue(): unknown;
121
- /** Serialize a default value; `undefined` clears the override (inherit). */
122
- setDefaultValue(value: unknown): void;
123
- save(): Promise<this>;
124
- private saveAfterIdentityChange;
125
- private saveAfterIdentityChangeInTransaction;
126
- private saveAfterIdentityChangeWithDeferredDelete;
127
- delete(): Promise<void>;
128
- private validateFieldPolicy;
129
- /**
130
- * Effective org-tier (code → app → tenant hierarchy) policy for this row's
131
- * field, computed by the RESOLVER so write-time checks share the one
132
- * precedence implementation — including ancestor-tenant cascades via the
133
- * default hierarchy loader. The chain tenant is the row's own tenant for
134
- * tenant-scope rows, the ambient context tenant for user-scope rows, and
135
- * none for app-scope rows (their only lower layer is the code seed).
136
- *
137
- * On updates the resolver sees this row's PERSISTED version (there is no
138
- * self-exclusion), so a save that removes the only default while demoting
139
- * can pass here; the resolver-side safety net stays authoritative at read
140
- * time.
141
- */
142
- private resolveOrgTierFieldPolicy;
143
- /**
144
- * Exactly-one-owner scope shape: app rows carry neither id, tenant rows
145
- * carry only `tenantId`, user rows carry only `userId` (the user tier is
146
- * keyed by user alone so preferences follow the user across tenants).
147
- */
148
- private validateScopeConsistency;
149
- /** `userId ?? tenantId ?? '__app__'` — the `conflictColumns` scope key. */
150
- private computeScopeKey;
151
- /**
152
- * Derive a scope-owner column the transport could not carry (#2047).
153
- *
154
- * Core's mass-assignment guard treats `tenantId` as server-managed and
155
- * strips it from EVERY generated create/update body, and `FieldPolicy` is
156
- * deliberately not `@TenantScoped`, so the tenancy interceptor never
157
- * repopulates it. A `POST {scopeType:'tenant', tenantId}` therefore always
158
- * reached `validateScopeConsistency` with `tenantId === null`, making the
159
- * org tier write-dead over every generated surface — the model, as the
160
- * single validation authority for these rows, fills it in instead.
161
- *
162
- * This grants nothing: `assertScopeOwnedByAmbientContext` already requires
163
- * a non-bypass caller's tenant/user rows to name exactly the ambient
164
- * tenant/user, so the derived value is the ONLY value that could ever have
165
- * been accepted. An explicit value is never overwritten (a super-admin
166
- * bypass caller keeps naming other scopes), and with no ambient context
167
- * nothing is derived — scope-shape validation rejects the row as before.
168
- * Consequence: a bypass caller writing ANOTHER tenant's row must pass
169
- * `tenantId` through a server-side model call, because the generated routes
170
- * still strip it.
171
- */
172
- private attributeScopeToAmbientContext;
173
- /**
174
- * Fail-closed write boundary against the ambient tenant context, applied to
175
- * the NEW scope on save and to the PERSISTED scope on save/delete of an
176
- * existing row.
177
- *
178
- * With NO ambient identity at all (no tenant context entered — e.g. a
179
- * runtime API deployment whose auth middleware never enters the tenancy
180
- * ALS), only app-scope rows are accepted: tenant- and user-scope rows are
181
- * unattributable without a context, so they are rejected outright rather
182
- * than allowed by default. Inside a non-bypass context, a caller may only
183
- * touch rows for its own tenant, and user rows only for its own user id —
184
- * a context that carries NO user id may not touch the user tier at all.
185
- * App-wide rows then require super-admin bypass (or a context-less/system
186
- * caller).
187
- *
188
- * Package rule: a missing identity component DENIES, it never skips.
189
- */
190
- private assertScopeOwnedByAmbientContext;
191
- private validateTenantContextBoundary;
192
- private validateDefaultAgainstSecurityRail;
193
- private parseDefaultValueOrThrow;
194
- /**
195
- * The persisted row this save/delete would replace, looked up by primary
196
- * key and — when that misses — by the NATURAL key.
197
- *
198
- * The natural-key fallback is load-bearing for authorization: every
199
- * generated create arrives with a freshly minted UUID, so a primary-key
200
- * lookup always misses, yet the `conflictColumns` upsert still overwrites
201
- * whatever row already occupies `(objectRef, fieldName, scopeType,
202
- * scopeKey)`. Authorizing on the primary key alone therefore skipped the
203
- * persisted-scope guard on exactly the path that can replace an existing
204
- * row's contents.
205
- */
206
- private getPersistedIdentity;
207
- private getPersistedRowById;
208
- /**
209
- * The row occupying this row's `conflictColumns` tuple, if any. Column
210
- * names are the physical snake_case ones: this reads the driver directly
211
- * rather than through a collection (the model has no collection handle).
212
- */
213
- private getPersistedRowByNaturalKey;
214
- private normalizeDefaultValueForPersistence;
215
- }
216
-
217
- /** Result shape of the batch resolve action (`FieldPolicyCollection.resolveBatch`). */
218
- export declare interface FieldPolicyBatchResult {
219
- policies: Record<string, ResolvedObjectFieldPolicy>;
220
- }
221
-
222
- /**
223
- * Collection surface for {@link FieldPolicy} rows plus the batch resolve
224
- * action consumed by client bootstrapping (#2048).
225
- *
226
- * `resolveBatch` is a custom collection-scoped action (NOT a system route —
227
- * core's generated system trio is closed): the generated route parses the
228
- * body, calls the method on the app-configured collection instance (which
229
- * carries the app database), and serializes the plain result.
230
- *
231
- * Exposure note: a decorated collection's config becomes the RUNTIME registry
232
- * authority for its item class (core merges the collection registration onto
233
- * the item slot), while build-time generation reads each manifest object's
234
- * own config. This config therefore mirrors FieldPolicy's API posture —
235
- * writes open plus the batch action, reads CLOSED (generated list/get would
236
- * enumerate every tenant's/user's rows) — and closes the runtime CLI/MCP
237
- * surfaces entirely (the ContentContributions precedent; the cli↔api
238
- * coherence gate does not admit standard CRUD entries on a collection's
239
- * `cli.include`). Keep the api include lists in lockstep with FieldPolicy's.
240
- */
241
- export declare class FieldPolicyCollection extends SmrtCollection<FieldPolicy> {
242
- static readonly _itemClass: typeof FieldPolicy;
243
- /** All app-scope rows for an object, keyed by field name. */
244
- getAppRows(objectRef: string): Promise<Map<string, FieldPolicy>>;
245
- /**
246
- * All tenant-scope rows for an object across a tenant chain, keyed
247
- * `tenantId → fieldName → row`.
248
- */
249
- getTenantRows(objectRef: string, tenantIds: string[]): Promise<Map<string, Map<string, FieldPolicy>>>;
250
- /** All user-scope rows for an object and user, keyed by field name. */
251
- getUserRows(objectRef: string, userId: string): Promise<Map<string, FieldPolicy>>;
252
- /**
253
- * Resolve merged field policy for a set of objectRefs for the CURRENT
254
- * caller, gated for public consumption.
255
- *
256
- * Caller identity comes exclusively from the ambient tenant context
257
- * (established by the app's auth hook, e.g. smrt-users' session context) —
258
- * the request body cannot select another tenant or user (fail closed).
259
- * Server-side consumers wanting explicit identities call
260
- * `resolveFieldPolicy()` directly instead.
261
- *
262
- * Field gating mirrors the REST serializer's derivation (`sensitive` /
263
- * `readPermission` read from both the top level and `_meta`): sensitive and
264
- * read-permission-gated fields are ABSENT from the response for every
265
- * caller (the generated action route cannot convey per-caller grants to
266
- * this method, so gated fields fail closed), and transient fields are
267
- * stripped for parity with generated client field definitions.
268
- */
269
- resolveBatch(options?: {
270
- objectRefs?: string[];
271
- }): Promise<FieldPolicyBatchResult>;
272
- private gateResolvedForPublicResponse;
273
- }
274
-
275
- /**
276
- * The sparse contribution of one layer (code seed or one stored row).
277
- *
278
- * `default` is boxed so an explicit JSON `null` default (meaning "default to
279
- * null") stays distinguishable from "this layer contributes no default".
280
- */
281
- export declare interface FieldPolicyDelta {
282
- default?: {
283
- value: unknown;
284
- };
285
- visibility?: FieldPolicyVisibility;
286
- help?: string;
287
- label?: string;
288
- order?: number;
289
- locked?: boolean;
290
- }
291
-
292
- /** One layer's contribution to a field's resolved policy (explain variant). */
293
- export declare interface FieldPolicyLayerContribution {
294
- layer: 'code' | 'app' | 'tenant' | 'user';
295
- /** Chain node id for `tenant` layers (root → leaf order). */
296
- tenantId?: string;
297
- /** User id for the `user` layer. */
298
- userId?: string;
299
- delta: FieldPolicyDelta;
300
- }
301
-
302
- export declare interface FieldPolicyOptions extends SmrtObjectOptions {
303
- objectRef?: string;
304
- fieldName?: string;
305
- scopeType?: FieldPolicyScopeType;
306
- tenantId?: string | null;
307
- userId?: string | null;
308
- /**
309
- * ENCODED channel: an already-JSON-encoded default, exactly as the column
310
- * stores it (`'"Net 30"'`, `'42'`, `'null'`), or `null` to inherit.
311
- *
312
- * This is the wire contract: the generated write routes hand the request
313
- * body straight to the constructor, and the #2049/#2050 gear posts
314
- * `JSON.stringify(draft.defaultValue)`. It must therefore keep meaning
315
- * "already encoded" — auto-serializing here would double-encode every gear
316
- * write.
317
- *
318
- * Use {@link FieldPolicyOptions.defaultValueRaw} for a plain value. The two
319
- * are mutually exclusive: `'"TBD"'` and `'TBD'` are indistinguishable once
320
- * a single option carries both meanings, so the channel must be explicit
321
- * rather than sniffed.
322
- */
323
- defaultValue?: string | null;
324
- /**
325
- * PLAIN channel: any value, always serialized — strings included. The
326
- * constructor-option twin of {@link FieldPolicy.setDefaultValue}, so
327
- * `{ defaultValueRaw: 'Net 30' }` stores `'"Net 30"'` rather than the
328
- * unparseable literal `Net 30`.
329
- */
330
- defaultValueRaw?: unknown;
331
- visibility?: FieldPolicyVisibility | null;
332
- help?: string | null;
333
- label?: string | null;
334
- displayOrder?: number | null;
335
- locked?: boolean | null;
336
- updatedBy?: string | null;
337
- }
338
-
339
- /**
340
- * Scope tier a {@link FieldPolicy} row belongs to.
341
- *
342
- * Resolution precedence is code seed → `app` → `tenant` (hierarchy walk,
343
- * root → leaf) → `user`. Unlike smrt-features (which ships only
344
- * `global`/`tenant`), the user tier is implemented end to end here — both
345
- * defaults and visibility resolve through it.
346
- */
347
- export declare type FieldPolicyScopeType = 'app' | 'tenant' | 'user';
348
-
349
- /**
350
- * Loader-function DI seam (NOT a container registration): the resolver calls
351
- * it lazily and treats `null` as "no hierarchy available" (flat-tenant
352
- * fallback). The default loader dynamic-imports `@happyvertical/smrt-users`
353
- * and returns `null` when it is not installed.
354
- */
355
- export declare type FieldPolicyTenantHierarchyLoader = (options: SmrtClassOptions) => Promise<FieldPolicyTenantHierarchyProvider | null>;
356
-
357
- /** Provider returning the root → leaf tenant chain (mirrors smrt-features). */
358
- export declare interface FieldPolicyTenantHierarchyProvider {
359
- getChain(tenantId: string): Promise<FieldPolicyTenantNode[]>;
360
- }
361
-
362
- /** Minimal tenant node consumed by the hierarchy walk (mirrors smrt-features). */
363
- export declare interface FieldPolicyTenantNode {
364
- id: string;
365
- inheritPermissions: boolean;
366
- cascadePermissions: boolean;
367
- }
368
-
369
- export declare interface FieldPolicyUsersModule {
370
- TenantCollection: {
371
- create(options: SmrtClassOptions): Promise<{
372
- get(criteria: {
373
- id: string;
374
- }): Promise<FieldPolicyUsersTenantRecord | null>;
375
- getAncestorsFromRoot(tenantId: string): Promise<FieldPolicyUsersTenantRecord[]>;
376
- }>;
377
- };
378
- }
379
-
380
- /**
381
- * Minimal structural shape of the tenant surface loaded from
382
- * `@happyvertical/smrt-users` by the default hierarchy loader. Only the
383
- * permission-cascade fields consumed by the chain walk are modeled.
384
- */
385
- export declare interface FieldPolicyUsersTenantRecord {
386
- id: string;
387
- inheritPermissions?: boolean;
388
- cascadePermissions?: boolean;
389
- }
390
-
391
- /**
392
- * Visibility tier for a field in generated/consuming form UIs.
393
- *
394
- * - `basic`: shown before the advanced disclosure
395
- * - `advanced`: shown behind the advanced disclosure
396
- * - `hidden`: not rendered (requires the field to be optional or have a
397
- * resolved default — the required-field invariant)
398
- */
399
- export declare type FieldPolicyVisibility = 'basic' | 'advanced' | 'hidden';
400
-
401
- /** The manifest/code default for a field, boxed; `undefined` when none. */
402
- export declare function getCodeDefault(field: RegisteredFieldInfo): {
403
- value: unknown;
404
- } | undefined;
405
-
406
- /** Code-seed grouping key (`ui.group`) — not overridable by stored rows. */
407
- export declare function getCodeSeedGroup(field: RegisteredFieldInfo): string | null;
408
-
409
- export declare function getFieldPolicyCacheTtlMs(): number;
410
-
411
- /** Security rail: `readPermission` may live top-level or under `_meta`. */
412
- export declare function getFieldReadPermission(field: RegisteredFieldInfo): string | undefined;
413
-
414
- /** Load the full (inherited) field map for a registered objectRef. */
415
- export declare function getObjectFieldMap(objectRef: string): Promise<FieldDefinitionMap>;
416
-
417
- /**
418
- * Drop every cached resolution for `(db, objectRef)`.
419
- *
420
- * Deliberately coarser than the prompts precedent (which deletes a single
421
- * `(key, tenantId)` entry when the tenant is known): tenant HIERARCHY makes a
422
- * parent-tenant row change affect every descendant tenant's resolution, and a
423
- * lock or app-row change affects user-tier entries too, so precise
424
- * invalidation would have to know the whole tenant tree. Per-objectRef prefix
425
- * invalidation is always correct and the 30s TTL keeps the cost bounded.
426
- */
427
- export declare function invalidateFieldPolicyCache(objectRef: string, db: DatabaseInterface | unknown): void;
428
-
429
- /**
430
- * A field is required when flagged required and not explicitly nullable.
431
- *
432
- * `nullable` is read from BOTH the top level and `_meta` for the same reason
433
- * `sensitive`/`readPermission`/`transient` are: registrations reach the
434
- * registry through several paths (decorator, manifest, STI merge) and land the
435
- * flag in either place. Checking only one side made the pair asymmetric —
436
- * `required` was read from both while `nullable` was read from one.
437
- */
438
- export declare function isRequiredField(field: RegisteredFieldInfo): boolean;
439
-
440
- /** Security rail: `sensitive` may live top-level or under `_meta` (STI merges). */
441
- export declare function isSensitiveField(field: RegisteredFieldInfo): boolean;
442
-
443
- export declare function isTransientField(field: RegisteredFieldInfo): boolean;
444
-
445
- /**
446
- * A resolved default satisfies the required-field invariant only when it is a
447
- * value a form could actually submit: explicit `null` and the empty string do
448
- * not count (a required text field "defaulting" to `''` is still unfilled).
449
- */
450
- export declare function isUsableRequiredDefault(defaultBox: {
451
- value: unknown;
452
- } | undefined): boolean;
453
-
454
- /** One field's registered metadata, as returned by `getAllFields`. */
455
- export declare type RegisteredFieldInfo = FieldDefinitionMap extends Map<string, infer F> ? F : never;
456
-
457
- /**
458
- * Resolve `objectRef` (qualified `@package/name:ClassName`) against the live
459
- * registry, throwing a descriptive error when the class is unknown.
460
- */
461
- export declare function requireRegisteredObject(objectRef: string): void;
462
-
463
- /** Fully merged policy for a single field. */
464
- export declare interface ResolvedFieldPolicy {
465
- fieldName: string;
466
- /** True when any layer resolved a default (including an explicit null). */
467
- hasDefault: boolean;
468
- /** Parsed default value; `undefined` when {@link hasDefault} is false. */
469
- defaultValue: unknown;
470
- visibility: FieldPolicyVisibility;
471
- help: string | null;
472
- label: string | null;
473
- order: number | null;
474
- /** Grouping key — code-seed only (`ui.group`), not overridable by rows. */
475
- group: string | null;
476
- locked: boolean;
477
- /** Mirror of the manifest required flag (nullable fields are optional). */
478
- required: boolean;
479
- /**
480
- * True when the resolver forced `basic` visibility because the field is
481
- * required and no usable default resolved (the resolver-side safety net for
482
- * the required-field invariant).
483
- */
484
- visibilityForced?: boolean;
485
- }
486
-
487
- /** Merged policy for every field of one object. */
488
- export declare interface ResolvedObjectFieldPolicy {
489
- objectRef: string;
490
- fields: Record<string, ResolvedFieldPolicy>;
491
- }
492
-
493
- /**
494
- * Resolve the merged field policy for `objectRef` in the given
495
- * `(tenantId, userId)` context: code seed → app rows → tenant rows (hierarchy
496
- * walk root → leaf) → user rows. Defaults AND visibility both resolve through
497
- * the user tier.
498
- *
499
- * Results are cached per `(database, objectRef, tenantId, userId)` with a
500
- * short TTL; `FieldPolicy.save()`/`.delete()` invalidate the object's entries.
501
- */
502
- export declare function resolveFieldPolicy(objectRef: string, options?: ResolveFieldPolicyOptions): Promise<ResolvedObjectFieldPolicy>;
503
-
504
- /**
505
- * Explain variant: the merged result plus ordered per-layer contributions for
506
- * each field, so the gear UI (#2049, "shows inherited base") and the control
507
- * panel (#2050, effective-value-per-layer) never re-derive precedence.
508
- *
509
- * A user-layer row suppressed by an effective org lock is omitted from the
510
- * layer list too — the listed layers always reproduce the merged result.
511
- */
512
- export declare function resolveFieldPolicyExplained(objectRef: string, options?: ResolveFieldPolicyOptions): Promise<ExplainedObjectFieldPolicy>;
513
-
514
- export declare interface ResolveFieldPolicyOptions {
515
- tenantId?: string | null;
516
- userId?: string | null;
517
- /**
518
- * Database holding `_smrt_field_policies`. Without it, only the code seed
519
- * resolves (stored layers are skipped) — the smrt-prompts precedent.
520
- */
521
- db?: SmrtClassOptions['db'];
522
- tenantHierarchyLoader?: FieldPolicyTenantHierarchyLoader;
523
- }
524
-
525
- /**
526
- * Narrow a manifest `_meta.ui` bag to the known {@link FieldUIHints} keys with
527
- * their expected primitive types, dropping junk (mirrors the sanitization the
528
- * web emission applies in core's `web-collections.ts`).
529
- */
530
- export declare function sanitizeFieldUIHints(value: unknown): FieldUIHints | undefined;
531
-
532
- export { }
1
+ export { clearFieldPolicyCache, getFieldPolicyCacheTtlMs, invalidateFieldPolicyCache, } from './cache.js';
2
+ export { FieldPolicyCollection } from './collections/FieldPolicyCollection.js';
3
+ export { assertDefaultValueMatchesFieldType, buildCodeSeedDelta, buildCodeSeedVisibility, type FieldDefinitionMap, getCodeDefault, getCodeSeedGroup, getFieldReadPermission, getObjectFieldMap, isPolicyAddressableField, isRequiredField, isSensitiveField, isTransientField, isUsableRequiredDefault, type RegisteredFieldInfo, requireRegisteredObject, sanitizeFieldUIHints, } from './field-definitions.js';
4
+ export { resolveFieldPolicy, resolveFieldPolicyExplained, resolveSurvivingTenantChainIds, } from './field-policy-resolver.js';
5
+ export { FieldPolicy } from './models/FieldPolicy.js';
6
+ export { ensureFieldPolicyPermissionsRegistered, FIELD_POLICY_PERMISSION_DEFINITIONS, MANAGE_FIELD_POLICY_PERMISSION, PERSONALIZE_FIELD_POLICY_PERMISSION, } from './permissions.js';
7
+ export { type BuildFieldPolicySettingsCatalogOptions, buildFieldPolicySettingsCatalog, type FieldPolicyCatalogField, type FieldPolicyCatalogObjectSummary, type FieldPolicyDetailItem, type FieldPolicySettingsCatalogData, type FieldPolicySettingsCatalogPage, type FieldPolicySettingsCatalogQuery, type FieldPolicySummaryItem, fieldPolicyCatalogItemId, parseFieldPolicyCatalogQuery, } from './settings-catalog.js';
8
+ export { APP_FIELD_POLICY_SCOPE_KEY, type ExplainedObjectFieldPolicy, FIELD_POLICY_SCOPE_TYPES, FIELD_POLICY_VISIBILITIES, type FieldPolicyAuditRow, type FieldPolicyAuditSnapshot, type FieldPolicyBatchResult, type FieldPolicyDelta, type FieldPolicyDriftReason, type FieldPolicyDriftRow, type FieldPolicyEditorCapabilities, type FieldPolicyEditorRow, type FieldPolicyEditorState, type FieldPolicyEditorStateDenied, type FieldPolicyEditorStateResult, type FieldPolicyLayerContribution, type FieldPolicyOptions, type FieldPolicyScopeType, type FieldPolicyTenantHierarchyLoader, type FieldPolicyTenantHierarchyProvider, type FieldPolicyTenantNode, type FieldPolicyUsersModule, type FieldPolicyUsersTenantRecord, type FieldPolicyVisibility, type ResolvedFieldPolicy, type ResolvedObjectFieldPolicy, type ResolveFieldPolicyOptions, } from './types.js';
9
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAKH,OAAO,wBAAwB,CAAC;AAIhC,OAAO,EACL,qBAAqB,EACrB,wBAAwB,EACxB,0BAA0B,GAC3B,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,qBAAqB,EAAE,MAAM,wCAAwC,CAAC;AAC/E,OAAO,EACL,kCAAkC,EAClC,kBAAkB,EAClB,uBAAuB,EACvB,KAAK,kBAAkB,EACvB,cAAc,EACd,gBAAgB,EAChB,sBAAsB,EACtB,iBAAiB,EACjB,wBAAwB,EACxB,eAAe,EACf,gBAAgB,EAChB,gBAAgB,EAChB,uBAAuB,EACvB,KAAK,mBAAmB,EACxB,uBAAuB,EACvB,oBAAoB,GACrB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EACL,kBAAkB,EAClB,2BAA2B,EAC3B,8BAA8B,GAC/B,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AACtD,OAAO,EACL,sCAAsC,EACtC,mCAAmC,EACnC,8BAA8B,EAC9B,mCAAmC,GACpC,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,KAAK,sCAAsC,EAC3C,+BAA+B,EAC/B,KAAK,uBAAuB,EAC5B,KAAK,+BAA+B,EACpC,KAAK,qBAAqB,EAC1B,KAAK,8BAA8B,EACnC,KAAK,8BAA8B,EACnC,KAAK,+BAA+B,EACpC,KAAK,sBAAsB,EAC3B,wBAAwB,EACxB,4BAA4B,GAC7B,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,0BAA0B,EAC1B,KAAK,0BAA0B,EAC/B,wBAAwB,EACxB,yBAAyB,EACzB,KAAK,mBAAmB,EACxB,KAAK,wBAAwB,EAC7B,KAAK,sBAAsB,EAC3B,KAAK,gBAAgB,EACrB,KAAK,sBAAsB,EAC3B,KAAK,mBAAmB,EACxB,KAAK,6BAA6B,EAClC,KAAK,oBAAoB,EACzB,KAAK,sBAAsB,EAC3B,KAAK,4BAA4B,EACjC,KAAK,4BAA4B,EACjC,KAAK,4BAA4B,EACjC,KAAK,kBAAkB,EACvB,KAAK,oBAAoB,EACzB,KAAK,gCAAgC,EACrC,KAAK,kCAAkC,EACvC,KAAK,qBAAqB,EAC1B,KAAK,sBAAsB,EAC3B,KAAK,4BAA4B,EACjC,KAAK,qBAAqB,EAC1B,KAAK,mBAAmB,EACxB,KAAK,yBAAyB,EAC9B,KAAK,yBAAyB,GAC/B,MAAM,YAAY,CAAC"}