@happyvertical/smrt-features 0.55.10 → 0.55.14

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
@@ -1,6 +1,6 @@
1
1
  # smrt-features
2
2
 
3
- Code-first feature flag system with global, app-level, and tenant-hierarchy resolution.
3
+ Code-first feature flag system with global and tenant-hierarchy resolution.
4
4
 
5
5
  ## Core pieces
6
6
 
@@ -14,19 +14,20 @@ Code-first feature flag system with global, app-level, and tenant-hierarchy reso
14
14
 
15
15
  ## Resolution chain (priority high → low)
16
16
 
17
- 1. User scope (`scopeType: 'user'`) — when context includes a userId
18
- 2. Tenant scope (`scopeType: 'tenant'`) — walks up the tenant hierarchy when a `FeatureTenantHierarchyProvider` is configured (DI); otherwise resolves a flat tenant scope
19
- 3. Global scope (`scopeType: 'global'`, `scopeId: GLOBAL_FEATURE_SCOPE_ID`)
20
- 4. Definition default (registered in code)
17
+ 1. 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
18
+ 2. Global scope (`scopeType: 'global'`, `scopeId: GLOBAL_FEATURE_SCOPE_ID`)
19
+ 3. Boolean `defaultEnabled` from the registered class, falling back to its synced definition
20
+
21
+ `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.
21
22
 
22
23
  ## Conventions
23
24
 
24
- - Feature keys should be namespaced by package or domain, e.g. `commerce.invoice.draft-mode`, `content.editor.ai-suggestions`
25
- - Definitions are code-owned. Don't write `FeatureDefinition` rows directly — use `FeatureSyncService.syncDefinitions()` at startup with the manifest of expected features
25
+ - Declare features in `@smrt({ features: { drafts: { defaultEnabled: false } } })`. Keys are `<qualifiedClassName>#<localId>`, e.g. `@your/app:Invoice#drafts`.
26
+ - 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.
26
27
  - 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.
