@elevasis/core 0.11.1 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/dist/index.d.ts +2 -2
  2. package/dist/index.js +10 -11
  3. package/dist/organization-model/index.d.ts +2 -2
  4. package/dist/organization-model/index.js +10 -11
  5. package/dist/test-utils/index.d.ts +10 -3
  6. package/dist/test-utils/index.js +6 -6
  7. package/package.json +1 -1
  8. package/src/__tests__/template-core-compatibility.test.ts +6 -15
  9. package/src/_gen/__tests__/__snapshots__/contracts.md.snap +27 -270
  10. package/src/auth/multi-tenancy/credentials/server/encryption.ts +83 -39
  11. package/src/auth/multi-tenancy/credentials/server/kek-loader.ts +47 -0
  12. package/src/auth/multi-tenancy/index.ts +3 -0
  13. package/src/auth/multi-tenancy/invitations/api-schemas.ts +104 -107
  14. package/src/auth/multi-tenancy/memberships/api-schemas.ts +6 -5
  15. package/src/auth/multi-tenancy/memberships/membership.ts +130 -138
  16. package/src/auth/multi-tenancy/role-management/api-schemas.ts +78 -0
  17. package/src/auth/multi-tenancy/role-management/index.ts +16 -0
  18. package/src/execution/engine/tools/integration/server/adapters/apify/__tests__/apify-run-actor.integration.test.ts +299 -293
  19. package/src/execution/engine/tools/integration/service.test.ts +214 -0
  20. package/src/execution/engine/tools/integration/service.ts +169 -161
  21. package/src/integrations/credentials/__tests__/api-schemas.test.ts +420 -496
  22. package/src/integrations/credentials/api-schemas.ts +127 -143
  23. package/src/integrations/webhook-endpoints/__tests__/api-schemas.test.ts +327 -318
  24. package/src/integrations/webhook-endpoints/api-schemas.ts +103 -102
  25. package/src/integrations/webhook-endpoints/types.ts +58 -51
  26. package/src/operations/activities/api-schemas.ts +80 -79
  27. package/src/operations/activities/types.ts +64 -63
  28. package/src/organization-model/contracts.ts +1 -1
  29. package/src/organization-model/defaults.ts +6 -6
  30. package/src/organization-model/domains/navigation.ts +38 -37
  31. package/src/organization-model/foundation.ts +2 -3
  32. package/src/organization-model/published.ts +3 -3
  33. package/src/platform/constants/versions.ts +1 -1
  34. package/src/reference/_generated/contracts.md +27 -270
  35. package/src/scaffold-registry/__tests__/index.test.ts +72 -7
  36. package/src/scaffold-registry/index.ts +159 -26
  37. package/src/server.ts +281 -272
  38. package/src/supabase/database.types.ts +7 -3
@@ -180,7 +180,8 @@ export type OrganizationModelKeyResult = z.infer<typeof KeyResultSchema>
180
180
  ### `DeepPartial`
181
181
 
182
182
  ```typescript
183
- export type DeepPartial<T> = T extends Array<infer U> ? Array<DeepPartial<U>> : T extends object ? { [K in keyof T]?: DeepPartial<T[K]> } : T
183
+ export type DeepPartial<T> =
184
+ T extends Array<infer U> ? Array<DeepPartial<U>> : T extends object ? { [K in keyof T]?: DeepPartial<T[K]> } : T
184
185
  ```
185
186
 
186
187
  ## Feature System
@@ -189,13 +190,12 @@ export type DeepPartial<T> = T extends Array<infer U> ? Array<DeepPartial<U>> :
189
190
 
190
191
  ```typescript
191
192
  export type FeatureSidebarComponent = ComponentType
192
- export type FeatureIconComponent = ComponentType<{ size?: number;
193
193
  ```
194
194
 
195
195
  ### `FeatureIconComponent`
196
196
 
197
197
  ```typescript
198
- export type FeatureIconComponent = ComponentType<{ size?: number;
198
+ export type FeatureIconComponent = ComponentType<{ size?: number; stroke?: number }>
199
199
  ```
200
200
 
201
201
  ### `FeatureSidebarWidthResolver`
