@happyvertical/smrt-features 0.57.45 → 0.57.47

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 CHANGED
@@ -32,7 +32,7 @@ Code-first feature flag system with global and tenant-hierarchy resolution.
32
32
  ## Management UI (`./svelte`)
33
33
 
34
34
  - Built like every other Svelte-shipping package here (`smrt-tenancy`/`smrt-personas` are the closest models): `vite build --mode library && svelte-package -i src/svelte -o dist/svelte`, an `./svelte` export with a `svelte` condition, `svelte` as an **optional** peer dependency — plus `scripts/prune-svelte-package-artifacts.js` (as `smrt-content` does), because `svelte-package` otherwise copies `__tests__/` and `*.test.*` into the published `dist/svelte`. The root export stays Svelte-free so server-only consumers are unaffected.
35
- - `FeatureSettingsPanel` is presentational: rows and callbacks in, no fetching, no authorization. `src/svelte/types.ts` deliberately imports nothing from `smrt-core` or the `@smrt()` models, so the browser bundle stays thin; `FeatureSettingsRow` is structurally assignable to `FeatureSettingsView`.
35
+ - `FeatureSettingsPanel` is presentational: rows and callbacks in, no fetching, no authorization. `src/svelte/types.ts` deliberately imports nothing from `smrt-core` or the `@smrt()` models, so the browser bundle stays thin; `showKeys` (default true) hides the raw key/package lines for end-user surfaces while keeping the hidden `featureKey` input; `FeatureSettingsRow` is structurally assignable to `FeatureSettingsView`.
36
36
  - Each row is its own `<form>`, so `formAction` gives a working screen with no client JS; `onSave` intercepts the submit instead and reads the row's values straight off the form. The global column is opt-in (`showGlobal` / `globalEditable`) because most apps have no platform-admin tier.
37
37
  - Two invariants the selects have to keep, both regression-tested. (1) The tenant column's *Default* option must never name the code default: the tenant scope inherits the global override and, with a `FeatureTenantHierarchyProvider`, ancestor tenants' overrides too, so `defaultEnabled` would tell an operator the opposite of what choosing it does. `inheritedOptionLabel()` names `FeatureSettingsView.inheritedEnabled` when the row carries it and says "Default (inherited)" otherwise; `FeatureSettingsService` sets it only where it is exactly knowable (no tenant override → inherited is the effective state), never derived from `globalEffect`. The global column does name the code default — nothing sits above global. (2) The rows remount on `contextKey ?? features`, so an unsaved choice cannot survive a reload or a tenant switch and be submitted against the new rows.
38
38
  - Writes validate the **scope shape** before the authorizer runs: `scopeType` must be `global` or `tenant`, a global write must use `GLOBAL_FEATURE_SCOPE_ID`, and a tenant id must be non-blank and already trimmed. A host authorizer cannot catch this — `scopeType: 'global'` with someone else's tenant id reads as an ordinary global write to an authorizer that only gates the global scope — and the resolver looks rows up by the exact `(featureKey, scopeType, scopeId)` triple, so a row written elsewhere is invisible state, not a setting.
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@ import { FeatureOverrideEffect, GLOBAL_FEATURE_SCOPE_ID } from "./types.js";
2
2
  import { ObjectRegistry, SmrtCollection, SmrtObject, createQualifiedName, isQualifiedName, parseQualifiedName, smrt } from "@happyvertical/smrt-core";
3
3
  import { importWorkspaceModule } from "@happyvertical/smrt-core/utils/import-workspace-module";
4
4
  //#region src/__smrt-register__.ts
5
- ObjectRegistry.registerPackageManifest(JSON.parse(`{"version":"1.0.0","timestamp":0,"packageName":"@happyvertical/smrt-features","packageVersion":"0.57.45","objects":{"@happyvertical/smrt-features:FeatureDefinition":{"name":"featuredefinition","className":"FeatureDefinition","qualifiedName":"@happyvertical/smrt-features:FeatureDefinition","collection":"featuredefinitions","filePath":"/home/runner/work/smrt/smrt/packages/features/src/feature-definition.ts","packageName":"@happyvertical/smrt-features","fields":{"featureKey":{"type":"text","required":false,"default":""},"packageName":{"type":"text","required":false,"default":""},"qualifiedClassName":{"type":"text","required":false,"default":""},"className":{"type":"text","required":false,"default":""},"localId":{"type":"text","required":false,"default":""},"defaultEnabled":{"type":"boolean","required":false,"default":false},"label":{"type":"text","required":false,"default":""},"description":{"type":"text","required":false,"default":""},"metadata":{"type":"text","required":false,"default":""},"visibility":{"type":"text","required":false,"default":"public"}},"methods":{"getMetadata":{"name":"getMetadata","async":false,"parameters":[],"returnType":"FeatureMetadata","isStatic":false,"isPublic":true},"setMetadata":{"name":"setMetadata","async":false,"parameters":[{"name":"metadata","type":"FeatureMetadata | null","optional":false,"unionBranches":[{"type":"FeatureMetadata"},{"type":"null"}]}],"returnType":"void","isStatic":false,"isPublic":true}},"decoratorConfig":{"tableName":"_smrt_feature_definitions","api":false,"cli":false,"mcp":false,"conflictColumns":["feature_key"]},"extends":"SmrtObject","exportName":"FeatureDefinition","collectionExportName":"FeatureDefinitionCollection","displayLabelField":"label","schema":{"tableName":"_smrt_feature_definitions","ddl":"CREATE TABLE IF NOT EXISTS \\"_smrt_feature_definitions\\" (\\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 \\"feature_key\\" TEXT DEFAULT '',\\n \\"package_name\\" TEXT DEFAULT '',\\n \\"qualified_class_name\\" TEXT DEFAULT '',\\n \\"class_name\\" TEXT DEFAULT '',\\n \\"local_id\\" TEXT DEFAULT '',\\n \\"default_enabled\\" BOOLEAN DEFAULT FALSE,\\n \\"label\\" TEXT DEFAULT '',\\n \\"description\\" TEXT DEFAULT '',\\n \\"metadata\\" TEXT DEFAULT '',\\n \\"visibility\\" TEXT DEFAULT 'public'\\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"},"feature_key":{"type":"TEXT","notNull":false,"unique":false,"default":""},"package_name":{"type":"TEXT","notNull":false,"unique":false,"default":""},"qualified_class_name":{"type":"TEXT","notNull":false,"unique":false,"default":""},"class_name":{"type":"TEXT","notNull":false,"unique":false,"default":""},"local_id":{"type":"TEXT","notNull":false,"unique":false,"default":""},"default_enabled":{"type":"BOOLEAN","notNull":false,"unique":false,"default":false},"label":{"type":"TEXT","notNull":false,"unique":false,"default":""},"description":{"type":"TEXT","notNull":false,"unique":false,"default":""},"metadata":{"type":"TEXT","notNull":false,"unique":false,"default":""},"visibility":{"type":"TEXT","notNull":false,"unique":false,"default":"public"}},"indexes":[{"name":"_smrt_feature_definitions_feature_key_idx","columns":["feature_key"],"unique":true,"nullsNotDistinct":true},{"name":"_smrt_feature_definitions_slug_context_idx","columns":["slug","context"]},{"name":"_smrt_feature_definitions_created_at_idx","columns":["created_at"]}],"version":"8813204c"},"importPath":"@happyvertical/smrt-features"},"@happyvertical/smrt-features:FeatureDefinitionCollection":{"name":"featuredefinitioncollection","className":"FeatureDefinitionCollection","qualifiedName":"@happyvertical/smrt-features:FeatureDefinitionCollection","collection":"featuredefinitions","filePath":"/home/runner/work/smrt/smrt/packages/features/src/feature-definitions.ts","packageName":"@happyvertical/smrt-features","fields":{},"methods":{"findByFeatureKey":{"name":"findByFeatureKey","async":true,"parameters":[{"name":"featureKey","type":"string","optional":false}],"returnType":"Promise<FeatureDefinition | null>","isStatic":false,"isPublic":true},"findByPackageName":{"name":"findByPackageName","async":true,"parameters":[{"name":"packageName","type":"string","optional":false}],"returnType":"Promise<FeatureDefinition[]>","isStatic":false,"isPublic":true},"upsertDefinition":{"name":"upsertDefinition","async":true,"parameters":[{"name":"seed","type":"FeatureDefinitionSeed","optional":false}],"returnType":"Promise<object>","isStatic":false,"isPublic":true}},"decoratorConfig":{"tableName":"_smrt_feature_definitions","api":false,"mcp":false,"cli":false},"extends":"SmrtCollection","extendsTypeArg":"FeatureDefinition","exportName":"FeatureDefinitionCollection","collectionExportName":"FeatureDefinitionCollectionCollection","schema":{"tableName":"_smrt_feature_definitions","ddl":"CREATE TABLE IF NOT EXISTS \\"_smrt_feature_definitions\\" (\\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 \\"feature_key\\" TEXT DEFAULT '',\\n \\"package_name\\" TEXT DEFAULT '',\\n \\"qualified_class_name\\" TEXT DEFAULT '',\\n \\"class_name\\" TEXT DEFAULT '',\\n \\"local_id\\" TEXT DEFAULT '',\\n \\"default_enabled\\" BOOLEAN DEFAULT FALSE,\\n \\"label\\" TEXT DEFAULT '',\\n \\"description\\" TEXT DEFAULT '',\\n \\"metadata\\" TEXT DEFAULT '',\\n \\"visibility\\" TEXT DEFAULT 'public'\\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"},"feature_key":{"type":"TEXT","notNull":false,"unique":false,"default":""},"package_name":{"type":"TEXT","notNull":false,"unique":false,"default":""},"qualified_class_name":{"type":"TEXT","notNull":false,"unique":false,"default":""},"class_name":{"type":"TEXT","notNull":false,"unique":false,"default":""},"local_id":{"type":"TEXT","notNull":false,"unique":false,"default":""},"default_enabled":{"type":"BOOLEAN","notNull":false,"unique":false,"default":false},"label":{"type":"TEXT","notNull":false,"unique":false,"default":""},"description":{"type":"TEXT","notNull":false,"unique":false,"default":""},"metadata":{"type":"TEXT","notNull":false,"unique":false,"default":""},"visibility":{"type":"TEXT","notNull":false,"unique":false,"default":"public"}},"indexes":[{"name":"_smrt_feature_definitions_feature_key_idx","columns":["feature_key"],"unique":true,"nullsNotDistinct":true},{"name":"_smrt_feature_definitions_slug_context_idx","columns":["slug","context"]},{"name":"_smrt_feature_definitions_created_at_idx","columns":["created_at"]}],"version":"8813204c"},"importPath":"@happyvertical/smrt-features"},"@happyvertical/smrt-features:FeatureOverride":{"name":"featureoverride","className":"FeatureOverride","qualifiedName":"@happyvertical/smrt-features:FeatureOverride","collection":"featureoverrides","filePath":"/home/runner/work/smrt/smrt/packages/features/src/feature-override.ts","packageName":"@happyvertical/smrt-features","fields":{"featureKey":{"type":"text","required":false,"default":""},"scopeType":{"type":"text","required":false,"default":"global","enum":["global","tenant"]},"scopeId":{"type":"text","required":false},"effect":{"type":"text","required":false,"enum":["inherit","enable","disable"]}},"methods":{"isInherit":{"name":"isInherit","async":false,"parameters":[],"returnType":"boolean","isStatic":false,"isPublic":true},"isEnabled":{"name":"isEnabled","async":false,"parameters":[],"returnType":"boolean","isStatic":false,"isPublic":true},"isDisabled":{"name":"isDisabled","async":false,"parameters":[],"returnType":"boolean","isStatic":false,"isPublic":true}},"decoratorConfig":{"tableName":"_smrt_feature_overrides","api":false,"cli":false,"mcp":false,"conflictColumns":["feature_key","scope_type","scope_id"]},"extends":"SmrtObject","exportName":"FeatureOverride","collectionExportName":"FeatureOverrideCollection","schema":{"tableName":"_smrt_feature_overrides","ddl":"CREATE TABLE IF NOT EXISTS \\"_smrt_feature_overrides\\" (\\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 \\"feature_key\\" TEXT DEFAULT '',\\n \\"scope_type\\" TEXT DEFAULT 'global',\\n \\"scope_id\\" TEXT,\\n \\"effect\\" TEXT\\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"},"feature_key":{"type":"TEXT","notNull":false,"unique":false,"default":""},"scope_type":{"type":"TEXT","notNull":false,"unique":false,"default":"global"},"scope_id":{"type":"TEXT","notNull":false,"unique":false},"effect":{"type":"TEXT","notNull":false,"unique":false}},"indexes":[{"name":"_smrt_feature_overrides_feature_key_scope_type_idx","columns":["feature_key","scope_type","scope_id"],"unique":true,"nullsNotDistinct":true},{"name":"_smrt_feature_overrides_slug_context_idx","columns":["slug","context"]},{"name":"_smrt_feature_overrides_created_at_idx","columns":["created_at"]}],"version":"d61cf901"},"importPath":"@happyvertical/smrt-features"},"@happyvertical/smrt-features:FeatureOverrideCollection":{"name":"featureoverridecollection","className":"FeatureOverrideCollection","qualifiedName":"@happyvertical/smrt-features:FeatureOverrideCollection","collection":"featureoverrides","filePath":"/home/runner/work/smrt/smrt/packages/features/src/feature-overrides.ts","packageName":"@happyvertical/smrt-features","fields":{},"methods":{"findByFeatureKey":{"name":"findByFeatureKey","async":true,"parameters":[{"name":"featureKey","type":"string","optional":false}],"returnType":"Promise<FeatureOverride[]>","isStatic":false,"isPublic":true},"findByFeatureAndScope":{"name":"findByFeatureAndScope","async":true,"parameters":[{"name":"featureKey","type":"string","optional":false},{"name":"scopeType","type":"FeatureScopeType","optional":false},{"name":"scopeId","type":"string","optional":false}],"returnType":"Promise<FeatureOverride | null>","isStatic":false,"isPublic":true},"getGlobalOverride":{"name":"getGlobalOverride","async":true,"parameters":[{"name":"featureKey","type":"string","optional":false}],"returnType":"Promise<FeatureOverride | null>","isStatic":false,"isPublic":true},"getTenantOverride":{"name":"getTenantOverride","async":true,"parameters":[{"name":"featureKey","type":"string","optional":false},{"name":"tenantId","type":"string","optional":false}],"returnType":"Promise<FeatureOverride | null>","isStatic":false,"isPublic":true},"getOverrideMap":{"name":"getOverrideMap","async":true,"parameters":[{"name":"featureKey","type":"string","optional":false},{"name":"scopeType","type":"FeatureScopeType","optional":false},{"name":"scopeIds","type":"string[]","optional":false}],"returnType":"Promise<Map<string, FeatureOverride>>","isStatic":false,"isPublic":true},"setOverride":{"name":"setOverride","async":true,"parameters":[{"name":"featureKey","type":"string","optional":false},{"name":"scopeType","type":"FeatureScopeType","optional":false},{"name":"scopeId","type":"string","optional":false},{"name":"effect","type":"FeatureOverrideEffect","optional":false}],"returnType":"Promise<FeatureOverride>","isStatic":false,"isPublic":true},"setGlobalOverride":{"name":"setGlobalOverride","async":true,"parameters":[{"name":"featureKey","type":"string","optional":false},{"name":"effect","type":"FeatureOverrideEffect","optional":false}],"returnType":"Promise<FeatureOverride>","isStatic":false,"isPublic":true},"setTenantOverride":{"name":"setTenantOverride","async":true,"parameters":[{"name":"featureKey","type":"string","optional":false},{"name":"tenantId","type":"string","optional":false},{"name":"effect","type":"FeatureOverrideEffect","optional":false}],"returnType":"Promise<FeatureOverride>","isStatic":false,"isPublic":true},"removeOverride":{"name":"removeOverride","async":true,"parameters":[{"name":"featureKey","type":"string","optional":false},{"name":"scopeType","type":"FeatureScopeType","optional":false},{"name":"scopeId","type":"string","optional":false}],"returnType":"Promise<boolean>","isStatic":false,"isPublic":true}},"decoratorConfig":{"tableName":"_smrt_feature_overrides","api":false,"mcp":false,"cli":false},"extends":"SmrtCollection","extendsTypeArg":"FeatureOverride","exportName":"FeatureOverrideCollection","collectionExportName":"FeatureOverrideCollectionCollection","schema":{"tableName":"_smrt_feature_overrides","ddl":"CREATE TABLE IF NOT EXISTS \\"_smrt_feature_overrides\\" (\\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 \\"feature_key\\" TEXT DEFAULT '',\\n \\"scope_type\\" TEXT DEFAULT 'global',\\n \\"scope_id\\" TEXT,\\n \\"effect\\" TEXT\\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"},"feature_key":{"type":"TEXT","notNull":false,"unique":false,"default":""},"scope_type":{"type":"TEXT","notNull":false,"unique":false,"default":"global"},"scope_id":{"type":"TEXT","notNull":false,"unique":false},"effect":{"type":"TEXT","notNull":false,"unique":false}},"indexes":[{"name":"_smrt_feature_overrides_feature_key_scope_type_idx","columns":["feature_key","scope_type","scope_id"],"unique":true,"nullsNotDistinct":true},{"name":"_smrt_feature_overrides_slug_context_idx","columns":["slug","context"]},{"name":"_smrt_feature_overrides_created_at_idx","columns":["created_at"]}],"version":"d61cf901"},"importPath":"@happyvertical/smrt-features"}},"moduleType":"smrt","smrtDependencies":["@happyvertical/smrt-core","@happyvertical/smrt-users"],"browser":{"status":"server-only","issues":["#3618"],"reason":"imports core utils/import-workspace-module (node:url, node:fs, node:path)"},"stampsConstructors":true}`));
5
+ ObjectRegistry.registerPackageManifest(JSON.parse(`{"version":"1.0.0","timestamp":0,"packageName":"@happyvertical/smrt-features","packageVersion":"0.57.47","objects":{"@happyvertical/smrt-features:FeatureDefinition":{"name":"featuredefinition","className":"FeatureDefinition","qualifiedName":"@happyvertical/smrt-features:FeatureDefinition","collection":"featuredefinitions","filePath":"/home/runner/work/smrt/smrt/packages/features/src/feature-definition.ts","packageName":"@happyvertical/smrt-features","fields":{"featureKey":{"type":"text","required":false,"default":""},"packageName":{"type":"text","required":false,"default":""},"qualifiedClassName":{"type":"text","required":false,"default":""},"className":{"type":"text","required":false,"default":""},"localId":{"type":"text","required":false,"default":""},"defaultEnabled":{"type":"boolean","required":false,"default":false},"label":{"type":"text","required":false,"default":""},"description":{"type":"text","required":false,"default":""},"metadata":{"type":"text","required":false,"default":""},"visibility":{"type":"text","required":false,"default":"public"}},"methods":{"getMetadata":{"name":"getMetadata","async":false,"parameters":[],"returnType":"FeatureMetadata","isStatic":false,"isPublic":true},"setMetadata":{"name":"setMetadata","async":false,"parameters":[{"name":"metadata","type":"FeatureMetadata | null","optional":false,"unionBranches":[{"type":"FeatureMetadata"},{"type":"null"}]}],"returnType":"void","isStatic":false,"isPublic":true}},"decoratorConfig":{"tableName":"_smrt_feature_definitions","api":false,"cli":false,"mcp":false,"conflictColumns":["feature_key"]},"extends":"SmrtObject","exportName":"FeatureDefinition","collectionExportName":"FeatureDefinitionCollection","displayLabelField":"label","schema":{"tableName":"_smrt_feature_definitions","ddl":"CREATE TABLE IF NOT EXISTS \\"_smrt_feature_definitions\\" (\\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 \\"feature_key\\" TEXT DEFAULT '',\\n \\"package_name\\" TEXT DEFAULT '',\\n \\"qualified_class_name\\" TEXT DEFAULT '',\\n \\"class_name\\" TEXT DEFAULT '',\\n \\"local_id\\" TEXT DEFAULT '',\\n \\"default_enabled\\" BOOLEAN DEFAULT FALSE,\\n \\"label\\" TEXT DEFAULT '',\\n \\"description\\" TEXT DEFAULT '',\\n \\"metadata\\" TEXT DEFAULT '',\\n \\"visibility\\" TEXT DEFAULT 'public'\\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"},"feature_key":{"type":"TEXT","notNull":false,"unique":false,"default":""},"package_name":{"type":"TEXT","notNull":false,"unique":false,"default":""},"qualified_class_name":{"type":"TEXT","notNull":false,"unique":false,"default":""},"class_name":{"type":"TEXT","notNull":false,"unique":false,"default":""},"local_id":{"type":"TEXT","notNull":false,"unique":false,"default":""},"default_enabled":{"type":"BOOLEAN","notNull":false,"unique":false,"default":false},"label":{"type":"TEXT","notNull":false,"unique":false,"default":""},"description":{"type":"TEXT","notNull":false,"unique":false,"default":""},"metadata":{"type":"TEXT","notNull":false,"unique":false,"default":""},"visibility":{"type":"TEXT","notNull":false,"unique":false,"default":"public"}},"indexes":[{"name":"_smrt_feature_definitions_feature_key_idx","columns":["feature_key"],"unique":true,"nullsNotDistinct":true},{"name":"_smrt_feature_definitions_slug_context_idx","columns":["slug","context"]},{"name":"_smrt_feature_definitions_created_at_idx","columns":["created_at"]}],"version":"8813204c"},"importPath":"@happyvertical/smrt-features"},"@happyvertical/smrt-features:FeatureDefinitionCollection":{"name":"featuredefinitioncollection","className":"FeatureDefinitionCollection","qualifiedName":"@happyvertical/smrt-features:FeatureDefinitionCollection","collection":"featuredefinitions","filePath":"/home/runner/work/smrt/smrt/packages/features/src/feature-definitions.ts","packageName":"@happyvertical/smrt-features","fields":{},"methods":{"findByFeatureKey":{"name":"findByFeatureKey","async":true,"parameters":[{"name":"featureKey","type":"string","optional":false}],"returnType":"Promise<FeatureDefinition | null>","isStatic":false,"isPublic":true},"findByPackageName":{"name":"findByPackageName","async":true,"parameters":[{"name":"packageName","type":"string","optional":false}],"returnType":"Promise<FeatureDefinition[]>","isStatic":false,"isPublic":true},"upsertDefinition":{"name":"upsertDefinition","async":true,"parameters":[{"name":"seed","type":"FeatureDefinitionSeed","optional":false}],"returnType":"Promise<object>","isStatic":false,"isPublic":true}},"decoratorConfig":{"tableName":"_smrt_feature_definitions","api":false,"mcp":false,"cli":false},"extends":"SmrtCollection","extendsTypeArg":"FeatureDefinition","exportName":"FeatureDefinitionCollection","collectionExportName":"FeatureDefinitionCollectionCollection","collectionOf":"@happyvertical/smrt-features:FeatureDefinition","inheritedExposure":["api","mcp","cli"],"schema":{"tableName":"_smrt_feature_definitions","ddl":"CREATE TABLE IF NOT EXISTS \\"_smrt_feature_definitions\\" (\\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 \\"feature_key\\" TEXT DEFAULT '',\\n \\"package_name\\" TEXT DEFAULT '',\\n \\"qualified_class_name\\" TEXT DEFAULT '',\\n \\"class_name\\" TEXT DEFAULT '',\\n \\"local_id\\" TEXT DEFAULT '',\\n \\"default_enabled\\" BOOLEAN DEFAULT FALSE,\\n \\"label\\" TEXT DEFAULT '',\\n \\"description\\" TEXT DEFAULT '',\\n \\"metadata\\" TEXT DEFAULT '',\\n \\"visibility\\" TEXT DEFAULT 'public'\\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"},"feature_key":{"type":"TEXT","notNull":false,"unique":false,"default":""},"package_name":{"type":"TEXT","notNull":false,"unique":false,"default":""},"qualified_class_name":{"type":"TEXT","notNull":false,"unique":false,"default":""},"class_name":{"type":"TEXT","notNull":false,"unique":false,"default":""},"local_id":{"type":"TEXT","notNull":false,"unique":false,"default":""},"default_enabled":{"type":"BOOLEAN","notNull":false,"unique":false,"default":false},"label":{"type":"TEXT","notNull":false,"unique":false,"default":""},"description":{"type":"TEXT","notNull":false,"unique":false,"default":""},"metadata":{"type":"TEXT","notNull":false,"unique":false,"default":""},"visibility":{"type":"TEXT","notNull":false,"unique":false,"default":"public"}},"indexes":[{"name":"_smrt_feature_definitions_feature_key_idx","columns":["feature_key"],"unique":true,"nullsNotDistinct":true},{"name":"_smrt_feature_definitions_slug_context_idx","columns":["slug","context"]},{"name":"_smrt_feature_definitions_created_at_idx","columns":["created_at"]}],"version":"8813204c"},"importPath":"@happyvertical/smrt-features"},"@happyvertical/smrt-features:FeatureOverride":{"name":"featureoverride","className":"FeatureOverride","qualifiedName":"@happyvertical/smrt-features:FeatureOverride","collection":"featureoverrides","filePath":"/home/runner/work/smrt/smrt/packages/features/src/feature-override.ts","packageName":"@happyvertical/smrt-features","fields":{"featureKey":{"type":"text","required":false,"default":""},"scopeType":{"type":"text","required":false,"default":"global","enum":["global","tenant"]},"scopeId":{"type":"text","required":false},"effect":{"type":"text","required":false,"enum":["inherit","enable","disable"]}},"methods":{"isInherit":{"name":"isInherit","async":false,"parameters":[],"returnType":"boolean","isStatic":false,"isPublic":true},"isEnabled":{"name":"isEnabled","async":false,"parameters":[],"returnType":"boolean","isStatic":false,"isPublic":true},"isDisabled":{"name":"isDisabled","async":false,"parameters":[],"returnType":"boolean","isStatic":false,"isPublic":true}},"decoratorConfig":{"tableName":"_smrt_feature_overrides","api":false,"cli":false,"mcp":false,"conflictColumns":["feature_key","scope_type","scope_id"]},"extends":"SmrtObject","exportName":"FeatureOverride","collectionExportName":"FeatureOverrideCollection","schema":{"tableName":"_smrt_feature_overrides","ddl":"CREATE TABLE IF NOT EXISTS \\"_smrt_feature_overrides\\" (\\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 \\"feature_key\\" TEXT DEFAULT '',\\n \\"scope_type\\" TEXT DEFAULT 'global',\\n \\"scope_id\\" TEXT,\\n \\"effect\\" TEXT\\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"},"feature_key":{"type":"TEXT","notNull":false,"unique":false,"default":""},"scope_type":{"type":"TEXT","notNull":false,"unique":false,"default":"global"},"scope_id":{"type":"TEXT","notNull":false,"unique":false},"effect":{"type":"TEXT","notNull":false,"unique":false}},"indexes":[{"name":"_smrt_feature_overrides_feature_key_scope_type_idx","columns":["feature_key","scope_type","scope_id"],"unique":true,"nullsNotDistinct":true},{"name":"_smrt_feature_overrides_slug_context_idx","columns":["slug","context"]},{"name":"_smrt_feature_overrides_created_at_idx","columns":["created_at"]}],"version":"d61cf901"},"importPath":"@happyvertical/smrt-features"},"@happyvertical/smrt-features:FeatureOverrideCollection":{"name":"featureoverridecollection","className":"FeatureOverrideCollection","qualifiedName":"@happyvertical/smrt-features:FeatureOverrideCollection","collection":"featureoverrides","filePath":"/home/runner/work/smrt/smrt/packages/features/src/feature-overrides.ts","packageName":"@happyvertical/smrt-features","fields":{},"methods":{"findByFeatureKey":{"name":"findByFeatureKey","async":true,"parameters":[{"name":"featureKey","type":"string","optional":false}],"returnType":"Promise<FeatureOverride[]>","isStatic":false,"isPublic":true},"findByFeatureAndScope":{"name":"findByFeatureAndScope","async":true,"parameters":[{"name":"featureKey","type":"string","optional":false},{"name":"scopeType","type":"FeatureScopeType","optional":false},{"name":"scopeId","type":"string","optional":false}],"returnType":"Promise<FeatureOverride | null>","isStatic":false,"isPublic":true},"getGlobalOverride":{"name":"getGlobalOverride","async":true,"parameters":[{"name":"featureKey","type":"string","optional":false}],"returnType":"Promise<FeatureOverride | null>","isStatic":false,"isPublic":true},"getTenantOverride":{"name":"getTenantOverride","async":true,"parameters":[{"name":"featureKey","type":"string","optional":false},{"name":"tenantId","type":"string","optional":false}],"returnType":"Promise<FeatureOverride | null>","isStatic":false,"isPublic":true},"getOverrideMap":{"name":"getOverrideMap","async":true,"parameters":[{"name":"featureKey","type":"string","optional":false},{"name":"scopeType","type":"FeatureScopeType","optional":false},{"name":"scopeIds","type":"string[]","optional":false}],"returnType":"Promise<Map<string, FeatureOverride>>","isStatic":false,"isPublic":true},"setOverride":{"name":"setOverride","async":true,"parameters":[{"name":"featureKey","type":"string","optional":false},{"name":"scopeType","type":"FeatureScopeType","optional":false},{"name":"scopeId","type":"string","optional":false},{"name":"effect","type":"FeatureOverrideEffect","optional":false}],"returnType":"Promise<FeatureOverride>","isStatic":false,"isPublic":true},"setGlobalOverride":{"name":"setGlobalOverride","async":true,"parameters":[{"name":"featureKey","type":"string","optional":false},{"name":"effect","type":"FeatureOverrideEffect","optional":false}],"returnType":"Promise<FeatureOverride>","isStatic":false,"isPublic":true},"setTenantOverride":{"name":"setTenantOverride","async":true,"parameters":[{"name":"featureKey","type":"string","optional":false},{"name":"tenantId","type":"string","optional":false},{"name":"effect","type":"FeatureOverrideEffect","optional":false}],"returnType":"Promise<FeatureOverride>","isStatic":false,"isPublic":true},"removeOverride":{"name":"removeOverride","async":true,"parameters":[{"name":"featureKey","type":"string","optional":false},{"name":"scopeType","type":"FeatureScopeType","optional":false},{"name":"scopeId","type":"string","optional":false}],"returnType":"Promise<boolean>","isStatic":false,"isPublic":true}},"decoratorConfig":{"tableName":"_smrt_feature_overrides","api":false,"mcp":false,"cli":false},"extends":"SmrtCollection","extendsTypeArg":"FeatureOverride","exportName":"FeatureOverrideCollection","collectionExportName":"FeatureOverrideCollectionCollection","collectionOf":"@happyvertical/smrt-features:FeatureOverride","inheritedExposure":["api","mcp","cli"],"schema":{"tableName":"_smrt_feature_overrides","ddl":"CREATE TABLE IF NOT EXISTS \\"_smrt_feature_overrides\\" (\\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 \\"feature_key\\" TEXT DEFAULT '',\\n \\"scope_type\\" TEXT DEFAULT 'global',\\n \\"scope_id\\" TEXT,\\n \\"effect\\" TEXT\\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"},"feature_key":{"type":"TEXT","notNull":false,"unique":false,"default":""},"scope_type":{"type":"TEXT","notNull":false,"unique":false,"default":"global"},"scope_id":{"type":"TEXT","notNull":false,"unique":false},"effect":{"type":"TEXT","notNull":false,"unique":false}},"indexes":[{"name":"_smrt_feature_overrides_feature_key_scope_type_idx","columns":["feature_key","scope_type","scope_id"],"unique":true,"nullsNotDistinct":true},{"name":"_smrt_feature_overrides_slug_context_idx","columns":["slug","context"]},{"name":"_smrt_feature_overrides_created_at_idx","columns":["created_at"]}],"version":"d61cf901"},"importPath":"@happyvertical/smrt-features"}},"moduleType":"smrt","smrtDependencies":["@happyvertical/smrt-core","@happyvertical/smrt-users"],"browser":{"status":"server-only","issues":["#3618"],"reason":"imports core utils/import-workspace-module (node:url, node:fs, node:path)"},"stampsConstructors":true}`));
6
6
  //#endregion
