@elevasis/core 0.11.0 → 0.11.2

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 (32) hide show
  1. package/dist/index.d.ts +3 -2
  2. package/dist/index.js +8 -13
  3. package/dist/organization-model/index.d.ts +3 -2
  4. package/dist/organization-model/index.js +8 -13
  5. package/dist/test-utils/index.d.ts +175 -0
  6. package/dist/test-utils/index.js +8 -8
  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 +36 -38
  10. package/src/auth/multi-tenancy/index.ts +20 -17
  11. package/src/auth/multi-tenancy/memberships/api-schemas.ts +142 -126
  12. package/src/auth/multi-tenancy/memberships/index.ts +26 -22
  13. package/src/auth/multi-tenancy/permissions.test.ts +42 -0
  14. package/src/auth/multi-tenancy/permissions.ts +104 -0
  15. package/src/organization-model/README.md +1 -0
  16. package/src/organization-model/__tests__/defaults.test.ts +19 -6
  17. package/src/organization-model/contracts.ts +3 -3
  18. package/src/organization-model/defaults.ts +3 -8
  19. package/src/organization-model/domains/navigation.ts +26 -32
  20. package/src/organization-model/foundation.ts +2 -3
  21. package/src/organization-model/organization-model.mdx +3 -0
  22. package/src/organization-model/published.ts +5 -5
  23. package/src/platform/constants/versions.ts +3 -3
  24. package/src/platform/registry/index.ts +86 -91
  25. package/src/platform/registry/resource-registry.ts +905 -866
  26. package/src/reference/_generated/contracts.md +36 -38
  27. package/src/scaffold-registry/__tests__/index.test.ts +125 -1
  28. package/src/scaffold-registry/__tests__/schema.test.ts +48 -20
  29. package/src/scaffold-registry/index.ts +236 -188
  30. package/src/scaffold-registry/schema.ts +47 -22
  31. package/src/supabase/database.types.ts +2880 -2719
  32. package/src/test-utils/fixtures/memberships.ts +82 -80