@@ -361,26 +361,10 @@ export interface ElevasisFeaturesContextValue {
361
361
  ### `ResourceStatus`
362
362
 
363
363
  ```typescript
364
- /**
365
- * Resource Registry type definitions
366
- */
367
-
368
- import type { IntegrationType } from '../../execution/engine/tools/integration'
369
- import type { ResourceCategory, ResourceLink } from './resource-link'
370
-
371
- // ============================================================================
372
- // Core Resource Type Definitions
373
- // ============================================================================
374
-
375
364
  /**
376
365
  * Environment/deployment status for resources
377
- */
378
- export type ResourceStatus = 'dev' | 'prod'
379
-
380
- /**
381
- * All resource types in the platform
382
- * Used as the discriminator field in ResourceDefinition
383
- */
366
+ */
367
+ export type ResourceStatus = 'dev' | 'prod'
384
368
  ```
385
369
 
386
370
  ### `ResourceType`
@@ -389,13 +373,8 @@ export type ResourceStatus = 'dev' | 'prod'
389
373
  /**
390
374
  * All resource types in the platform
391
375
  * Used as the discriminator field in ResourceDefinition
392
- */
393
- export type ResourceType = 'agent' | 'workflow' | 'trigger' | 'integration' | 'external' | 'human'
394
-
395
- /**
396
- * Executable resource types (subset of ResourceType)
397
- * These resources can be directly executed by the execution engine
398
- */
376
+ */
377
+ export type ResourceType = 'agent' | 'workflow' | 'trigger' | 'integration' | 'external' | 'human'
399
378
  ```
400
379
 
401
380
  ### `ExecutableResourceType`
@@ -404,17 +383,8 @@ export type ResourceType = 'agent' | 'workflow' | 'trigger' | 'integration' | 'e
404
383
  /**
405
384
  * Executable resource types (subset of ResourceType)
406
385
  * These resources can be directly executed by the execution engine
407
- */
408
- export type ExecutableResourceType = 'workflow' | 'agent'
409
-
410
- // ============================================================================
411
- // Base Resource Interface
412
- // ============================================================================
413
-
414
- /**
415
- * Base interface for ALL platform resources
416
- * Shared by both executable (agents, workflows) and non-executable (triggers, integrations, etc.) resources
417
- */
386
+ */
387
+ export type ExecutableResourceType = 'workflow' | 'agent'
418
388
  ```
419
389
 
420
390
  ### `ResourceDefinition`
@@ -423,7 +393,7 @@ export type ExecutableResourceType = 'workflow' | 'agent'
423
393
  /**
424
394
  * Base interface for ALL platform resources
425
395
  * Shared by both executable (agents, workflows) and non-executable (triggers, integrations, etc.) resources
426
- */
396
+ */
427
397
  export interface ResourceDefinition {
428
398
  /** Unique resource identifier */
429
399
  resourceId: string
@@ -463,44 +433,10 @@ export interface ResourceDefinition {
463
433
  ### `ResourceList`
464
434
 
465
435
  ```typescript
466
- /** Unique resource identifier */
467
- resourceId: string
468
-
469
- /** Display name */
470
- name: string
471
-
472
- /** Purpose and functionality description */
473
- description: string
474
-
475
- /** Version for change tracking and evolution */
476
- version: string
477
-
478
- /** Resource type discriminator */
479
- type: ResourceType
480
-
481
- /** Environment/deployment status */
482
- status: ResourceStatus
483
-
484
- /** Graph links to Organization Model nodes */
485
- links?: ResourceLink[]
486
-
487
- /** Infrastructure category for filtering */
488
- category?: ResourceCategory
489
-
490
- /** Whether the agent supports multi-turn sessions (agents only) */
491
- sessionCapable?: boolean
492
-
493
- /** Whether the resource is local (monorepo) or remote (externally deployed) */
494
- origin?: 'local' | 'remote'
495
-
496
- /** Whether this resource is archived and should be excluded from registration and deployment */
497
- archived?: boolean
498
- }
499
-
500
436
  /**
501
437
  * Resource list for organization
502
438
  * Returns ResourceDefinition metadata (not full definitions)
503
- */
439
+ */
504
440
  export interface ResourceList {
505
441
  workflows: ResourceDefinition[]
506
442
  agents: ResourceDefinition[]
@@ -513,16 +449,14 @@ export interface ResourceList {
513
449
  ### `WebhookProviderType`
514
450
 
515
451
  ```typescript
516
- /** Webhook provider identifiers */
517
- export type WebhookProviderType = 'cal-com' | 'stripe' | 'signature-api' | 'instantly' | 'apify' | 'test'
518
-
519
- /** Webhook trigger configuration */
452
+ /** Webhook provider identifiers */
453
+ export type WebhookProviderType = 'cal-com' | 'stripe' | 'signature-api' | 'instantly' | 'apify' | 'test'
520
454
  ```
521
455
 
522
456
  ### `WebhookTriggerConfig`
523
457
 
524
458
  ```typescript
525
- /** Webhook trigger configuration */
459
+ /** Webhook trigger configuration */
526
460
  export interface WebhookTriggerConfig {
527
461
  /** Provider identifier */
528
462
  provider: WebhookProviderType
@@ -538,17 +472,7 @@ export interface WebhookTriggerConfig {
538
472
  ### `ScheduleTriggerConfig`
539
473
 
540
474
  ```typescript
541
- /** Provider identifier */
542
- provider: WebhookProviderType
543
- /** Event type for documentation (not used for matching - workflow handles routing) */
544
- event?: string
545
- /** Optional filtering (e.g., specific form ID for Fillout) */
546
- filter?: Record<string, string>
547
- /** References credential in credentials table for per-org webhook secrets */
548
- credentialName?: string
549
- }
550
-
551
- /** Schedule trigger configuration */
475
+ /** Schedule trigger configuration */
552
476
  export interface ScheduleTriggerConfig {
553
477
  /** Cron expression (e.g., '0 6 * * *') */
554
478
  cron: string
@@ -560,13 +484,7 @@ export interface ScheduleTriggerConfig {
560
484
  ### `EventTriggerConfig`
561
485
 
562
486
  ```typescript
563
- /** Cron expression (e.g., '0 6 * * *') */
564
- cron: string
565
- /** Optional timezone (default: UTC) */
566
- timezone?: string
567
- }
568
-
569
- /** Event trigger configuration */
487
+ /** Event trigger configuration */
570
488
  export interface EventTriggerConfig {
571
489
  /** Internal event type */
572
490
  eventType: string
@@ -578,50 +496,8 @@ export interface EventTriggerConfig {
578
496
  ### `TriggerConfig`
579
497
 
580
498
  ```typescript
581
- /** Internal event type */
582
- eventType: string
583
- /** Event source */
584
- source?: string
585
- }
586
-
587
- /** Union of all trigger configs */
588
- export type TriggerConfig = WebhookTriggerConfig | ScheduleTriggerConfig | EventTriggerConfig
589
-
590
- // ============================================================================
591
- // Trigger Definition
592
- // ============================================================================
593
-
594
- /**
595
- * Trigger metadata - entry points that initiate resource execution
596
- *
597
- * Triggers represent how executions start: webhooks from external services,
598
- * scheduled cron jobs, platform events, or manual user actions.
599
- *
600
- * BREAKING CHANGES (2025-11-30):
601
- * - Now extends ResourceDefinition (inherits: resourceId, name, description, version, type, status, links, category)
602
- * - Field renames: `id` -> `resourceId` (inherited), `type` -> `triggerType`
603
- * - Relationship rename: `invokes` -> `triggers` (unified vocabulary)
604
- * - New required fields: `version` (inherited), `type: 'trigger'` (inherited)
605
- * - triggers object now includes `externalResources` option
606
- *
607
- * @example
608
- * // TriggerDefinition - metadata only
609
- * {
610
- * resourceId: 'trigger-new-order',
611
- * type: 'trigger',
612
- * triggerType: 'webhook',
613
- * name: 'New Order',
614
- * description: 'Webhook from Shopify on new orders',
615
- * version: '1.0.0',
616
- * status: 'prod',
617
- * webhookPath: '/webhooks/shopify/orders'
618
- * }
619
- *
620
- * // Relationships declared in ResourceRelationships (not on TriggerDefinition):
621
- * // relationships: {
622
- * // 'trigger-new-order': { triggers: { workflows: ['order-fulfillment-workflow'] } }
623
- * // }
624
- */
499
+ /** Union of all trigger configs */
500
+ export type TriggerConfig = WebhookTriggerConfig | ScheduleTriggerConfig | EventTriggerConfig
625
501
  ```
626
502
 
627
503
  ### `TriggerDefinition`
@@ -657,7 +533,7 @@ export type TriggerConfig = WebhookTriggerConfig | ScheduleTriggerConfig | Event
657
533
  * // relationships: {
658
534
  * // 'trigger-new-order': { triggers: { workflows: ['order-fulfillment-workflow'] } }
659
535
  * // }
660
- */
536
+ */
661
537
  export interface TriggerDefinition extends ResourceDefinition {
662
538
  /** Resource type discriminator (narrowed from base union) */
663
539
  type: 'trigger'
@@ -684,27 +560,6 @@ export interface TriggerDefinition extends ResourceDefinition {
684
560
  ### `IntegrationDefinition`
685
561
 
686
562
  ```typescript
687
- /** Resource type discriminator (narrowed from base union) */
688
- type: 'trigger'
689
-
690
- /** Trigger mechanism type (renamed from 'type' to avoid collision with base type discriminator) */
691
- triggerType: 'webhook' | 'schedule' | 'manual' | 'event'
692
-
693
- /** Type-specific configuration */
694
- config?: TriggerConfig
695
-
696
- // Legacy fields (deprecated, use config instead)
697
- /** For webhook triggers: path like '/webhooks/shopify/orders' */
698
- webhookPath?: string
699
- /** For schedule triggers: cron expression like '0 6 * * *' */
700
- schedule?: string
701
- /** For event triggers: event type like 'low-stock-alert' */
702
- eventType?: string
703
-
704
- // NOTE: What this trigger starts is declared in ResourceRelationships, not here
705
- // This prevents duplication - triggers are forward-declared in relationships
706
- }
707
-
708
563
  /**
709
564
  * Integration metadata - external service connections
710
565
  *
@@ -729,7 +584,7 @@ export interface TriggerDefinition extends ResourceDefinition {
729
584
  * version: '1.0.0',
730
585
  * status: 'prod'
731
586
  * }
732
- */
587
+ */
733
588
  export interface IntegrationDefinition extends ResourceDefinition {
734
589
  /** Resource type discriminator (narrowed from base union) */
735
590
  type: 'integration'
@@ -744,15 +599,6 @@ export interface IntegrationDefinition extends ResourceDefinition {
744
599
  ### `RelationshipDeclaration`
745
600
 
746
601
  ```typescript
747
- /** Resource type discriminator (narrowed from base union) */
748
- type: 'integration'
749
-
750
- /** Integration provider type */
751
- provider: IntegrationType
752
- /** References credentials table (e.g., 'shopify-prod', 'zendesk-api') */
753
- credentialName: string
754
- }
755
-
756
602
  /**
757
603
  * Explicit resource relationship declaration
758
604
  *
@@ -764,7 +610,7 @@ export interface IntegrationDefinition extends ResourceDefinition {
764
610
  * triggers: { workflows: ['order-fulfillment-workflow'] },
765
611
  * uses: { integrations: ['integration-shopify-prod', 'integration-postgres'] }
766
612
  * }
767
- */
613
+ */
768
614
  export interface RelationshipDeclaration {
769
615
  /** Resources this resource triggers */
770
616
  triggers?: {
@@ -784,20 +630,6 @@ export interface RelationshipDeclaration {
784
630
  ### `ResourceRelationships`
785
631
 
786
632
  ```typescript
787
- /** Resources this resource triggers */
788
- triggers?: {
789
- /** Agent resourceIds this resource triggers */
790
- agents?: string[]
791
- /** Workflow resourceIds this resource triggers */
792
- workflows?: string[]
793
- }
794
- /** Integrations this resource uses */
795
- uses?: {
796
- /** Integration IDs this resource uses */
797
- integrations?: string[]
798
- }
799
- }
800
-
801
633
  /**
802
634
  * Resource relationships map
803
635
  * Maps resourceId to its relationship declarations
@@ -809,17 +641,8 @@ export interface RelationshipDeclaration {
809
641
  * uses: { integrations: ['integration-shopify-prod'] }
810
642
  * }
811
643
  * }
812
- */
813
- export type ResourceRelationships = Record<string, RelationshipDeclaration>
814
-
815
- // ============================================================================
816
- // External Resource Types
817
- // ============================================================================
818
-
819
- /**
820
- * External platform type
821
- * Supported third-party automation platforms
822
- */
644
+ */
645
+ export type ResourceRelationships = Record<string, RelationshipDeclaration>
823
646
  ```
824
647
 
825
648
  ### `ExternalPlatform`
@@ -828,39 +651,8 @@ export type ResourceRelationships = Record<string, RelationshipDeclaration>
828
651
  /**
829
652
  * External platform type
830
653
  * Supported third-party automation platforms
831
- */
832
- export type ExternalPlatform = 'n8n' | 'make' | 'zapier' | 'other'
833
-
834
- /**
835
- * External automation resource metadata
836
- *
837
- * Represents workflows/automations running on third-party platforms
838
- * (n8n, Make, Zapier, etc.) for visualization in Command View.
839
- *
840
- * NOTE: This is metadata ONLY for visualization. No execution logic,
841
- * no API integration with external platforms, no status syncing.
842
- *
843
- * BREAKING CHANGES (2025-11-30):
844
- * - Now extends ResourceDefinition (inherits: resourceId, name, description, version, type, status, links, category)
845
- * - Field renames: `id` -> `resourceId` (inherited)
846
- * - New required field: `version` (inherited) - organizations must add version to all external resources
847
- * - New required field: `type: 'external'` (inherited) - resource type discriminator
848
- * - REMOVED FIELD: `triggeredBy` - per relationship-consolidation design, all relationships are forward-only declarations
849
- *
850
- * @example
851
- * {
852
- * resourceId: 'external-n8n-order-sync',
853
- * type: 'external',
854
- * version: '1.0.0',
855
- * platform: 'n8n',
856
- * name: 'Shopify Order Sync',
857
- * description: 'Legacy n8n workflow for syncing Shopify orders',
858
- * status: 'prod',
859
- * platformUrl: 'https://n8n.client.com/workflow/123',
860
- * triggers: { workflows: ['order-fulfillment-workflow'] },
861
- * uses: { integrations: ['integration-shopify-prod'] }
862
- * }
863
- */
654
+ */
655
+ export type ExternalPlatform = 'n8n' | 'make' | 'zapier' | 'other'
864
656
  ```
865
657
 
866
658
  ### `ExternalResourceDefinition`
@@ -895,7 +687,7 @@ export type ExternalPlatform = 'n8n' | 'make' | 'zapier' | 'other'
895
687
  * triggers: { workflows: ['order-fulfillment-workflow'] },
896
688
  * uses: { integrations: ['integration-shopify-prod'] }
897
689
  * }
898
- */
690
+ */
899
691
  export interface ExternalResourceDefinition extends ResourceDefinition {
900
692
  /** Resource type discriminator (narrowed from base union) */
901
693
  type: 'external'
@@ -932,37 +724,6 @@ export interface ExternalResourceDefinition extends ResourceDefinition {
932
724
  ### `HumanCheckpointDefinition`
933
725
 
934
726
  ```typescript
935
- /** Resource type discriminator (narrowed from base union) */
936
- type: 'external'
937
-
938
- /** Platform type */
939
- platform: ExternalPlatform
940
-
941
- // Optional platform-specific metadata
942
- /** Link to external platform (e.g., n8n workflow editor URL) */
943
- platformUrl?: string
944
- /** Platform's internal ID/reference */
945
- externalId?: string
946
-
947
- /** What this external resource triggers (external -> internal) */
948
- triggers?: {
949
- /** Elevasis workflow resourceIds this external automation triggers */
950
- workflows?: string[]
951
- /** Elevasis agent resourceIds this external automation triggers */
952
- agents?: string[]
953
- }
954
-
955
- /** Integrations this external resource uses (shared credentials) */
956
- uses?: {
957
- /** Integration IDs this external automation uses */
958
- integrations?: string[]
959
- }
960
-
961
- // NOTE: triggeredBy field removed - per relationship-consolidation design,
962
- // all relationships are forward-only declarations. Graph edges are built
963
- // from forward declarations only.
964
- }
965
-
966
727
  /**
967
728
  * Human Checkpoint definition - human decision points in automation
968
729
  *
@@ -987,7 +748,7 @@ export interface ExternalResourceDefinition extends ResourceDefinition {
987
748
  * requestedBy: { agents: ['order-processor-agent'] },
988
749
  * routesTo: { agents: ['order-fulfillment-agent'] }
989
750
  * }
990
- */
751
+ */
991
752
  export interface HumanCheckpointDefinition extends ResourceDefinition {
992
753
  /** Resource type discriminator (narrowed from base union) */
993
754
  type: 'human'
@@ -1015,10 +776,6 @@ export interface HumanCheckpointDefinition extends ResourceDefinition {
1015
776
  ### `DeploymentSpec`
1016
777
 
1017
778
  ```typescript
1018
- /** Always undefined for system resources; present for API compatibility with RemoteOrgConfig consumers */
1019
- sdkVersion?: never
1020
- }
1021
-
1022
779
  /**
1023
780
  * Organization-specific resource collection
1024
781
  *
@@ -1,39 +1,83 @@
1
- import crypto from 'crypto'
2
-
3
- const MASTER_KEY = process.env.SECRETS_ENCRYPTION_KEY
4
- const ALGORITHM = 'aes-256-gcm'
5
-
6
- interface EncryptedData {
7
- iv: string
8
- authTag: string
9
- data: string
10
- }
11
-
12
- export function encryptCredential(plaintext: string): string {
13
- if (!MASTER_KEY) {
14
- throw new Error('SECRETS_ENCRYPTION_KEY not configured')
15
- }
16
-
17
- const iv = crypto.randomBytes(16)
18
- const cipher = crypto.createCipheriv(ALGORITHM, Buffer.from(MASTER_KEY, 'hex'), iv)
19
- const encrypted = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()])
20
- const authTag = cipher.getAuthTag()
21
-
22
- return JSON.stringify({
23
- iv: iv.toString('base64'),
24
- authTag: authTag.toString('base64'),
25
- data: encrypted.toString('base64')
26
- })
27
- }
28
-
29
- export function decryptCredential(encrypted: string): string {
30
- if (!MASTER_KEY) {
31
- throw new Error('SECRETS_ENCRYPTION_KEY not configured')
32
- }
33
-
34
- const { iv, authTag, data } = JSON.parse(encrypted) as EncryptedData
35
- const decipher = crypto.createDecipheriv(ALGORITHM, Buffer.from(MASTER_KEY, 'hex'), Buffer.from(iv, 'base64'))
36
- decipher.setAuthTag(Buffer.from(authTag, 'base64'))
37
-
38
- return Buffer.concat([decipher.update(Buffer.from(data, 'base64')), decipher.final()]).toString('utf8')
39
- }
1
+ import crypto from 'crypto'
2
+
3
+ const ALGORITHM = 'aes-256-gcm'
4
+
5
+ // keyId stamped on all newly encrypted ciphertexts.
6
+ export const CURRENT_KEY_ID = 'platform-v1'
7
+
8
+ // Implicit keyId for pre-Vault ciphertext rows that were encrypted before this
9
+ // field existed. The kek-loader registers the legacy SECRETS_ENCRYPTION_KEY env
10
+ // value under this id during the migration window so existing rows decrypt
11
+ // until the re-encryption script (B4) restamps them with CURRENT_KEY_ID.
12
+ export const LEGACY_KEY_ID = 'platform-v0-legacy'
13
+
14
+ interface EncryptedData {
15
+ iv: string
16
+ authTag: string
17
+ data: string
18
+ keyId?: string
19
+ }
20
+
21
+ const kekMap = new Map<string, Buffer>()
22
+
23
+ export function setKek(keyId: string, key: Buffer): void {
24
+ if (key.length !== 32) {
25
+ throw new Error(`KEK must be 32 bytes (256 bits); got ${key.length}`)
26
+ }
27
+ kekMap.set(keyId, key)
28
+ }
29
+
30
+ export function clearKeks(): void {
31
+ kekMap.clear()
32
+ }
33
+
34
+ function resolveKek(keyId: string): Buffer | undefined {
35
+ const cached = kekMap.get(keyId)
36
+ if (cached) return cached
37
+
38
+ // Non-production fallback: bootstrap from SECRETS_ENCRYPTION_KEY so tests and
39
+ // local dev keep working without an explicit setKek() boot step. Production
40
+ // must register KEKs via the kek-loader; the env var is removed in Wave B5.
41
+ if (process.env.NODE_ENV !== 'production' && process.env.SECRETS_ENCRYPTION_KEY) {
42
+ const envKey = Buffer.from(process.env.SECRETS_ENCRYPTION_KEY, 'hex')
43
+ if (envKey.length === 32) {
44
+ kekMap.set(keyId, envKey)
45
+ return envKey
46
+ }
47
+ }
48
+
49
+ return undefined
50
+ }
51
+
52
+ export function encryptCredential(plaintext: string): string {
53
+ const key = resolveKek(CURRENT_KEY_ID)
54
+ if (!key) {
55
+ throw new Error(`Encryption KEK '${CURRENT_KEY_ID}' not loaded; call setKek() at boot`)
56
+ }
57
+
58
+ const iv = crypto.randomBytes(16)
59
+ const cipher = crypto.createCipheriv(ALGORITHM, key, iv)
60
+ const encrypted = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()])
61
+ const authTag = cipher.getAuthTag()
62
+
63
+ return JSON.stringify({
64
+ iv: iv.toString('base64'),
65
+ authTag: authTag.toString('base64'),
66
+ data: encrypted.toString('base64'),
67
+ keyId: CURRENT_KEY_ID
68
+ })
69
+ }
70
+
71
+ export function decryptCredential(encrypted: string): string {
72
+ const { iv, authTag, data, keyId } = JSON.parse(encrypted) as EncryptedData
73
+ const resolvedKeyId = keyId ?? LEGACY_KEY_ID
74
+ const key = resolveKek(resolvedKeyId)
75
+ if (!key) {
76
+ throw new Error(`Decryption KEK '${resolvedKeyId}' not loaded`)
77
+ }
78
+
79
+ const decipher = crypto.createDecipheriv(ALGORITHM, key, Buffer.from(iv, 'base64'))
80
+ decipher.setAuthTag(Buffer.from(authTag, 'base64'))
81
+
82
+ return Buffer.concat([decipher.update(Buffer.from(data, 'base64')), decipher.final()]).toString('utf8')
83
+ }
@@ -0,0 +1,47 @@
1
+ import { getSupabaseClient } from '../../../../supabase/server/client'
2
+ import { setKek, CURRENT_KEY_ID, LEGACY_KEY_ID } from './encryption'
3
+
4
+ let loaded = false
5
+
6
+ /**
7
+ * Loads the platform credential KEK from Supabase Vault and registers it under
8
+ * `CURRENT_KEY_ID` ('platform-v1'). During the migration window, also registers
9
+ * the legacy `SECRETS_ENCRYPTION_KEY` env var under `LEGACY_KEY_ID` so existing
10
+ * ciphertext rows (no `keyId` field) can still be decrypted.
11
+ *
12
+ * Idempotent: subsequent calls are no-ops.
13
+ *
14
+ * Fails fast on missing / malformed Vault KEK so misconfigured deploys do not
15
+ * silently fall through to env-only encryption.
16
+ */
17
+ export async function loadCredentialKEKs(): Promise<void> {
18
+ if (loaded) return
19
+
20
+ const supabase = await getSupabaseClient()
21
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- RPC isn't in generated types yet (run `supabase gen types` post-merge)
22
+ const { data, error } = await (supabase.rpc as any)('get_platform_credential_kek')
23
+ if (error) {
24
+ throw new Error(
25
+ `Failed to load platform credential KEK from Vault: ${error.message}. ` +
26
+ `Did you run provision-credential-kek.sql against this environment?`
27
+ )
28
+ }
29
+ if (typeof data !== 'string' || data.length === 0) {
30
+ throw new Error('Vault returned null/empty platform credential KEK')
31
+ }
32
+ const vaultKek = Buffer.from(data, 'hex')
33
+ if (vaultKek.length !== 32) {
34
+ throw new Error(`Vault KEK is ${vaultKek.length} bytes, expected 32`)
35
+ }
36
+ setKek(CURRENT_KEY_ID, vaultKek)
37
+
38
+ const legacyHex = process.env.SECRETS_ENCRYPTION_KEY
39
+ if (legacyHex) {
40
+ const legacyKey = Buffer.from(legacyHex, 'hex')
41
+ if (legacyKey.length === 32) {
42
+ setKek(LEGACY_KEY_ID, legacyKey)
43
+ }
44
+ }
45
+
46
+ loaded = true
47
+ }
@@ -4,6 +4,9 @@ export * from './types'
4
4
  // Permission catalog (canonical PERMISSIONS constant + types)
5
5
  export * from './permissions'
6
6
 
7
+ // Role management schemas
8
+ export * from './role-management/index'
9
+
7
10
  // Organization types
8
11
  export * from './organizations/index'
9
12