@elevasis/core 0.10.0 → 0.11.1

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 (58) hide show
  1. package/dist/index.d.ts +69 -159
  2. package/dist/index.js +324 -613
  3. package/dist/organization-model/index.d.ts +69 -159
  4. package/dist/organization-model/index.js +324 -613
  5. package/dist/test-utils/index.d.ts +192 -45
  6. package/dist/test-utils/index.js +260 -600
  7. package/package.json +1 -1
  8. package/src/__tests__/template-core-compatibility.test.ts +73 -91
  9. package/src/_gen/__tests__/__snapshots__/contracts.md.snap +94 -182
  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 +102 -97
  16. package/src/organization-model/__tests__/defaults.test.ts +19 -6
  17. package/src/organization-model/__tests__/domains/resource-mappings.test.ts +24 -93
  18. package/src/organization-model/__tests__/graph.test.ts +82 -894
  19. package/src/organization-model/__tests__/resolve.test.ts +59 -690
  20. package/src/organization-model/__tests__/schema.test.ts +83 -407
  21. package/src/organization-model/contracts.ts +4 -3
  22. package/src/organization-model/defaults.ts +277 -141
  23. package/src/organization-model/domains/features.ts +31 -22
  24. package/src/organization-model/domains/navigation.ts +27 -20
  25. package/src/organization-model/foundation.ts +42 -54
  26. package/src/organization-model/graph/build.ts +42 -217
  27. package/src/organization-model/graph/index.ts +4 -4
  28. package/src/organization-model/graph/link.ts +10 -0
  29. package/src/organization-model/graph/schema.ts +21 -16
  30. package/src/organization-model/graph/types.ts +10 -10
  31. package/src/organization-model/helpers.ts +74 -0
  32. package/src/organization-model/index.ts +7 -7
  33. package/src/organization-model/organization-graph.mdx +89 -272
  34. package/src/organization-model/organization-model.mdx +152 -320
  35. package/src/organization-model/published.ts +20 -19
  36. package/src/organization-model/resolve.ts +8 -33
  37. package/src/organization-model/schema.ts +63 -205
  38. package/src/organization-model/types.ts +12 -11
  39. package/src/platform/constants/versions.ts +3 -3
  40. package/src/platform/registry/__tests__/command-view.test.ts +6 -5
  41. package/src/platform/registry/__tests__/resource-link.test.ts +30 -0
  42. package/src/platform/registry/__tests__/resource-registry.integration.test.ts +15 -15
  43. package/src/platform/registry/command-view.ts +10 -12
  44. package/src/platform/registry/index.ts +93 -93
  45. package/src/platform/registry/resource-link.ts +32 -0
  46. package/src/platform/registry/resource-registry.ts +917 -876
  47. package/src/platform/registry/serialization.ts +56 -73
  48. package/src/platform/registry/serialized-types.ts +17 -12
  49. package/src/platform/registry/types.ts +14 -43
  50. package/src/reference/_generated/contracts.md +94 -182
  51. package/src/reference/glossary.md +71 -105
  52. package/src/scaffold-registry/__tests__/index.test.ts +125 -1
  53. package/src/scaffold-registry/__tests__/schema.test.ts +48 -20
  54. package/src/scaffold-registry/index.ts +236 -188
  55. package/src/scaffold-registry/schema.ts +47 -22
  56. package/src/supabase/database.types.ts +2880 -2719
  57. package/src/test-utils/fixtures/memberships.ts +82 -80
  58. package/src/platform/registry/domains.ts +0 -165
@@ -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)