@happyvertical/smrt-config 0.43.5 → 0.43.7

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
@@ -20,12 +20,16 @@ Configuration management with cosmiconfig, secret sanitization, and SSG export.
20
20
  | `clearCache()` | Reset cached config — affects all modules |
21
21
  | `exportConfig({ includeSecrets })` | SSG-safe export (defaults to no secrets) |
22
22
  | `sanitizeConfig(config)` | Strips keys matching: apiKey, password, secret, token, credential, private, auth, key |
23
+ | `resolveApplicationRuntime(config)` | Resolve and validate a local, self-hosted, or cloud infrastructure profile |
24
+ | `resolveConfiguredApplicationRuntime()` | Resolve loaded file config plus highest-priority `setConfig()` runtime overrides |
25
+ | `getApplicationRuntimePreset(profile)` | Inspect a profile's safe provider defaults |
23
26
 
24
27
  ## Key Files
25
28
 
26
29
  - `src/loader.ts` — cosmiconfig integration and file discovery
27
30
  - `src/merge.ts` — deep merge logic, runtime config store
28
31
  - `src/export.ts` — sanitization and export formatting (JSON/JS)
32
+ - `src/runtime-profile.ts` — application runtime presets, validation, capabilities, and diagnostics
29
33
  - `src/types.ts` — full config schema (~800 lines)
30
34
 
31
35
  ## Gotchas
@@ -33,3 +37,5 @@ Configuration management with cosmiconfig, secret sanitization, and SSG export.
33
37
  - **clearCache() is global**: affects all modules sharing the config instance
34
38
  - **SSG export defaults to no secrets**: must explicitly set `includeSecrets: true` to include them
35
39
  - **Deep merge**: later values override earlier ones at each key level
40
+ - **Profiles do not alter application policy**: domain objects, generated REST/CLI/MCP/WebMCP surfaces, action effects, approval policy, authorization records, and job invocation stay identical across profiles.
41
+ - **Runtime snapshots contain selectors, never credentials**: provider-specific URLs, tokens, paths, and secret values belong in the implementing provider's configuration. Unknown runtime fields fail closed.
package/README.md CHANGED
@@ -15,6 +15,10 @@ pnpm add @happyvertical/smrt-config
15
15
  ```javascript
16
16
  // smrt.config.js
17
17
  export default {
18
+ runtime: {
19
+ profile: 'local',
20
+ },
21
+
18
22
  smrt: {
19
23
  cacheDir: '.cache',
20
24
  logLevel: 'info',
@@ -39,6 +43,69 @@ export default {
39
43
  };
40
44
  ```
41
45
 
46
+ ### Application runtime profiles
47
+
48
+ `runtime.profile` selects a validated infrastructure composition without
49
+ forking application objects or workflows. Validation happens before startup;
50
+ unsupported combinations throw `RuntimeProfileValidationError` with a recovery
51
+ action.
52
+
53
+ ```typescript
54
+ // smrt.config.ts
55
+ import { defineConfig } from '@happyvertical/smrt-config';
56
+
57
+ export default defineConfig({
58
+ runtime: {
59
+ profile: 'self-hosted',
60
+ providers: {
61
+ authentication: { provider: 'magic-link' },
62
+ tenancy: { mode: 'multi-tenant', context: 'required' },
63
+ assets: { provider: 'local-files' },
64
+ },
65
+ },
66
+ });
67
+ ```
68
+
69
+ ```typescript
70
+ // application startup
71
+ import {
72
+ loadConfig,
73
+ resolveConfiguredApplicationRuntime,
74
+ } from '@happyvertical/smrt-config';
75
+
76
+ await loadConfig();
77
+ const runtime = resolveConfiguredApplicationRuntime();
78
+ // Safe for doctor output and agent inspection: provider selectors and derived
79
+ // capabilities are present; URLs, paths, credentials, and secret values are not.
80
+ console.log(JSON.stringify(runtime));
81
+ ```
82
+
83
+ | Profile | Database | Authentication | Tenancy | Assets / secrets | Jobs | Network |
84
+ | --- | --- | --- | --- | --- | --- | --- |
85
+ | `local` | user-owned SQLite | single-use owner bootstrap | real default tenant | user-owned local files | embedded (`inline` override allowed) | loopback |
86
+ | `self-hosted` | operator PostgreSQL | OIDC (`magic-link` allowed) | single tenant (`multi-tenant` allowed) | S3-compatible + environment secrets (documented local/external overrides allowed) | external worker | public TLS |
87
+ | `cloud` | managed PostgreSQL | hosted identity | required multi-tenant context, application isolation or RLS | managed object storage/secrets | scalable workers | public TLS |
88
+
89
+ Every profile supports logical export/import. The local profile additionally
90
+ advertises file-snapshot backup; deployment profiles assign physical backup to
91
+ the operator or managed provider.
92
+
93
+ The override seam selects only infrastructure providers. It cannot modify
94
+ domain models, generated REST/CLI/MCP/WebMCP definitions, action-effect
95
+ metadata, approval policy, authorization records, or the job invocation API.
96
+ Those are cross-profile invariants and are emitted in every resolved snapshot.
97
+ Provider implementations keep their credentials and connection values in their
98
+ own configuration; do not add those values to `runtime.providers`.
99
+
100
+ Use `resolveApplicationRuntime(config.runtime)` for a pure, explicit value such
101
+ as a test fixture. Application startup should use
102
+ `resolveConfiguredApplicationRuntime()`: it composes the loaded file with
103
+ highest-priority `setConfig()` overrides before validation. `getConfig()` keeps
104
+ its legacy meaning and returns only loaded file state. Successive `setConfig()`
105
+ calls deep-merge providers while the profile is unchanged; an explicit profile
106
+ switch resets earlier runtime provider selections before applying the new
107
+ profile.
108
+
42
109
  ### Use config in code