27
- - Overrides are write-time validated against the matching definition (effect must be a known `FeatureOverrideEffect`, scope must be valid for the definition's `allowedScopes`)
28
- - Tenant-hierarchy resolution is an optional integration wired by dependency injection (`FeatureTenantHierarchyProvider`), not a static dependency — `smrt-features` never imports `@happyvertical/smrt-users`. Without a configured provider, tenant-hierarchy resolution falls back to a flat tenant scope. (The consumer that wants hierarchy installs `smrt-users` itself and supplies the provider; `smrt-users` is a dev-only dependency here for tests.)
29
- - 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. This package still never imports `smrt-users`, so the two-line helper is a local copy rather than a shared import.
28
+ - `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.
29
+ - 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.
30
+ - 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`.
30
31
 
31
32
  ## Management UI (`./svelte`)
32
33
 
@@ -40,8 +41,8 @@ Code-first feature flag system with global, app-level, and tenant-hierarchy reso
40
41
 
41
42
  ## Feature keys are persisted identifiers
42
43
 
43
- Overrides 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 — keep the old key namespace and let `packageName` track the new package.
44
+ Overrides 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.
44
45
 
45
46
  ## Integration with `@happyvertical/smrt-users`
46
47
 
47
- When `smrt-users` is present, `FeatureResolver` uses it to walk the tenant hierarchy (parent → grandparent → root). Configure via `FeatureTenantHierarchyProvider`. Without it, tenant lookup is single-level.
48
+ When `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.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @happyvertical/smrt-features
2
2
 
3
- Code-first feature flags for s-m-r-t applications, with global, app-level, and tenant-hierarchy resolution.
3
+ Code-first feature flags for s-m-r-t applications, with global and tenant-hierarchy resolution.
4
4
 
5
5
  ## Installation
6
6
 
@@ -10,48 +10,71 @@ pnpm add @happyvertical/smrt-features
10
10
 
11
11
  ## Quick start
12
12
 
13
+ Declare features on their owning class. Import that class before syncing or
14
+ resolving it. Supply `dbOptions` for your migrated application database and
15
+ `currentTenantId` from your authenticated server context.
16
+
13
17
  ```typescript
18
+ import { SmrtObject, smrt } from '@happyvertical/smrt-core';
14
19
  import {
15
- FeatureDefinition,
16
20
  FeatureResolver,
17
21
  FeatureSyncService,
18
22
  } from '@happyvertical/smrt-features';
19
23
 
20
- // 1. Sync code-defined feature definitions at startup
21
- await FeatureSyncService.syncDefinitions({
22
- manifest: [
23
- {
24
- key: 'commerce.invoice.draft-mode',
25
- description: 'Allow users to save invoices as drafts before sending',
26
- defaultEffect: 'enabled',
27
- allowedScopes: ['global', 'tenant', 'user'],
24
+ @smrt({
25
+ packageName: '@your/app',
26
+ features: {
27
+ drafts: {
28
+ defaultEnabled: false,
29
+ label: 'Invoice drafts',
30
+ description: 'Save invoices as drafts before sending.',
28
31
  },
29
- // ...
30
- ],
31
- });
32
+ },
33
+ })
34
+ export class Invoice extends SmrtObject {}
32
35
 
33
- // 2. Resolve at runtime
34
- const enabled = await FeatureResolver.isEnabled('commerce.invoice.draft-mode', {
36
+ const sync = new FeatureSyncService(dbOptions);
37
+ await sync.syncDefinitions({ constructors: [Invoice] });
38
+
39
+ // This example uses flat tenant resolution.
40
+ const resolver = new FeatureResolver(dbOptions, {
41
+ tenantHierarchyLoader: async () => null,
42
+ });
43
+ const enabled = await resolver.isEnabledFor(Invoice, 'drafts', {
44
+ tenantId: currentTenantId,
45
+ });
46
+ // Equivalent key-based lookup:
47
+ const enabledByKey = await resolver.isEnabled('@your/app:Invoice#drafts', {
35
48
  tenantId: currentTenantId,
36
- userId: currentUserId,
37
49
  });
38
50
  ```
39
51
 
40
- ## Resolution
52
+ Keys have the form `<qualifiedClassName>#<localId>`. The example's key is
53
+ `@your/app:Invoice#drafts`. `syncDefinitions()` takes optional `classNames`,
54
+ `constructors`, and `pruneStale` options; a filtered sync never prunes sibling
55
+ class definitions. An unfiltered sync uses all registered classes and prunes
56
+ stale definitions in touched packages by default. `syncManifest()` separately
57
+ accepts a s-m-r-t `SmartObjectManifest` containing class decorator metadata.
41
58
 
42
- Features resolve in this priority order:
59
+ ## Resolution
43
60
 
44
- 1. **User override** (most specific) — `FeatureOverride` row with `scopeType: 'user'` for the active user
45
- 2. **Tenant override** — `FeatureOverride` row with `scopeType: 'tenant'`. Walks the tenant hierarchy if `@happyvertical/smrt-users` is installed.
46
- 3. **Global override** — `FeatureOverride` row with `scopeType: 'global'`
47
- 4. **Code default** — registered via `FeatureSyncService` from the manifest
61
+ The resolver starts with the registered class's `defaultEnabled` (falling
62
+ back to the synced definition), then applies the global override and tenant
63
+ overrides. Without a tenant context, only the global override applies.
48
64
 
49
- The first match wins.
65
+ The default hierarchy loader discovers `@happyvertical/smrt-users` when
66
+ available. Alternatively, supply `tenantHierarchyLoader` as the resolver's
67
+ second constructor argument; return a `FeatureTenantHierarchyProvider` or
68
+ `null` for flat tenant resolution. Hierarchy traversal runs root to leaf:
69
+ a child inherits its parent's state only when the parent has
70
+ `cascadePermissions` and the child has `inheritPermissions`; otherwise its
71
+ baseline resets to the global state. Each tenant's own override then applies.
72
+ `inherit` preserves that baseline. Supported scopes are `global` and `tenant`.
50
73
 
51
74
  ## Storage
52
75
 
53
- - `_smrt_feature_definitions` — code-owned feature shape (default effect, allowed scopes, metadata)
54
- - `_smrt_feature_overrides` — runtime overrides at any scope level
76
+ - `_smrt_feature_definitions` — code-owned class identity, local id, boolean default, label, description, and metadata
77
+ - `_smrt_feature_overrides` — runtime override keyed by feature key, scope type, and scope id
55
78
 
56
79
  ## Generated surfaces
57
80
 
@@ -219,8 +242,7 @@ silently reverting all tenants to the definition default — the change looks
219
242
  harmless in a diff and shows up as flags mysteriously resetting. Treat a key
220
243
  as immutable once shipped. A rename needs a data migration that rewrites
221
244
  `_smrt_feature_overrides.feature_key`, not an edit. (Renaming the owning
222
- package is the usual way this happens: keep the old key namespace and let
223
- `packageName` track the new package.)
245
+ package is the usual way this happens: migrate stored keys when the qualified class identity changes.)
224
246
 
225
247
  ## Documentation
226
248
 
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.55.10","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.55.14","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}`));
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.55.10\\\",\\\"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.55.14\\\",\\\"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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"}
@@ -2,7 +2,7 @@
2
2
  "version": "1.0.0",
3
3
  "timestamp": 0,
4
4
  "packageName": "@happyvertical/smrt-features",
5
- "packageVersion": "0.55.10",
5
+ "packageVersion": "0.55.14",
6
6
  "objects": {
7
7
  "@happyvertical/smrt-features:FeatureDefinition": {
8
8
  "name": "featuredefinition",
@@ -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.55.10",
6
+ "packageVersion": "0.55.14",
7
7
  "sourceManifestPath": "dist/manifest.json",
8
8
  "agentDocPath": "AGENTS.md",
9
9
  "sourceHashes": {
10
- "manifest": "e8a3038faa7d45ce90a3990ecc1e8c2011717b17fe892f4324253605c7ada677",
11
- "packageJson": "812e66afc1b49b2734aa13ec816c2a109dd09d5b3f6ac2340f5b4ccacabc4fe0",
12
- "agents": "9e350e9338ed878227fa9152a33d0fe8a804c6567e51876f6ba3bd56967a4deb"
10
+ "manifest": "864db34fb12b5cad179e26955195d9370dd6cf99a57428e98c549ee2ed1089f5",
11
+ "packageJson": "45d96e0f535f41000087633b992f0b52599edb41e5ce8caafc3d304effcdda06",
12
+ "agents": "7ed27edb3420ece6c7ebb7be4673eaac780191f2cf77c65b1740dc8b3b867bc3"
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, app-level, 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. User scope (`scopeType: 'user'`) — when context includes a userId\n2. Tenant scope (`scopeType: 'tenant'`) — walks up the tenant hierarchy when a `FeatureTenantHierarchyProvider` is configured (DI); otherwise resolves a flat tenant scope\n3. Global scope (`scopeType: 'global'`, `scopeId: GLOBAL_FEATURE_SCOPE_ID`)\n4. Definition default (registered in code)\n\n## Conventions\n\n- Feature keys should be namespaced by package or domain, e.g. `commerce.invoice.draft-mode`, `content.editor.ai-suggestions`\n- Definitions are code-owned. Don't write `FeatureDefinition` rows directly — use `FeatureSyncService.syncDefinitions()` at startup with the manifest of expected features\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- Overrides are write-time validated against the matching definition (effect must be a known `FeatureOverrideEffect`, scope must be valid for the definition's `allowedScopes`)\n- Tenant-hierarchy resolution is an optional integration wired by dependency injection (`FeatureTenantHierarchyProvider`), not a static dependency — `smrt-features` never imports `@happyvertical/smrt-users`. Without a configured provider, tenant-hierarchy resolution falls back to a flat tenant scope. (The consumer that wants hierarchy installs `smrt-users` itself and supplies the provider; `smrt-users` is a dev-only dependency here for tests.)\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. This package still never imports `smrt-users`, so the two-line helper is a local copy rather than a shared import.\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 — keep the old key namespace and let `packageName` track the new package.\n\n## Integration with `@happyvertical/smrt-users`\n\nWhen `smrt-users` is present, `FeatureResolver` uses it to walk the tenant hierarchy (parent → grandparent → root). Configure via `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; `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": [
package/dist/types.d.ts CHANGED
@@ -7,7 +7,7 @@ export type FeatureScopeType = 'global' | 'tenant';
7
7
  * `@happyvertical/smrt-core`'s internal (non-exported) `SmrtObjectConstructor`
8
8
  * and remains assignable to it for `ObjectRegistry.getClassByConstructor`.
9
9
  */
10
- export type FeatureTargetConstructor = new (...args: unknown[]) => SmrtObject;
10
+ export type FeatureTargetConstructor = new (...args: ConstructorParameters<typeof SmrtObject>) => SmrtObject;
11
11
  export declare enum FeatureOverrideEffect {
12
12
  INHERIT = "inherit",
13
13
  ENABLE = "enable",
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/dist/types.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","names":["FeatureOverrideEffect"],"sources":["../src/types.ts"],"sourcesContent":["import type {\n SmartObjectManifest,\n SmrtClassOptions,\n SmrtObject,\n} from '@happyvertical/smrt-core';\n\nexport const GLOBAL_FEATURE_SCOPE_ID = '*';\n\nexport type FeatureScopeType = 'global' | 'tenant';\n\n/**\n * Constructor type for any `@smrt()`-decorated class that may declare feature\n * toggles. Feature targets always extend `SmrtObject`, so this mirrors\n * `@happyvertical/smrt-core`'s internal (non-exported) `SmrtObjectConstructor`\n * and remains assignable to it for `ObjectRegistry.getClassByConstructor`.\n */\nexport type FeatureTargetConstructor = new (...args: unknown[]) => SmrtObject;\n\nexport enum FeatureOverrideEffect {\n INHERIT = 'inherit',\n ENABLE = 'enable',\n DISABLE = 'disable',\n}\n\nexport interface FeatureMetadata {\n [key: string]: unknown;\n}\n\nexport interface FeatureDefinitionOptions {\n id?: string;\n featureKey?: string;\n packageName?: string;\n qualifiedClassName?: string;\n className?: string;\n localId?: string;\n defaultEnabled?: boolean;\n label?: string;\n description?: string;\n metadata?: FeatureMetadata | string | null;\n visibility?: string;\n createdAt?: Date;\n updatedAt?: Date;\n}\n\nexport interface FeatureOverrideOptions {\n id?: string;\n featureKey?: string;\n scopeType?: FeatureScopeType;\n scopeId?: string;\n effect?: FeatureOverrideEffect;\n createdAt?: Date;\n updatedAt?: Date;\n}\n\nexport interface FeatureDefinitionSeed {\n featureKey: string;\n packageName: string;\n qualifiedClassName: string;\n className: string;\n localId: string;\n defaultEnabled: boolean;\n label?: string;\n description?: string;\n metadata?: FeatureMetadata;\n visibility?: string;\n}\n\nexport interface SyncDefinitionsOptions {\n classNames?: string[];\n constructors?: FeatureTargetConstructor[];\n pruneStale?: boolean;\n}\n\nexport interface SyncManifestOptions {\n pruneStale?: boolean;\n}\n\nexport interface FeatureSyncResult {\n total: number;\n created: number;\n updated: number;\n unchanged: number;\n deleted: number;\n featureKeys: string[];\n}\n\nexport interface FeatureResolutionContext {\n tenantId?: string;\n}\n\nexport interface FeatureTenantNode {\n id: string;\n inheritPermissions: boolean;\n cascadePermissions: boolean;\n}\n\nexport interface FeatureTenantHierarchyProvider {\n getChain(tenantId: string): Promise<FeatureTenantNode[]>;\n}\n\nexport type FeatureTenantHierarchyLoader = (\n options: SmrtClassOptions,\n) => Promise<FeatureTenantHierarchyProvider | null>;\n\nexport interface FeatureResolverOptions {\n tenantHierarchyLoader?: FeatureTenantHierarchyLoader;\n}\n\n/**\n * Minimal structural shape of a tenant record returned by\n * `@happyvertical/smrt-users`. Only the permission-cascade fields consumed by\n * feature resolution are modeled; everything else is left unconstrained.\n */\nexport interface FeatureUsersTenantRecord {\n id: string;\n inheritPermissions?: boolean;\n cascadePermissions?: boolean;\n}\n\nexport interface FeatureUsersModule {\n TenantCollection: {\n create(options: SmrtClassOptions): Promise<{\n get(criteria: { id: string }): Promise<FeatureUsersTenantRecord | null>;\n getAncestorsFromRoot(\n tenantId: string,\n ): Promise<FeatureUsersTenantRecord[]>;\n }>;\n };\n}\n\nexport type { SmartObjectManifest, SmrtClassOptions };\n"],"mappings":";AAMO,IAAM,0BAA0B;AAYhC,IAAK,wBAAL,kBAAKA,2BAAL;CACLA,uBAAA,aAAU;CACVA,uBAAA,YAAS;CACTA,uBAAA,aAAU;CAHA,OAAAA;AAAA,EAAA,CAAA,yBAAA,CAAA,CAAA"}
1
+ {"version":3,"file":"types.js","names":["FeatureOverrideEffect"],"sources":["../src/types.ts"],"sourcesContent":["import type {\n SmartObjectManifest,\n SmrtClassOptions,\n SmrtObject,\n} from '@happyvertical/smrt-core';\n\nexport const GLOBAL_FEATURE_SCOPE_ID = '*';\n\nexport type FeatureScopeType = 'global' | 'tenant';\n\n/**\n * Constructor type for any `@smrt()`-decorated class that may declare feature\n * toggles. Feature targets always extend `SmrtObject`, so this mirrors\n * `@happyvertical/smrt-core`'s internal (non-exported) `SmrtObjectConstructor`\n * and remains assignable to it for `ObjectRegistry.getClassByConstructor`.\n */\nexport type FeatureTargetConstructor = new (\n ...args: ConstructorParameters<typeof SmrtObject>\n) => SmrtObject;\n\nexport enum FeatureOverrideEffect {\n INHERIT = 'inherit',\n ENABLE = 'enable',\n DISABLE = 'disable',\n}\n\nexport interface FeatureMetadata {\n [key: string]: unknown;\n}\n\nexport interface FeatureDefinitionOptions {\n id?: string;\n featureKey?: string;\n packageName?: string;\n qualifiedClassName?: string;\n className?: string;\n localId?: string;\n defaultEnabled?: boolean;\n label?: string;\n description?: string;\n metadata?: FeatureMetadata | string | null;\n visibility?: string;\n createdAt?: Date;\n updatedAt?: Date;\n}\n\nexport interface FeatureOverrideOptions {\n id?: string;\n featureKey?: string;\n scopeType?: FeatureScopeType;\n scopeId?: string;\n effect?: FeatureOverrideEffect;\n createdAt?: Date;\n updatedAt?: Date;\n}\n\nexport interface FeatureDefinitionSeed {\n featureKey: string;\n packageName: string;\n qualifiedClassName: string;\n className: string;\n localId: string;\n defaultEnabled: boolean;\n label?: string;\n description?: string;\n metadata?: FeatureMetadata;\n visibility?: string;\n}\n\nexport interface SyncDefinitionsOptions {\n classNames?: string[];\n constructors?: FeatureTargetConstructor[];\n pruneStale?: boolean;\n}\n\nexport interface SyncManifestOptions {\n pruneStale?: boolean;\n}\n\nexport interface FeatureSyncResult {\n total: number;\n created: number;\n updated: number;\n unchanged: number;\n deleted: number;\n featureKeys: string[];\n}\n\nexport interface FeatureResolutionContext {\n tenantId?: string;\n}\n\nexport interface FeatureTenantNode {\n id: string;\n inheritPermissions: boolean;\n cascadePermissions: boolean;\n}\n\nexport interface FeatureTenantHierarchyProvider {\n getChain(tenantId: string): Promise<FeatureTenantNode[]>;\n}\n\nexport type FeatureTenantHierarchyLoader = (\n options: SmrtClassOptions,\n) => Promise<FeatureTenantHierarchyProvider | null>;\n\nexport interface FeatureResolverOptions {\n tenantHierarchyLoader?: FeatureTenantHierarchyLoader;\n}\n\n/**\n * Minimal structural shape of a tenant record returned by\n * `@happyvertical/smrt-users`. Only the permission-cascade fields consumed by\n * feature resolution are modeled; everything else is left unconstrained.\n */\nexport interface FeatureUsersTenantRecord {\n id: string;\n inheritPermissions?: boolean;\n cascadePermissions?: boolean;\n}\n\nexport interface FeatureUsersModule {\n TenantCollection: {\n create(options: SmrtClassOptions): Promise<{\n get(criteria: { id: string }): Promise<FeatureUsersTenantRecord | null>;\n getAncestorsFromRoot(\n tenantId: string,\n ): Promise<FeatureUsersTenantRecord[]>;\n }>;\n };\n}\n\nexport type { SmartObjectManifest, SmrtClassOptions };\n"],"mappings":";AAMO,IAAM,0BAA0B;AAchC,IAAK,wBAAL,kBAAKA,2BAAL;CACLA,uBAAA,aAAU;CACVA,uBAAA,YAAS;CACTA,uBAAA,aAAU;CAHA,OAAAA;AAAA,EAAA,CAAA,yBAAA,CAAA,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@happyvertical/smrt-features",
3
- "version": "0.55.10",
3
+ "version": "0.55.14",
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.55.10",
28
- "@happyvertical/smrt-ui": "0.55.10"
27
+ "@happyvertical/smrt-core": "0.55.14",
28
+ "@happyvertical/smrt-ui": "0.55.14"
29
29
  },
30
30
  "devDependencies": {
31
- "@happyvertical/smrt-cli": "0.55.10",
32
- "@happyvertical/smrt-users": "0.55.10",
33
- "@happyvertical/smrt-vitest": "0.55.10",
31
+ "@happyvertical/smrt-cli": "0.55.14",
32
+ "@happyvertical/smrt-users": "0.55.14",
33
+ "@happyvertical/smrt-vitest": "0.55.14",
34
34
  "@sveltejs/package": "^2.5.8",
35
35
  "@sveltejs/vite-plugin-svelte": "^7.3.0",
36
36
  "@types/node": "24.13.6",