@@ -198,6 +198,12 @@ export type FeatureIconComponent = ComponentType<{ size?: number;
198
198
  export type FeatureIconComponent = ComponentType<{ size?: number;
199
199
  ```
200
200
 
201
+ ### `FeatureSidebarWidthResolver`
202
+
203
+ ```typescript
204
+ export type FeatureSidebarWidthResolver = number | ((context: { currentPath: string }) => number)
205
+ ```
206
+
201
207
  ### `FeatureModule`
202
208
 
203
209
  ```typescript
@@ -212,6 +218,8 @@ export interface FeatureModule {
212
218
  icon?: FeatureIconComponent
213
219
  /** Sidebar component rendered when this feature's subtree route is active. */
214
220
  sidebar?: FeatureSidebarComponent
221
+ /** Optional shell sidebar width override. Defaults to 250px. */
222
+ sidebarWidth?: FeatureSidebarWidthResolver
215
223
  /** Operations-only bridge connecting this feature to the organization graph node. */
216
224
  organizationGraph?: OrganizationGraphFeatureBridge
217
225
  }
@@ -1007,44 +1015,34 @@ export interface HumanCheckpointDefinition extends ResourceDefinition {
1007
1015
  ### `DeploymentSpec`
1008
1016
 
1009
1017
  ```typescript
1010
- /** Supabase Storage path: "{orgId}/{deploymentId}/bundle.js" */
1011
- storagePath: string
1012
- /** Deployment record ID */
1013
- deploymentId: string
1014
- /** OS temp path to bundle -- set after first download, used by worker threads */
1015
- cachedTempPath?: string
1016
- /** Platform tool name -> credential name mapping */
1017
- toolCredentials?: Record<string, string>
1018
- /** SDK version used to deploy this bundle */
1019
- sdkVersion?: string
1020
- /** Deployment version (semver) of the deployed bundle */
1021
- deploymentVersion?: string
1022
- }
1023
-
1024
- /**
1025
- * Organization-specific resource collection
1026
- *
1027
- * Complete manifest of all automation resources for an organization.
1028
- * Used by ResourceRegistry for discovery and Command View for visualization.
1018
+ /** Always undefined for system resources; present for API compatibility with RemoteOrgConfig consumers */
1019
+ sdkVersion?: never
1020
+ }
1021
+
1022
+ /**
1023
+ * Organization-specific resource collection
1024
+ *
1025
+ * Complete manifest of all automation resources for an organization.
1026
+ * Used by ResourceRegistry for discovery and Command View for visualization.
1029
1027
  */
1030
- export interface DeploymentSpec {
1031
- /** Deployment version (semver) */
1032
- version: string
1033
- /** Workflow definitions */
1034
- workflows?: WorkflowDefinition[]
1035
- /** Agent definitions */
1036
- agents?: AgentDefinition[]
1037
-
1038
- // Resource Manifest fields (optional for backwards compatibility)
1039
- /** Trigger definitions - entry points that initiate executions */
1040
- triggers?: TriggerDefinition[]
1041
- /** Integration definitions - external service connections */
1042
- integrations?: IntegrationDefinition[]
1043
- /** Explicit relationship declarations between resources */
1044
- relationships?: ResourceRelationships
1045
- /** External automation resources (n8n, Make, Zapier, etc.) */
1046
- externalResources?: ExternalResourceDefinition[]
1047
- /** Human checkpoint definitions - human decision points in automation */
1048
- humanCheckpoints?: HumanCheckpointDefinition[]
1028
+ export interface DeploymentSpec {
1029
+ /** Deployment version (semver) */
1030
+ version: string
1031
+ /** Workflow definitions */
1032
+ workflows?: WorkflowDefinition[]
1033
+ /** Agent definitions */
1034
+ agents?: AgentDefinition[]
1035
+
1036
+ // Resource Manifest fields (optional for backwards compatibility)
1037
+ /** Trigger definitions - entry points that initiate executions */
1038
+ triggers?: TriggerDefinition[]
1039
+ /** Integration definitions - external service connections */
1040
+ integrations?: IntegrationDefinition[]
1041
+ /** Explicit relationship declarations between resources */
1042
+ relationships?: ResourceRelationships
1043
+ /** External automation resources (n8n, Make, Zapier, etc.) */
1044
+ externalResources?: ExternalResourceDefinition[]
1045
+ /** Human checkpoint definitions - human decision points in automation */
1046
+ humanCheckpoints?: HumanCheckpointDefinition[]
1049
1047
  }
1050
1048
  ```
@@ -1,5 +1,24 @@
1
1
  import { describe, expect, it } from 'vitest'
2
- import { loadScaffoldRegistryFast } from '../index'
2
+ import { findMatchingEntries, findMissingDependentPaths, loadScaffoldRegistryFast } from '../index'
3
+ import type { ScaffoldRegistry, ScaffoldRegistryEntry } from '../schema'
4
+
5
+ function makeEntry(id: string, sources: string[]): ScaffoldRegistryEntry {
6
+ return {
7
+ id,
8
+ kind: 'manual-scaffold',
9
+ owner: 'packages/core',
10
+ sources,
11
+ dependents: [{ path: 'packages/core/package.json', regen: 'manual', hint: 'h' }]
12
+ }
13
+ }
14
+
15
+ function registryFromEntries(entries: ScaffoldRegistryEntry[]): ScaffoldRegistry {
16
+ return { version: '1', entries }
17
+ }
18
+
19
+ function matchedIds(registry: ScaffoldRegistry, filePath: string): string[] {
20
+ return findMatchingEntries(registry, filePath).map((e) => e.id)
21
+ }
3
22
 
4
23
  describe('loadScaffoldRegistryFast', () => {
5
24
  it('preserves external sync metadata from the compiled registry', () => {
@@ -15,3 +34,108 @@ describe('loadScaffoldRegistryFast', () => {
15
34
  })
16
35
  })
17
36
  })
37
+
38
+ describe('findMissingDependentPaths', () => {
39
+ const baseEntry = {
40
+ id: 'fake-entry',
41
+ kind: 'manual-scaffold' as const,
42
+ owner: 'packages/core',
43
+ sources: ['packages/core/src/**'],
44
+ dependents: [{ path: 'packages/core/this-file-does-not-exist.ts', regen: 'manual', hint: 'h' }]
45
+ }
46
+
47
+ it('flags dependents whose paths are missing on disk', () => {
48
+ const registry: ScaffoldRegistry = { version: '1', entries: [baseEntry] }
49
+ const missing = findMissingDependentPaths(registry, process.cwd())
50
+ expect(missing).toEqual([{ entryId: 'fake-entry', path: 'packages/core/this-file-does-not-exist.ts' }])
51
+ })
52
+
53
+ it('skips symbolic targets, globs, and (self)', () => {
54
+ const registry: ScaffoldRegistry = {
55
+ version: '1',
56
+ entries: [
57
+ {
58
+ ...baseEntry,
59
+ dependents: [
60
+ { path: 'docs: sync-preservation-matrix', regen: 'manual', hint: 'h' },
61
+ { path: 'autogen-target:foo', regen: 'manual', hint: 'h' },
62
+ { path: 'packages/core/src/**/*.ts', regen: 'manual', hint: 'h' },
63
+ { path: '(self)', regen: 'manual', hint: 'h' }
64
+ ]
65
+ }
66
+ ]
67
+ }
68
+ expect(findMissingDependentPaths(registry, process.cwd())).toEqual([])
69
+ })
70
+
71
+ it('skips sync-preservation entries (their paths target derived external projects)', () => {
72
+ const registry: ScaffoldRegistry = {
73
+ version: '1',
74
+ entries: [
75
+ {
76
+ id: 'sync-entry',
77
+ kind: 'sync-preservation',
78
+ owner: '@elevasis/core',
79
+ category: 'merge',
80
+ strategy: 'merge-regions',
81
+ delete_policy: 'none',
82
+ sources: ['external/_template/foo.ts'],
83
+ dependents: [{ path: 'core/src/foo-not-in-monorepo.ts', regen: 'manual', hint: 'h' }]
84
+ }
85
+ ]
86
+ }
87
+ expect(findMissingDependentPaths(registry, process.cwd())).toEqual([])
88
+ })
89
+ })
90
+
91
+ describe('findMatchingEntries (pattern matching)', () => {
92
+ it('matches exact paths', () => {
93
+ const reg = registryFromEntries([makeEntry('exact', ['packages/core/src/index.ts'])])
94
+ expect(matchedIds(reg, 'packages/core/src/index.ts')).toEqual(['exact'])
95
+ expect(matchedIds(reg, 'packages/core/src/other.ts')).toEqual([])
96
+ })
97
+
98
+ it('matches /** suffix as deep prefix', () => {
99
+ const reg = registryFromEntries([makeEntry('deep', ['packages/core/src/**'])])
100
+ expect(matchedIds(reg, 'packages/core/src/foo.ts')).toEqual(['deep'])
101
+ expect(matchedIds(reg, 'packages/core/src/nested/deeply/foo.ts')).toEqual(['deep'])
102
+ expect(matchedIds(reg, 'packages/core/package.json')).toEqual([])
103
+ expect(matchedIds(reg, 'packages/core/src')).toEqual([])
104
+ })
105
+
106
+ it('matches /* suffix as single-level prefix', () => {
107
+ const reg = registryFromEntries([makeEntry('shallow', ['packages/*/package.json'])])
108
+ expect(matchedIds(reg, 'packages/core/package.json')).toEqual(['shallow'])
109
+ expect(matchedIds(reg, 'packages/ui/package.json')).toEqual(['shallow'])
110
+ expect(matchedIds(reg, 'packages/core/nested/package.json')).toEqual([])
111
+ })
112
+
113
+ it('current matcher does NOT support mixed **/*.ext patterns (Step 3 upgrade)', () => {
114
+ // The simple matcher's `/*` suffix branch only handles a single trailing
115
+ // wildcard. Patterns like `**/*.ts` fall through and effectively miss real
116
+ // files. Locked in to flag any change when full micromatch lands.
117
+ const reg = registryFromEntries([makeEntry('ext', ['apps/api/src/**/*.ts'])])
118
+ expect(matchedIds(reg, 'apps/api/src/foo.ts')).toEqual([])
119
+ })
120
+
121
+ it('normalizes Windows backslashes to forward slashes', () => {
122
+ const reg = registryFromEntries([makeEntry('winpath', ['packages/core/src/**'])])
123
+ expect(matchedIds(reg, 'packages\\core\\src\\foo.ts')).toEqual(['winpath'])
124
+ })
125
+
126
+ it('matches a directory-prefix pattern (no glob characters)', () => {
127
+ const reg = registryFromEntries([makeEntry('dir', ['packages/core/src'])])
128
+ expect(matchedIds(reg, 'packages/core/src/foo.ts')).toEqual(['dir'])
129
+ expect(matchedIds(reg, 'packages/core/srcfoo.ts')).toEqual([])
130
+ })
131
+
132
+ it('returns multiple entries when several match the same file', () => {
133
+ const reg = registryFromEntries([makeEntry('a', ['packages/core/src/**']), makeEntry('b', ['packages/core/src'])])
134
+ expect(matchedIds(reg, 'packages/core/src/foo.ts').sort()).toEqual(['a', 'b'])
135
+ })
136
+
137
+ it('returns empty when no entries match', () => {
138
+ const reg = registryFromEntries([makeEntry('a', ['packages/core/src/**'])])
139
+ expect(matchedIds(reg, 'apps/api/src/foo.ts')).toEqual([])
140
+ })
141
+ })
@@ -18,12 +18,9 @@ describe('ScaffoldEntryKindSchema', () => {
18
18
  const validKinds = [
19
19
  'autogen',
20
20
  'manual-scaffold',
21
- 'typed-id',
22
- 'sync-preservation',
23
- 'vibe-gated',
24
- 'sdk-cli-generator',
25
- 'validator',
26
- 'other'
21
+ 'sync-preservation',
22
+ 'validator',
23
+ 'other'
27
24
  ]
28
25
  for (const kind of validKinds) {
29
26
  expect(ScaffoldEntryKindSchema.safeParse(kind).success).toBe(true)
@@ -91,9 +88,8 @@ const validEntry = {
91
88
  dependents: [
92
89
  {
93
90
  path: '.navigation/_generated/skeleton.md',
94
- regen: 'pnpm navigation:generate',
95
- kind: 'autogen',
96
- hint: 'Regenerate after rename.'
91
+ regen: 'pnpm navigation:generate',
92
+ hint: 'Regenerate after rename.'
97
93
  }
98
94
  ]
99
95
  }
@@ -176,10 +172,24 @@ describe('ScaffoldRegistryEntrySchema — valid entry', () => {
176
172
  expect(ScaffoldRegistryEntrySchema.safeParse(withBadAutoRegen).success).toBe(false)
177
173
  })
178
174
 
179
- it('parses all six seed-entry kinds', () => {
180
- const kinds = ['autogen', 'manual-scaffold', 'typed-id', 'sync-preservation', 'vibe-gated', 'validator']
181
- for (const kind of kinds) {
182
- const result = ScaffoldRegistryEntrySchema.safeParse({ ...validEntry, kind })
175
+ it('parses all seed-entry kinds with required invariants', () => {
176
+ const entries = [
177
+ { ...validEntry, kind: 'autogen', regen: 'pnpm navigation:generate' },
178
+ { ...validEntry, kind: 'manual-scaffold' },
179
+ {
180
+ ...validEntry,
181
+ kind: 'sync-preservation',
182
+ owner: 'template',
183
+ category: 'replace',
184
+ strategy: 'replace-all',
185
+ delete_policy: 'none'
186
+ },
187
+ { ...validEntry, kind: 'validator', dependents: [{ path: '(self)', regen: 'pnpm meta:verify' }] },
188
+ { ...validEntry, kind: 'other', notes: 'Vendor generated.' }
189
+ ]
190
+ for (const entry of entries) {
191
+ const result = ScaffoldRegistryEntrySchema.safeParse(entry)
192
+ const kind = entry.kind
183
193
  expect(result.success, `kind ${kind} should parse`).toBe(true)
184
194
  }
185
195
  })
@@ -201,6 +211,21 @@ describe('ScaffoldRegistryEntrySchema — valid entry', () => {
201
211
  }
202
212
  })
203
213
 
214
+ it('rejects autogen entries without top-level regen', () => {
215
+ const result = ScaffoldRegistryEntrySchema.safeParse({ ...validEntry, kind: 'autogen' })
216
+ expect(result.success).toBe(false)
217
+ })
218
+
219
+ it('rejects other entries without notes', () => {
220
+ const result = ScaffoldRegistryEntrySchema.safeParse({ ...validEntry, kind: 'other' })
221
+ expect(result.success).toBe(false)
222
+ })
223
+
224
+ it('rejects sync-preservation entries without external sync metadata', () => {
225
+ const result = ScaffoldRegistryEntrySchema.safeParse({ ...validEntry, kind: 'sync-preservation' })
226
+ expect(result.success).toBe(false)
227
+ })
228
+
204
229
  it('keeps legacy free-form owner values valid when external sync fields are omitted', () => {
205
230
  const result = ScaffoldRegistryEntrySchema.safeParse({
206
231
  ...validEntry,
@@ -361,13 +386,16 @@ describe('ScaffoldRegistrySchema', () => {
361
386
  sources: ['apps/docs/content/docs/**/meta.json'],
362
387
  dependents: [{ path: '(self)', regen: 'pnpm check-docs-meta' }]
363
388
  },
364
- {
365
- id: 'external-template-hook-added',
366
- kind: 'sync-preservation',
367
- owner: 'external/_template',
368
- sources: ['external/_template/.claude/hooks/*.mjs'],
369
- dependents: [{ path: 'external/_template/.claude/settings.json', regen: 'manual' }]
370
- }
389
+ {
390
+ id: 'external-template-hook-added',
391
+ kind: 'sync-preservation',
392
+ owner: 'template',
393
+ category: 'replace',
394
+ strategy: 'replace-all',
395
+ delete_policy: 'none',
396
+ sources: ['external/_template/.claude/hooks/*.mjs'],
397
+ dependents: [{ path: 'external/_template/.claude/settings.json', regen: 'manual' }]
398
+ }
371
399
  ]
372
400
  }
373
401
  const result = ScaffoldRegistrySchema.safeParse(multiEntry)