43
110
 
44
111
  ```typescript
@@ -118,6 +185,9 @@ const sanitized = sanitizeConfig(config);
118
185
  | `defineConfig(config)` | Type-safe config file helper |
119
186
  | `exportConfig(config, options?)` | SSG-safe export (defaults to no secrets) |
120
187
  | `sanitizeConfig(config)` | Strip secret-matching keys |
188
+ | `resolveApplicationRuntime(config)` | Resolve and fail-closed validate a runtime profile |
189
+ | `resolveConfiguredApplicationRuntime()` | Resolve effective file plus runtime-overridden profile config |
190
+ | `getApplicationRuntimePreset(profile)` | Inspect an immutable copy of a profile preset |
121
191
  | `mergeExportedConfig(baseConfig, exportedConfig)` | Merge an exported config over a base |
122
192
  | `parseExportedConfig(raw)` | Parse an exported config string |
123
193
 
@@ -131,7 +201,7 @@ Configuration merging (highest to lowest):
131
201
 
132
202
  ### Key Types
133
203
 
134
- `SmrtConfig`, `SmrtGlobalConfig`, `DatabaseConfig`, `SiteConfig`, `MigrationsConfig`, `LoadConfigOptions`, `ExportConfig`, `ExportConfigOptions`
204
+ `SmrtConfig`, `SmrtGlobalConfig`, `ApplicationRuntimeConfig`, `ResolvedApplicationRuntime`, `RuntimeProviders`, `RuntimeCapabilities`, `RuntimeInvariants`, `RuntimeProfileValidationError`, `DatabaseConfig`, `SiteConfig`, `MigrationsConfig`, `LoadConfigOptions`, `ExportConfig`, `ExportConfigOptions`
135
205
 
136
206
  ## Dependencies
137
207
 
package/dist/index.d.ts CHANGED
@@ -1,5 +1,28 @@
1
1
  import { DomainKnowledgeConfig } from '@happyvertical/smrt-types';
2
2
 
3
+ /** Canonical config stored at `runtime` in `smrt.config.ts`. */
4
+ export declare interface ApplicationRuntimeConfig {
5
+ profile: ApplicationRuntimeProfile;
6
+ providers?: RuntimeProviderOverrides;
7
+ }
8
+
9
+ /**
10
+ * Stable application-runtime profile contract.
11
+ *
12
+ * Profiles select infrastructure composition only. Domain models, generated
13
+ * surfaces, effect metadata, and approval policy are deliberately outside the
14
+ * override surface so moving an application cannot silently change behavior.
15
+ */
16
+ export declare type ApplicationRuntimeProfile = 'local' | 'self-hosted' | 'cloud';
17
+
18
+ export declare type AssetOwnership = 'user' | 'operator' | 'managed';
19
+
20
+ export declare type AssetStorageProvider = 'local-files' | 's3-compatible' | 'managed-object-storage';
21
+
22
+ export declare type AuthenticationProvider = 'owner-bootstrap' | 'oidc' | 'magic-link' | 'hosted-identity';
23
+
24
+ export declare type BackupProvider = 'file-snapshot' | 'operator' | 'managed';
25
+
3
26
  /**
4
27
  * Reset all cached configuration state.
5
28
  *
@@ -71,6 +94,8 @@ export declare interface CliConfig {
71
94
  schemaContract?: SchemaContractConfig;
72
95
  }
73
96
 
97
+ export declare type ConnectionOwnership = 'user' | 'operator' | 'managed';
98
+
74
99
  /**
75
100
  * Database connection configuration for CLI commands.
76
101
  *
@@ -95,6 +120,8 @@ export declare interface DatabaseConfig {
95
120
  url?: string;
96
121
  }
97
122
 
123
+ export declare type DatabaseEngine = 'sqlite' | 'postgres';
124
+
98
125
  /**
99
126
  * Type-safe helper for authoring `smrt.config.js` files.
100
127
  *
@@ -327,8 +354,11 @@ export declare interface ExportFilterValue {
327
354
  contains?: string;
328
355
  }
329
356
 
357
+ /** Return a fresh copy of the documented preset for inspection and tooling. */
358
+ export declare function getApplicationRuntimePreset(profile: ApplicationRuntimeProfile): Readonly<RuntimeProviders>;
359
+
330
360
  /**
331
- * Return the full merged configuration synchronously.
361
+ * Return the loaded file configuration synchronously.
332
362
  *
333
363
  * Reads from `globalThis.__smrtConfigCache` without triggering a file search.
334
364
  * Returns `null` when {@link loadConfig} has not been called yet (or after
@@ -345,6 +375,7 @@ export declare interface ExportFilterValue {
345
375
  * ```