7
7
  //#region src/utils.ts
8
8
  function createFeatureKey(qualifiedClassName, localId) {
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/__smrt-register__.ts","../src/utils.ts","../src/feature-definition.ts","../src/feature-definitions.ts","../src/feature-override.ts","../src/feature-override-service.ts","../src/feature-overrides.ts","../src/list-bounds.ts","../src/feature-resolver.ts","../src/feature-settings.ts","../src/feature-sync.ts"],"sourcesContent":["// Generated by @happyvertical/smrt-core smrtPlugin at library build time.\n// The package manifest is inlined so self-registration cannot miss when a\n// downstream bundler relocates this module away from dist/manifest.json\n// (https://github.com/happyvertical/smrt/issues/1506,\n// https://github.com/happyvertical/smrt/issues/1507).\nimport { ObjectRegistry } from '@happyvertical/smrt-core';\n\nObjectRegistry.registerPackageManifest(JSON.parse(\"{\\\"version\\\":\\\"1.0.0\\\",\\\"timestamp\\\":0,\\\"packageName\\\":\\\"@happyvertical/smrt-features\\\",\\\"packageVersion\\\":\\\"0.57.45\\\",\\\"objects\\\":{\\\"@happyvertical/smrt-features:FeatureDefinition\\\":{\\\"name\\\":\\\"featuredefinition\\\",\\\"className\\\":\\\"FeatureDefinition\\\",\\\"qualifiedName\\\":\\\"@happyvertical/smrt-features:FeatureDefinition\\\",\\\"collection\\\":\\\"featuredefinitions\\\",\\\"filePath\\\":\\\"/home/runner/work/smrt/smrt/packages/features/src/feature-definition.ts\\\",\\\"packageName\\\":\\\"@happyvertical/smrt-features\\\",\\\"fields\\\":{\\\"featureKey\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":false,\\\"default\\\":\\\"\\\"},\\\"packageName\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":false,\\\"default\\\":\\\"\\\"},\\\"qualifiedClassName\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":false,\\\"default\\\":\\\"\\\"},\\\"className\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":false,\\\"default\\\":\\\"\\\"},\\\"localId\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":false,\\\"default\\\":\\\"\\\"},\\\"defaultEnabled\\\":{\\\"type\\\":\\\"boolean\\\",\\\"required\\\":false,\\\"default\\\":false},\\\"label\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":false,\\\"default\\\":\\\"\\\"},\\\"description\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":false,\\\"default\\\":\\\"\\\"},\\\"metadata\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":false,\\\"default\\\":\\\"\\\"},\\\"visibility\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":false,\\\"default\\\":\\\"public\\\"}},\\\"methods\\\":{\\\"getMetadata\\\":{\\\"name\\\":\\\"getMetadata\\\",\\\"async\\\":false,\\\"parameters\\\":[],\\\"returnType\\\":\\\"FeatureMetadata\\\",\\\"isStatic\\\":false,\\\"isPublic\\\":true},\\\"setMetadata\\\":{\\\"name\\\":\\\"setMetadata\\\",\\\"async\\\":false,\\\"parameters\\\":[{\\\"name\\\":\\\"metadata\\\",\\\"type\\\":\\\"FeatureMetadata | null\\\",\\\"optional\\\":false,\\\"unionBranches\\\":[{\\\"type\\\":\\\"FeatureMetadata\\\"},{\\\"type\\\":\\\"null\\\"}]}],\\\"returnType\\\":\\\"void\\\",\\\"isStatic\\\":false,\\\"isPublic\\\":true}},\\\"decoratorConfig\\\":{\\\"tableName\\\":\\\"_smrt_feature_definitions\\\",\\\"api\\\":false,\\\"cli\\\":false,\\\"mcp\\\":false,\\\"conflictColumns\\\":[\\\"feature_key\\\"]},\\\"extends\\\":\\\"SmrtObject\\\",\\\"exportName\\\":\\\"FeatureDefinition\\\",\\\"collectionExportName\\\":\\\"FeatureDefinitionCollection\\\",\\\"displayLabelField\\\":\\\"label\\\",\\\"schema\\\":{\\\"tableName\\\":\\\"_smrt_feature_definitions\\\",\\\"ddl\\\":\\\"CREATE TABLE IF NOT EXISTS \\\\\\\"_smrt_feature_definitions\\\\\\\" (\\\\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 \\\\\\\"feature_key\\\\\\\" TEXT DEFAULT '',\\\\n \\\\\\\"package_name\\\\\\\" TEXT DEFAULT '',\\\\n \\\\\\\"qualified_class_name\\\\\\\" TEXT DEFAULT '',\\\\n \\\\\\\"class_name\\\\\\\" TEXT DEFAULT '',\\\\n \\\\\\\"local_id\\\\\\\" TEXT DEFAULT '',\\\\n \\\\\\\"default_enabled\\\\\\\" BOOLEAN DEFAULT FALSE,\\\\n \\\\\\\"label\\\\\\\" TEXT DEFAULT '',\\\\n \\\\\\\"description\\\\\\\" TEXT DEFAULT '',\\\\n \\\\\\\"metadata\\\\\\\" TEXT DEFAULT '',\\\\n \\\\\\\"visibility\\\\\\\" TEXT DEFAULT 'public'\\\\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\\\"},\\\"feature_key\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false,\\\"default\\\":\\\"\\\"},\\\"package_name\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false,\\\"default\\\":\\\"\\\"},\\\"qualified_class_name\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false,\\\"default\\\":\\\"\\\"},\\\"class_name\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false,\\\"default\\\":\\\"\\\"},\\\"local_id\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false,\\\"default\\\":\\\"\\\"},\\\"default_enabled\\\":{\\\"type\\\":\\\"BOOLEAN\\\",\\\"notNull\\\":false,\\\"unique\\\":false,\\\"default\\\":false},\\\"label\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false,\\\"default\\\":\\\"\\\"},\\\"description\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false,\\\"default\\\":\\\"\\\"},\\\"metadata\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false,\\\"default\\\":\\\"\\\"},\\\"visibility\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false,\\\"default\\\":\\\"public\\\"}},\\\"indexes\\\":[{\\\"name\\\":\\\"_smrt_feature_definitions_feature_key_idx\\\",\\\"columns\\\":[\\\"feature_key\\\"],\\\"unique\\\":true,\\\"nullsNotDistinct\\\":true},{\\\"name\\\":\\\"_smrt_feature_definitions_slug_context_idx\\\",\\\"columns\\\":[\\\"slug\\\",\\\"context\\\"]},{\\\"name\\\":\\\"_smrt_feature_definitions_created_at_idx\\\",\\\"columns\\\":[\\\"created_at\\\"]}],\\\"version\\\":\\\"8813204c\\\"},\\\"importPath\\\":\\\"@happyvertical/smrt-features\\\"},\\\"@happyvertical/smrt-features:FeatureDefinitionCollection\\\":{\\\"name\\\":\\\"featuredefinitioncollection\\\",\\\"className\\\":\\\"FeatureDefinitionCollection\\\",\\\"qualifiedName\\\":\\\"@happyvertical/smrt-features:FeatureDefinitionCollection\\\",\\\"collection\\\":\\\"featuredefinitions\\\",\\\"filePath\\\":\\\"/home/runner/work/smrt/smrt/packages/features/src/feature-definitions.ts\\\",\\\"packageName\\\":\\\"@happyvertical/smrt-features\\\",\\\"fields\\\":{},\\\"methods\\\":{\\\"findByFeatureKey\\\":{\\\"name\\\":\\\"findByFeatureKey\\\",\\\"async\\\":true,\\\"parameters\\\":[{\\\"name\\\":\\\"featureKey\\\",\\\"type\\\":\\\"string\\\",\\\"optional\\\":false}],\\\"returnType\\\":\\\"Promise<FeatureDefinition | null>\\\",\\\"isStatic\\\":false,\\\"isPublic\\\":true},\\\"findByPackageName\\\":{\\\"name\\\":\\\"findByPackageName\\\",\\\"async\\\":true,\\\"parameters\\\":[{\\\"name\\\":\\\"packageName\\\",\\\"type\\\":\\\"string\\\",\\\"optional\\\":false}],\\\"returnType\\\":\\\"Promise<FeatureDefinition[]>\\\",\\\"isStatic\\\":false,\\\"isPublic\\\":true},\\\"upsertDefinition\\\":{\\\"name\\\":\\\"upsertDefinition\\\",\\\"async\\\":true,\\\"parameters\\\":[{\\\"name\\\":\\\"seed\\\",\\\"type\\\":\\\"FeatureDefinitionSeed\\\",\\\"optional\\\":false}],\\\"returnType\\\":\\\"Promise<object>\\\",\\\"isStatic\\\":false,\\\"isPublic\\\":true}},\\\"decoratorConfig\\\":{\\\"tableName\\\":\\\"_smrt_feature_definitions\\\",\\\"api\\\":false,\\\"mcp\\\":false,\\\"cli\\\":false},\\\"extends\\\":\\\"SmrtCollection\\\",\\\"extendsTypeArg\\\":\\\"FeatureDefinition\\\",\\\"exportName\\\":\\\"FeatureDefinitionCollection\\\",\\\"collectionExportName\\\":\\\"FeatureDefinitionCollectionCollection\\\",\\\"schema\\\":{\\\"tableName\\\":\\\"_smrt_feature_definitions\\\",\\\"ddl\\\":\\\"CREATE TABLE IF NOT EXISTS \\\\\\\"_smrt_feature_definitions\\\\\\\" (\\\\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 \\\\\\\"feature_key\\\\\\\" TEXT DEFAULT '',\\\\n \\\\\\\"package_name\\\\\\\" TEXT DEFAULT '',\\\\n \\\\\\\"qualified_class_name\\\\\\\" TEXT DEFAULT '',\\\\n \\\\\\\"class_name\\\\\\\" TEXT DEFAULT '',\\\\n \\\\\\\"local_id\\\\\\\" TEXT DEFAULT '',\\\\n \\\\\\\"default_enabled\\\\\\\" BOOLEAN DEFAULT FALSE,\\\\n \\\\\\\"label\\\\\\\" TEXT DEFAULT '',\\\\n \\\\\\\"description\\\\\\\" TEXT DEFAULT '',\\\\n \\\\\\\"metadata\\\\\\\" TEXT DEFAULT '',\\\\n \\\\\\\"visibility\\\\\\\" TEXT DEFAULT 'public'\\\\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\\\"},\\\"feature_key\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false,\\\"default\\\":\\\"\\\"},\\\"package_name\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false,\\\"default\\\":\\\"\\\"},\\\"qualified_class_name\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false,\\\"default\\\":\\\"\\\"},\\\"class_name\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false,\\\"default\\\":\\\"\\\"},\\\"local_id\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false,\\\"default\\\":\\\"\\\"},\\\"default_enabled\\\":{\\\"type\\\":\\\"BOOLEAN\\\",\\\"notNull\\\":false,\\\"unique\\\":false,\\\"default\\\":false},\\\"label\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false,\\\"default\\\":\\\"\\\"},\\\"description\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false,\\\"default\\\":\\\"\\\"},\\\"metadata\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false,\\\"default\\\":\\\"\\\"},\\\"visibility\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false,\\\"default\\\":\\\"public\\\"}},\\\"indexes\\\":[{\\\"name\\\":\\\"_smrt_feature_definitions_feature_key_idx\\\",\\\"columns\\\":[\\\"feature_key\\\"],\\\"unique\\\":true,\\\"nullsNotDistinct\\\":true},{\\\"name\\\":\\\"_smrt_feature_definitions_slug_context_idx\\\",\\\"columns\\\":[\\\"slug\\\",\\\"context\\\"]},{\\\"name\\\":\\\"_smrt_feature_definitions_created_at_idx\\\",\\\"columns\\\":[\\\"created_at\\\"]}],\\\"version\\\":\\\"8813204c\\\"},\\\"importPath\\\":\\\"@happyvertical/smrt-features\\\"},\\\"@happyvertical/smrt-features:FeatureOverride\\\":{\\\"name\\\":\\\"featureoverride\\\",\\\"className\\\":\\\"FeatureOverride\\\",\\\"qualifiedName\\\":\\\"@happyvertical/smrt-features:FeatureOverride\\\",\\\"collection\\\":\\\"featureoverrides\\\",\\\"filePath\\\":\\\"/home/runner/work/smrt/smrt/packages/features/src/feature-override.ts\\\",\\\"packageName\\\":\\\"@happyvertical/smrt-features\\\",\\\"fields\\\":{\\\"featureKey\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":false,\\\"default\\\":\\\"\\\"},\\\"scopeType\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":false,\\\"default\\\":\\\"global\\\",\\\"enum\\\":[\\\"global\\\",\\\"tenant\\\"]},\\\"scopeId\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":false},\\\"effect\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":false,\\\"enum\\\":[\\\"inherit\\\",\\\"enable\\\",\\\"disable\\\"]}},\\\"methods\\\":{\\\"isInherit\\\":{\\\"name\\\":\\\"isInherit\\\",\\\"async\\\":false,\\\"parameters\\\":[],\\\"returnType\\\":\\\"boolean\\\",\\\"isStatic\\\":false,\\\"isPublic\\\":true},\\\"isEnabled\\\":{\\\"name\\\":\\\"isEnabled\\\",\\\"async\\\":false,\\\"parameters\\\":[],\\\"returnType\\\":\\\"boolean\\\",\\\"isStatic\\\":false,\\\"isPublic\\\":true},\\\"isDisabled\\\":{\\\"name\\\":\\\"isDisabled\\\",\\\"async\\\":false,\\\"parameters\\\":[],\\\"returnType\\\":\\\"boolean\\\",\\\"isStatic\\\":false,\\\"isPublic\\\":true}},\\\"decoratorConfig\\\":{\\\"tableName\\\":\\\"_smrt_feature_overrides\\\",\\\"api\\\":false,\\\"cli\\\":false,\\\"mcp\\\":false,\\\"conflictColumns\\\":[\\\"feature_key\\\",\\\"scope_type\\\",\\\"scope_id\\\"]},\\\"extends\\\":\\\"SmrtObject\\\",\\\"exportName\\\":\\\"FeatureOverride\\\",\\\"collectionExportName\\\":\\\"FeatureOverrideCollection\\\",\\\"schema\\\":{\\\"tableName\\\":\\\"_smrt_feature_overrides\\\",\\\"ddl\\\":\\\"CREATE TABLE IF NOT EXISTS \\\\\\\"_smrt_feature_overrides\\\\\\\" (\\\\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 \\\\\\\"feature_key\\\\\\\" TEXT DEFAULT '',\\\\n \\\\\\\"scope_type\\\\\\\" TEXT DEFAULT 'global',\\\\n \\\\\\\"scope_id\\\\\\\" TEXT,\\\\n \\\\\\\"effect\\\\\\\" TEXT\\\\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\\\"},\\\"feature_key\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false,\\\"default\\\":\\\"\\\"},\\\"scope_type\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false,\\\"default\\\":\\\"global\\\"},\\\"scope_id\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"effect\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false}},\\\"indexes\\\":[{\\\"name\\\":\\\"_smrt_feature_overrides_feature_key_scope_type_idx\\\",\\\"columns\\\":[\\\"feature_key\\\",\\\"scope_type\\\",\\\"scope_id\\\"],\\\"unique\\\":true,\\\"nullsNotDistinct\\\":true},{\\\"name\\\":\\\"_smrt_feature_overrides_slug_context_idx\\\",\\\"columns\\\":[\\\"slug\\\",\\\"context\\\"]},{\\\"name\\\":\\\"_smrt_feature_overrides_created_at_idx\\\",\\\"columns\\\":[\\\"created_at\\\"]}],\\\"version\\\":\\\"d61cf901\\\"},\\\"importPath\\\":\\\"@happyvertical/smrt-features\\\"},\\\"@happyvertical/smrt-features:FeatureOverrideCollection\\\":{\\\"name\\\":\\\"featureoverridecollection\\\",\\\"className\\\":\\\"FeatureOverrideCollection\\\",\\\"qualifiedName\\\":\\\"@happyvertical/smrt-features:FeatureOverrideCollection\\\",\\\"collection\\\":\\\"featureoverrides\\\",\\\"filePath\\\":\\\"/home/runner/work/smrt/smrt/packages/features/src/feature-overrides.ts\\\",\\\"packageName\\\":\\\"@happyvertical/smrt-features\\\",\\\"fields\\\":{},\\\"methods\\\":{\\\"findByFeatureKey\\\":{\\\"name\\\":\\\"findByFeatureKey\\\",\\\"async\\\":true,\\\"parameters\\\":[{\\\"name\\\":\\\"featureKey\\\",\\\"type\\\":\\\"string\\\",\\\"optional\\\":false}],\\\"returnType\\\":\\\"Promise<FeatureOverride[]>\\\",\\\"isStatic\\\":false,\\\"isPublic\\\":true},\\\"findByFeatureAndScope\\\":{\\\"name\\\":\\\"findByFeatureAndScope\\\",\\\"async\\\":true,\\\"parameters\\\":[{\\\"name\\\":\\\"featureKey\\\",\\\"type\\\":\\\"string\\\",\\\"optional\\\":false},{\\\"name\\\":\\\"scopeType\\\",\\\"type\\\":\\\"FeatureScopeType\\\",\\\"optional\\\":false},{\\\"name\\\":\\\"scopeId\\\",\\\"type\\\":\\\"string\\\",\\\"optional\\\":false}],\\\"returnType\\\":\\\"Promise<FeatureOverride | null>\\\",\\\"isStatic\\\":false,\\\"isPublic\\\":true},\\\"getGlobalOverride\\\":{\\\"name\\\":\\\"getGlobalOverride\\\",\\\"async\\\":true,\\\"parameters\\\":[{\\\"name\\\":\\\"featureKey\\\",\\\"type\\\":\\\"string\\\",\\\"optional\\\":false}],\\\"returnType\\\":\\\"Promise<FeatureOverride | null>\\\",\\\"isStatic\\\":false,\\\"isPublic\\\":true},\\\"getTenantOverride\\\":{\\\"name\\\":\\\"getTenantOverride\\\",\\\"async\\\":true,\\\"parameters\\\":[{\\\"name\\\":\\\"featureKey\\\",\\\"type\\\":\\\"string\\\",\\\"optional\\\":false},{\\\"name\\\":\\\"tenantId\\\",\\\"type\\\":\\\"string\\\",\\\"optional\\\":false}],\\\"returnType\\\":\\\"Promise<FeatureOverride | null>\\\",\\\"isStatic\\\":false,\\\"isPublic\\\":true},\\\"getOverrideMap\\\":{\\\"name\\\":\\\"getOverrideMap\\\",\\\"async\\\":true,\\\"parameters\\\":[{\\\"name\\\":\\\"featureKey\\\",\\\"type\\\":\\\"string\\\",\\\"optional\\\":false},{\\\"name\\\":\\\"scopeType\\\",\\\"type\\\":\\\"FeatureScopeType\\\",\\\"optional\\\":false},{\\\"name\\\":\\\"scopeIds\\\",\\\"type\\\":\\\"string[]\\\",\\\"optional\\\":false}],\\\"returnType\\\":\\\"Promise<Map<string, FeatureOverride>>\\\",\\\"isStatic\\\":false,\\\"isPublic\\\":true},\\\"setOverride\\\":{\\\"name\\\":\\\"setOverride\\\",\\\"async\\\":true,\\\"parameters\\\":[{\\\"name\\\":\\\"featureKey\\\",\\\"type\\\":\\\"string\\\",\\\"optional\\\":false},{\\\"name\\\":\\\"scopeType\\\",\\\"type\\\":\\\"FeatureScopeType\\\",\\\"optional\\\":false},{\\\"name\\\":\\\"scopeId\\\",\\\"type\\\":\\\"string\\\",\\\"optional\\\":false},{\\\"name\\\":\\\"effect\\\",\\\"type\\\":\\\"FeatureOverrideEffect\\\",\\\"optional\\\":false}],\\\"returnType\\\":\\\"Promise<FeatureOverride>\\\",\\\"isStatic\\\":false,\\\"isPublic\\\":true},\\\"setGlobalOverride\\\":{\\\"name\\\":\\\"setGlobalOverride\\\",\\\"async\\\":true,\\\"parameters\\\":[{\\\"name\\\":\\\"featureKey\\\",\\\"type\\\":\\\"string\\\",\\\"optional\\\":false},{\\\"name\\\":\\\"effect\\\",\\\"type\\\":\\\"FeatureOverrideEffect\\\",\\\"optional\\\":false}],\\\"returnType\\\":\\\"Promise<FeatureOverride>\\\",\\\"isStatic\\\":false,\\\"isPublic\\\":true},\\\"setTenantOverride\\\":{\\\"name\\\":\\\"setTenantOverride\\\",\\\"async\\\":true,\\\"parameters\\\":[{\\\"name\\\":\\\"featureKey\\\",\\\"type\\\":\\\"string\\\",\\\"optional\\\":false},{\\\"name\\\":\\\"tenantId\\\",\\\"type\\\":\\\"string\\\",\\\"optional\\\":false},{\\\"name\\\":\\\"effect\\\",\\\"type\\\":\\\"FeatureOverrideEffect\\\",\\\"optional\\\":false}],\\\"returnType\\\":\\\"Promise<FeatureOverride>\\\",\\\"isStatic\\\":false,\\\"isPublic\\\":true},\\\"removeOverride\\\":{\\\"name\\\":\\\"removeOverride\\\",\\\"async\\\":true,\\\"parameters\\\":[{\\\"name\\\":\\\"featureKey\\\",\\\"type\\\":\\\"string\\\",\\\"optional\\\":false},{\\\"name\\\":\\\"scopeType\\\",\\\"type\\\":\\\"FeatureScopeType\\\",\\\"optional\\\":false},{\\\"name\\\":\\\"scopeId\\\",\\\"type\\\":\\\"string\\\",\\\"optional\\\":false}],\\\"returnType\\\":\\\"Promise<boolean>\\\",\\\"isStatic\\\":false,\\\"isPublic\\\":true}},\\\"decoratorConfig\\\":{\\\"tableName\\\":\\\"_smrt_feature_overrides\\\",\\\"api\\\":false,\\\"mcp\\\":false,\\\"cli\\\":false},\\\"extends\\\":\\\"SmrtCollection\\\",\\\"extendsTypeArg\\\":\\\"FeatureOverride\\\",\\\"exportName\\\":\\\"FeatureOverrideCollection\\\",\\\"collectionExportName\\\":\\\"FeatureOverrideCollectionCollection\\\",\\\"schema\\\":{\\\"tableName\\\":\\\"_smrt_feature_overrides\\\",\\\"ddl\\\":\\\"CREATE TABLE IF NOT EXISTS \\\\\\\"_smrt_feature_overrides\\\\\\\" (\\\\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 \\\\\\\"feature_key\\\\\\\" TEXT DEFAULT '',\\\\n \\\\\\\"scope_type\\\\\\\" TEXT DEFAULT 'global',\\\\n \\\\\\\"scope_id\\\\\\\" TEXT,\\\\n \\\\\\\"effect\\\\\\\" TEXT\\\\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\\\"},\\\"feature_key\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false,\\\"default\\\":\\\"\\\"},\\\"scope_type\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false,\\\"default\\\":\\\"global\\\"},\\\"scope_id\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"effect\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false}},\\\"indexes\\\":[{\\\"name\\\":\\\"_smrt_feature_overrides_feature_key_scope_type_idx\\\",\\\"columns\\\":[\\\"feature_key\\\",\\\"scope_type\\\",\\\"scope_id\\\"],\\\"unique\\\":true,\\\"nullsNotDistinct\\\":true},{\\\"name\\\":\\\"_smrt_feature_overrides_slug_context_idx\\\",\\\"columns\\\":[\\\"slug\\\",\\\"context\\\"]},{\\\"name\\\":\\\"_smrt_feature_overrides_created_at_idx\\\",\\\"columns\\\":[\\\"created_at\\\"]}],\\\"version\\\":\\\"d61cf901\\\"},\\\"importPath\\\":\\\"@happyvertical/smrt-features\\\"}},\\\"moduleType\\\":\\\"smrt\\\",\\\"smrtDependencies\\\":[\\\"@happyvertical/smrt-core\\\",\\\"@happyvertical/smrt-users\\\"],\\\"browser\\\":{\\\"status\\\":\\\"server-only\\\",\\\"issues\\\":[\\\"#3618\\\"],\\\"reason\\\":\\\"imports core utils/import-workspace-module (node:url, node:fs, node:path)\\\"},\\\"stampsConstructors\\\":true}\"));\n","import {\n createQualifiedName,\n isQualifiedName,\n ObjectRegistry,\n parseQualifiedName,\n type SmartObjectConfig,\n} from '@happyvertical/smrt-core';\nimport type {\n SmartObjectDefinition,\n SmartObjectManifest,\n} from '@happyvertical/smrt-core/manifest';\nimport type {\n FeatureDefinitionSeed,\n FeatureMetadata,\n FeatureTargetConstructor,\n} from './types.js';\n\nexport type RegistryEntryLike = {\n name: string;\n qualifiedName?: string;\n packageName?: string;\n config: SmartObjectConfig;\n visibility?: string;\n};\n\nexport function createFeatureKey(\n qualifiedClassName: string,\n localId: string,\n): string {\n if (!isQualifiedName(qualifiedClassName)) {\n throw new Error(\n `Feature keys require a qualified class name. Received \"${qualifiedClassName}\".`,\n );\n }\n\n if (!localId) {\n throw new Error('Feature localId is required.');\n }\n\n if (localId.includes('#')) {\n throw new Error(\n `Feature localId \"${localId}\" cannot contain \"#\". This character is reserved for canonical feature keys.`,\n );\n }\n\n return `${qualifiedClassName}#${localId}`;\n}\n\nexport function parseFeatureKey(featureKey: string): {\n qualifiedClassName: string;\n localId: string;\n} {\n const separatorIndex = featureKey.lastIndexOf('#');\n if (separatorIndex <= 0 || separatorIndex === featureKey.length - 1) {\n throw new Error(\n `Invalid feature key \"${featureKey}\". Expected format \"<qualifiedClassName>#<localId>\".`,\n );\n }\n\n const qualifiedClassName = featureKey.slice(0, separatorIndex);\n const localId = featureKey.slice(separatorIndex + 1);\n\n if (!isQualifiedName(qualifiedClassName)) {\n throw new Error(\n `Invalid feature key \"${featureKey}\". \"${qualifiedClassName}\" is not a qualified class name.`,\n );\n }\n\n return { qualifiedClassName, localId };\n}\n\nexport function serializeFeatureMetadata(\n metadata: FeatureMetadata | string | null | undefined,\n): string {\n if (!metadata) {\n return '';\n }\n\n if (typeof metadata === 'string') {\n const parsed = parseFeatureMetadataString(metadata);\n return JSON.stringify(parsed);\n }\n\n if (!isFeatureMetadataRecord(metadata)) {\n throw new Error(\n 'Feature metadata must be a plain object or a JSON string representing a plain object.',\n );\n }\n\n return JSON.stringify(metadata);\n}\n\nexport function parseFeatureMetadata(\n metadata: string | null | undefined,\n): FeatureMetadata {\n if (!metadata) {\n return {};\n }\n\n try {\n return parseFeatureMetadataString(metadata);\n } catch {\n return {};\n }\n}\n\nfunction parseFeatureMetadataString(metadata: string): FeatureMetadata {\n let parsed: unknown;\n\n try {\n parsed = JSON.parse(metadata);\n } catch (error) {\n throw new Error(\n 'Feature metadata must be a plain object or a JSON string representing a plain object.',\n { cause: error },\n );\n }\n\n if (!isFeatureMetadataRecord(parsed)) {\n throw new Error(\n 'Feature metadata must be a plain object or a JSON string representing a plain object.',\n );\n }\n\n return parsed;\n}\n\nfunction isFeatureMetadataRecord(value: unknown): value is FeatureMetadata {\n if (!value || typeof value !== 'object' || Array.isArray(value)) {\n return false;\n }\n\n const prototype = Object.getPrototypeOf(value);\n return prototype === Object.prototype || prototype === null;\n}\n\nexport function getQualifiedClassNameFromRegistry(\n registration: RegistryEntryLike,\n): string | null {\n if (\n registration.qualifiedName &&\n isQualifiedName(registration.qualifiedName)\n ) {\n return registration.qualifiedName;\n }\n\n if (registration.packageName) {\n return createQualifiedName(registration.packageName, registration.name);\n }\n\n return null;\n}\n\nexport function getPackageNameFromRegistry(\n registration: RegistryEntryLike,\n): string | null {\n if (registration.packageName) {\n return registration.packageName;\n }\n\n const qualifiedClassName = getQualifiedClassNameFromRegistry(registration);\n if (!qualifiedClassName) {\n return null;\n }\n\n return parseQualifiedName(qualifiedClassName).packageName;\n}\n\nexport function extractFeatureSeedsFromRegistryEntry(\n registration: RegistryEntryLike,\n): FeatureDefinitionSeed[] {\n if (registration.visibility === 'test') {\n return [];\n }\n\n const qualifiedClassName = getQualifiedClassNameFromRegistry(registration);\n if (!qualifiedClassName) {\n return [];\n }\n\n const featureConfig = registration.config.features;\n if (!featureConfig) {\n return [];\n }\n\n const packageName =\n registration.packageName ||\n parseQualifiedName(qualifiedClassName).packageName;\n\n return Object.entries(featureConfig).map(([localId, feature]) => ({\n featureKey: createFeatureKey(qualifiedClassName, localId),\n packageName,\n qualifiedClassName,\n className: registration.name,\n localId,\n defaultEnabled: feature.defaultEnabled,\n label: feature.label,\n description: feature.description,\n metadata: feature.metadata,\n visibility: registration.visibility,\n }));\n}\n\nexport function extractFeatureSeedsFromManifest(\n manifest: SmartObjectManifest,\n): FeatureDefinitionSeed[] {\n const seeds: FeatureDefinitionSeed[] = [];\n\n for (const [objectKey, objectDef] of Object.entries(manifest.objects)) {\n seeds.push(...extractFeatureSeedsFromManifestObject(objectKey, objectDef));\n }\n\n return seeds;\n}\n\nexport function extractTouchedPackagesFromManifest(\n manifest: SmartObjectManifest,\n): Set<string> {\n const packages = new Set<string>();\n\n if (manifest.packageName) {\n packages.add(manifest.packageName);\n }\n\n for (const [objectKey, objectDef] of Object.entries(manifest.objects)) {\n if (objectDef.visibility === 'test') {\n continue;\n }\n\n if (objectDef.packageName) {\n packages.add(objectDef.packageName);\n continue;\n }\n\n const qualifiedClassName = resolveManifestQualifiedClassName(\n objectKey,\n objectDef,\n );\n if (qualifiedClassName) {\n packages.add(parseQualifiedName(qualifiedClassName).packageName);\n }\n }\n\n return packages;\n}\n\nfunction extractFeatureSeedsFromManifestObject(\n objectKey: string,\n objectDef: SmartObjectDefinition,\n): FeatureDefinitionSeed[] {\n if (objectDef.visibility === 'test') {\n return [];\n }\n\n const features = objectDef.decoratorConfig.features;\n if (!features) {\n return [];\n }\n\n const qualifiedClassName = resolveManifestQualifiedClassName(\n objectKey,\n objectDef,\n );\n if (!qualifiedClassName) {\n return [];\n }\n\n const packageName =\n objectDef.packageName || parseQualifiedName(qualifiedClassName).packageName;\n\n return Object.entries(features).map(([localId, feature]) => ({\n featureKey: createFeatureKey(qualifiedClassName, localId),\n packageName,\n qualifiedClassName,\n className: objectDef.className,\n localId,\n defaultEnabled: feature.defaultEnabled,\n label: feature.label,\n description: feature.description,\n metadata:\n feature.metadata && typeof feature.metadata === 'object'\n ? (feature.metadata as FeatureMetadata)\n : undefined,\n visibility: objectDef.visibility,\n }));\n}\n\nfunction resolveManifestQualifiedClassName(\n objectKey: string,\n objectDef: SmartObjectDefinition,\n): string | null {\n if (objectDef.qualifiedName && isQualifiedName(objectDef.qualifiedName)) {\n return objectDef.qualifiedName;\n }\n\n if (isQualifiedName(objectKey)) {\n return objectKey;\n }\n\n if (objectDef.packageName) {\n return createQualifiedName(objectDef.packageName, objectDef.className);\n }\n\n return null;\n}\n\nexport function resolveFeatureKeyForTarget(\n classOrInstance: object | FeatureTargetConstructor,\n localId: string,\n): {\n featureKey: string;\n defaultEnabled: boolean;\n} {\n const ctor: FeatureTargetConstructor =\n typeof classOrInstance === 'function'\n ? (classOrInstance as FeatureTargetConstructor)\n : (classOrInstance.constructor as FeatureTargetConstructor);\n\n const registration = ObjectRegistry.getClassByConstructor(ctor);\n if (!registration) {\n throw new Error(\n `Cannot resolve feature \"${localId}\" because ${ctor?.name || 'the target class'} is not registered with @smrt().`,\n );\n }\n\n const qualifiedClassName = getQualifiedClassNameFromRegistry(registration);\n if (!qualifiedClassName) {\n throw new Error(\n `Cannot resolve feature \"${localId}\" for ${registration.name} because the class has no qualified package identity.`,\n );\n }\n\n const feature = registration.config.features?.[localId];\n if (!feature) {\n throw new Error(\n `Feature \"${localId}\" is not declared on ${qualifiedClassName}.`,\n );\n }\n\n return {\n featureKey: createFeatureKey(qualifiedClassName, localId),\n defaultEnabled: feature.defaultEnabled,\n };\n}\n\nexport function findFeatureDefaultInRegistry(\n featureKey: string,\n): boolean | undefined {\n const { qualifiedClassName, localId } = parseFeatureKey(featureKey);\n const registration =\n ObjectRegistry.getClassByQualifiedName(qualifiedClassName) ||\n ObjectRegistry.getClass(qualifiedClassName);\n\n const feature = registration?.config.features?.[localId];\n return feature?.defaultEnabled;\n}\n","import { SmrtObject, smrt } from '@happyvertical/smrt-core';\nimport type { FeatureDefinitionOptions, FeatureMetadata } from './types.js';\nimport { parseFeatureMetadata, serializeFeatureMetadata } from './utils.js';\n\n@smrt({\n tableName: '_smrt_feature_definitions',\n // Closed by default like FeatureOverride (#3013): the catalog is framework\n // state synced from manifests, not a tenant resource.\n api: false,\n cli: false,\n mcp: false,\n conflictColumns: ['feature_key'],\n})\nexport class FeatureDefinition extends SmrtObject { static __smrtPackage__ = \"@happyvertical/smrt-features\";\n featureKey: string = '';\n packageName: string = '';\n qualifiedClassName: string = '';\n className: string = '';\n localId: string = '';\n defaultEnabled: boolean = false;\n label: string = '';\n description: string = '';\n metadata: string = '';\n visibility: string = 'public';\n\n constructor(options: FeatureDefinitionOptions = {}) {\n super(options);\n\n if (options.featureKey !== undefined) this.featureKey = options.featureKey;\n if (options.packageName !== undefined)\n this.packageName = options.packageName;\n if (options.qualifiedClassName !== undefined) {\n this.qualifiedClassName = options.qualifiedClassName;\n }\n if (options.className !== undefined) this.className = options.className;\n if (options.localId !== undefined) this.localId = options.localId;\n if (options.defaultEnabled !== undefined) {\n this.defaultEnabled = options.defaultEnabled;\n }\n if (options.label !== undefined) this.label = options.label;\n if (options.description !== undefined)\n this.description = options.description;\n if (options.visibility !== undefined) this.visibility = options.visibility;\n if (options.metadata !== undefined) {\n this.metadata = serializeFeatureMetadata(options.metadata);\n }\n }\n\n getMetadata(): FeatureMetadata {\n return parseFeatureMetadata(this.metadata);\n }\n\n setMetadata(metadata: FeatureMetadata | null): void {\n this.metadata = serializeFeatureMetadata(metadata);\n }\n}\n","import { SmrtCollection } from '@happyvertical/smrt-core';\nimport { FeatureDefinition } from './feature-definition.js';\nimport type { FeatureDefinitionSeed } from './types.js';\nimport { serializeFeatureMetadata } from './utils.js';\n\nexport class FeatureDefinitionCollection extends SmrtCollection<FeatureDefinition> {\n static readonly _itemClass = FeatureDefinition;\n\n async findByFeatureKey(\n featureKey: string,\n ): Promise<FeatureDefinition | null> {\n const results = await this.list({\n where: { featureKey },\n limit: 1,\n });\n return results[0] ?? null;\n }\n\n async findByPackageName(packageName: string): Promise<FeatureDefinition[]> {\n return this.list({\n where: { packageName },\n });\n }\n\n async upsertDefinition(seed: FeatureDefinitionSeed): Promise<{\n definition: FeatureDefinition;\n status: 'created' | 'updated' | 'unchanged';\n }> {\n const existing = await this.findByFeatureKey(seed.featureKey);\n\n if (!existing) {\n const created = await this.create({\n ...seed,\n metadata: serializeFeatureMetadata(seed.metadata),\n });\n await created.save();\n return { definition: created, status: 'created' };\n }\n\n const nextMetadata = serializeFeatureMetadata(seed.metadata);\n const changed =\n existing.packageName !== seed.packageName ||\n existing.qualifiedClassName !== seed.qualifiedClassName ||\n existing.className !== seed.className ||\n existing.localId !== seed.localId ||\n existing.defaultEnabled !== seed.defaultEnabled ||\n existing.label !== (seed.label ?? '') ||\n existing.description !== (seed.description ?? '') ||\n existing.metadata !== nextMetadata ||\n existing.visibility !== (seed.visibility ?? 'public');\n\n if (!changed) {\n return { definition: existing, status: 'unchanged' };\n }\n\n existing.packageName = seed.packageName;\n existing.qualifiedClassName = seed.qualifiedClassName;\n existing.className = seed.className;\n existing.localId = seed.localId;\n existing.defaultEnabled = seed.defaultEnabled;\n existing.label = seed.label ?? '';\n existing.description = seed.description ?? '';\n existing.metadata = nextMetadata;\n existing.visibility = seed.visibility ?? 'public';\n await existing.save();\n\n return { definition: existing, status: 'updated' };\n }\n}\n","import { SmrtObject, smrt } from '@happyvertical/smrt-core';\nimport {\n FeatureOverrideEffect,\n type FeatureOverrideOptions,\n type FeatureScopeType,\n GLOBAL_FEATURE_SCOPE_ID,\n} from './types.js';\n\n// Override rows are authorization state: any scope (global, another tenant,\n// another user) can be written, and the object is not tenant-scoped. Generated\n// REST routes and MCP tools only check authentication, so they are closed\n// (#3013). Hosts write overrides server-side through\n// `FeatureOverrideService`, which requires an explicit authorizer for every\n// write (or the collection's `setOverride()` / `removeOverride()` after their\n// own authorization decision). The CLI is closed too: generated CLI commands\n// dispatch through the generated API.\n@smrt({\n tableName: '_smrt_feature_overrides',\n api: false,\n cli: false,\n mcp: false,\n conflictColumns: ['feature_key', 'scope_type', 'scope_id'],\n})\nexport class FeatureOverride extends SmrtObject { static __smrtPackage__ = \"@happyvertical/smrt-features\";\n featureKey: string = '';\n scopeType: FeatureScopeType = 'global';\n scopeId: string = GLOBAL_FEATURE_SCOPE_ID;\n effect: FeatureOverrideEffect = FeatureOverrideEffect.INHERIT;\n\n constructor(options: FeatureOverrideOptions = {}) {\n super(options);\n\n if (options.featureKey !== undefined) this.featureKey = options.featureKey;\n if (options.scopeType !== undefined) this.scopeType = options.scopeType;\n if (options.scopeId !== undefined) this.scopeId = options.scopeId;\n if (options.effect !== undefined) this.effect = options.effect;\n }\n\n isInherit(): boolean {\n return this.effect === FeatureOverrideEffect.INHERIT;\n }\n\n isEnabled(): boolean {\n return this.effect === FeatureOverrideEffect.ENABLE;\n }\n\n isDisabled(): boolean {\n return this.effect === FeatureOverrideEffect.DISABLE;\n }\n}\n","import type { FeatureOverride } from './feature-override.js';\nimport type { FeatureOverrideCollection } from './feature-overrides.js';\nimport type { FeatureOverrideEffect, FeatureScopeType } from './types.js';\n\n/** One override write the host must authorize before it runs (#3013). */\nexport interface FeatureOverrideWriteRequest {\n operation: 'set' | 'remove';\n featureKey: string;\n scopeType: FeatureScopeType;\n scopeId: string;\n /** Present for `set`. */\n effect?: FeatureOverrideEffect;\n}\n\n/**\n * The host's explicit authorization decision for one override write. Return\n * `true` only when the current caller may write that exact scope; anything\n * else denies.\n */\nexport type FeatureOverrideAuthorizer = (\n request: FeatureOverrideWriteRequest,\n) => boolean | Promise<boolean>;\n\n/** Thrown when the authorizer does not return `true`. Nothing is written. */\nexport class FeatureOverrideAuthorizationError extends Error {\n readonly status = 403;\n constructor(readonly request: FeatureOverrideWriteRequest) {\n super(\n `Feature override ${request.operation} denied for ${request.scopeType} scope`,\n );\n this.name = 'FeatureOverrideAuthorizationError';\n }\n}\n\n/**\n * Authorized write path for feature overrides (#3013). Override rows are\n * authorization state for every scope and `FeatureOverride` is not\n * tenant-scoped, so generated surfaces are closed. Hosts that let callers\n * change flags construct this service with an authorizer bound to the\n * current caller and write through it; every write asks the authorizer\n * first and fails closed.\n */\nexport class FeatureOverrideService {\n constructor(\n private readonly overrides: FeatureOverrideCollection,\n private readonly authorize: FeatureOverrideAuthorizer,\n ) {}\n\n async setOverride(\n featureKey: string,\n scopeType: FeatureScopeType,\n scopeId: string,\n effect: FeatureOverrideEffect,\n ): Promise<FeatureOverride> {\n await this.require({\n operation: 'set',\n featureKey,\n scopeType,\n scopeId,\n effect,\n });\n return this.overrides.setOverride(featureKey, scopeType, scopeId, effect);\n }\n\n async removeOverride(\n featureKey: string,\n scopeType: FeatureScopeType,\n scopeId: string,\n ): Promise<boolean> {\n await this.require({ operation: 'remove', featureKey, scopeType, scopeId });\n return this.overrides.removeOverride(featureKey, scopeType, scopeId);\n }\n\n private async require(request: FeatureOverrideWriteRequest): Promise<void> {\n let allowed = false;\n try {\n allowed = (await this.authorize(request)) === true;\n } catch {\n allowed = false;\n }\n if (!allowed) throw new FeatureOverrideAuthorizationError(request);\n }\n}\n","import { SmrtCollection } from '@happyvertical/smrt-core';\nimport { FeatureOverride } from './feature-override.js';\nimport {\n type FeatureOverrideEffect,\n type FeatureScopeType,\n GLOBAL_FEATURE_SCOPE_ID,\n} from './types.js';\n\nexport class FeatureOverrideCollection extends SmrtCollection<FeatureOverride> {\n static readonly _itemClass = FeatureOverride;\n\n async findByFeatureKey(featureKey: string): Promise<FeatureOverride[]> {\n return this.list({\n where: { featureKey },\n });\n }\n\n async findByFeatureAndScope(\n featureKey: string,\n scopeType: FeatureScopeType,\n scopeId: string,\n ): Promise<FeatureOverride | null> {\n const results = await this.list({\n where: { featureKey, scopeType, scopeId },\n limit: 1,\n });\n return results[0] ?? null;\n }\n\n async getGlobalOverride(featureKey: string): Promise<FeatureOverride | null> {\n return this.findByFeatureAndScope(\n featureKey,\n 'global',\n GLOBAL_FEATURE_SCOPE_ID,\n );\n }\n\n async getTenantOverride(\n featureKey: string,\n tenantId: string,\n ): Promise<FeatureOverride | null> {\n return this.findByFeatureAndScope(featureKey, 'tenant', tenantId);\n }\n\n async getOverrideMap(\n featureKey: string,\n scopeType: FeatureScopeType,\n scopeIds: string[],\n ): Promise<Map<string, FeatureOverride>> {\n const result = new Map<string, FeatureOverride>();\n if (scopeIds.length === 0) {\n return result;\n }\n\n const overrides = await this.list({\n where: { featureKey, scopeType, scopeId: scopeIds },\n });\n\n for (const override of overrides) {\n result.set(override.scopeId, override);\n }\n\n return result;\n }\n\n async setOverride(\n featureKey: string,\n scopeType: FeatureScopeType,\n scopeId: string,\n effect: FeatureOverrideEffect,\n ): Promise<FeatureOverride> {\n const existing = await this.findByFeatureAndScope(\n featureKey,\n scopeType,\n scopeId,\n );\n\n if (existing) {\n existing.effect = effect;\n await existing.save();\n return existing;\n }\n\n const created = await this.create({\n featureKey,\n scopeType,\n scopeId,\n effect,\n });\n await created.save();\n return created;\n }\n\n async setGlobalOverride(\n featureKey: string,\n effect: FeatureOverrideEffect,\n ): Promise<FeatureOverride> {\n return this.setOverride(\n featureKey,\n 'global',\n GLOBAL_FEATURE_SCOPE_ID,\n effect,\n );\n }\n\n async setTenantOverride(\n featureKey: string,\n tenantId: string,\n effect: FeatureOverrideEffect,\n ): Promise<FeatureOverride> {\n return this.setOverride(featureKey, 'tenant', tenantId, effect);\n }\n\n async removeOverride(\n featureKey: string,\n scopeType: FeatureScopeType,\n scopeId: string,\n ): Promise<boolean> {\n const existing = await this.findByFeatureAndScope(\n featureKey,\n scopeType,\n scopeId,\n );\n\n if (!existing) {\n return false;\n }\n\n await existing.delete();\n return true;\n }\n}\n","/** Collection options for framework-internal, correctness-critical reads. @packageDocumentation */\nimport type {\n SmrtClassOptions,\n SmrtCollectionOptions,\n} from '@happyvertical/smrt-core';\n\n/** Host options this package accepts before stripping internal list bounds. */\nexport type FeatureHostOptions = SmrtClassOptions &\n Pick<SmrtCollectionOptions, 'defaultListLimit' | 'maxListLimit'>;\n\n/**\n * The caller's options with its list bounds removed.\n *\n * `SmrtCollection.create()` forwards `defaultListLimit`/`maxListLimit` from\n * any options bag it is handed (#2367), so a host that configures those\n * application-wide — a perfectly ordinary thing to do — would silently cap\n * every internal enumeration this package runs. Two of those are\n * correctness-critical and neither is a user-facing page of results\n * (`FeatureDefinition`/`FeatureOverride` expose no generated list surface,\n * #3013): a dropped ancestor override changes the flag's *resolved value*\n * (`FeatureResolver.isEnabled()`), and a dropped definition makes a feature\n * invisible to a management screen or a sync prune pass (#3056, same hazard\n * class as #3048). The collections built here are therefore always created\n * from this bag. Only the list bounds are stripped; the database, tenancy\n * context, and every other option still apply.\n *\n * This is a local copy of `@happyvertical/smrt-users`'\n * `authorization-read-options.ts#withoutListBounds` (added for #3048/#3051).\n * `smrt-features` deliberately never imports `smrt-users` (see package\n * AGENTS.md), and no shared `smrt-core` helper exists for this yet, so the\n * two-line implementation is duplicated here rather than adding a\n * cross-package dependency for it.\n */\nexport function withoutListBounds(\n options: SmrtClassOptions | SmrtCollectionOptions,\n): SmrtCollectionOptions {\n return { ...options, defaultListLimit: undefined, maxListLimit: undefined };\n}\n","import type { SmrtClassOptions } from '@happyvertical/smrt-core';\nimport { importWorkspaceModule } from '@happyvertical/smrt-core/utils/import-workspace-module';\nimport { FeatureDefinitionCollection } from './feature-definitions.js';\nimport { FeatureOverrideCollection } from './feature-overrides.js';\nimport { type FeatureHostOptions, withoutListBounds } from './list-bounds.js';\nimport {\n FeatureOverrideEffect,\n type FeatureResolutionContext,\n type FeatureResolverOptions,\n type FeatureTargetConstructor,\n type FeatureTenantHierarchyProvider,\n type FeatureTenantNode,\n type FeatureUsersModule,\n} from './types.js';\nimport {\n findFeatureDefaultInRegistry,\n resolveFeatureKeyForTarget,\n} from './utils.js';\n\nexport class FeatureResolver {\n private readonly options: FeatureHostOptions;\n private readonly resolverOptions: FeatureResolverOptions;\n private featureDefinitions!: FeatureDefinitionCollection;\n private featureOverrides!: FeatureOverrideCollection;\n private initializationPromise: Promise<void> | null = null;\n private tenantHierarchyPromise: Promise<FeatureTenantHierarchyProvider | null> | null =\n null;\n\n constructor(\n options: FeatureHostOptions = {},\n resolverOptions: FeatureResolverOptions = {},\n ) {\n this.options = options;\n this.resolverOptions = resolverOptions;\n }\n\n async isEnabled(\n featureKey: string,\n context: FeatureResolutionContext = {},\n ): Promise<boolean> {\n await this.ensureInitialized();\n\n const baseState = await this.resolveBaseState(featureKey);\n const globalOverride =\n await this.featureOverrides.getGlobalOverride(featureKey);\n const globalState = this.applyOverride(baseState, globalOverride?.effect);\n\n if (!context.tenantId) {\n return globalState;\n }\n\n const tenantHierarchy = await this.getTenantHierarchy();\n if (!tenantHierarchy) {\n const directOverride = await this.featureOverrides.getTenantOverride(\n featureKey,\n context.tenantId,\n );\n return this.applyOverride(globalState, directOverride?.effect);\n }\n\n const chain = await tenantHierarchy.getChain(context.tenantId);\n if (chain.length === 0) {\n const directOverride = await this.featureOverrides.getTenantOverride(\n featureKey,\n context.tenantId,\n );\n return this.applyOverride(globalState, directOverride?.effect);\n }\n\n const overrides = await this.featureOverrides.getOverrideMap(\n featureKey,\n 'tenant',\n chain.map((node) => node.id),\n );\n\n let inheritedState = globalState;\n\n for (let index = 0; index < chain.length; index++) {\n const current = chain[index];\n const previous = index > 0 ? chain[index - 1] : null;\n const shouldInherit =\n index === 0 ||\n (!!previous?.cascadePermissions && current.inheritPermissions);\n\n const baseline = shouldInherit ? inheritedState : globalState;\n const override = overrides.get(current.id);\n inheritedState = this.applyOverride(baseline, override?.effect);\n }\n\n return inheritedState;\n }\n\n async isEnabledFor(\n classOrInstance: object | FeatureTargetConstructor,\n localId: string,\n context: FeatureResolutionContext = {},\n ): Promise<boolean> {\n const { featureKey } = resolveFeatureKeyForTarget(classOrInstance, localId);\n return this.isEnabled(featureKey, context);\n }\n\n private async ensureInitialized(): Promise<void> {\n if (!this.initializationPromise) {\n this.initializationPromise = (async () => {\n // A host-configured `defaultListLimit`/`maxListLimit` must never reach\n // these reads: `getOverrideMap()` walks the tenant ancestor chain, and\n // a dropped ancestor override would change the flag's resolved value\n // silently (#3056, same hazard class as #3048).\n const options = withoutListBounds(this.options);\n this.featureDefinitions =\n await FeatureDefinitionCollection.create(options);\n this.featureOverrides = await FeatureOverrideCollection.create(options);\n })();\n }\n\n await this.initializationPromise;\n }\n\n private async resolveBaseState(featureKey: string): Promise<boolean> {\n const registryDefault = findFeatureDefaultInRegistry(featureKey);\n if (registryDefault !== undefined) {\n return registryDefault;\n }\n\n const definition =\n await this.featureDefinitions.findByFeatureKey(featureKey);\n if (!definition) {\n throw new Error(\n `Feature \"${featureKey}\" is not declared in the registry or synced feature definitions.`,\n );\n }\n\n return definition.defaultEnabled;\n }\n\n private applyOverride(\n currentState: boolean,\n effect: FeatureOverrideEffect | undefined,\n ): boolean {\n switch (effect) {\n case FeatureOverrideEffect.ENABLE:\n return true;\n case FeatureOverrideEffect.DISABLE:\n return false;\n default:\n return currentState;\n }\n }\n\n private async getTenantHierarchy(): Promise<FeatureTenantHierarchyProvider | null> {\n if (!this.tenantHierarchyPromise) {\n const loader =\n this.resolverOptions.tenantHierarchyLoader ||\n defaultTenantHierarchyLoader;\n this.tenantHierarchyPromise = loader(this.options);\n }\n\n return this.tenantHierarchyPromise;\n }\n}\n\nasync function defaultTenantHierarchyLoader(\n options: SmrtClassOptions,\n): Promise<FeatureTenantHierarchyProvider | null> {\n try {\n const usersModule = await importWorkspaceModule<FeatureUsersModule>({\n packageName: '@happyvertical/smrt-users',\n sourceEntry: 'packages/users/src/collections/index.ts',\n purpose: 'tenant-aware feature flag resolution',\n });\n\n const tenantCollection = await usersModule.TenantCollection.create(options);\n return {\n async getChain(tenantId: string): Promise<FeatureTenantNode[]> {\n const tenant = await tenantCollection.get({ id: tenantId });\n if (!tenant) {\n return [];\n }\n\n const ancestors = await tenantCollection.getAncestorsFromRoot(tenantId);\n return [...ancestors, tenant].map((node) => ({\n id: String(node.id),\n inheritPermissions: Boolean(node.inheritPermissions),\n cascadePermissions: Boolean(node.cascadePermissions),\n }));\n },\n };\n } catch (error) {\n if (isMissingUsersDependency(error)) {\n return null;\n }\n throw error;\n }\n}\n\nfunction isMissingUsersDependency(error: unknown): boolean {\n if (!(error instanceof Error)) {\n return false;\n }\n\n const causeMessage =\n error.cause instanceof Error ? ` ${error.cause.message}` : '';\n const text = `${error.message}${causeMessage}`;\n\n return (\n text.includes('@happyvertical/smrt-users') &&\n (text.includes('Cannot find module') ||\n text.includes('Failed to load') ||\n text.includes('could not find'))\n );\n}\n","import type { FeatureDefinition } from './feature-definition.js';\nimport { FeatureDefinitionCollection } from './feature-definitions.js';\nimport type { FeatureOverride } from './feature-override.js';\nimport {\n type FeatureOverrideAuthorizer,\n FeatureOverrideService,\n} from './feature-override-service.js';\nimport { FeatureOverrideCollection } from './feature-overrides.js';\nimport { FeatureResolver } from './feature-resolver.js';\nimport { type FeatureHostOptions, withoutListBounds } from './list-bounds.js';\nimport {\n type FeatureMetadata,\n FeatureOverrideEffect,\n type FeatureResolverOptions,\n type FeatureScopeType,\n GLOBAL_FEATURE_SCOPE_ID,\n} from './types.js';\n\n/**\n * One feature, rendered for a management screen: the code-owned definition,\n * the value that is actually in effect for the requested tenant, and the raw\n * override row at each scope level so the UI can show *why* it is in effect.\n *\n * Structurally assignable to the presentational `FeatureSettingsView` consumed\n * by `@happyvertical/smrt-features/svelte`, so a server load can hand its rows\n * straight to `FeatureSettingsPanel` without a mapping step.\n */\nexport interface FeatureSettingsRow {\n /**\n * The persisted identifier. Renaming it orphans every existing override —\n * see the warning on {@link FeatureSettingsService}.\n */\n featureKey: string;\n /** Human label from the definition; may be empty. */\n label: string;\n /** Human description from the definition; may be empty. */\n description: string;\n /** Owning package, as synced from the manifest. */\n packageName: string;\n /** Qualified `package:Class` name of the feature's target. */\n qualifiedClassName: string;\n /** Unqualified class name of the feature's target. */\n className: string;\n /** Class-local feature id. */\n localId: string;\n /** Definition visibility marker (`public` unless the seed says otherwise). */\n visibility: string;\n /** Parsed definition metadata. */\n metadata: FeatureMetadata;\n /** The code-owned default, used when no override applies. */\n defaultEnabled: boolean;\n /**\n * What {@link FeatureResolver} resolves for the requested context — the\n * value the application actually behaves by.\n */\n effectiveEnabled: boolean;\n /** Effect of the global override row, or `null` when there is none. */\n globalEffect: FeatureOverrideEffect | null;\n /**\n * Effect of the override row for the requested tenant, or `null` when there\n * is none (or when no `tenantId` was requested).\n */\n tenantEffect: FeatureOverrideEffect | null;\n /**\n * What this feature resolves to for the requested tenant with **no override\n * of its own** — the state that removing the tenant override would produce,\n * which is what a management UI must name on its \"back to Default\" control.\n *\n * `null` when no `tenantId` was requested — nothing tenant-inherited was\n * computed, and the global state is not an answer to a question about a\n * tenant — and `null` when the tenant holds an override of its own, because\n * then the inherited state depends on the levels above this tenant, which a\n * configured `FeatureTenantHierarchyProvider` extends to ancestor tenants'\n * overrides. It is reported only for a requested tenant with no override of\n * its own, where the inherited state *is* the effective state. Rows that\n * leave it `null` get the neutral \"Default (inherited)\" label in the panel,\n * never a claim about a state nothing computed.\n */\n inheritedEnabled: boolean | null;\n}\n\n/** Selection for {@link FeatureSettingsService.listFeatureSettings}. */\nexport interface ListFeatureSettingsOptions {\n /**\n * Tenant whose overrides and effective values to report. Omit for a\n * global-only view.\n */\n tenantId?: string;\n /** Restrict to one owning package (the usual case for an app's own screen). */\n packageName?: string;\n /**\n * Restrict to an explicit allowlist of keys. Keys with no definition are\n * simply absent from the result — this filters, it does not validate.\n */\n featureKeys?: string[];\n}\n\n/** One override write requested through {@link FeatureSettingsService}. */\nexport interface SetFeatureOverrideRequest {\n featureKey: string;\n scopeType: FeatureScopeType;\n scopeId: string;\n /**\n * `INHERIT` removes the override row at that scope, returning the feature to\n * whatever the next level up resolves to.\n */\n effect: FeatureOverrideEffect;\n}\n\n/**\n * Thrown when a write names a `featureKey` that has no {@link FeatureDefinition}\n * row. Nothing is written.\n *\n * This is the generalized form of the per-app \"is this one of my keys?\" guard\n * every consumer would otherwise hand-roll to keep a form post from creating an\n * override for an arbitrary string.\n */\nexport class UnknownFeatureKeyError extends Error {\n readonly status = 400;\n constructor(readonly featureKey: string) {\n super(`Unknown feature key: ${featureKey}`);\n this.name = 'UnknownFeatureKeyError';\n }\n}\n\n/**\n * Thrown when a write names a scope the resolver would never read back: an\n * unknown `scopeType`, a global write under anything but\n * {@link GLOBAL_FEATURE_SCOPE_ID}, or a tenant write with a blank or untrimmed\n * `scopeId`. Nothing is written.\n *\n * `FeatureOverride` rows are looked up by the exact `(featureKey, scopeType,\n * scopeId)` triple, so a row stored under a scope nothing queries is invisible\n * state: it never takes effect, and it silently reappears if the scope naming\n * is ever corrected.\n */\nexport class InvalidFeatureScopeError extends Error {\n readonly status = 400;\n constructor(\n readonly scopeType: string,\n readonly scopeId: string,\n reason: string,\n ) {\n super(`Invalid feature override scope (${scopeType}): ${reason}`);\n this.name = 'InvalidFeatureScopeError';\n }\n}\n\n/** Options for {@link FeatureSettingsService.create}. */\nexport interface FeatureSettingsServiceOptions {\n /**\n * The host's authorization decision for each override write, bound to the\n * current caller. **Omitting it makes the service read-only**: every write\n * then fails closed with `FeatureOverrideAuthorizationError`. This\n * package never decides who may write.\n */\n authorize?: FeatureOverrideAuthorizer;\n /** Forwarded to {@link FeatureResolver} (e.g. the tenant-hierarchy loader). */\n resolver?: FeatureResolverOptions;\n}\n\nconst KNOWN_EFFECTS = new Set<string>(Object.values(FeatureOverrideEffect));\n\n/**\n * Coerce an untrusted value (a form field, a query parameter, a JSON body) to a\n * {@link FeatureOverrideEffect}. Anything unrecognized — including `null` and\n * `undefined` — becomes `INHERIT`, i.e. \"back to the default\", which is the\n * safe reading of a value the host could not parse.\n */\nexport function featureOverrideEffectFromValue(\n value: unknown,\n): FeatureOverrideEffect {\n if (value === FeatureOverrideEffect.ENABLE)\n return FeatureOverrideEffect.ENABLE;\n if (value === FeatureOverrideEffect.DISABLE)\n return FeatureOverrideEffect.DISABLE;\n return FeatureOverrideEffect.INHERIT;\n}\n\n/**\n * Read and write side of a feature-flag management screen, so consumers do not\n * re-derive the row shape or the unknown-key guard.\n *\n * **Authorization stays with the consumer.** This service makes no\n * authorization decision of its own: reads are unfiltered, and every write is\n * delegated to {@link FeatureOverrideService}, which asks the host-supplied\n * {@link FeatureOverrideAuthorizer} first and fails closed. Call it only from\n * server code that has already established who the caller is. See\n * `packages/features/README.md` for the consumer recipe.\n *\n * **Feature keys are persisted identifiers.** Overrides are stored keyed by the\n * exact `featureKey` string and resolved by that string at runtime. Renaming a\n * key orphans every existing override, silently reverting all tenants to the\n * definition default. Treat a key as immutable once shipped; a rename needs a\n * data migration, not an edit.\n */\nexport class FeatureSettingsService {\n private readonly writes: FeatureOverrideService;\n\n constructor(\n private readonly definitions: FeatureDefinitionCollection,\n private readonly overrides: FeatureOverrideCollection,\n private readonly resolver: FeatureResolver,\n authorize?: FeatureOverrideAuthorizer,\n ) {\n this.writes = new FeatureOverrideService(\n overrides,\n authorize ?? (() => false),\n );\n }\n\n /**\n * Build a service over the given database options.\n *\n * @param options - SMRT database options, as passed to any collection.\n * @param serviceOptions - Host authorizer and resolver wiring. Omit\n * `authorize` for a read-only (load-function) service.\n */\n static async create(\n options: FeatureHostOptions = {},\n serviceOptions: FeatureSettingsServiceOptions = {},\n ): Promise<FeatureSettingsService> {\n // The catalog enumeration (`loadDefinitions()`) and every override read\n // this service does are internal, correctness-critical reads — not a\n // user-facing page of results — so a host-configured `defaultListLimit`/\n // `maxListLimit` must never truncate them (#3056, same hazard class as\n // #3048).\n const boundedOptions = withoutListBounds(options);\n const [definitions, overrides] = await Promise.all([\n FeatureDefinitionCollection.create(boundedOptions),\n FeatureOverrideCollection.create(boundedOptions),\n ]);\n return new FeatureSettingsService(\n definitions as FeatureDefinitionCollection,\n overrides as FeatureOverrideCollection,\n new FeatureResolver(options, serviceOptions.resolver ?? {}),\n serviceOptions.authorize,\n );\n }\n\n /**\n * One {@link FeatureSettingsRow} per matching definition, ordered by owning\n * package, then label, then key, so a rendered list is stable across loads.\n *\n * Makes no authorization decision — the caller has already decided that this\n * principal may see this tenant's flags.\n */\n async listFeatureSettings(\n options: ListFeatureSettingsOptions = {},\n ): Promise<FeatureSettingsRow[]> {\n const definitions = await this.loadDefinitions(options);\n\n return Promise.all(\n definitions.map((definition) => this.toRow(definition, options.tenantId)),\n );\n }\n\n /** The single row for one key, or `null` when no definition owns that key. */\n async getFeatureSetting(\n featureKey: string,\n tenantId?: string,\n ): Promise<FeatureSettingsRow | null> {\n const definition = await this.definitions.findByFeatureKey(featureKey);\n return definition ? this.toRow(definition, tenantId) : null;\n }\n\n /**\n * Apply one override change.\n *\n * Refuses any `featureKey` without a {@link FeatureDefinition} row and any\n * scope the resolver would never read back, then delegates to\n * {@link FeatureOverrideService} so the host authorizer decides whether this\n * caller may write this scope. `INHERIT` removes the row.\n *\n * @returns the stored override, or `null` when the row was removed (or was\n * already absent).\n * @throws {UnknownFeatureKeyError} when the key has no definition.\n * @throws {InvalidFeatureScopeError} when the scope is not one the resolver reads.\n * @throws `FeatureOverrideAuthorizationError` when the authorizer declines.\n */\n async setFeatureOverride(\n request: SetFeatureOverrideRequest,\n ): Promise<FeatureOverride | null> {\n const { featureKey, scopeType, scopeId, effect } = request;\n\n if (!KNOWN_EFFECTS.has(effect)) {\n throw new TypeError(`Unknown feature override effect: ${String(effect)}`);\n }\n assertValidFeatureScope(scopeType, scopeId);\n await this.assertKnownFeatureKey(featureKey);\n\n if (effect === FeatureOverrideEffect.INHERIT) {\n await this.writes.removeOverride(featureKey, scopeType, scopeId);\n return null;\n }\n\n return this.writes.setOverride(featureKey, scopeType, scopeId, effect);\n }\n\n /** {@link setFeatureOverride} for the global scope. */\n async setGlobalFeatureOverride(\n featureKey: string,\n effect: FeatureOverrideEffect,\n ): Promise<FeatureOverride | null> {\n return this.setFeatureOverride({\n featureKey,\n scopeType: 'global',\n scopeId: GLOBAL_FEATURE_SCOPE_ID,\n effect,\n });\n }\n\n /** {@link setFeatureOverride} for one tenant. */\n async setTenantFeatureOverride(\n featureKey: string,\n tenantId: string,\n effect: FeatureOverrideEffect,\n ): Promise<FeatureOverride | null> {\n return this.setFeatureOverride({\n featureKey,\n scopeType: 'tenant',\n scopeId: tenantId,\n effect,\n });\n }\n\n private async assertKnownFeatureKey(featureKey: string): Promise<void> {\n const key = typeof featureKey === 'string' ? featureKey.trim() : '';\n if (!key || key !== featureKey) {\n throw new UnknownFeatureKeyError(String(featureKey));\n }\n const definition = await this.definitions.findByFeatureKey(key);\n if (!definition) {\n throw new UnknownFeatureKeyError(key);\n }\n }\n\n private async loadDefinitions(\n options: ListFeatureSettingsOptions,\n ): Promise<FeatureDefinition[]> {\n const definitions = options.packageName\n ? await this.definitions.findByPackageName(options.packageName)\n : await this.definitions.list({});\n\n const allowed = options.featureKeys ? new Set(options.featureKeys) : null;\n const selected = allowed\n ? definitions.filter((definition) => allowed.has(definition.featureKey))\n : definitions;\n\n return [...selected].sort(compareDefinitions);\n }\n\n private async toRow(\n definition: FeatureDefinition,\n tenantId?: string,\n ): Promise<FeatureSettingsRow> {\n const { featureKey } = definition;\n const [globalOverride, tenantOverride, effectiveEnabled] =\n await Promise.all([\n this.overrides.getGlobalOverride(featureKey),\n tenantId\n ? this.overrides.getTenantOverride(featureKey, tenantId)\n : Promise.resolve(null),\n this.resolver.isEnabled(featureKey, tenantId ? { tenantId } : {}),\n ]);\n\n return {\n featureKey,\n label: definition.label,\n description: definition.description,\n packageName: definition.packageName,\n qualifiedClassName: definition.qualifiedClassName,\n className: definition.className,\n localId: definition.localId,\n visibility: definition.visibility,\n metadata: definition.getMetadata(),\n defaultEnabled: definition.defaultEnabled,\n effectiveEnabled,\n globalEffect: globalOverride?.effect ?? null,\n tenantEffect: tenantOverride?.effect ?? null,\n inheritedEnabled: tenantId && !tenantOverride ? effectiveEnabled : null,\n };\n }\n}\n\n/**\n * Reject a scope the resolver would never read back.\n *\n * `FeatureResolver` looks overrides up by the exact `(featureKey, scopeType,\n * scopeId)` triple: the global scope only ever at {@link GLOBAL_FEATURE_SCOPE_ID},\n * and the tenant scope only at ids it gets from the session or the tenant\n * hierarchy. A row written anywhere else is never read, so it is invisible\n * state rather than a setting — and the host authorizer cannot catch it,\n * because `scopeType: 'global'` with someone else's tenant id looks like an\n * ordinary global write to an authorizer that only gates the global scope.\n */\nfunction assertValidFeatureScope(\n scopeType: FeatureScopeType,\n scopeId: string,\n): void {\n if (scopeType !== 'global' && scopeType !== 'tenant') {\n throw new InvalidFeatureScopeError(\n String(scopeType),\n String(scopeId),\n 'scope type must be \"global\" or \"tenant\"',\n );\n }\n if (typeof scopeId !== 'string') {\n throw new InvalidFeatureScopeError(\n scopeType,\n String(scopeId),\n 'scope id must be a string',\n );\n }\n if (scopeType === 'global' && scopeId !== GLOBAL_FEATURE_SCOPE_ID) {\n throw new InvalidFeatureScopeError(\n scopeType,\n scopeId,\n `the global scope is only ever \"${GLOBAL_FEATURE_SCOPE_ID}\"`,\n );\n }\n if (\n scopeType === 'tenant' &&\n (!scopeId.trim() || scopeId !== scopeId.trim())\n ) {\n throw new InvalidFeatureScopeError(\n scopeType,\n scopeId,\n 'a tenant scope id must be non-blank and already trimmed',\n );\n }\n}\n\nfunction compareDefinitions(\n a: FeatureDefinition,\n b: FeatureDefinition,\n): number {\n return (\n a.packageName.localeCompare(b.packageName) ||\n (a.label || a.featureKey).localeCompare(b.label || b.featureKey) ||\n a.featureKey.localeCompare(b.featureKey)\n );\n}\n","import { ObjectRegistry } from '@happyvertical/smrt-core';\nimport type { SmartObjectManifest } from '@happyvertical/smrt-core/manifest';\nimport { FeatureDefinitionCollection } from './feature-definitions.js';\nimport { type FeatureHostOptions, withoutListBounds } from './list-bounds.js';\nimport type {\n FeatureDefinitionSeed,\n FeatureSyncResult,\n SyncDefinitionsOptions,\n SyncManifestOptions,\n} from './types.js';\nimport {\n extractFeatureSeedsFromManifest,\n extractFeatureSeedsFromRegistryEntry,\n extractTouchedPackagesFromManifest,\n getPackageNameFromRegistry,\n type RegistryEntryLike,\n} from './utils.js';\n\nexport class FeatureSyncService {\n private readonly options: FeatureHostOptions;\n private featureDefinitions!: FeatureDefinitionCollection;\n private initializationPromise: Promise<void> | null = null;\n\n constructor(options: FeatureHostOptions = {}) {\n this.options = options;\n }\n\n async syncDefinitions(\n options: SyncDefinitionsOptions = {},\n ): Promise<FeatureSyncResult> {\n await this.ensureInitialized();\n const registrations = this.collectRegistrations(options);\n const definitions = this.collectDefinitionsFromRegistrations(registrations);\n const touchedPackages =\n this.collectTouchedPackagesFromRegistrations(registrations);\n const isFilteredSync =\n Boolean(options.classNames?.length) ||\n Boolean(options.constructors?.length);\n const pruneStale = isFilteredSync ? false : (options.pruneStale ?? true);\n\n return this.applyDefinitions(definitions, touchedPackages, pruneStale);\n }\n\n async syncManifest(\n manifest: SmartObjectManifest,\n options: SyncManifestOptions = {},\n ): Promise<FeatureSyncResult> {\n await this.ensureInitialized();\n const definitions = extractFeatureSeedsFromManifest(manifest);\n const touchedPackages = extractTouchedPackagesFromManifest(manifest);\n return this.applyDefinitions(\n definitions,\n touchedPackages,\n options.pruneStale ?? true,\n );\n }\n\n private async ensureInitialized(): Promise<void> {\n if (!this.initializationPromise) {\n this.initializationPromise = (async () => {\n // `applyDefinitions()` enumerates the full catalog per touched package\n // to prune stale rows; a host-configured list bound must not make a\n // prune pass see a partial catalog (#3056, same hazard class as #3048).\n this.featureDefinitions = await FeatureDefinitionCollection.create(\n withoutListBounds(this.options),\n );\n })();\n }\n\n await this.initializationPromise;\n }\n\n private collectDefinitionsFromRegistrations(\n registrations: RegistryEntryLike[],\n ): FeatureDefinitionSeed[] {\n const definitions: FeatureDefinitionSeed[] = [];\n\n for (const registration of registrations) {\n definitions.push(...extractFeatureSeedsFromRegistryEntry(registration));\n }\n\n return definitions;\n }\n\n private collectTouchedPackagesFromRegistrations(\n registrations: RegistryEntryLike[],\n ): Set<string> {\n const packages = new Set<string>();\n\n for (const registration of registrations) {\n if (registration.visibility === 'test') {\n continue;\n }\n\n const packageName = getPackageNameFromRegistry(registration);\n if (packageName) {\n packages.add(packageName);\n }\n }\n\n return packages;\n }\n\n private collectRegistrations(\n options: SyncDefinitionsOptions,\n ): RegistryEntryLike[] {\n if (options.classNames?.length) {\n return options.classNames\n .map((name) => ObjectRegistry.getClass(name))\n .filter((value): value is NonNullable<typeof value> => !!value);\n }\n\n if (options.constructors?.length) {\n return options.constructors\n .map((ctor) => ObjectRegistry.getClassByConstructor(ctor))\n .filter((value): value is NonNullable<typeof value> => !!value);\n }\n\n const registrations: RegistryEntryLike[] = [];\n const seen = new Set<RegistryEntryLike>();\n\n for (const registration of ObjectRegistry.getAllClasses().values()) {\n if (seen.has(registration)) {\n continue;\n }\n seen.add(registration);\n registrations.push(registration);\n }\n\n return registrations;\n }\n\n private async applyDefinitions(\n definitions: FeatureDefinitionSeed[],\n touchedPackages: Set<string>,\n pruneStale: boolean,\n ): Promise<FeatureSyncResult> {\n const deduped = new Map<string, FeatureDefinitionSeed>();\n for (const definition of definitions) {\n deduped.set(definition.featureKey, definition);\n }\n\n let created = 0;\n let updated = 0;\n let unchanged = 0;\n let deleted = 0;\n\n const dedupedDefinitions = Array.from(deduped.values());\n const currentKeys = new Set(\n dedupedDefinitions.map((definition) => definition.featureKey),\n );\n const upsertResults = await Promise.all(\n dedupedDefinitions.map((definition) =>\n this.featureDefinitions.upsertDefinition(definition),\n ),\n );\n for (const result of upsertResults) {\n if (result.status === 'created') created++;\n if (result.status === 'updated') updated++;\n if (result.status === 'unchanged') unchanged++;\n }\n\n if (pruneStale) {\n const existingByPackage = await Promise.all(\n Array.from(touchedPackages, (packageName) =>\n this.featureDefinitions.findByPackageName(packageName),\n ),\n );\n\n const staleDefinitions = existingByPackage.flatMap((existing) =>\n existing.filter(\n (definition) => !currentKeys.has(definition.featureKey),\n ),\n );\n\n await Promise.all(\n staleDefinitions.map((definition) => definition.delete()),\n );\n deleted = staleDefinitions.length;\n }\n\n return {\n total: deduped.size,\n created,\n updated,\n unchanged,\n deleted,\n featureKeys: Array.from(currentKeys).sort(),\n };\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/__smrt-register__.ts","../src/utils.ts","../src/feature-definition.ts","../src/feature-definitions.ts","../src/feature-override.ts","../src/feature-override-service.ts","../src/feature-overrides.ts","../src/list-bounds.ts","../src/feature-resolver.ts","../src/feature-settings.ts","../src/feature-sync.ts"],"sourcesContent":["// Generated by @happyvertical/smrt-core smrtPlugin at library build time.\n// The package manifest is inlined so self-registration cannot miss when a\n// downstream bundler relocates this module away from dist/manifest.json\n// (https://github.com/happyvertical/smrt/issues/1506,\n// https://github.com/happyvertical/smrt/issues/1507).\nimport { ObjectRegistry } from '@happyvertical/smrt-core';\n\nObjectRegistry.registerPackageManifest(JSON.parse(\"{\\\"version\\\":\\\"1.0.0\\\",\\\"timestamp\\\":0,\\\"packageName\\\":\\\"@happyvertical/smrt-features\\\",\\\"packageVersion\\\":\\\"0.57.47\\\",\\\"objects\\\":{\\\"@happyvertical/smrt-features:FeatureDefinition\\\":{\\\"name\\\":\\\"featuredefinition\\\",\\\"className\\\":\\\"FeatureDefinition\\\",\\\"qualifiedName\\\":\\\"@happyvertical/smrt-features:FeatureDefinition\\\",\\\"collection\\\":\\\"featuredefinitions\\\",\\\"filePath\\\":\\\"/home/runner/work/smrt/smrt/packages/features/src/feature-definition.ts\\\",\\\"packageName\\\":\\\"@happyvertical/smrt-features\\\",\\\"fields\\\":{\\\"featureKey\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":false,\\\"default\\\":\\\"\\\"},\\\"packageName\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":false,\\\"default\\\":\\\"\\\"},\\\"qualifiedClassName\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":false,\\\"default\\\":\\\"\\\"},\\\"className\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":false,\\\"default\\\":\\\"\\\"},\\\"localId\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":false,\\\"default\\\":\\\"\\\"},\\\"defaultEnabled\\\":{\\\"type\\\":\\\"boolean\\\",\\\"required\\\":false,\\\"default\\\":false},\\\"label\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":false,\\\"default\\\":\\\"\\\"},\\\"description\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":false,\\\"default\\\":\\\"\\\"},\\\"metadata\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":false,\\\"default\\\":\\\"\\\"},\\\"visibility\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":false,\\\"default\\\":\\\"public\\\"}},\\\"methods\\\":{\\\"getMetadata\\\":{\\\"name\\\":\\\"getMetadata\\\",\\\"async\\\":false,\\\"parameters\\\":[],\\\"returnType\\\":\\\"FeatureMetadata\\\",\\\"isStatic\\\":false,\\\"isPublic\\\":true},\\\"setMetadata\\\":{\\\"name\\\":\\\"setMetadata\\\",\\\"async\\\":false,\\\"parameters\\\":[{\\\"name\\\":\\\"metadata\\\",\\\"type\\\":\\\"FeatureMetadata | null\\\",\\\"optional\\\":false,\\\"unionBranches\\\":[{\\\"type\\\":\\\"FeatureMetadata\\\"},{\\\"type\\\":\\\"null\\\"}]}],\\\"returnType\\\":\\\"void\\\",\\\"isStatic\\\":false,\\\"isPublic\\\":true}},\\\"decoratorConfig\\\":{\\\"tableName\\\":\\\"_smrt_feature_definitions\\\",\\\"api\\\":false,\\\"cli\\\":false,\\\"mcp\\\":false,\\\"conflictColumns\\\":[\\\"feature_key\\\"]},\\\"extends\\\":\\\"SmrtObject\\\",\\\"exportName\\\":\\\"FeatureDefinition\\\",\\\"collectionExportName\\\":\\\"FeatureDefinitionCollection\\\",\\\"displayLabelField\\\":\\\"label\\\",\\\"schema\\\":{\\\"tableName\\\":\\\"_smrt_feature_definitions\\\",\\\"ddl\\\":\\\"CREATE TABLE IF NOT EXISTS \\\\\\\"_smrt_feature_definitions\\\\\\\" (\\\\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 \\\\\\\"feature_key\\\\\\\" TEXT DEFAULT '',\\\\n \\\\\\\"package_name\\\\\\\" TEXT DEFAULT '',\\\\n \\\\\\\"qualified_class_name\\\\\\\" TEXT DEFAULT '',\\\\n \\\\\\\"class_name\\\\\\\" TEXT DEFAULT '',\\\\n \\\\\\\"local_id\\\\\\\" TEXT DEFAULT '',\\\\n \\\\\\\"default_enabled\\\\\\\" BOOLEAN DEFAULT FALSE,\\\\n \\\\\\\"label\\\\\\\" TEXT DEFAULT '',\\\\n \\\\\\\"description\\\\\\\" TEXT DEFAULT '',\\\\n \\\\\\\"metadata\\\\\\\" TEXT DEFAULT '',\\\\n \\\\\\\"visibility\\\\\\\" TEXT DEFAULT 'public'\\\\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\\\"},\\\"feature_key\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false,\\\"default\\\":\\\"\\\"},\\\"package_name\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false,\\\"default\\\":\\\"\\\"},\\\"qualified_class_name\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false,\\\"default\\\":\\\"\\\"},\\\"class_name\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false,\\\"default\\\":\\\"\\\"},\\\"local_id\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false,\\\"default\\\":\\\"\\\"},\\\"default_enabled\\\":{\\\"type\\\":\\\"BOOLEAN\\\",\\\"notNull\\\":false,\\\"unique\\\":false,\\\"default\\\":false},\\\"label\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false,\\\"default\\\":\\\"\\\"},\\\"description\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false,\\\"default\\\":\\\"\\\"},\\\"metadata\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false,\\\"default\\\":\\\"\\\"},\\\"visibility\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false,\\\"default\\\":\\\"public\\\"}},\\\"indexes\\\":[{\\\"name\\\":\\\"_smrt_feature_definitions_feature_key_idx\\\",\\\"columns\\\":[\\\"feature_key\\\"],\\\"unique\\\":true,\\\"nullsNotDistinct\\\":true},{\\\"name\\\":\\\"_smrt_feature_definitions_slug_context_idx\\\",\\\"columns\\\":[\\\"slug\\\",\\\"context\\\"]},{\\\"name\\\":\\\"_smrt_feature_definitions_created_at_idx\\\",\\\"columns\\\":[\\\"created_at\\\"]}],\\\"version\\\":\\\"8813204c\\\"},\\\"importPath\\\":\\\"@happyvertical/smrt-features\\\"},\\\"@happyvertical/smrt-features:FeatureDefinitionCollection\\\":{\\\"name\\\":\\\"featuredefinitioncollection\\\",\\\"className\\\":\\\"FeatureDefinitionCollection\\\",\\\"qualifiedName\\\":\\\"@happyvertical/smrt-features:FeatureDefinitionCollection\\\",\\\"collection\\\":\\\"featuredefinitions\\\",\\\"filePath\\\":\\\"/home/runner/work/smrt/smrt/packages/features/src/feature-definitions.ts\\\",\\\"packageName\\\":\\\"@happyvertical/smrt-features\\\",\\\"fields\\\":{},\\\"methods\\\":{\\\"findByFeatureKey\\\":{\\\"name\\\":\\\"findByFeatureKey\\\",\\\"async\\\":true,\\\"parameters\\\":[{\\\"name\\\":\\\"featureKey\\\",\\\"type\\\":\\\"string\\\",\\\"optional\\\":false}],\\\"returnType\\\":\\\"Promise<FeatureDefinition | null>\\\",\\\"isStatic\\\":false,\\\"isPublic\\\":true},\\\"findByPackageName\\\":{\\\"name\\\":\\\"findByPackageName\\\",\\\"async\\\":true,\\\"parameters\\\":[{\\\"name\\\":\\\"packageName\\\",\\\"type\\\":\\\"string\\\",\\\"optional\\\":false}],\\\"returnType\\\":\\\"Promise<FeatureDefinition[]>\\\",\\\"isStatic\\\":false,\\\"isPublic\\\":true},\\\"upsertDefinition\\\":{\\\"name\\\":\\\"upsertDefinition\\\",\\\"async\\\":true,\\\"parameters\\\":[{\\\"name\\\":\\\"seed\\\",\\\"type\\\":\\\"FeatureDefinitionSeed\\\",\\\"optional\\\":false}],\\\"returnType\\\":\\\"Promise<object>\\\",\\\"isStatic\\\":false,\\\"isPublic\\\":true}},\\\"decoratorConfig\\\":{\\\"tableName\\\":\\\"_smrt_feature_definitions\\\",\\\"api\\\":false,\\\"mcp\\\":false,\\\"cli\\\":false},\\\"extends\\\":\\\"SmrtCollection\\\",\\\"extendsTypeArg\\\":\\\"FeatureDefinition\\\",\\\"exportName\\\":\\\"FeatureDefinitionCollection\\\",\\\"collectionExportName\\\":\\\"FeatureDefinitionCollectionCollection\\\",\\\"collectionOf\\\":\\\"@happyvertical/smrt-features:FeatureDefinition\\\",\\\"inheritedExposure\\\":[\\\"api\\\",\\\"mcp\\\",\\\"cli\\\"],\\\"schema\\\":{\\\"tableName\\\":\\\"_smrt_feature_definitions\\\",\\\"ddl\\\":\\\"CREATE TABLE IF NOT EXISTS \\\\\\\"_smrt_feature_definitions\\\\\\\" (\\\\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 \\\\\\\"feature_key\\\\\\\" TEXT DEFAULT '',\\\\n \\\\\\\"package_name\\\\\\\" TEXT DEFAULT '',\\\\n \\\\\\\"qualified_class_name\\\\\\\" TEXT DEFAULT '',\\\\n \\\\\\\"class_name\\\\\\\" TEXT DEFAULT '',\\\\n \\\\\\\"local_id\\\\\\\" TEXT DEFAULT '',\\\\n \\\\\\\"default_enabled\\\\\\\" BOOLEAN DEFAULT FALSE,\\\\n \\\\\\\"label\\\\\\\" TEXT DEFAULT '',\\\\n \\\\\\\"description\\\\\\\" TEXT DEFAULT '',\\\\n \\\\\\\"metadata\\\\\\\" TEXT DEFAULT '',\\\\n \\\\\\\"visibility\\\\\\\" TEXT DEFAULT 'public'\\\\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\\\"},\\\"feature_key\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false,\\\"default\\\":\\\"\\\"},\\\"package_name\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false,\\\"default\\\":\\\"\\\"},\\\"qualified_class_name\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false,\\\"default\\\":\\\"\\\"},\\\"class_name\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false,\\\"default\\\":\\\"\\\"},\\\"local_id\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false,\\\"default\\\":\\\"\\\"},\\\"default_enabled\\\":{\\\"type\\\":\\\"BOOLEAN\\\",\\\"notNull\\\":false,\\\"unique\\\":false,\\\"default\\\":false},\\\"label\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false,\\\"default\\\":\\\"\\\"},\\\"description\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false,\\\"default\\\":\\\"\\\"},\\\"metadata\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false,\\\"default\\\":\\\"\\\"},\\\"visibility\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false,\\\"default\\\":\\\"public\\\"}},\\\"indexes\\\":[{\\\"name\\\":\\\"_smrt_feature_definitions_feature_key_idx\\\",\\\"columns\\\":[\\\"feature_key\\\"],\\\"unique\\\":true,\\\"nullsNotDistinct\\\":true},{\\\"name\\\":\\\"_smrt_feature_definitions_slug_context_idx\\\",\\\"columns\\\":[\\\"slug\\\",\\\"context\\\"]},{\\\"name\\\":\\\"_smrt_feature_definitions_created_at_idx\\\",\\\"columns\\\":[\\\"created_at\\\"]}],\\\"version\\\":\\\"8813204c\\\"},\\\"importPath\\\":\\\"@happyvertical/smrt-features\\\"},\\\"@happyvertical/smrt-features:FeatureOverride\\\":{\\\"name\\\":\\\"featureoverride\\\",\\\"className\\\":\\\"FeatureOverride\\\",\\\"qualifiedName\\\":\\\"@happyvertical/smrt-features:FeatureOverride\\\",\\\"collection\\\":\\\"featureoverrides\\\",\\\"filePath\\\":\\\"/home/runner/work/smrt/smrt/packages/features/src/feature-override.ts\\\",\\\"packageName\\\":\\\"@happyvertical/smrt-features\\\",\\\"fields\\\":{\\\"featureKey\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":false,\\\"default\\\":\\\"\\\"},\\\"scopeType\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":false,\\\"default\\\":\\\"global\\\",\\\"enum\\\":[\\\"global\\\",\\\"tenant\\\"]},\\\"scopeId\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":false},\\\"effect\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":false,\\\"enum\\\":[\\\"inherit\\\",\\\"enable\\\",\\\"disable\\\"]}},\\\"methods\\\":{\\\"isInherit\\\":{\\\"name\\\":\\\"isInherit\\\",\\\"async\\\":false,\\\"parameters\\\":[],\\\"returnType\\\":\\\"boolean\\\",\\\"isStatic\\\":false,\\\"isPublic\\\":true},\\\"isEnabled\\\":{\\\"name\\\":\\\"isEnabled\\\",\\\"async\\\":false,\\\"parameters\\\":[],\\\"returnType\\\":\\\"boolean\\\",\\\"isStatic\\\":false,\\\"isPublic\\\":true},\\\"isDisabled\\\":{\\\"name\\\":\\\"isDisabled\\\",\\\"async\\\":false,\\\"parameters\\\":[],\\\"returnType\\\":\\\"boolean\\\",\\\"isStatic\\\":false,\\\"isPublic\\\":true}},\\\"decoratorConfig\\\":{\\\"tableName\\\":\\\"_smrt_feature_overrides\\\",\\\"api\\\":false,\\\"cli\\\":false,\\\"mcp\\\":false,\\\"conflictColumns\\\":[\\\"feature_key\\\",\\\"scope_type\\\",\\\"scope_id\\\"]},\\\"extends\\\":\\\"SmrtObject\\\",\\\"exportName\\\":\\\"FeatureOverride\\\",\\\"collectionExportName\\\":\\\"FeatureOverrideCollection\\\",\\\"schema\\\":{\\\"tableName\\\":\\\"_smrt_feature_overrides\\\",\\\"ddl\\\":\\\"CREATE TABLE IF NOT EXISTS \\\\\\\"_smrt_feature_overrides\\\\\\\" (\\\\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 \\\\\\\"feature_key\\\\\\\" TEXT DEFAULT '',\\\\n \\\\\\\"scope_type\\\\\\\" TEXT DEFAULT 'global',\\\\n \\\\\\\"scope_id\\\\\\\" TEXT,\\\\n \\\\\\\"effect\\\\\\\" TEXT\\\\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\\\"},\\\"feature_key\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false,\\\"default\\\":\\\"\\\"},\\\"scope_type\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false,\\\"default\\\":\\\"global\\\"},\\\"scope_id\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"effect\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false}},\\\"indexes\\\":[{\\\"name\\\":\\\"_smrt_feature_overrides_feature_key_scope_type_idx\\\",\\\"columns\\\":[\\\"feature_key\\\",\\\"scope_type\\\",\\\"scope_id\\\"],\\\"unique\\\":true,\\\"nullsNotDistinct\\\":true},{\\\"name\\\":\\\"_smrt_feature_overrides_slug_context_idx\\\",\\\"columns\\\":[\\\"slug\\\",\\\"context\\\"]},{\\\"name\\\":\\\"_smrt_feature_overrides_created_at_idx\\\",\\\"columns\\\":[\\\"created_at\\\"]}],\\\"version\\\":\\\"d61cf901\\\"},\\\"importPath\\\":\\\"@happyvertical/smrt-features\\\"},\\\"@happyvertical/smrt-features:FeatureOverrideCollection\\\":{\\\"name\\\":\\\"featureoverridecollection\\\",\\\"className\\\":\\\"FeatureOverrideCollection\\\",\\\"qualifiedName\\\":\\\"@happyvertical/smrt-features:FeatureOverrideCollection\\\",\\\"collection\\\":\\\"featureoverrides\\\",\\\"filePath\\\":\\\"/home/runner/work/smrt/smrt/packages/features/src/feature-overrides.ts\\\",\\\"packageName\\\":\\\"@happyvertical/smrt-features\\\",\\\"fields\\\":{},\\\"methods\\\":{\\\"findByFeatureKey\\\":{\\\"name\\\":\\\"findByFeatureKey\\\",\\\"async\\\":true,\\\"parameters\\\":[{\\\"name\\\":\\\"featureKey\\\",\\\"type\\\":\\\"string\\\",\\\"optional\\\":false}],\\\"returnType\\\":\\\"Promise<FeatureOverride[]>\\\",\\\"isStatic\\\":false,\\\"isPublic\\\":true},\\\"findByFeatureAndScope\\\":{\\\"name\\\":\\\"findByFeatureAndScope\\\",\\\"async\\\":true,\\\"parameters\\\":[{\\\"name\\\":\\\"featureKey\\\",\\\"type\\\":\\\"string\\\",\\\"optional\\\":false},{\\\"name\\\":\\\"scopeType\\\",\\\"type\\\":\\\"FeatureScopeType\\\",\\\"optional\\\":false},{\\\"name\\\":\\\"scopeId\\\",\\\"type\\\":\\\"string\\\",\\\"optional\\\":false}],\\\"returnType\\\":\\\"Promise<FeatureOverride | null>\\\",\\\"isStatic\\\":false,\\\"isPublic\\\":true},\\\"getGlobalOverride\\\":{\\\"name\\\":\\\"getGlobalOverride\\\",\\\"async\\\":true,\\\"parameters\\\":[{\\\"name\\\":\\\"featureKey\\\",\\\"type\\\":\\\"string\\\",\\\"optional\\\":false}],\\\"returnType\\\":\\\"Promise<FeatureOverride | null>\\\",\\\"isStatic\\\":false,\\\"isPublic\\\":true},\\\"getTenantOverride\\\":{\\\"name\\\":\\\"getTenantOverride\\\",\\\"async\\\":true,\\\"parameters\\\":[{\\\"name\\\":\\\"featureKey\\\",\\\"type\\\":\\\"string\\\",\\\"optional\\\":false},{\\\"name\\\":\\\"tenantId\\\",\\\"type\\\":\\\"string\\\",\\\"optional\\\":false}],\\\"returnType\\\":\\\"Promise<FeatureOverride | null>\\\",\\\"isStatic\\\":false,\\\"isPublic\\\":true},\\\"getOverrideMap\\\":{\\\"name\\\":\\\"getOverrideMap\\\",\\\"async\\\":true,\\\"parameters\\\":[{\\\"name\\\":\\\"featureKey\\\",\\\"type\\\":\\\"string\\\",\\\"optional\\\":false},{\\\"name\\\":\\\"scopeType\\\",\\\"type\\\":\\\"FeatureScopeType\\\",\\\"optional\\\":false},{\\\"name\\\":\\\"scopeIds\\\",\\\"type\\\":\\\"string[]\\\",\\\"optional\\\":false}],\\\"returnType\\\":\\\"Promise<Map<string, FeatureOverride>>\\\",\\\"isStatic\\\":false,\\\"isPublic\\\":true},\\\"setOverride\\\":{\\\"name\\\":\\\"setOverride\\\",\\\"async\\\":true,\\\"parameters\\\":[{\\\"name\\\":\\\"featureKey\\\",\\\"type\\\":\\\"string\\\",\\\"optional\\\":false},{\\\"name\\\":\\\"scopeType\\\",\\\"type\\\":\\\"FeatureScopeType\\\",\\\"optional\\\":false},{\\\"name\\\":\\\"scopeId\\\",\\\"type\\\":\\\"string\\\",\\\"optional\\\":false},{\\\"name\\\":\\\"effect\\\",\\\"type\\\":\\\"FeatureOverrideEffect\\\",\\\"optional\\\":false}],\\\"returnType\\\":\\\"Promise<FeatureOverride>\\\",\\\"isStatic\\\":false,\\\"isPublic\\\":true},\\\"setGlobalOverride\\\":{\\\"name\\\":\\\"setGlobalOverride\\\",\\\"async\\\":true,\\\"parameters\\\":[{\\\"name\\\":\\\"featureKey\\\",\\\"type\\\":\\\"string\\\",\\\"optional\\\":false},{\\\"name\\\":\\\"effect\\\",\\\"type\\\":\\\"FeatureOverrideEffect\\\",\\\"optional\\\":false}],\\\"returnType\\\":\\\"Promise<FeatureOverride>\\\",\\\"isStatic\\\":false,\\\"isPublic\\\":true},\\\"setTenantOverride\\\":{\\\"name\\\":\\\"setTenantOverride\\\",\\\"async\\\":true,\\\"parameters\\\":[{\\\"name\\\":\\\"featureKey\\\",\\\"type\\\":\\\"string\\\",\\\"optional\\\":false},{\\\"name\\\":\\\"tenantId\\\",\\\"type\\\":\\\"string\\\",\\\"optional\\\":false},{\\\"name\\\":\\\"effect\\\",\\\"type\\\":\\\"FeatureOverrideEffect\\\",\\\"optional\\\":false}],\\\"returnType\\\":\\\"Promise<FeatureOverride>\\\",\\\"isStatic\\\":false,\\\"isPublic\\\":true},\\\"removeOverride\\\":{\\\"name\\\":\\\"removeOverride\\\",\\\"async\\\":true,\\\"parameters\\\":[{\\\"name\\\":\\\"featureKey\\\",\\\"type\\\":\\\"string\\\",\\\"optional\\\":false},{\\\"name\\\":\\\"scopeType\\\",\\\"type\\\":\\\"FeatureScopeType\\\",\\\"optional\\\":false},{\\\"name\\\":\\\"scopeId\\\",\\\"type\\\":\\\"string\\\",\\\"optional\\\":false}],\\\"returnType\\\":\\\"Promise<boolean>\\\",\\\"isStatic\\\":false,\\\"isPublic\\\":true}},\\\"decoratorConfig\\\":{\\\"tableName\\\":\\\"_smrt_feature_overrides\\\",\\\"api\\\":false,\\\"mcp\\\":false,\\\"cli\\\":false},\\\"extends\\\":\\\"SmrtCollection\\\",\\\"extendsTypeArg\\\":\\\"FeatureOverride\\\",\\\"exportName\\\":\\\"FeatureOverrideCollection\\\",\\\"collectionExportName\\\":\\\"FeatureOverrideCollectionCollection\\\",\\\"collectionOf\\\":\\\"@happyvertical/smrt-features:FeatureOverride\\\",\\\"inheritedExposure\\\":[\\\"api\\\",\\\"mcp\\\",\\\"cli\\\"],\\\"schema\\\":{\\\"tableName\\\":\\\"_smrt_feature_overrides\\\",\\\"ddl\\\":\\\"CREATE TABLE IF NOT EXISTS \\\\\\\"_smrt_feature_overrides\\\\\\\" (\\\\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 \\\\\\\"feature_key\\\\\\\" TEXT DEFAULT '',\\\\n \\\\\\\"scope_type\\\\\\\" TEXT DEFAULT 'global',\\\\n \\\\\\\"scope_id\\\\\\\" TEXT,\\\\n \\\\\\\"effect\\\\\\\" TEXT\\\\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\\\"},\\\"feature_key\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false,\\\"default\\\":\\\"\\\"},\\\"scope_type\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false,\\\"default\\\":\\\"global\\\"},\\\"scope_id\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"effect\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false}},\\\"indexes\\\":[{\\\"name\\\":\\\"_smrt_feature_overrides_feature_key_scope_type_idx\\\",\\\"columns\\\":[\\\"feature_key\\\",\\\"scope_type\\\",\\\"scope_id\\\"],\\\"unique\\\":true,\\\"nullsNotDistinct\\\":true},{\\\"name\\\":\\\"_smrt_feature_overrides_slug_context_idx\\\",\\\"columns\\\":[\\\"slug\\\",\\\"context\\\"]},{\\\"name\\\":\\\"_smrt_feature_overrides_created_at_idx\\\",\\\"columns\\\":[\\\"created_at\\\"]}],\\\"version\\\":\\\"d61cf901\\\"},\\\"importPath\\\":\\\"@happyvertical/smrt-features\\\"}},\\\"moduleType\\\":\\\"smrt\\\",\\\"smrtDependencies\\\":[\\\"@happyvertical/smrt-core\\\",\\\"@happyvertical/smrt-users\\\"],\\\"browser\\\":{\\\"status\\\":\\\"server-only\\\",\\\"issues\\\":[\\\"#3618\\\"],\\\"reason\\\":\\\"imports core utils/import-workspace-module (node:url, node:fs, node:path)\\\"},\\\"stampsConstructors\\\":true}\"));\n","import {\n createQualifiedName,\n isQualifiedName,\n ObjectRegistry,\n parseQualifiedName,\n type SmartObjectConfig,\n} from '@happyvertical/smrt-core';\nimport type {\n SmartObjectDefinition,\n SmartObjectManifest,\n} from '@happyvertical/smrt-core/manifest';\nimport type {\n FeatureDefinitionSeed,\n FeatureMetadata,\n FeatureTargetConstructor,\n} from './types.js';\n\nexport type RegistryEntryLike = {\n name: string;\n qualifiedName?: string;\n packageName?: string;\n config: SmartObjectConfig;\n visibility?: string;\n};\n\nexport function createFeatureKey(\n qualifiedClassName: string,\n localId: string,\n): string {\n if (!isQualifiedName(qualifiedClassName)) {\n throw new Error(\n `Feature keys require a qualified class name. Received \"${qualifiedClassName}\".`,\n );\n }\n\n if (!localId) {\n throw new Error('Feature localId is required.');\n }\n\n if (localId.includes('#')) {\n throw new Error(\n `Feature localId \"${localId}\" cannot contain \"#\". This character is reserved for canonical feature keys.`,\n );\n }\n\n return `${qualifiedClassName}#${localId}`;\n}\n\nexport function parseFeatureKey(featureKey: string): {\n qualifiedClassName: string;\n localId: string;\n} {\n const separatorIndex = featureKey.lastIndexOf('#');\n if (separatorIndex <= 0 || separatorIndex === featureKey.length - 1) {\n throw new Error(\n `Invalid feature key \"${featureKey}\". Expected format \"<qualifiedClassName>#<localId>\".`,\n );\n }\n\n const qualifiedClassName = featureKey.slice(0, separatorIndex);\n const localId = featureKey.slice(separatorIndex + 1);\n\n if (!isQualifiedName(qualifiedClassName)) {\n throw new Error(\n `Invalid feature key \"${featureKey}\". \"${qualifiedClassName}\" is not a qualified class name.`,\n );\n }\n\n return { qualifiedClassName, localId };\n}\n\nexport function serializeFeatureMetadata(\n metadata: FeatureMetadata | string | null | undefined,\n): string {\n if (!metadata) {\n return '';\n }\n\n if (typeof metadata === 'string') {\n const parsed = parseFeatureMetadataString(metadata);\n return JSON.stringify(parsed);\n }\n\n if (!isFeatureMetadataRecord(metadata)) {\n throw new Error(\n 'Feature metadata must be a plain object or a JSON string representing a plain object.',\n );\n }\n\n return JSON.stringify(metadata);\n}\n\nexport function parseFeatureMetadata(\n metadata: string | null | undefined,\n): FeatureMetadata {\n if (!metadata) {\n return {};\n }\n\n try {\n return parseFeatureMetadataString(metadata);\n } catch {\n return {};\n }\n}\n\nfunction parseFeatureMetadataString(metadata: string): FeatureMetadata {\n let parsed: unknown;\n\n try {\n parsed = JSON.parse(metadata);\n } catch (error) {\n throw new Error(\n 'Feature metadata must be a plain object or a JSON string representing a plain object.',\n { cause: error },\n );\n }\n\n if (!isFeatureMetadataRecord(parsed)) {\n throw new Error(\n 'Feature metadata must be a plain object or a JSON string representing a plain object.',\n );\n }\n\n return parsed;\n}\n\nfunction isFeatureMetadataRecord(value: unknown): value is FeatureMetadata {\n if (!value || typeof value !== 'object' || Array.isArray(value)) {\n return false;\n }\n\n const prototype = Object.getPrototypeOf(value);\n return prototype === Object.prototype || prototype === null;\n}\n\nexport function getQualifiedClassNameFromRegistry(\n registration: RegistryEntryLike,\n): string | null {\n if (\n registration.qualifiedName &&\n isQualifiedName(registration.qualifiedName)\n ) {\n return registration.qualifiedName;\n }\n\n if (registration.packageName) {\n return createQualifiedName(registration.packageName, registration.name);\n }\n\n return null;\n}\n\nexport function getPackageNameFromRegistry(\n registration: RegistryEntryLike,\n): string | null {\n if (registration.packageName) {\n return registration.packageName;\n }\n\n const qualifiedClassName = getQualifiedClassNameFromRegistry(registration);\n if (!qualifiedClassName) {\n return null;\n }\n\n return parseQualifiedName(qualifiedClassName).packageName;\n}\n\nexport function extractFeatureSeedsFromRegistryEntry(\n registration: RegistryEntryLike,\n): FeatureDefinitionSeed[] {\n if (registration.visibility === 'test') {\n return [];\n }\n\n const qualifiedClassName = getQualifiedClassNameFromRegistry(registration);\n if (!qualifiedClassName) {\n return [];\n }\n\n const featureConfig = registration.config.features;\n if (!featureConfig) {\n return [];\n }\n\n const packageName =\n registration.packageName ||\n parseQualifiedName(qualifiedClassName).packageName;\n\n return Object.entries(featureConfig).map(([localId, feature]) => ({\n featureKey: createFeatureKey(qualifiedClassName, localId),\n packageName,\n qualifiedClassName,\n className: registration.name,\n localId,\n defaultEnabled: feature.defaultEnabled,\n label: feature.label,\n description: feature.description,\n metadata: feature.metadata,\n visibility: registration.visibility,\n }));\n}\n\nexport function extractFeatureSeedsFromManifest(\n manifest: SmartObjectManifest,\n): FeatureDefinitionSeed[] {\n const seeds: FeatureDefinitionSeed[] = [];\n\n for (const [objectKey, objectDef] of Object.entries(manifest.objects)) {\n seeds.push(...extractFeatureSeedsFromManifestObject(objectKey, objectDef));\n }\n\n return seeds;\n}\n\nexport function extractTouchedPackagesFromManifest(\n manifest: SmartObjectManifest,\n): Set<string> {\n const packages = new Set<string>();\n\n if (manifest.packageName) {\n packages.add(manifest.packageName);\n }\n\n for (const [objectKey, objectDef] of Object.entries(manifest.objects)) {\n if (objectDef.visibility === 'test') {\n continue;\n }\n\n if (objectDef.packageName) {\n packages.add(objectDef.packageName);\n continue;\n }\n\n const qualifiedClassName = resolveManifestQualifiedClassName(\n objectKey,\n objectDef,\n );\n if (qualifiedClassName) {\n packages.add(parseQualifiedName(qualifiedClassName).packageName);\n }\n }\n\n return packages;\n}\n\nfunction extractFeatureSeedsFromManifestObject(\n objectKey: string,\n objectDef: SmartObjectDefinition,\n): FeatureDefinitionSeed[] {\n if (objectDef.visibility === 'test') {\n return [];\n }\n\n const features = objectDef.decoratorConfig.features;\n if (!features) {\n return [];\n }\n\n const qualifiedClassName = resolveManifestQualifiedClassName(\n objectKey,\n objectDef,\n );\n if (!qualifiedClassName) {\n return [];\n }\n\n const packageName =\n objectDef.packageName || parseQualifiedName(qualifiedClassName).packageName;\n\n return Object.entries(features).map(([localId, feature]) => ({\n featureKey: createFeatureKey(qualifiedClassName, localId),\n packageName,\n qualifiedClassName,\n className: objectDef.className,\n localId,\n defaultEnabled: feature.defaultEnabled,\n label: feature.label,\n description: feature.description,\n metadata:\n feature.metadata && typeof feature.metadata === 'object'\n ? (feature.metadata as FeatureMetadata)\n : undefined,\n visibility: objectDef.visibility,\n }));\n}\n\nfunction resolveManifestQualifiedClassName(\n objectKey: string,\n objectDef: SmartObjectDefinition,\n): string | null {\n if (objectDef.qualifiedName && isQualifiedName(objectDef.qualifiedName)) {\n return objectDef.qualifiedName;\n }\n\n if (isQualifiedName(objectKey)) {\n return objectKey;\n }\n\n if (objectDef.packageName) {\n return createQualifiedName(objectDef.packageName, objectDef.className);\n }\n\n return null;\n}\n\nexport function resolveFeatureKeyForTarget(\n classOrInstance: object | FeatureTargetConstructor,\n localId: string,\n): {\n featureKey: string;\n defaultEnabled: boolean;\n} {\n const ctor: FeatureTargetConstructor =\n typeof classOrInstance === 'function'\n ? (classOrInstance as FeatureTargetConstructor)\n : (classOrInstance.constructor as FeatureTargetConstructor);\n\n const registration = ObjectRegistry.getClassByConstructor(ctor);\n if (!registration) {\n throw new Error(\n `Cannot resolve feature \"${localId}\" because ${ctor?.name || 'the target class'} is not registered with @smrt().`,\n );\n }\n\n const qualifiedClassName = getQualifiedClassNameFromRegistry(registration);\n if (!qualifiedClassName) {\n throw new Error(\n `Cannot resolve feature \"${localId}\" for ${registration.name} because the class has no qualified package identity.`,\n );\n }\n\n const feature = registration.config.features?.[localId];\n if (!feature) {\n throw new Error(\n `Feature \"${localId}\" is not declared on ${qualifiedClassName}.`,\n );\n }\n\n return {\n featureKey: createFeatureKey(qualifiedClassName, localId),\n defaultEnabled: feature.defaultEnabled,\n };\n}\n\nexport function findFeatureDefaultInRegistry(\n featureKey: string,\n): boolean | undefined {\n const { qualifiedClassName, localId } = parseFeatureKey(featureKey);\n const registration =\n ObjectRegistry.getClassByQualifiedName(qualifiedClassName) ||\n ObjectRegistry.getClass(qualifiedClassName);\n\n const feature = registration?.config.features?.[localId];\n return feature?.defaultEnabled;\n}\n","import { SmrtObject, smrt } from '@happyvertical/smrt-core';\nimport type { FeatureDefinitionOptions, FeatureMetadata } from './types.js';\nimport { parseFeatureMetadata, serializeFeatureMetadata } from './utils.js';\n\n@smrt({\n tableName: '_smrt_feature_definitions',\n // Closed by default like FeatureOverride (#3013): the catalog is framework\n // state synced from manifests, not a tenant resource.\n api: false,\n cli: false,\n mcp: false,\n conflictColumns: ['feature_key'],\n})\nexport class FeatureDefinition extends SmrtObject { static __smrtPackage__ = \"@happyvertical/smrt-features\";\n featureKey: string = '';\n packageName: string = '';\n qualifiedClassName: string = '';\n className: string = '';\n localId: string = '';\n defaultEnabled: boolean = false;\n label: string = '';\n description: string = '';\n metadata: string = '';\n visibility: string = 'public';\n\n constructor(options: FeatureDefinitionOptions = {}) {\n super(options);\n\n if (options.featureKey !== undefined) this.featureKey = options.featureKey;\n if (options.packageName !== undefined)\n this.packageName = options.packageName;\n if (options.qualifiedClassName !== undefined) {\n this.qualifiedClassName = options.qualifiedClassName;\n }\n if (options.className !== undefined) this.className = options.className;\n if (options.localId !== undefined) this.localId = options.localId;\n if (options.defaultEnabled !== undefined) {\n this.defaultEnabled = options.defaultEnabled;\n }\n if (options.label !== undefined) this.label = options.label;\n if (options.description !== undefined)\n this.description = options.description;\n if (options.visibility !== undefined) this.visibility = options.visibility;\n if (options.metadata !== undefined) {\n this.metadata = serializeFeatureMetadata(options.metadata);\n }\n }\n\n getMetadata(): FeatureMetadata {\n return parseFeatureMetadata(this.metadata);\n }\n\n setMetadata(metadata: FeatureMetadata | null): void {\n this.metadata = serializeFeatureMetadata(metadata);\n }\n}\n","import { SmrtCollection } from '@happyvertical/smrt-core';\nimport { FeatureDefinition } from './feature-definition.js';\nimport type { FeatureDefinitionSeed } from './types.js';\nimport { serializeFeatureMetadata } from './utils.js';\n\nexport class FeatureDefinitionCollection extends SmrtCollection<FeatureDefinition> {\n static readonly _itemClass = FeatureDefinition;\n\n async findByFeatureKey(\n featureKey: string,\n ): Promise<FeatureDefinition | null> {\n const results = await this.list({\n where: { featureKey },\n limit: 1,\n });\n return results[0] ?? null;\n }\n\n async findByPackageName(packageName: string): Promise<FeatureDefinition[]> {\n return this.list({\n where: { packageName },\n });\n }\n\n async upsertDefinition(seed: FeatureDefinitionSeed): Promise<{\n definition: FeatureDefinition;\n status: 'created' | 'updated' | 'unchanged';\n }> {\n const existing = await this.findByFeatureKey(seed.featureKey);\n\n if (!existing) {\n const created = await this.create({\n ...seed,\n metadata: serializeFeatureMetadata(seed.metadata),\n });\n await created.save();\n return { definition: created, status: 'created' };\n }\n\n const nextMetadata = serializeFeatureMetadata(seed.metadata);\n const changed =\n existing.packageName !== seed.packageName ||\n existing.qualifiedClassName !== seed.qualifiedClassName ||\n existing.className !== seed.className ||\n existing.localId !== seed.localId ||\n existing.defaultEnabled !== seed.defaultEnabled ||\n existing.label !== (seed.label ?? '') ||\n existing.description !== (seed.description ?? '') ||\n existing.metadata !== nextMetadata ||\n existing.visibility !== (seed.visibility ?? 'public');\n\n if (!changed) {\n return { definition: existing, status: 'unchanged' };\n }\n\n existing.packageName = seed.packageName;\n existing.qualifiedClassName = seed.qualifiedClassName;\n existing.className = seed.className;\n existing.localId = seed.localId;\n existing.defaultEnabled = seed.defaultEnabled;\n existing.label = seed.label ?? '';\n existing.description = seed.description ?? '';\n existing.metadata = nextMetadata;\n existing.visibility = seed.visibility ?? 'public';\n await existing.save();\n\n return { definition: existing, status: 'updated' };\n }\n}\n","import { SmrtObject, smrt } from '@happyvertical/smrt-core';\nimport {\n FeatureOverrideEffect,\n type FeatureOverrideOptions,\n type FeatureScopeType,\n GLOBAL_FEATURE_SCOPE_ID,\n} from './types.js';\n\n// Override rows are authorization state: any scope (global, another tenant,\n// another user) can be written, and the object is not tenant-scoped. Generated\n// REST routes and MCP tools only check authentication, so they are closed\n// (#3013). Hosts write overrides server-side through\n// `FeatureOverrideService`, which requires an explicit authorizer for every\n// write (or the collection's `setOverride()` / `removeOverride()` after their\n// own authorization decision). The CLI is closed too: generated CLI commands\n// dispatch through the generated API.\n@smrt({\n tableName: '_smrt_feature_overrides',\n api: false,\n cli: false,\n mcp: false,\n conflictColumns: ['feature_key', 'scope_type', 'scope_id'],\n})\nexport class FeatureOverride extends SmrtObject { static __smrtPackage__ = \"@happyvertical/smrt-features\";\n featureKey: string = '';\n scopeType: FeatureScopeType = 'global';\n scopeId: string = GLOBAL_FEATURE_SCOPE_ID;\n effect: FeatureOverrideEffect = FeatureOverrideEffect.INHERIT;\n\n constructor(options: FeatureOverrideOptions = {}) {\n super(options);\n\n if (options.featureKey !== undefined) this.featureKey = options.featureKey;\n if (options.scopeType !== undefined) this.scopeType = options.scopeType;\n if (options.scopeId !== undefined) this.scopeId = options.scopeId;\n if (options.effect !== undefined) this.effect = options.effect;\n }\n\n isInherit(): boolean {\n return this.effect === FeatureOverrideEffect.INHERIT;\n }\n\n isEnabled(): boolean {\n return this.effect === FeatureOverrideEffect.ENABLE;\n }\n\n isDisabled(): boolean {\n return this.effect === FeatureOverrideEffect.DISABLE;\n }\n}\n","import type { FeatureOverride } from './feature-override.js';\nimport type { FeatureOverrideCollection } from './feature-overrides.js';\nimport type { FeatureOverrideEffect, FeatureScopeType } from './types.js';\n\n/** One override write the host must authorize before it runs (#3013). */\nexport interface FeatureOverrideWriteRequest {\n operation: 'set' | 'remove';\n featureKey: string;\n scopeType: FeatureScopeType;\n scopeId: string;\n /** Present for `set`. */\n effect?: FeatureOverrideEffect;\n}\n\n/**\n * The host's explicit authorization decision for one override write. Return\n * `true` only when the current caller may write that exact scope; anything\n * else denies.\n */\nexport type FeatureOverrideAuthorizer = (\n request: FeatureOverrideWriteRequest,\n) => boolean | Promise<boolean>;\n\n/** Thrown when the authorizer does not return `true`. Nothing is written. */\nexport class FeatureOverrideAuthorizationError extends Error {\n readonly status = 403;\n constructor(readonly request: FeatureOverrideWriteRequest) {\n super(\n `Feature override ${request.operation} denied for ${request.scopeType} scope`,\n );\n this.name = 'FeatureOverrideAuthorizationError';\n }\n}\n\n/**\n * Authorized write path for feature overrides (#3013). Override rows are\n * authorization state for every scope and `FeatureOverride` is not\n * tenant-scoped, so generated surfaces are closed. Hosts that let callers\n * change flags construct this service with an authorizer bound to the\n * current caller and write through it; every write asks the authorizer\n * first and fails closed.\n */\nexport class FeatureOverrideService {\n constructor(\n private readonly overrides: FeatureOverrideCollection,\n private readonly authorize: FeatureOverrideAuthorizer,\n ) {}\n\n async setOverride(\n featureKey: string,\n scopeType: FeatureScopeType,\n scopeId: string,\n effect: FeatureOverrideEffect,\n ): Promise<FeatureOverride> {\n await this.require({\n operation: 'set',\n featureKey,\n scopeType,\n scopeId,\n effect,\n });\n return this.overrides.setOverride(featureKey, scopeType, scopeId, effect);\n }\n\n async removeOverride(\n featureKey: string,\n scopeType: FeatureScopeType,\n scopeId: string,\n ): Promise<boolean> {\n await this.require({ operation: 'remove', featureKey, scopeType, scopeId });\n return this.overrides.removeOverride(featureKey, scopeType, scopeId);\n }\n\n private async require(request: FeatureOverrideWriteRequest): Promise<void> {\n let allowed = false;\n try {\n allowed = (await this.authorize(request)) === true;\n } catch {\n allowed = false;\n }\n if (!allowed) throw new FeatureOverrideAuthorizationError(request);\n }\n}\n","import { SmrtCollection } from '@happyvertical/smrt-core';\nimport { FeatureOverride } from './feature-override.js';\nimport {\n type FeatureOverrideEffect,\n type FeatureScopeType,\n GLOBAL_FEATURE_SCOPE_ID,\n} from './types.js';\n\nexport class FeatureOverrideCollection extends SmrtCollection<FeatureOverride> {\n static readonly _itemClass = FeatureOverride;\n\n async findByFeatureKey(featureKey: string): Promise<FeatureOverride[]> {\n return this.list({\n where: { featureKey },\n });\n }\n\n async findByFeatureAndScope(\n featureKey: string,\n scopeType: FeatureScopeType,\n scopeId: string,\n ): Promise<FeatureOverride | null> {\n const results = await this.list({\n where: { featureKey, scopeType, scopeId },\n limit: 1,\n });\n return results[0] ?? null;\n }\n\n async getGlobalOverride(featureKey: string): Promise<FeatureOverride | null> {\n return this.findByFeatureAndScope(\n featureKey,\n 'global',\n GLOBAL_FEATURE_SCOPE_ID,\n );\n }\n\n async getTenantOverride(\n featureKey: string,\n tenantId: string,\n ): Promise<FeatureOverride | null> {\n return this.findByFeatureAndScope(featureKey, 'tenant', tenantId);\n }\n\n async getOverrideMap(\n featureKey: string,\n scopeType: FeatureScopeType,\n scopeIds: string[],\n ): Promise<Map<string, FeatureOverride>> {\n const result = new Map<string, FeatureOverride>();\n if (scopeIds.length === 0) {\n return result;\n }\n\n const overrides = await this.list({\n where: { featureKey, scopeType, scopeId: scopeIds },\n });\n\n for (const override of overrides) {\n result.set(override.scopeId, override);\n }\n\n return result;\n }\n\n async setOverride(\n featureKey: string,\n scopeType: FeatureScopeType,\n scopeId: string,\n effect: FeatureOverrideEffect,\n ): Promise<FeatureOverride> {\n const existing = await this.findByFeatureAndScope(\n featureKey,\n scopeType,\n scopeId,\n );\n\n if (existing) {\n existing.effect = effect;\n await existing.save();\n return existing;\n }\n\n const created = await this.create({\n featureKey,\n scopeType,\n scopeId,\n effect,\n });\n await created.save();\n return created;\n }\n\n async setGlobalOverride(\n featureKey: string,\n effect: FeatureOverrideEffect,\n ): Promise<FeatureOverride> {\n return this.setOverride(\n featureKey,\n 'global',\n GLOBAL_FEATURE_SCOPE_ID,\n effect,\n );\n }\n\n async setTenantOverride(\n featureKey: string,\n tenantId: string,\n effect: FeatureOverrideEffect,\n ): Promise<FeatureOverride> {\n return this.setOverride(featureKey, 'tenant', tenantId, effect);\n }\n\n async removeOverride(\n featureKey: string,\n scopeType: FeatureScopeType,\n scopeId: string,\n ): Promise<boolean> {\n const existing = await this.findByFeatureAndScope(\n featureKey,\n scopeType,\n scopeId,\n );\n\n if (!existing) {\n return false;\n }\n\n await existing.delete();\n return true;\n }\n}\n","/** Collection options for framework-internal, correctness-critical reads. @packageDocumentation */\nimport type {\n SmrtClassOptions,\n SmrtCollectionOptions,\n} from '@happyvertical/smrt-core';\n\n/** Host options this package accepts before stripping internal list bounds. */\nexport type FeatureHostOptions = SmrtClassOptions &\n Pick<SmrtCollectionOptions, 'defaultListLimit' | 'maxListLimit'>;\n\n/**\n * The caller's options with its list bounds removed.\n *\n * `SmrtCollection.create()` forwards `defaultListLimit`/`maxListLimit` from\n * any options bag it is handed (#2367), so a host that configures those\n * application-wide — a perfectly ordinary thing to do — would silently cap\n * every internal enumeration this package runs. Two of those are\n * correctness-critical and neither is a user-facing page of results\n * (`FeatureDefinition`/`FeatureOverride` expose no generated list surface,\n * #3013): a dropped ancestor override changes the flag's *resolved value*\n * (`FeatureResolver.isEnabled()`), and a dropped definition makes a feature\n * invisible to a management screen or a sync prune pass (#3056, same hazard\n * class as #3048). The collections built here are therefore always created\n * from this bag. Only the list bounds are stripped; the database, tenancy\n * context, and every other option still apply.\n *\n * This is a local copy of `@happyvertical/smrt-users`'\n * `authorization-read-options.ts#withoutListBounds` (added for #3048/#3051).\n * `smrt-features` deliberately never imports `smrt-users` (see package\n * AGENTS.md), and no shared `smrt-core` helper exists for this yet, so the\n * two-line implementation is duplicated here rather than adding a\n * cross-package dependency for it.\n */\nexport function withoutListBounds(\n options: SmrtClassOptions | SmrtCollectionOptions,\n): SmrtCollectionOptions {\n return { ...options, defaultListLimit: undefined, maxListLimit: undefined };\n}\n","import type { SmrtClassOptions } from '@happyvertical/smrt-core';\nimport { importWorkspaceModule } from '@happyvertical/smrt-core/utils/import-workspace-module';\nimport { FeatureDefinitionCollection } from './feature-definitions.js';\nimport { FeatureOverrideCollection } from './feature-overrides.js';\nimport { type FeatureHostOptions, withoutListBounds } from './list-bounds.js';\nimport {\n FeatureOverrideEffect,\n type FeatureResolutionContext,\n type FeatureResolverOptions,\n type FeatureTargetConstructor,\n type FeatureTenantHierarchyProvider,\n type FeatureTenantNode,\n type FeatureUsersModule,\n} from './types.js';\nimport {\n findFeatureDefaultInRegistry,\n resolveFeatureKeyForTarget,\n} from './utils.js';\n\nexport class FeatureResolver {\n private readonly options: FeatureHostOptions;\n private readonly resolverOptions: FeatureResolverOptions;\n private featureDefinitions!: FeatureDefinitionCollection;\n private featureOverrides!: FeatureOverrideCollection;\n private initializationPromise: Promise<void> | null = null;\n private tenantHierarchyPromise: Promise<FeatureTenantHierarchyProvider | null> | null =\n null;\n\n constructor(\n options: FeatureHostOptions = {},\n resolverOptions: FeatureResolverOptions = {},\n ) {\n this.options = options;\n this.resolverOptions = resolverOptions;\n }\n\n async isEnabled(\n featureKey: string,\n context: FeatureResolutionContext = {},\n ): Promise<boolean> {\n await this.ensureInitialized();\n\n const baseState = await this.resolveBaseState(featureKey);\n const globalOverride =\n await this.featureOverrides.getGlobalOverride(featureKey);\n const globalState = this.applyOverride(baseState, globalOverride?.effect);\n\n if (!context.tenantId) {\n return globalState;\n }\n\n const tenantHierarchy = await this.getTenantHierarchy();\n if (!tenantHierarchy) {\n const directOverride = await this.featureOverrides.getTenantOverride(\n featureKey,\n context.tenantId,\n );\n return this.applyOverride(globalState, directOverride?.effect);\n }\n\n const chain = await tenantHierarchy.getChain(context.tenantId);\n if (chain.length === 0) {\n const directOverride = await this.featureOverrides.getTenantOverride(\n featureKey,\n context.tenantId,\n );\n return this.applyOverride(globalState, directOverride?.effect);\n }\n\n const overrides = await this.featureOverrides.getOverrideMap(\n featureKey,\n 'tenant',\n chain.map((node) => node.id),\n );\n\n let inheritedState = globalState;\n\n for (let index = 0; index < chain.length; index++) {\n const current = chain[index];\n const previous = index > 0 ? chain[index - 1] : null;\n const shouldInherit =\n index === 0 ||\n (!!previous?.cascadePermissions && current.inheritPermissions);\n\n const baseline = shouldInherit ? inheritedState : globalState;\n const override = overrides.get(current.id);\n inheritedState = this.applyOverride(baseline, override?.effect);\n }\n\n return inheritedState;\n }\n\n async isEnabledFor(\n classOrInstance: object | FeatureTargetConstructor,\n localId: string,\n context: FeatureResolutionContext = {},\n ): Promise<boolean> {\n const { featureKey } = resolveFeatureKeyForTarget(classOrInstance, localId);\n return this.isEnabled(featureKey, context);\n }\n\n private async ensureInitialized(): Promise<void> {\n if (!this.initializationPromise) {\n this.initializationPromise = (async () => {\n // A host-configured `defaultListLimit`/`maxListLimit` must never reach\n // these reads: `getOverrideMap()` walks the tenant ancestor chain, and\n // a dropped ancestor override would change the flag's resolved value\n // silently (#3056, same hazard class as #3048).\n const options = withoutListBounds(this.options);\n this.featureDefinitions =\n await FeatureDefinitionCollection.create(options);\n this.featureOverrides = await FeatureOverrideCollection.create(options);\n })();\n }\n\n await this.initializationPromise;\n }\n\n private async resolveBaseState(featureKey: string): Promise<boolean> {\n const registryDefault = findFeatureDefaultInRegistry(featureKey);\n if (registryDefault !== undefined) {\n return registryDefault;\n }\n\n const definition =\n await this.featureDefinitions.findByFeatureKey(featureKey);\n if (!definition) {\n throw new Error(\n `Feature \"${featureKey}\" is not declared in the registry or synced feature definitions.`,\n );\n }\n\n return definition.defaultEnabled;\n }\n\n private applyOverride(\n currentState: boolean,\n effect: FeatureOverrideEffect | undefined,\n ): boolean {\n switch (effect) {\n case FeatureOverrideEffect.ENABLE:\n return true;\n case FeatureOverrideEffect.DISABLE:\n return false;\n default:\n return currentState;\n }\n }\n\n private async getTenantHierarchy(): Promise<FeatureTenantHierarchyProvider | null> {\n if (!this.tenantHierarchyPromise) {\n const loader =\n this.resolverOptions.tenantHierarchyLoader ||\n defaultTenantHierarchyLoader;\n this.tenantHierarchyPromise = loader(this.options);\n }\n\n return this.tenantHierarchyPromise;\n }\n}\n\nasync function defaultTenantHierarchyLoader(\n options: SmrtClassOptions,\n): Promise<FeatureTenantHierarchyProvider | null> {\n try {\n const usersModule = await importWorkspaceModule<FeatureUsersModule>({\n packageName: '@happyvertical/smrt-users',\n sourceEntry: 'packages/users/src/collections/index.ts',\n purpose: 'tenant-aware feature flag resolution',\n });\n\n const tenantCollection = await usersModule.TenantCollection.create(options);\n return {\n async getChain(tenantId: string): Promise<FeatureTenantNode[]> {\n const tenant = await tenantCollection.get({ id: tenantId });\n if (!tenant) {\n return [];\n }\n\n const ancestors = await tenantCollection.getAncestorsFromRoot(tenantId);\n return [...ancestors, tenant].map((node) => ({\n id: String(node.id),\n inheritPermissions: Boolean(node.inheritPermissions),\n cascadePermissions: Boolean(node.cascadePermissions),\n }));\n },\n };\n } catch (error) {\n if (isMissingUsersDependency(error)) {\n return null;\n }\n throw error;\n }\n}\n\nfunction isMissingUsersDependency(error: unknown): boolean {\n if (!(error instanceof Error)) {\n return false;\n }\n\n const causeMessage =\n error.cause instanceof Error ? ` ${error.cause.message}` : '';\n const text = `${error.message}${causeMessage}`;\n\n return (\n text.includes('@happyvertical/smrt-users') &&\n (text.includes('Cannot find module') ||\n text.includes('Failed to load') ||\n text.includes('could not find'))\n );\n}\n","import type { FeatureDefinition } from './feature-definition.js';\nimport { FeatureDefinitionCollection } from './feature-definitions.js';\nimport type { FeatureOverride } from './feature-override.js';\nimport {\n type FeatureOverrideAuthorizer,\n FeatureOverrideService,\n} from './feature-override-service.js';\nimport { FeatureOverrideCollection } from './feature-overrides.js';\nimport { FeatureResolver } from './feature-resolver.js';\nimport { type FeatureHostOptions, withoutListBounds } from './list-bounds.js';\nimport {\n type FeatureMetadata,\n FeatureOverrideEffect,\n type FeatureResolverOptions,\n type FeatureScopeType,\n GLOBAL_FEATURE_SCOPE_ID,\n} from './types.js';\n\n/**\n * One feature, rendered for a management screen: the code-owned definition,\n * the value that is actually in effect for the requested tenant, and the raw\n * override row at each scope level so the UI can show *why* it is in effect.\n *\n * Structurally assignable to the presentational `FeatureSettingsView` consumed\n * by `@happyvertical/smrt-features/svelte`, so a server load can hand its rows\n * straight to `FeatureSettingsPanel` without a mapping step.\n */\nexport interface FeatureSettingsRow {\n /**\n * The persisted identifier. Renaming it orphans every existing override —\n * see the warning on {@link FeatureSettingsService}.\n */\n featureKey: string;\n /** Human label from the definition; may be empty. */\n label: string;\n /** Human description from the definition; may be empty. */\n description: string;\n /** Owning package, as synced from the manifest. */\n packageName: string;\n /** Qualified `package:Class` name of the feature's target. */\n qualifiedClassName: string;\n /** Unqualified class name of the feature's target. */\n className: string;\n /** Class-local feature id. */\n localId: string;\n /** Definition visibility marker (`public` unless the seed says otherwise). */\n visibility: string;\n /** Parsed definition metadata. */\n metadata: FeatureMetadata;\n /** The code-owned default, used when no override applies. */\n defaultEnabled: boolean;\n /**\n * What {@link FeatureResolver} resolves for the requested context — the\n * value the application actually behaves by.\n */\n effectiveEnabled: boolean;\n /** Effect of the global override row, or `null` when there is none. */\n globalEffect: FeatureOverrideEffect | null;\n /**\n * Effect of the override row for the requested tenant, or `null` when there\n * is none (or when no `tenantId` was requested).\n */\n tenantEffect: FeatureOverrideEffect | null;\n /**\n * What this feature resolves to for the requested tenant with **no override\n * of its own** — the state that removing the tenant override would produce,\n * which is what a management UI must name on its \"back to Default\" control.\n *\n * `null` when no `tenantId` was requested — nothing tenant-inherited was\n * computed, and the global state is not an answer to a question about a\n * tenant — and `null` when the tenant holds an override of its own, because\n * then the inherited state depends on the levels above this tenant, which a\n * configured `FeatureTenantHierarchyProvider` extends to ancestor tenants'\n * overrides. It is reported only for a requested tenant with no override of\n * its own, where the inherited state *is* the effective state. Rows that\n * leave it `null` get the neutral \"Default (inherited)\" label in the panel,\n * never a claim about a state nothing computed.\n */\n inheritedEnabled: boolean | null;\n}\n\n/** Selection for {@link FeatureSettingsService.listFeatureSettings}. */\nexport interface ListFeatureSettingsOptions {\n /**\n * Tenant whose overrides and effective values to report. Omit for a\n * global-only view.\n */\n tenantId?: string;\n /** Restrict to one owning package (the usual case for an app's own screen). */\n packageName?: string;\n /**\n * Restrict to an explicit allowlist of keys. Keys with no definition are\n * simply absent from the result — this filters, it does not validate.\n */\n featureKeys?: string[];\n}\n\n/** One override write requested through {@link FeatureSettingsService}. */\nexport interface SetFeatureOverrideRequest {\n featureKey: string;\n scopeType: FeatureScopeType;\n scopeId: string;\n /**\n * `INHERIT` removes the override row at that scope, returning the feature to\n * whatever the next level up resolves to.\n */\n effect: FeatureOverrideEffect;\n}\n\n/**\n * Thrown when a write names a `featureKey` that has no {@link FeatureDefinition}\n * row. Nothing is written.\n *\n * This is the generalized form of the per-app \"is this one of my keys?\" guard\n * every consumer would otherwise hand-roll to keep a form post from creating an\n * override for an arbitrary string.\n */\nexport class UnknownFeatureKeyError extends Error {\n readonly status = 400;\n constructor(readonly featureKey: string) {\n super(`Unknown feature key: ${featureKey}`);\n this.name = 'UnknownFeatureKeyError';\n }\n}\n\n/**\n * Thrown when a write names a scope the resolver would never read back: an\n * unknown `scopeType`, a global write under anything but\n * {@link GLOBAL_FEATURE_SCOPE_ID}, or a tenant write with a blank or untrimmed\n * `scopeId`. Nothing is written.\n *\n * `FeatureOverride` rows are looked up by the exact `(featureKey, scopeType,\n * scopeId)` triple, so a row stored under a scope nothing queries is invisible\n * state: it never takes effect, and it silently reappears if the scope naming\n * is ever corrected.\n */\nexport class InvalidFeatureScopeError extends Error {\n readonly status = 400;\n constructor(\n readonly scopeType: string,\n readonly scopeId: string,\n reason: string,\n ) {\n super(`Invalid feature override scope (${scopeType}): ${reason}`);\n this.name = 'InvalidFeatureScopeError';\n }\n}\n\n/** Options for {@link FeatureSettingsService.create}. */\nexport interface FeatureSettingsServiceOptions {\n /**\n * The host's authorization decision for each override write, bound to the\n * current caller. **Omitting it makes the service read-only**: every write\n * then fails closed with `FeatureOverrideAuthorizationError`. This\n * package never decides who may write.\n */\n authorize?: FeatureOverrideAuthorizer;\n /** Forwarded to {@link FeatureResolver} (e.g. the tenant-hierarchy loader). */\n resolver?: FeatureResolverOptions;\n}\n\nconst KNOWN_EFFECTS = new Set<string>(Object.values(FeatureOverrideEffect));\n\n/**\n * Coerce an untrusted value (a form field, a query parameter, a JSON body) to a\n * {@link FeatureOverrideEffect}. Anything unrecognized — including `null` and\n * `undefined` — becomes `INHERIT`, i.e. \"back to the default\", which is the\n * safe reading of a value the host could not parse.\n */\nexport function featureOverrideEffectFromValue(\n value: unknown,\n): FeatureOverrideEffect {\n if (value === FeatureOverrideEffect.ENABLE)\n return FeatureOverrideEffect.ENABLE;\n if (value === FeatureOverrideEffect.DISABLE)\n return FeatureOverrideEffect.DISABLE;\n return FeatureOverrideEffect.INHERIT;\n}\n\n/**\n * Read and write side of a feature-flag management screen, so consumers do not\n * re-derive the row shape or the unknown-key guard.\n *\n * **Authorization stays with the consumer.** This service makes no\n * authorization decision of its own: reads are unfiltered, and every write is\n * delegated to {@link FeatureOverrideService}, which asks the host-supplied\n * {@link FeatureOverrideAuthorizer} first and fails closed. Call it only from\n * server code that has already established who the caller is. See\n * `packages/features/README.md` for the consumer recipe.\n *\n * **Feature keys are persisted identifiers.** Overrides are stored keyed by the\n * exact `featureKey` string and resolved by that string at runtime. Renaming a\n * key orphans every existing override, silently reverting all tenants to the\n * definition default. Treat a key as immutable once shipped; a rename needs a\n * data migration, not an edit.\n */\nexport class FeatureSettingsService {\n private readonly writes: FeatureOverrideService;\n\n constructor(\n private readonly definitions: FeatureDefinitionCollection,\n private readonly overrides: FeatureOverrideCollection,\n private readonly resolver: FeatureResolver,\n authorize?: FeatureOverrideAuthorizer,\n ) {\n this.writes = new FeatureOverrideService(\n overrides,\n authorize ?? (() => false),\n );\n }\n\n /**\n * Build a service over the given database options.\n *\n * @param options - SMRT database options, as passed to any collection.\n * @param serviceOptions - Host authorizer and resolver wiring. Omit\n * `authorize` for a read-only (load-function) service.\n */\n static async create(\n options: FeatureHostOptions = {},\n serviceOptions: FeatureSettingsServiceOptions = {},\n ): Promise<FeatureSettingsService> {\n // The catalog enumeration (`loadDefinitions()`) and every override read\n // this service does are internal, correctness-critical reads — not a\n // user-facing page of results — so a host-configured `defaultListLimit`/\n // `maxListLimit` must never truncate them (#3056, same hazard class as\n // #3048).\n const boundedOptions = withoutListBounds(options);\n const [definitions, overrides] = await Promise.all([\n FeatureDefinitionCollection.create(boundedOptions),\n FeatureOverrideCollection.create(boundedOptions),\n ]);\n return new FeatureSettingsService(\n definitions as FeatureDefinitionCollection,\n overrides as FeatureOverrideCollection,\n new FeatureResolver(options, serviceOptions.resolver ?? {}),\n serviceOptions.authorize,\n );\n }\n\n /**\n * One {@link FeatureSettingsRow} per matching definition, ordered by owning\n * package, then label, then key, so a rendered list is stable across loads.\n *\n * Makes no authorization decision — the caller has already decided that this\n * principal may see this tenant's flags.\n */\n async listFeatureSettings(\n options: ListFeatureSettingsOptions = {},\n ): Promise<FeatureSettingsRow[]> {\n const definitions = await this.loadDefinitions(options);\n\n return Promise.all(\n definitions.map((definition) => this.toRow(definition, options.tenantId)),\n );\n }\n\n /** The single row for one key, or `null` when no definition owns that key. */\n async getFeatureSetting(\n featureKey: string,\n tenantId?: string,\n ): Promise<FeatureSettingsRow | null> {\n const definition = await this.definitions.findByFeatureKey(featureKey);\n return definition ? this.toRow(definition, tenantId) : null;\n }\n\n /**\n * Apply one override change.\n *\n * Refuses any `featureKey` without a {@link FeatureDefinition} row and any\n * scope the resolver would never read back, then delegates to\n * {@link FeatureOverrideService} so the host authorizer decides whether this\n * caller may write this scope. `INHERIT` removes the row.\n *\n * @returns the stored override, or `null` when the row was removed (or was\n * already absent).\n * @throws {UnknownFeatureKeyError} when the key has no definition.\n * @throws {InvalidFeatureScopeError} when the scope is not one the resolver reads.\n * @throws `FeatureOverrideAuthorizationError` when the authorizer declines.\n */\n async setFeatureOverride(\n request: SetFeatureOverrideRequest,\n ): Promise<FeatureOverride | null> {\n const { featureKey, scopeType, scopeId, effect } = request;\n\n if (!KNOWN_EFFECTS.has(effect)) {\n throw new TypeError(`Unknown feature override effect: ${String(effect)}`);\n }\n assertValidFeatureScope(scopeType, scopeId);\n await this.assertKnownFeatureKey(featureKey);\n\n if (effect === FeatureOverrideEffect.INHERIT) {\n await this.writes.removeOverride(featureKey, scopeType, scopeId);\n return null;\n }\n\n return this.writes.setOverride(featureKey, scopeType, scopeId, effect);\n }\n\n /** {@link setFeatureOverride} for the global scope. */\n async setGlobalFeatureOverride(\n featureKey: string,\n effect: FeatureOverrideEffect,\n ): Promise<FeatureOverride | null> {\n return this.setFeatureOverride({\n featureKey,\n scopeType: 'global',\n scopeId: GLOBAL_FEATURE_SCOPE_ID,\n effect,\n });\n }\n\n /** {@link setFeatureOverride} for one tenant. */\n async setTenantFeatureOverride(\n featureKey: string,\n tenantId: string,\n effect: FeatureOverrideEffect,\n ): Promise<FeatureOverride | null> {\n return this.setFeatureOverride({\n featureKey,\n scopeType: 'tenant',\n scopeId: tenantId,\n effect,\n });\n }\n\n private async assertKnownFeatureKey(featureKey: string): Promise<void> {\n const key = typeof featureKey === 'string' ? featureKey.trim() : '';\n if (!key || key !== featureKey) {\n throw new UnknownFeatureKeyError(String(featureKey));\n }\n const definition = await this.definitions.findByFeatureKey(key);\n if (!definition) {\n throw new UnknownFeatureKeyError(key);\n }\n }\n\n private async loadDefinitions(\n options: ListFeatureSettingsOptions,\n ): Promise<FeatureDefinition[]> {\n const definitions = options.packageName\n ? await this.definitions.findByPackageName(options.packageName)\n : await this.definitions.list({});\n\n const allowed = options.featureKeys ? new Set(options.featureKeys) : null;\n const selected = allowed\n ? definitions.filter((definition) => allowed.has(definition.featureKey))\n : definitions;\n\n return [...selected].sort(compareDefinitions);\n }\n\n private async toRow(\n definition: FeatureDefinition,\n tenantId?: string,\n ): Promise<FeatureSettingsRow> {\n const { featureKey } = definition;\n const [globalOverride, tenantOverride, effectiveEnabled] =\n await Promise.all([\n this.overrides.getGlobalOverride(featureKey),\n tenantId\n ? this.overrides.getTenantOverride(featureKey, tenantId)\n : Promise.resolve(null),\n this.resolver.isEnabled(featureKey, tenantId ? { tenantId } : {}),\n ]);\n\n return {\n featureKey,\n label: definition.label,\n description: definition.description,\n packageName: definition.packageName,\n qualifiedClassName: definition.qualifiedClassName,\n className: definition.className,\n localId: definition.localId,\n visibility: definition.visibility,\n metadata: definition.getMetadata(),\n defaultEnabled: definition.defaultEnabled,\n effectiveEnabled,\n globalEffect: globalOverride?.effect ?? null,\n tenantEffect: tenantOverride?.effect ?? null,\n inheritedEnabled: tenantId && !tenantOverride ? effectiveEnabled : null,\n };\n }\n}\n\n/**\n * Reject a scope the resolver would never read back.\n *\n * `FeatureResolver` looks overrides up by the exact `(featureKey, scopeType,\n * scopeId)` triple: the global scope only ever at {@link GLOBAL_FEATURE_SCOPE_ID},\n * and the tenant scope only at ids it gets from the session or the tenant\n * hierarchy. A row written anywhere else is never read, so it is invisible\n * state rather than a setting — and the host authorizer cannot catch it,\n * because `scopeType: 'global'` with someone else's tenant id looks like an\n * ordinary global write to an authorizer that only gates the global scope.\n */\nfunction assertValidFeatureScope(\n scopeType: FeatureScopeType,\n scopeId: string,\n): void {\n if (scopeType !== 'global' && scopeType !== 'tenant') {\n throw new InvalidFeatureScopeError(\n String(scopeType),\n String(scopeId),\n 'scope type must be \"global\" or \"tenant\"',\n );\n }\n if (typeof scopeId !== 'string') {\n throw new InvalidFeatureScopeError(\n scopeType,\n String(scopeId),\n 'scope id must be a string',\n );\n }\n if (scopeType === 'global' && scopeId !== GLOBAL_FEATURE_SCOPE_ID) {\n throw new InvalidFeatureScopeError(\n scopeType,\n scopeId,\n `the global scope is only ever \"${GLOBAL_FEATURE_SCOPE_ID}\"`,\n );\n }\n if (\n scopeType === 'tenant' &&\n (!scopeId.trim() || scopeId !== scopeId.trim())\n ) {\n throw new InvalidFeatureScopeError(\n scopeType,\n scopeId,\n 'a tenant scope id must be non-blank and already trimmed',\n );\n }\n}\n\nfunction compareDefinitions(\n a: FeatureDefinition,\n b: FeatureDefinition,\n): number {\n return (\n a.packageName.localeCompare(b.packageName) ||\n (a.label || a.featureKey).localeCompare(b.label || b.featureKey) ||\n a.featureKey.localeCompare(b.featureKey)\n );\n}\n","import { ObjectRegistry } from '@happyvertical/smrt-core';\nimport type { SmartObjectManifest } from '@happyvertical/smrt-core/manifest';\nimport { FeatureDefinitionCollection } from './feature-definitions.js';\nimport { type FeatureHostOptions, withoutListBounds } from './list-bounds.js';\nimport type {\n FeatureDefinitionSeed,\n FeatureSyncResult,\n SyncDefinitionsOptions,\n SyncManifestOptions,\n} from './types.js';\nimport {\n extractFeatureSeedsFromManifest,\n extractFeatureSeedsFromRegistryEntry,\n extractTouchedPackagesFromManifest,\n getPackageNameFromRegistry,\n type RegistryEntryLike,\n} from './utils.js';\n\nexport class FeatureSyncService {\n private readonly options: FeatureHostOptions;\n private featureDefinitions!: FeatureDefinitionCollection;\n private initializationPromise: Promise<void> | null = null;\n\n constructor(options: FeatureHostOptions = {}) {\n this.options = options;\n }\n\n async syncDefinitions(\n options: SyncDefinitionsOptions = {},\n ): Promise<FeatureSyncResult> {\n await this.ensureInitialized();\n const registrations = this.collectRegistrations(options);\n const definitions = this.collectDefinitionsFromRegistrations(registrations);\n const touchedPackages =\n this.collectTouchedPackagesFromRegistrations(registrations);\n const isFilteredSync =\n Boolean(options.classNames?.length) ||\n Boolean(options.constructors?.length);\n const pruneStale = isFilteredSync ? false : (options.pruneStale ?? true);\n\n return this.applyDefinitions(definitions, touchedPackages, pruneStale);\n }\n\n async syncManifest(\n manifest: SmartObjectManifest,\n options: SyncManifestOptions = {},\n ): Promise<FeatureSyncResult> {\n await this.ensureInitialized();\n const definitions = extractFeatureSeedsFromManifest(manifest);\n const touchedPackages = extractTouchedPackagesFromManifest(manifest);\n return this.applyDefinitions(\n definitions,\n touchedPackages,\n options.pruneStale ?? true,\n );\n }\n\n private async ensureInitialized(): Promise<void> {\n if (!this.initializationPromise) {\n this.initializationPromise = (async () => {\n // `applyDefinitions()` enumerates the full catalog per touched package\n // to prune stale rows; a host-configured list bound must not make a\n // prune pass see a partial catalog (#3056, same hazard class as #3048).\n this.featureDefinitions = await FeatureDefinitionCollection.create(\n withoutListBounds(this.options),\n );\n })();\n }\n\n await this.initializationPromise;\n }\n\n private collectDefinitionsFromRegistrations(\n registrations: RegistryEntryLike[],\n ): FeatureDefinitionSeed[] {\n const definitions: FeatureDefinitionSeed[] = [];\n\n for (const registration of registrations) {\n definitions.push(...extractFeatureSeedsFromRegistryEntry(registration));\n }\n\n return definitions;\n }\n\n private collectTouchedPackagesFromRegistrations(\n registrations: RegistryEntryLike[],\n ): Set<string> {\n const packages = new Set<string>();\n\n for (const registration of registrations) {\n if (registration.visibility === 'test') {\n continue;\n }\n\n const packageName = getPackageNameFromRegistry(registration);\n if (packageName) {\n packages.add(packageName);\n }\n }\n\n return packages;\n }\n\n private collectRegistrations(\n options: SyncDefinitionsOptions,\n ): RegistryEntryLike[] {\n if (options.classNames?.length) {\n return options.classNames\n .map((name) => ObjectRegistry.getClass(name))\n .filter((value): value is NonNullable<typeof value> => !!value);\n }\n\n if (options.constructors?.length) {\n return options.constructors\n .map((ctor) => ObjectRegistry.getClassByConstructor(ctor))\n .filter((value): value is NonNullable<typeof value> => !!value);\n }\n\n const registrations: RegistryEntryLike[] = [];\n const seen = new Set<RegistryEntryLike>();\n\n for (const registration of ObjectRegistry.getAllClasses().values()) {\n if (seen.has(registration)) {\n continue;\n }\n seen.add(registration);\n registrations.push(registration);\n }\n\n return registrations;\n }\n\n private async applyDefinitions(\n definitions: FeatureDefinitionSeed[],\n touchedPackages: Set<string>,\n pruneStale: boolean,\n ): Promise<FeatureSyncResult> {\n const deduped = new Map<string, FeatureDefinitionSeed>();\n for (const definition of definitions) {\n deduped.set(definition.featureKey, definition);\n }\n\n let created = 0;\n let updated = 0;\n let unchanged = 0;\n let deleted = 0;\n\n const dedupedDefinitions = Array.from(deduped.values());\n const currentKeys = new Set(\n dedupedDefinitions.map((definition) => definition.featureKey),\n );\n const upsertResults = await Promise.all(\n dedupedDefinitions.map((definition) =>\n this.featureDefinitions.upsertDefinition(definition),\n ),\n );\n for (const result of upsertResults) {\n if (result.status === 'created') created++;\n if (result.status === 'updated') updated++;\n if (result.status === 'unchanged') unchanged++;\n }\n\n if (pruneStale) {\n const existingByPackage = await Promise.all(\n Array.from(touchedPackages, (packageName) =>\n this.featureDefinitions.findByPackageName(packageName),\n ),\n );\n\n const staleDefinitions = existingByPackage.flatMap((existing) =>\n existing.filter(\n (definition) => !currentKeys.has(definition.featureKey),\n ),\n );\n\n await Promise.all(\n staleDefinitions.map((definition) => definition.delete()),\n );\n deleted = staleDefinitions.length;\n }\n\n return {\n total: deduped.size,\n created,\n updated,\n unchanged,\n deleted,\n featureKeys: Array.from(currentKeys).sort(),\n };\n }\n}\n"],"mappings":"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"}
@@ -2,7 +2,7 @@
2
2
  "version": "1.0.0",
3
3
  "timestamp": 0,
4
4
  "packageName": "@happyvertical/smrt-features",
5
- "packageVersion": "0.57.45",
5
+ "packageVersion": "0.57.47",
6
6
  "objects": {
7
7
  "@happyvertical/smrt-features:FeatureDefinition": {
8
8
  "name": "featuredefinition",
@@ -287,6 +287,12 @@
287
287
  "extendsTypeArg": "FeatureDefinition",
288
288
  "exportName": "FeatureDefinitionCollection",
289
289
  "collectionExportName": "FeatureDefinitionCollectionCollection",
290
+ "collectionOf": "@happyvertical/smrt-features:FeatureDefinition",
291
+ "inheritedExposure": [
292
+ "api",
293
+ "mcp",
294
+ "cli"
295
+ ],
290
296
  "schema": {
291
297
  "tableName": "_smrt_feature_definitions",
292
298
  "ddl": "CREATE TABLE IF NOT EXISTS \"_smrt_feature_definitions\" (\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 \"feature_key\" TEXT DEFAULT '',\n \"package_name\" TEXT DEFAULT '',\n \"qualified_class_name\" TEXT DEFAULT '',\n \"class_name\" TEXT DEFAULT '',\n \"local_id\" TEXT DEFAULT '',\n \"default_enabled\" BOOLEAN DEFAULT FALSE,\n \"label\" TEXT DEFAULT '',\n \"description\" TEXT DEFAULT '',\n \"metadata\" TEXT DEFAULT '',\n \"visibility\" TEXT DEFAULT 'public'\n);",
@@ -772,6 +778,12 @@
772
778
  "extendsTypeArg": "FeatureOverride",
773
779
  "exportName": "FeatureOverrideCollection",
774
780
  "collectionExportName": "FeatureOverrideCollectionCollection",
781
+ "collectionOf": "@happyvertical/smrt-features:FeatureOverride",
782
+ "inheritedExposure": [
783
+ "api",
784
+ "mcp",
785
+ "cli"
786
+ ],
775
787
  "schema": {
776
788
  "tableName": "_smrt_feature_overrides",
777
789
  "ddl": "CREATE TABLE IF NOT EXISTS \"_smrt_feature_overrides\" (\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 \"feature_key\" TEXT DEFAULT '',\n \"scope_type\" TEXT DEFAULT 'global',\n \"scope_id\" TEXT,\n \"effect\" TEXT\n);",
@@ -3,13 +3,13 @@
3
3
  "sensitiveFieldsExcluded": true,
4
4
  "generatedAt": "1970-01-01T00:00:00.000Z",
5
5
  "packageName": "@happyvertical/smrt-features",
6
- "packageVersion": "0.57.45",
6
+ "packageVersion": "0.57.47",
7
7
  "sourceManifestPath": "dist/manifest.json",
8
8
  "agentDocPath": "AGENTS.md",
9
9
  "sourceHashes": {
10
- "manifest": "67b18dd0c74908d587a067441ba877695fefcf63d9cbb22fbca9ca685a1186b1",
11
- "packageJson": "06b71f2a45821b64d01b04108d6fd23917441e69fef56bb100413eba0157e576",
12
- "agents": "7ed27edb3420ece6c7ebb7be4673eaac780191f2cf77c65b1740dc8b3b867bc3"
10
+ "manifest": "b07749e538663eb1d009aaccc2f412526de04808ba37a9d06813bbd600b56c08",
11
+ "packageJson": "8490a39777efb33fdb9d69f0c480b4fbb33904e478f60f73342150a4e4395caa",
12
+ "agents": "3d7eb6e9e8c2996895b15abe9be955d339305866c4ea3a4c16fb3fd00f301e4e"
13
13
  },
14
14
  "exports": [
15
15
  ".",
@@ -527,7 +527,7 @@
527
527
  "polymorphicAssociations": 0,
528
528
  "uuidColumns": 4
529
529
  },
530
- "agentDoc": "# smrt-features\n\nCode-first feature flag system with global and tenant-hierarchy resolution.\n\n## Core pieces\n\n- `FeatureDefinition` (`_smrt_feature_definitions` table) — code-owned definition mirrored to DB at registration\n- `FeatureOverride` (`_smrt_feature_overrides` table) — runtime override state, scoped by `(featureKey, scopeType, scopeId)`\n- `FeatureResolver` — resolves the effective state of a feature in a given context, walking the scope chain\n- `FeatureSyncService` — keeps `_smrt_feature_definitions` in sync with code-registered defaults at boot\n- `FeatureOverrideService` — the only write path; asks a host-supplied authorizer before every write and fails closed (#3013)\n- `FeatureSettingsService` — read/write helper for a management screen: `listFeatureSettings()` returns one row per definition (definition fields, `effectiveEnabled`, `globalEffect`, `tenantEffect`), `setFeatureOverride()` refuses unknown keys and scopes the resolver never reads back, then delegates to `FeatureOverrideService` (#3052)\n- `./svelte` subpath — `FeatureSettingsPanel`, the presentational management panel (#3052)\n\n## Resolution chain (priority high → low)\n\n1. Tenant scope (`scopeType: 'tenant'`) — root-to-leaf traversal; a child inherits only when its parent allows cascading and it allows inheritance, otherwise its baseline resets to global\n2. Global scope (`scopeType: 'global'`, `scopeId: GLOBAL_FEATURE_SCOPE_ID`)\n3. Boolean `defaultEnabled` from the registered class, falling back to its synced definition\n\n`FeatureResolver` is instantiated with database options; call `isEnabled(key, { tenantId })` or `isEnabledFor(Class, localId, { tenantId })` on that instance. With no tenant context only global/default apply. There is no user scope.\n\n## Conventions\n\n- Declare features in `@smrt({ features: { drafts: { defaultEnabled: false } } })`. Keys are `<qualifiedClassName>#<localId>`, e.g. `@your/app:Invoice#drafts`.\n- Definitions are code-owned. Construct `new FeatureSyncService(dbOptions)` and call `syncDefinitions({ classNames?, constructors?, pruneStale? })`; filtered syncs never prune siblings. `syncManifest(manifest)` accepts a SMRT class manifest with decorator metadata.\n- Neither object exposes generated REST routes, MCP tools, or CLI commands (`api: false`, `mcp: false`, `cli: false`, #3013): override rows can target any scope and the object is not tenant-scoped, so a generated authentication-only surface would let any signed-in principal set another tenant's flag. Hosts write overrides server-side with `FeatureOverrideService` (which requires an explicit authorizer for every write) or `FeatureOverrideCollection.setOverride()` / `removeOverride()` only after their own authorization decision.\n- `FeatureOverrideCollection.setOverride()` persists typed values but does not validate them against a definition. `FeatureOverrideService` adds authorization; `FeatureSettingsService` additionally rejects unknown keys and invalid scope shapes. Definitions have no `allowedScopes` or `defaultEffect` fields.\n- The default hierarchy loader dynamically discovers `@happyvertical/smrt-users`; a missing dependency falls back to flat tenant lookup. Hosts can override `tenantHierarchyLoader` with a provider or `null`. Other loading failures propagate.\n- Every collection this package builds internally (`FeatureResolver`'s ancestor-chain override read, `FeatureSettingsService`'s catalog enumeration, `FeatureSyncService`'s prune enumeration) is created through the internal `src/list-bounds.ts#withoutListBounds`, so a host-configured `defaultListLimit`/`maxListLimit` on the caller's options bag can never silently truncate one of these correctness-critical reads (#3056, same hazard class as `smrt-users`' `authorization-read-options.ts#withoutListBounds`, #3048). Neither object exposes a generated, paginated list surface (see above), so there is no legitimate internal read here that a host bound should reach. The helper is local to avoid a required dependency on `smrt-users`.\n\n## Management UI (`./svelte`)\n\n- Built like every other Svelte-shipping package here (`smrt-tenancy`/`smrt-personas` are the closest models): `vite build --mode library && svelte-package -i src/svelte -o dist/svelte`, an `./svelte` export with a `svelte` condition, `svelte` as an **optional** peer dependency — plus `scripts/prune-svelte-package-artifacts.js` (as `smrt-content` does), because `svelte-package` otherwise copies `__tests__/` and `*.test.*` into the published `dist/svelte`. The root export stays Svelte-free so server-only consumers are unaffected.\n- `FeatureSettingsPanel` is presentational: rows and callbacks in, no fetching, no authorization. `src/svelte/types.ts` deliberately imports nothing from `smrt-core` or the `@smrt()` models, so the browser bundle stays thin; `FeatureSettingsRow` is structurally assignable to `FeatureSettingsView`.\n- Each row is its own `<form>`, so `formAction` gives a working screen with no client JS; `onSave` intercepts the submit instead and reads the row's values straight off the form. The global column is opt-in (`showGlobal` / `globalEditable`) because most apps have no platform-admin tier.\n- Two invariants the selects have to keep, both regression-tested. (1) The tenant column's *Default* option must never name the code default: the tenant scope inherits the global override and, with a `FeatureTenantHierarchyProvider`, ancestor tenants' overrides too, so `defaultEnabled` would tell an operator the opposite of what choosing it does. `inheritedOptionLabel()` names `FeatureSettingsView.inheritedEnabled` when the row carries it and says \"Default (inherited)\" otherwise; `FeatureSettingsService` sets it only where it is exactly knowable (no tenant override → inherited is the effective state), never derived from `globalEffect`. The global column does name the code default — nothing sits above global. (2) The rows remount on `contextKey ?? features`, so an unsaved choice cannot survive a reload or a tenant switch and be submitted against the new rows.\n- Writes validate the **scope shape** before the authorizer runs: `scopeType` must be `global` or `tenant`, a global write must use `GLOBAL_FEATURE_SCOPE_ID`, and a tenant id must be non-blank and already trimmed. A host authorizer cannot catch this — `scopeType: 'global'` with someone else's tenant id reads as an ordinary global write to an authorizer that only gates the global scope — and the resolver looks rows up by the exact `(featureKey, scopeType, scopeId)` triple, so a row written elsewhere is invisible state, not a setting.\n- **Authorization stays with the consumer.** `FeatureSettingsService` reads are unfiltered and every write goes through `FeatureOverrideService`; constructing the settings service without an `authorize` option makes it read-only (writes fail closed). Never add an authorization decision to this package — hosts differ, and the generated surfaces are closed precisely so that decision has to be theirs.\n- `vitest.config.ts` runs two projects because the halves need incompatible resolution: `server` (node, `smrtVitestPlugin`) and `svelte` (jsdom, `@sveltejs/vite-plugin-svelte`, `conditions: ['browser']`). The browser condition would send `smrt-core` to its browser build, so it must not be global.\n\n## Feature keys are persisted identifiers\n\nOverrides are stored keyed by the exact `featureKey` string and the resolver looks them up by that string. **Renaming a key orphans every existing override and silently reverts all tenants to the definition default.** Treat a shipped key as immutable; a rename needs a data migration over `_smrt_feature_overrides.feature_key`, not an edit. Renaming the owning package is the usual trigger — migrate persisted keys when the qualified class identity changes.\n\n## Integration with `@happyvertical/smrt-users`\n\nWhen `smrt-users` is present, `FeatureResolver` uses it to walk the tenant hierarchy (parent → grandparent → root). The default loader discovers it dynamically; a custom `tenantHierarchyLoader` may supply a `FeatureTenantHierarchyProvider`. Without it, tenant lookup is single-level.\n",
530
+ "agentDoc": "# smrt-features\n\nCode-first feature flag system with global and tenant-hierarchy resolution.\n\n## Core pieces\n\n- `FeatureDefinition` (`_smrt_feature_definitions` table) — code-owned definition mirrored to DB at registration\n- `FeatureOverride` (`_smrt_feature_overrides` table) — runtime override state, scoped by `(featureKey, scopeType, scopeId)`\n- `FeatureResolver` — resolves the effective state of a feature in a given context, walking the scope chain\n- `FeatureSyncService` — keeps `_smrt_feature_definitions` in sync with code-registered defaults at boot\n- `FeatureOverrideService` — the only write path; asks a host-supplied authorizer before every write and fails closed (#3013)\n- `FeatureSettingsService` — read/write helper for a management screen: `listFeatureSettings()` returns one row per definition (definition fields, `effectiveEnabled`, `globalEffect`, `tenantEffect`), `setFeatureOverride()` refuses unknown keys and scopes the resolver never reads back, then delegates to `FeatureOverrideService` (#3052)\n- `./svelte` subpath — `FeatureSettingsPanel`, the presentational management panel (#3052)\n\n## Resolution chain (priority high → low)\n\n1. Tenant scope (`scopeType: 'tenant'`) — root-to-leaf traversal; a child inherits only when its parent allows cascading and it allows inheritance, otherwise its baseline resets to global\n2. Global scope (`scopeType: 'global'`, `scopeId: GLOBAL_FEATURE_SCOPE_ID`)\n3. Boolean `defaultEnabled` from the registered class, falling back to its synced definition\n\n`FeatureResolver` is instantiated with database options; call `isEnabled(key, { tenantId })` or `isEnabledFor(Class, localId, { tenantId })` on that instance. With no tenant context only global/default apply. There is no user scope.\n\n## Conventions\n\n- Declare features in `@smrt({ features: { drafts: { defaultEnabled: false } } })`. Keys are `<qualifiedClassName>#<localId>`, e.g. `@your/app:Invoice#drafts`.\n- Definitions are code-owned. Construct `new FeatureSyncService(dbOptions)` and call `syncDefinitions({ classNames?, constructors?, pruneStale? })`; filtered syncs never prune siblings. `syncManifest(manifest)` accepts a SMRT class manifest with decorator metadata.\n- Neither object exposes generated REST routes, MCP tools, or CLI commands (`api: false`, `mcp: false`, `cli: false`, #3013): override rows can target any scope and the object is not tenant-scoped, so a generated authentication-only surface would let any signed-in principal set another tenant's flag. Hosts write overrides server-side with `FeatureOverrideService` (which requires an explicit authorizer for every write) or `FeatureOverrideCollection.setOverride()` / `removeOverride()` only after their own authorization decision.\n- `FeatureOverrideCollection.setOverride()` persists typed values but does not validate them against a definition. `FeatureOverrideService` adds authorization; `FeatureSettingsService` additionally rejects unknown keys and invalid scope shapes. Definitions have no `allowedScopes` or `defaultEffect` fields.\n- The default hierarchy loader dynamically discovers `@happyvertical/smrt-users`; a missing dependency falls back to flat tenant lookup. Hosts can override `tenantHierarchyLoader` with a provider or `null`. Other loading failures propagate.\n- Every collection this package builds internally (`FeatureResolver`'s ancestor-chain override read, `FeatureSettingsService`'s catalog enumeration, `FeatureSyncService`'s prune enumeration) is created through the internal `src/list-bounds.ts#withoutListBounds`, so a host-configured `defaultListLimit`/`maxListLimit` on the caller's options bag can never silently truncate one of these correctness-critical reads (#3056, same hazard class as `smrt-users`' `authorization-read-options.ts#withoutListBounds`, #3048). Neither object exposes a generated, paginated list surface (see above), so there is no legitimate internal read here that a host bound should reach. The helper is local to avoid a required dependency on `smrt-users`.\n\n## Management UI (`./svelte`)\n\n- Built like every other Svelte-shipping package here (`smrt-tenancy`/`smrt-personas` are the closest models): `vite build --mode library && svelte-package -i src/svelte -o dist/svelte`, an `./svelte` export with a `svelte` condition, `svelte` as an **optional** peer dependency — plus `scripts/prune-svelte-package-artifacts.js` (as `smrt-content` does), because `svelte-package` otherwise copies `__tests__/` and `*.test.*` into the published `dist/svelte`. The root export stays Svelte-free so server-only consumers are unaffected.\n- `FeatureSettingsPanel` is presentational: rows and callbacks in, no fetching, no authorization. `src/svelte/types.ts` deliberately imports nothing from `smrt-core` or the `@smrt()` models, so the browser bundle stays thin; `showKeys` (default true) hides the raw key/package lines for end-user surfaces while keeping the hidden `featureKey` input; `FeatureSettingsRow` is structurally assignable to `FeatureSettingsView`.\n- Each row is its own `<form>`, so `formAction` gives a working screen with no client JS; `onSave` intercepts the submit instead and reads the row's values straight off the form. The global column is opt-in (`showGlobal` / `globalEditable`) because most apps have no platform-admin tier.\n- Two invariants the selects have to keep, both regression-tested. (1) The tenant column's *Default* option must never name the code default: the tenant scope inherits the global override and, with a `FeatureTenantHierarchyProvider`, ancestor tenants' overrides too, so `defaultEnabled` would tell an operator the opposite of what choosing it does. `inheritedOptionLabel()` names `FeatureSettingsView.inheritedEnabled` when the row carries it and says \"Default (inherited)\" otherwise; `FeatureSettingsService` sets it only where it is exactly knowable (no tenant override → inherited is the effective state), never derived from `globalEffect`. The global column does name the code default — nothing sits above global. (2) The rows remount on `contextKey ?? features`, so an unsaved choice cannot survive a reload or a tenant switch and be submitted against the new rows.\n- Writes validate the **scope shape** before the authorizer runs: `scopeType` must be `global` or `tenant`, a global write must use `GLOBAL_FEATURE_SCOPE_ID`, and a tenant id must be non-blank and already trimmed. A host authorizer cannot catch this — `scopeType: 'global'` with someone else's tenant id reads as an ordinary global write to an authorizer that only gates the global scope — and the resolver looks rows up by the exact `(featureKey, scopeType, scopeId)` triple, so a row written elsewhere is invisible state, not a setting.\n- **Authorization stays with the consumer.** `FeatureSettingsService` reads are unfiltered and every write goes through `FeatureOverrideService`; constructing the settings service without an `authorize` option makes it read-only (writes fail closed). Never add an authorization decision to this package — hosts differ, and the generated surfaces are closed precisely so that decision has to be theirs.\n- `vitest.config.ts` runs two projects because the halves need incompatible resolution: `server` (node, `smrtVitestPlugin`) and `svelte` (jsdom, `@sveltejs/vite-plugin-svelte`, `conditions: ['browser']`). The browser condition would send `smrt-core` to its browser build, so it must not be global.\n\n## Feature keys are persisted identifiers\n\nOverrides are stored keyed by the exact `featureKey` string and the resolver looks them up by that string. **Renaming a key orphans every existing override and silently reverts all tenants to the definition default.** Treat a shipped key as immutable; a rename needs a data migration over `_smrt_feature_overrides.feature_key`, not an edit. Renaming the owning package is the usual trigger — migrate persisted keys when the qualified class identity changes.\n\n## Integration with `@happyvertical/smrt-users`\n\nWhen `smrt-users` is present, `FeatureResolver` uses it to walk the tenant hierarchy (parent → grandparent → root). The default loader discovers it dynamically; a custom `tenantHierarchyLoader` may supply a `FeatureTenantHierarchyProvider`. Without it, tenant lookup is single-level.\n",
531
531
  "browser": {
532
532
  "status": "server-only",
533
533
  "issues": [
@@ -48,6 +48,11 @@ export interface Props {
48
48
  error?: string | null;
49
49
  /** Text for the empty state. */
50
50
  emptyMessage?: string;
51
+ /**
52
+ * Show each feature's raw key and package name. Default true; set false for
53
+ * end-user surfaces (the hidden `featureKey` form input is always kept).
54
+ */
55
+ showKeys?: boolean;
51
56
  /**
52
57
  * Identity of what is being edited (typically the tenant id). Unsaved edits
53
58
  * are discarded whenever this changes — or, when it is omitted, whenever
@@ -80,6 +85,7 @@ const {
80
85
  contextKey,
81
86
  message = null,
82
87
  error = null,
88
+ showKeys = true,
83
89
  emptyMessage = 'No features are registered yet. Definitions appear here once they are synced from code.',
84
90
  saveLabel = 'Save',
85
91
  formAction,
@@ -142,9 +148,11 @@ function readOnlyEffectLabel(feature: FeatureSettingsView): string {
142
148
  </Badge>
143
149
  </div>
144
150
  <p class="fs-description">{feature.description || feature.featureKey}</p>
145
- <code class="fs-key">{feature.featureKey}</code>
146
- {#if feature.packageName}
147
- <span class="fs-package">{feature.packageName}</span>
151
+ {#if showKeys}
152
+ <code class="fs-key">{feature.featureKey}</code>
153
+ {#if feature.packageName}
154
+ <span class="fs-package">{feature.packageName}</span>
155
+ {/if}
148
156
  {/if}
149
157
  </div>
150
158
 
@@ -25,6 +25,11 @@ export interface Props {
25
25
  error?: string | null;
26
26
  /** Text for the empty state. */
27
27
  emptyMessage?: string;
28
+ /**
29
+ * Show each feature's raw key and package name. Default true; set false for
30
+ * end-user surfaces (the hidden `featureKey` form input is always kept).
31
+ */
32
+ showKeys?: boolean;
28
33
  /**
29
34
  * Identity of what is being edited (typically the tenant id). Unsaved edits
30
35
  * are discarded whenever this changes — or, when it is omitted, whenever
@@ -1 +1 @@
1
- {"version":3,"file":"FeatureSettingsPanel.svelte.d.ts","sourceRoot":"","sources":["../../../src/svelte/components/FeatureSettingsPanel.svelte.ts"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAAE,qBAAqB,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAS9E,MAAM,WAAW,KAAK;IACpB,oEAAoE;IACpE,QAAQ,CAAC,EAAE,mBAAmB,EAAE,CAAC;IACjC,mDAAmD;IACnD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;;OAIG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,mDAAmD;IACnD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,uDAAuD;IACvD,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,2CAA2C;IAC3C,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,yCAAyC;IACzC,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,gCAAgC;IAChC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,2CAA2C;IAC3C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;OAGG;IACH,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,qBAAqB,KAAK,IAAI,CAAC;CAClD;AAgJD,QAAA,MAAM,oBAAoB,2CAAwC,CAAC;AACnE,KAAK,oBAAoB,GAAG,UAAU,CAAC,OAAO,oBAAoB,CAAC,CAAC;AACpE,eAAe,oBAAoB,CAAC"}
1
+ {"version":3,"file":"FeatureSettingsPanel.svelte.d.ts","sourceRoot":"","sources":["../../../src/svelte/components/FeatureSettingsPanel.svelte.ts"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAAE,qBAAqB,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAS9E,MAAM,WAAW,KAAK;IACpB,oEAAoE;IACpE,QAAQ,CAAC,EAAE,mBAAmB,EAAE,CAAC;IACjC,mDAAmD;IACnD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;;OAIG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,mDAAmD;IACnD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,uDAAuD;IACvD,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,2CAA2C;IAC3C,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,yCAAyC;IACzC,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,gCAAgC;IAChC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;OAGG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,2CAA2C;IAC3C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;OAGG;IACH,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,qBAAqB,KAAK,IAAI,CAAC;CAClD;AAmJD,QAAA,MAAM,oBAAoB,2CAAwC,CAAC;AACnE,KAAK,oBAAoB,GAAG,UAAU,CAAC,OAAO,oBAAoB,CAAC,CAAC;AACpE,eAAe,oBAAoB,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@happyvertical/smrt-features",
3
- "version": "0.57.45",
3
+ "version": "0.57.47",
4
4
  "description": "Code-first feature flags and tenant-aware overrides for the SMRT framework",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -24,13 +24,13 @@
24
24
  "./manifest.json": "./dist/manifest.json"
25
25
  },
26
26
  "dependencies": {
27
- "@happyvertical/smrt-core": "0.57.45",
28
- "@happyvertical/smrt-ui": "0.57.45"
27
+ "@happyvertical/smrt-core": "0.57.47",
28
+ "@happyvertical/smrt-ui": "0.57.47"
29
29
  },
30
30
  "devDependencies": {
31
- "@happyvertical/smrt-cli": "0.57.45",
32
- "@happyvertical/smrt-users": "0.57.45",
33
- "@happyvertical/smrt-vitest": "0.57.45",
31
+ "@happyvertical/smrt-cli": "0.57.47",
32
+ "@happyvertical/smrt-users": "0.57.47",
33
+ "@happyvertical/smrt-vitest": "0.57.47",
34
34
  "@sveltejs/package": "^2.5.8",
35
35
  "@sveltejs/vite-plugin-svelte": "^7.3.0",
36
36
  "@types/node": "24.13.6",