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