346
376
  *
347
377
  * @see {@link loadConfig}
378
+ * @see {@link resolveConfiguredApplicationRuntime}
348
379
  * @see {@link getSiteConfig}
349
380
  * @see {@link getModuleConfig}
350
381
  */
@@ -445,6 +476,8 @@ export declare function getRuntimeConfig(): Partial<SmrtConfig>;
445
476
  */
446
477
  export declare function getSiteConfig(): SiteConfig | null;
447
478
 
479
+ export declare type JobTopology = 'inline' | 'embedded' | 'external' | 'scalable';
480
+
448
481
  /**
449
482
  * Load and parse configuration from the project root.
450
483
  *
@@ -684,6 +717,10 @@ export declare interface MigrationsPostgresConfig {
684
717
  statementTimeout?: string;
685
718
  }
686
719
 
720
+ export declare type NetworkExposure = 'loopback' | 'public';
721
+
722
+ export declare type OwnerBootstrapMode = 'single-use' | 'disabled';
723
+
687
724
  /**
688
725
  * Parse a config string previously produced by {@link exportConfig} back into
689
726
  * a plain JavaScript object.
@@ -709,6 +746,166 @@ export declare interface MigrationsPostgresConfig {
709
746
  */
710
747
  export declare function parseExportedConfig(content: string): unknown;
711
748
 
749
+ /**
750
+ * Resolve a safe preset and explicit provider overrides into a stable snapshot.
751
+ *
752
+ * The resolver is pure: it reads no environment variables, paths, credentials,
753
+ * clocks, or process state. Invalid and unknown fields fail before startup.
754
+ */
755
+ export declare function resolveApplicationRuntime(config: ApplicationRuntimeConfig): Readonly<ResolvedApplicationRuntime>;
756
+
757
+ /**
758
+ * Resolve the effective application runtime using the documented config
759
+ * priority: runtime overrides set by {@link setConfig} > loaded file config >
760
+ * profile defaults.
761
+ *
762
+ * This accessor is intentionally separate from {@link getConfig}, whose legacy
763
+ * contract returns the loaded file object only. When a runtime override selects
764
+ * a different profile, lower-priority provider overrides are discarded so they
765
+ * cannot leak profile-specific choices (for example local files or inline jobs)
766
+ * into the new profile. Provider overrides still deep-merge when the profile is
767
+ * unchanged.
768
+ *
769
+ * @returns A validated, deterministic, secret-free runtime snapshot.
770
+ * @throws {RuntimeProfileValidationError} When no profile is configured or the
771
+ * effective provider composition violates a profile invariant.
772
+ */
773
+ export declare function resolveConfiguredApplicationRuntime(): Readonly<ResolvedApplicationRuntime>;
774
+
775
+ /** Deterministic, secret-free runtime diagnostic snapshot. */
776
+ export declare interface ResolvedApplicationRuntime {
777
+ readonly schemaVersion: 1;
778
+ readonly profile: ApplicationRuntimeProfile;
779
+ readonly providers: RuntimeProviders;
780
+ readonly capabilities: RuntimeCapabilities;
781
+ readonly invariants: RuntimeInvariants;
782
+ readonly diagnostics: {
783
+ readonly secretValuesIncluded: false;
784
+ readonly overrides: readonly RuntimeOverrideReport[];
785
+ readonly unsafeOverrides: readonly [];
786
+ };
787
+ }
788
+
789
+ export declare interface RuntimeAssetProvider {
790
+ readonly provider: AssetStorageProvider;
791
+ readonly ownership: AssetOwnership;
792
+ }
793
+
794
+ export declare interface RuntimeAuthenticationProvider {
795
+ readonly provider: AuthenticationProvider;
796
+ readonly ownerBootstrap: OwnerBootstrapMode;
797
+ }
798
+
799
+ /** Derived capabilities used by startup, doctor, tests, and agent inspection. */
800
+ export declare interface RuntimeCapabilities {
801
+ readonly ownerBootstrap: boolean;
802
+ readonly publicAuthentication: boolean;
803
+ readonly defaultTenant: boolean;
804
+ readonly multiTenant: boolean;
805
+ readonly localAssets: boolean;
806
+ readonly externalWorkers: boolean;
807
+ readonly backup: true;
808
+ readonly logicalExport: true;
809
+ readonly logicalImport: true;
810
+ readonly diagnostics: true;
811
+ readonly orchestrationHealth: boolean;
812
+ }
813
+
814
+ export declare interface RuntimeDatabaseProvider {
815
+ readonly engine: DatabaseEngine;
816
+ readonly connectionOwnership: ConnectionOwnership;
817
+ }
818
+
819
+ /** Behaviors that provider/profile selection is never allowed to change. */
820
+ export declare interface RuntimeInvariants {
821
+ readonly domainModels: 'identical';
822
+ readonly generatedRest: 'identical';
823
+ readonly generatedCli: 'identical';
824
+ readonly generatedMcp: 'identical';
825
+ readonly generatedWebMcp: 'identical';
826
+ readonly mcpExposurePolicy: 'identical';
827
+ readonly webMcpExposurePolicy: 'identical';
828
+ readonly actionEffects: 'identical';
829
+ readonly approvalPolicy: 'identical';
830
+ readonly authorizationRecords: 'required';
831
+ readonly jobInvocation: 'identical';
832
+ readonly portability: 'logical-export-import';
833
+ }
834
+
835
+ export declare interface RuntimeJobProvider {
836
+ readonly topology: JobTopology;
837
+ }
838
+
839
+ export declare interface RuntimeNetworkProvider {
840
+ readonly exposure: NetworkExposure;
841
+ readonly tls: boolean;
842
+ }
843
+
844
+ export declare interface RuntimeOverrideReport {
845
+ readonly path: string;
846
+ readonly from: string | boolean;
847
+ readonly to: string | boolean;
848
+ }
849
+
850
+ export declare interface RuntimePortabilityProvider {
851
+ readonly backup: BackupProvider;
852
+ readonly logicalExport: true;
853
+ readonly logicalImport: true;
854
+ }
855
+
856
+ /** Aggregates deterministic, actionable failures before application startup. */
857
+ export declare class RuntimeProfileValidationError extends Error {
858
+ readonly issues: readonly RuntimeProfileValidationIssue[];
859
+ constructor(issues: RuntimeProfileValidationIssue[]);
860
+ }
861
+
862
+ export declare interface RuntimeProfileValidationIssue {
863
+ code: 'invalid_config' | 'unsupported_field' | 'incompatible_provider';
864
+ path: string;
865
+ message: string;
866
+ recovery: string;
867
+ }
868
+
869
+ /**
870
+ * Explicit provider changes layered over a profile's safe preset.
871
+ *
872
+ * Every override is revalidated with the completed composition. Capability and
873
+ * surface-policy fields are intentionally absent: providers cannot use this
874
+ * seam to weaken cross-profile invariants.
875
+ */
876
+ export declare interface RuntimeProviderOverrides {
877
+ database?: Partial<RuntimeDatabaseProvider>;
878
+ authentication?: Partial<RuntimeAuthenticationProvider>;
879
+ tenancy?: Partial<RuntimeTenancyProvider>;
880
+ assets?: Partial<RuntimeAssetProvider>;
881
+ secrets?: Partial<RuntimeSecretProvider>;
882
+ jobs?: Partial<RuntimeJobProvider>;
883
+ network?: Partial<RuntimeNetworkProvider>;
884
+ }
885
+
886
+ /** Fully selected infrastructure providers for an application runtime. */
887
+ export declare interface RuntimeProviders {
888
+ readonly database: RuntimeDatabaseProvider;
889
+ readonly authentication: RuntimeAuthenticationProvider;
890
+ readonly tenancy: RuntimeTenancyProvider;
891
+ readonly assets: RuntimeAssetProvider;
892
+ readonly secrets: RuntimeSecretProvider;
893
+ readonly jobs: RuntimeJobProvider;
894
+ readonly network: RuntimeNetworkProvider;
895
+ readonly portability: RuntimePortabilityProvider;
896
+ }
897
+
898
+ export declare interface RuntimeSecretProvider {
899
+ readonly provider: SecretProvider;
900
+ readonly ownership: SecretOwnership;
901
+ }
902
+
903
+ export declare interface RuntimeTenancyProvider {
904
+ readonly mode: TenancyMode;
905
+ readonly context: TenantContextMode;
906
+ readonly isolation: TenantIsolation;
907
+ }
908
+
712
909
  /**
713
910
  * Config export utilities for static site generation
714
911
  *
@@ -779,12 +976,18 @@ export declare interface SchemaContractConfig {
779
976
  };
780
977
  }
781
978
 
979
+ export declare type SecretOwnership = 'user' | 'operator' | 'managed';
980
+
981
+ export declare type SecretProvider = 'local-file' | 'environment' | 'external' | 'managed';
982
+
782
983
  /**
783
984
  * Apply runtime configuration overrides.
784
985
  *
785
986
  * Deep-merges `config` into an in-memory store that takes the highest priority
786
987
  * in every subsequent {@link getModuleConfig} / {@link getPackageConfig} call.
787
- * Successive calls accumulate — they do not replace earlier overrides.
988
+ * Successive calls accumulate. When a call explicitly changes the runtime
989
+ * profile, the stored runtime provider subtree is reset before merging so
990
+ * profile-specific selectors cannot leak into the new profile.
788
991
  *
789
992
  * Common uses:
790
993
  * - Inject test doubles or environment-specific values without a config file.
@@ -967,6 +1170,8 @@ export declare interface SmrtConfig {
967
1170
  export?: ExportConfig;
968
1171
  /** Domain-scoped agent/developer knowledge generation and exposure. */
969
1172
  knowledge?: DomainKnowledgeConfig;
1173
+ /** Validated application infrastructure composition. */
1174
+ runtime?: ApplicationRuntimeConfig;
970
1175
  /**
971
1176
  * Module-scoped configurations keyed by module name.
972
1177
  * Retrieved via {@link getModuleConfig}.
@@ -1112,4 +1317,10 @@ export declare interface SmrtGlobalConfig {
1112
1317
  [key: string]: unknown;
1113
1318
  }
1114
1319
 
1320
+ export declare type TenancyMode = 'default-tenant' | 'single-tenant' | 'multi-tenant';
1321
+
1322
+ export declare type TenantContextMode = 'defaulted' | 'required';
1323
+
1324
+ export declare type TenantIsolation = 'application' | 'database-rls';
1325
+
1115
1326
  export { }