@astrale-os/cli 1.0.0-beta.26 → 1.0.0-beta.28
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/README.md +31 -5
- package/dist/astrale.js +1201 -358
- package/package.json +3 -2
- package/src/commands/__tests__/domain-install-operation.test.ts +23 -0
- package/src/commands/__tests__/install-identity-override.test.ts +3 -3
- package/src/commands/domain/install.ts +19 -11
- package/src/commands/get.ts +1 -1
- package/src/commands/identity/register.ts +3 -4
- package/src/commands/logs.ts +2 -5
- package/src/commands/session/analyze.ts +26 -4
- package/src/commands/update.ts +61 -40
- package/src/lib/__tests__/skills.test.ts +809 -0
- package/src/lib/skills/lock.ts +117 -0
- package/src/lib/skills/sync.ts +814 -0
- package/src/lib/skills.ts +1 -52
- package/src/program/__tests__/program.test.ts +1 -1
- package/src/setup/steps/skills.ts +40 -38
- package/src/telemetry/__tests__/retention.test.ts +180 -0
- package/src/telemetry/__tests__/settings.test.ts +102 -0
- package/src/telemetry/__tests__/store-scan.test.ts +128 -0
- package/src/telemetry/__tests__/trigger.test.ts +33 -4
- package/src/telemetry/recorder.ts +6 -3
- package/src/telemetry/retention.ts +129 -0
- package/src/telemetry/session.ts +18 -12
- package/src/telemetry/settings.ts +64 -11
- package/src/telemetry/store.ts +87 -9
- package/src/telemetry/trigger.ts +16 -28
- package/studio/client/dist/assets/index-BFVFs_8x.js +81 -0
- package/studio/client/dist/assets/index-DuB9iUdu.css +2 -0
- package/studio/client/dist/assets/schema-studio--a0kX3QU.css +1 -0
- package/studio/client/dist/assets/schema-studio-DH6oEWME.js +8 -0
- package/studio/client/dist/index.html +3 -3
- package/studio/server/agent/prompts/anchors.test.ts +17 -4
- package/studio/server/agent/prompts/anchors.ts +3 -2
- package/studio/server/agent/prompts/system.test.ts +2 -3
- package/studio/server/agent/prompts/system.ts +3 -3
- package/studio/server/agent/run/preparation.ts +1 -1
- package/studio/server/api/context.ts +1 -1
- package/studio/server/api/deployment.ts +1 -1
- package/studio/server/api/views.ts +2 -2
- package/studio/server/api/workspace.ts +1 -1
- package/studio/server/cache.test.ts +3 -8
- package/studio/server/cache.ts +4 -45
- package/studio/server/cli-consumers.test.ts +31 -2
- package/studio/server/handoff/copy.ts +1 -1
- package/studio/server/introspect/canonical-schema.test.ts +154 -128
- package/studio/server/introspect/canonical-schema.ts +183 -302
- package/studio/server/introspect/core.ts +14 -21
- package/studio/server/introspect/extractor.ts +11 -15
- package/studio/server/introspect/overlay-tsmorph.test.ts +1 -5
- package/studio/server/introspect/overlay-tsmorph.ts +0 -1
- package/studio/server/introspect/overlay.ts +3 -24
- package/studio/server/introspect/revision.test.ts +24 -28
- package/studio/server/introspect/revision.ts +10 -21
- package/studio/server/introspect/runtime.test.ts +14 -14
- package/studio/server/introspect/runtime.ts +1 -46
- package/studio/server/lifecycle.ts +4 -1
- package/studio/server/state/documents.test.ts +69 -0
- package/studio/server/state/documents.ts +57 -10
- package/studio/server/workspace/updates.ts +23 -1
- package/studio/shared/contracts/runtime.ts +4 -0
- package/studio/shared/contracts/schema.ts +10 -26
- package/studio/shared/contracts/surface.test.ts +2 -2
- package/studio/shared/contracts/workspace.ts +3 -0
- package/studio/shared/schema/identity.ts +0 -1
- package/tsconfig.json +1 -1
- package/studio/client/dist/assets/index-C0FAnwNw.css +0 -2
- package/studio/client/dist/assets/index-OeJK1TjG.js +0 -81
- package/studio/client/dist/assets/schema-studio-BilUNb6Z.css +0 -1
- package/studio/client/dist/assets/schema-studio-DRdfu_cQ.js +0 -8
- package/studio/server/introspect/core-extractor.ts +0 -30
- package/studio/server/introspect/overlay.test.ts +0 -44
- package/studio/server/introspect/source-overlay/annotations.ts +0 -14
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* Project the DSL's portable Schema into Studio's deliberately smaller render
|
|
3
|
+
* model. Admission, dependency reachability, and reference resolution remain
|
|
4
|
+
* owned by the Domain's installed DSL.
|
|
5
5
|
*/
|
|
6
6
|
import type {
|
|
7
7
|
IrCallableAuth,
|
|
@@ -17,37 +17,78 @@ import type {
|
|
|
17
17
|
IrView,
|
|
18
18
|
JsonSchema,
|
|
19
19
|
SchemaIR,
|
|
20
|
+
SchemaRevision,
|
|
20
21
|
StudioCore,
|
|
21
22
|
} from '../../shared/types'
|
|
22
23
|
|
|
23
|
-
import { classRefKey, isIrClassRef, schemaRefKey } from '../../shared/schema/identity'
|
|
24
24
|
import { isSchemaRevision } from '../../shared/types'
|
|
25
25
|
|
|
26
26
|
type AnyRecord = Record<string, unknown>
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
/** The portable V1 value transported across the extractor process boundary. */
|
|
29
|
+
export interface CanonicalDomainSchemaV1 {
|
|
29
30
|
readonly format: 'astrale.dsl'
|
|
30
31
|
readonly version: 'v1'
|
|
31
32
|
readonly origin: string
|
|
33
|
+
readonly dependencies: Readonly<
|
|
34
|
+
Record<string, { readonly origin: string; readonly revision: string }>
|
|
35
|
+
>
|
|
36
|
+
readonly classes: Readonly<Record<string, unknown>>
|
|
37
|
+
readonly functions: Readonly<Record<string, unknown>>
|
|
38
|
+
readonly policies: Readonly<Record<string, unknown>>
|
|
39
|
+
readonly views: Readonly<Record<string, unknown>>
|
|
40
|
+
readonly core: unknown
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
interface ResolvedSchemaDomain {
|
|
44
|
+
readonly source: CanonicalDomainSchemaV1
|
|
45
|
+
readonly revision: unknown
|
|
46
|
+
definition(ref: IrClassRef): { readonly definition: unknown } | undefined
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Runtime facade supplied by the inspected Domain, not by Studio's package. */
|
|
50
|
+
export interface SchemaSdk {
|
|
51
|
+
readonly bundle: {
|
|
52
|
+
create(input: any): unknown
|
|
53
|
+
accept(input: any): {
|
|
54
|
+
readonly root: CanonicalDomainSchemaV1
|
|
55
|
+
readonly closure: readonly CanonicalDomainSchemaV1[]
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
readonly schema: {
|
|
59
|
+
resolve(input: any): ResolvedSchemaDomain
|
|
60
|
+
compareDependencyMeaning(
|
|
61
|
+
source: CanonicalDomainSchemaV1,
|
|
62
|
+
target: CanonicalDomainSchemaV1,
|
|
63
|
+
): { readonly footprint: readonly unknown[] }
|
|
64
|
+
}
|
|
65
|
+
readonly ClassKey: {
|
|
66
|
+
is(input: unknown): input is IrClassKey
|
|
67
|
+
ref(input: IrClassKey): IrClassRef
|
|
68
|
+
}
|
|
32
69
|
}
|
|
33
70
|
|
|
34
|
-
|
|
71
|
+
/** Smaller facade used when re-admitting an installed Bundle for drift checks. */
|
|
72
|
+
export interface SchemaAdmissionSdk {
|
|
73
|
+
readonly bundle: {
|
|
74
|
+
accept(input: any): { readonly root: CanonicalDomainSchemaV1 }
|
|
75
|
+
}
|
|
76
|
+
readonly schema: {
|
|
77
|
+
resolve(input: any): { readonly source: unknown; readonly revision: unknown }
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export interface CanonicalSchemaExtraction {
|
|
82
|
+
readonly status: 'admitted' | 'preview'
|
|
83
|
+
readonly root: CanonicalDomainSchemaV1
|
|
84
|
+
readonly revision: SchemaRevision | null
|
|
35
85
|
readonly ir: SchemaIR
|
|
36
86
|
}
|
|
37
87
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
readonly closure: CanonicalDomainSchemaV1[]
|
|
43
|
-
readonly revision: `sha256:${string}`
|
|
44
|
-
}
|
|
45
|
-
| {
|
|
46
|
-
readonly status: 'preview'
|
|
47
|
-
readonly root: CanonicalDomainSchemaV1
|
|
48
|
-
readonly closure: CanonicalDomainSchemaV1[]
|
|
49
|
-
readonly revision: null
|
|
50
|
-
}
|
|
88
|
+
interface ImportProjection {
|
|
89
|
+
importsByKey: Record<IrClassKey, IrImportDescriptor>
|
|
90
|
+
importedClassesByKey: Record<IrClassKey, IrClass>
|
|
91
|
+
}
|
|
51
92
|
|
|
52
93
|
export function isCanonicalDomainSchemaV1(value: unknown): value is CanonicalDomainSchemaV1 {
|
|
53
94
|
const candidate = asRecord(value)
|
|
@@ -74,134 +115,32 @@ export function findCanonicalDomainSchemaExport(
|
|
|
74
115
|
)
|
|
75
116
|
}
|
|
76
117
|
|
|
77
|
-
/**
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
try {
|
|
85
|
-
const candidate = asRecord(Reflect.apply(create, undefined, [root]))
|
|
86
|
-
if (candidate?.root !== root) return []
|
|
87
|
-
return admitClosure(candidate.closure, root) ?? []
|
|
88
|
-
} catch {
|
|
89
|
-
return []
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/** Re-admit one portable root and prove its revision through the authored SDK. */
|
|
94
|
-
export function admitCanonicalSchemaFromSdk(
|
|
95
|
-
sdkModule: Record<string, unknown>,
|
|
118
|
+
/**
|
|
119
|
+
* Re-admit the authored Schema as one exact DSL Bundle and resolve its Domain.
|
|
120
|
+
* A V1-shaped value rejected by the installed DSL remains a best-effort local
|
|
121
|
+
* preview while the author is in the middle of an edit.
|
|
122
|
+
*/
|
|
123
|
+
export function extractCanonicalSchemaFromSdk(
|
|
124
|
+
sdk: SchemaSdk,
|
|
96
125
|
candidate: CanonicalDomainSchemaV1,
|
|
97
|
-
):
|
|
98
|
-
const retainedClosure = closureFromSdk(sdkModule, candidate)
|
|
99
|
-
const schemaApi = asRecord(sdkModule.schema)
|
|
100
|
-
const accept = schemaApi?.accept
|
|
101
|
-
const revision = schemaApi?.revision
|
|
102
|
-
const resolve = schemaApi?.resolve
|
|
103
|
-
if (
|
|
104
|
-
typeof accept !== 'function' ||
|
|
105
|
-
typeof revision !== 'function' ||
|
|
106
|
-
typeof resolve !== 'function'
|
|
107
|
-
) {
|
|
108
|
-
return { status: 'preview', root: candidate, closure: retainedClosure, revision: null }
|
|
109
|
-
}
|
|
110
|
-
|
|
126
|
+
): CanonicalSchemaExtraction {
|
|
111
127
|
try {
|
|
112
|
-
const
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
if (!isSchemaRevision(value)) throw new TypeError('SDK returned an invalid revision.')
|
|
116
|
-
revisions.set(dependency.origin, value)
|
|
128
|
+
const admitted = sdk.bundle.accept(sdk.bundle.create(candidate))
|
|
129
|
+
if (admitted.root.origin !== candidate.origin) {
|
|
130
|
+
throw new TypeError('DSL Bundle admission returned another Schema root.')
|
|
117
131
|
}
|
|
118
|
-
const
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
return dependency && revisions.get(origin) === expected ? dependency : undefined
|
|
122
|
-
},
|
|
132
|
+
const domain = sdk.schema.resolve(admitted.root)
|
|
133
|
+
if (domain.source !== admitted.root || !isSchemaRevision(domain.revision)) {
|
|
134
|
+
throw new TypeError('DSL resolution did not retain the admitted Schema root.')
|
|
123
135
|
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
if (!isSchemaRevision(acceptedRevision))
|
|
130
|
-
throw new TypeError('SDK returned an invalid revision.')
|
|
131
|
-
const domain = asRecord(Reflect.apply(resolve, schemaApi, [accepted]))
|
|
132
|
-
if (domain?.source !== accepted || domain.origin !== accepted.origin) {
|
|
133
|
-
throw new TypeError('SDK resolution did not retain the admitted root.')
|
|
136
|
+
return {
|
|
137
|
+
status: 'admitted',
|
|
138
|
+
root: admitted.root,
|
|
139
|
+
revision: domain.revision,
|
|
140
|
+
ir: projectSchema(admitted.root, projectImports(sdk, domain, admitted.closure)),
|
|
134
141
|
}
|
|
135
|
-
const closure = closureFromSdk(sdkModule, accepted)
|
|
136
|
-
return { status: 'admitted', root: accepted, closure, revision: acceptedRevision }
|
|
137
142
|
} catch {
|
|
138
|
-
return { status: 'preview', root: candidate,
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
export function projectCanonicalSchema(
|
|
143
|
-
root: CanonicalDomainSchemaV1,
|
|
144
|
-
closure: readonly CanonicalDomainSchemaV1[] = [],
|
|
145
|
-
): CanonicalSchemaProjection {
|
|
146
|
-
const origin = root.origin
|
|
147
|
-
const classes = Object.fromEntries(
|
|
148
|
-
entriesOf(root.classes).map(([name, declaration]) => [
|
|
149
|
-
name,
|
|
150
|
-
projectClass(origin, name, declaration),
|
|
151
|
-
]),
|
|
152
|
-
)
|
|
153
|
-
const functions = Object.fromEntries(
|
|
154
|
-
entriesOf(root.functions).map(([name, declaration]) => [
|
|
155
|
-
name,
|
|
156
|
-
projectFunction(name, declaration),
|
|
157
|
-
]),
|
|
158
|
-
)
|
|
159
|
-
const views = Object.fromEntries(
|
|
160
|
-
entriesOf(root.views).map(([name, declaration]) => [name, projectView(name, declaration)]),
|
|
161
|
-
)
|
|
162
|
-
const schemas = new Map(
|
|
163
|
-
closure.filter(isCanonicalDomainSchemaV1).map((value) => [value.origin, value]),
|
|
164
|
-
)
|
|
165
|
-
const importsByKey = {} as Record<IrClassKey, IrImportDescriptor>
|
|
166
|
-
const importedClassesByKey = {} as Record<IrClassKey, IrClass>
|
|
167
|
-
const pending = collectClassRefs(root)
|
|
168
|
-
const visited = new Set<string>()
|
|
169
|
-
|
|
170
|
-
for (let index = 0; index < pending.length; index += 1) {
|
|
171
|
-
const ref = pending[index]
|
|
172
|
-
if (ref.origin === origin) continue
|
|
173
|
-
const key = classRefKey(ref)
|
|
174
|
-
if (visited.has(key)) continue
|
|
175
|
-
visited.add(key)
|
|
176
|
-
importsByKey[key] = { origin: ref.origin, ref, key }
|
|
177
|
-
const owner = schemas.get(ref.origin)
|
|
178
|
-
const declaration = asRecord(asRecord(owner?.classes)?.[ref.name])
|
|
179
|
-
if (owner === undefined || declaration === null) continue
|
|
180
|
-
importedClassesByKey[key] = projectClass(owner.origin, ref.name, declaration)
|
|
181
|
-
pending.push(...collectClassRefsFromClass(declaration))
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
const dependencies = arrayOf(root.dependencies).flatMap((value) => {
|
|
185
|
-
const dependency = asRecord(value)
|
|
186
|
-
return typeof dependency?.origin === 'string' && typeof dependency.revision === 'string'
|
|
187
|
-
? [{ origin: dependency.origin, revision: dependency.revision }]
|
|
188
|
-
: []
|
|
189
|
-
})
|
|
190
|
-
|
|
191
|
-
return {
|
|
192
|
-
ir: {
|
|
193
|
-
format: 'astrale.dsl',
|
|
194
|
-
version: 'v1',
|
|
195
|
-
domain: origin,
|
|
196
|
-
classes,
|
|
197
|
-
importsByKey,
|
|
198
|
-
importedClassesByKey,
|
|
199
|
-
functions,
|
|
200
|
-
views,
|
|
201
|
-
policies: recordCopy(root.policies),
|
|
202
|
-
dependencies,
|
|
203
|
-
core: jsonCopy(root.core),
|
|
204
|
-
},
|
|
143
|
+
return { status: 'preview', root: candidate, revision: null, ir: projectSchema(candidate) }
|
|
205
144
|
}
|
|
206
145
|
}
|
|
207
146
|
|
|
@@ -212,10 +151,9 @@ export function projectCanonicalCore(
|
|
|
212
151
|
const core = asRecord(root.core) ?? {}
|
|
213
152
|
const nodes = entriesOf(core.nodes).map(([slug, raw]) => {
|
|
214
153
|
const node = asRecord(raw) ?? {}
|
|
215
|
-
const selectedClass = classRef(node.class)
|
|
216
154
|
return {
|
|
217
155
|
path: corePath({ origin: root.origin, name: slug }),
|
|
218
|
-
className:
|
|
156
|
+
className: classRef(node.class)?.name ?? '?',
|
|
219
157
|
data: recordCopy(node.properties),
|
|
220
158
|
}
|
|
221
159
|
})
|
|
@@ -231,20 +169,64 @@ export function projectCanonicalCore(
|
|
|
231
169
|
return { domain: root.origin, nodes, edges }
|
|
232
170
|
}
|
|
233
171
|
|
|
172
|
+
function projectSchema(root: CanonicalDomainSchemaV1, imports?: ImportProjection): SchemaIR {
|
|
173
|
+
return {
|
|
174
|
+
format: 'astrale.dsl',
|
|
175
|
+
version: 'v1',
|
|
176
|
+
domain: root.origin,
|
|
177
|
+
classes: Object.fromEntries(
|
|
178
|
+
entriesOf(root.classes).map(([name, value]) => [
|
|
179
|
+
name,
|
|
180
|
+
projectClass(root.origin, name, value),
|
|
181
|
+
]),
|
|
182
|
+
),
|
|
183
|
+
importsByKey: imports?.importsByKey ?? {},
|
|
184
|
+
importedClassesByKey: imports?.importedClassesByKey ?? {},
|
|
185
|
+
functions: Object.fromEntries(
|
|
186
|
+
entriesOf(root.functions).map(([name, value]) => [name, projectFunction(name, value)]),
|
|
187
|
+
),
|
|
188
|
+
views: Object.fromEntries(
|
|
189
|
+
entriesOf(root.views).map(([name, value]) => [name, projectView(name, value)]),
|
|
190
|
+
),
|
|
191
|
+
policies: recordCopy(root.policies),
|
|
192
|
+
dependencies: Object.values(root.dependencies ?? {}).map(({ origin, revision }) => ({
|
|
193
|
+
origin,
|
|
194
|
+
revision,
|
|
195
|
+
})),
|
|
196
|
+
core: jsonCopy(root.core),
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function projectImports(
|
|
201
|
+
sdk: SchemaSdk,
|
|
202
|
+
domain: ReturnType<SchemaSdk['schema']['resolve']>,
|
|
203
|
+
closure: readonly CanonicalDomainSchemaV1[],
|
|
204
|
+
): ImportProjection {
|
|
205
|
+
const importsByKey = {} as Record<IrClassKey, IrImportDescriptor>
|
|
206
|
+
const importedClassesByKey = {} as Record<IrClassKey, IrClass>
|
|
207
|
+
|
|
208
|
+
// The DSL computes the exact reachable footprint. Studio no longer recurses
|
|
209
|
+
// through JSON Schemas, policies, Views, and Core declarations to rediscover it.
|
|
210
|
+
for (const dependency of closure) {
|
|
211
|
+
const footprint = sdk.schema.compareDependencyMeaning(domain.source, dependency).footprint
|
|
212
|
+
for (const candidate of footprint) {
|
|
213
|
+
if (!sdk.ClassKey.is(candidate)) continue
|
|
214
|
+
const ref = sdk.ClassKey.ref(candidate)
|
|
215
|
+
const definition = domain.definition(ref)
|
|
216
|
+
if (!definition) continue
|
|
217
|
+
const key = candidate as IrClassKey
|
|
218
|
+
importsByKey[key] = { origin: ref.origin, ref, key }
|
|
219
|
+
importedClassesByKey[key] = projectClass(ref.origin, ref.name, definition.definition)
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
return { importsByKey, importedClassesByKey }
|
|
223
|
+
}
|
|
224
|
+
|
|
234
225
|
function projectClass(origin: string, name: string, value: unknown): IrClass {
|
|
235
226
|
const declaration = asRecord(value) ?? {}
|
|
236
227
|
const kind = declaration.kind === 'edge' ? 'edge' : 'node'
|
|
237
228
|
const extendsRefs = refsOf(declaration.extends)
|
|
238
229
|
const propertyEntries = entriesOf(declaration.properties)
|
|
239
|
-
const properties = Object.fromEntries(
|
|
240
|
-
propertyEntries.map(([propertyName, raw]) => [
|
|
241
|
-
propertyName,
|
|
242
|
-
studioSchema(asRecord(raw)?.schema),
|
|
243
|
-
]),
|
|
244
|
-
)
|
|
245
|
-
const required = propertyEntries.flatMap(([propertyName, raw]) =>
|
|
246
|
-
asRecord(raw)?.required === true ? [propertyName] : [],
|
|
247
|
-
)
|
|
248
230
|
const propertyMetadata = Object.fromEntries(
|
|
249
231
|
propertyEntries.map(([propertyName, raw]) => {
|
|
250
232
|
const member = { ...(asRecord(raw) ?? {}) }
|
|
@@ -265,52 +247,54 @@ function projectClass(origin: string, name: string, value: unknown): IrClass {
|
|
|
265
247
|
ref: { origin, kind: 'class', name },
|
|
266
248
|
extends: extendsRefs.map((ref) => ref.name),
|
|
267
249
|
extendsRefs,
|
|
268
|
-
properties
|
|
269
|
-
|
|
270
|
-
|
|
250
|
+
properties: Object.fromEntries(
|
|
251
|
+
propertyEntries.map(([propertyName, raw]) => [
|
|
252
|
+
propertyName,
|
|
253
|
+
studioSchema(asRecord(raw)?.schema),
|
|
254
|
+
]),
|
|
255
|
+
),
|
|
256
|
+
required: propertyEntries.flatMap(([propertyName, raw]) =>
|
|
257
|
+
asRecord(raw)?.required === true ? [propertyName] : [],
|
|
258
|
+
),
|
|
259
|
+
methods: Object.fromEntries(
|
|
260
|
+
entriesOf(declaration.methods).map(([methodName, raw]) => [
|
|
261
|
+
methodName,
|
|
262
|
+
projectMethod(methodName, raw),
|
|
263
|
+
]),
|
|
264
|
+
),
|
|
271
265
|
...(kind === 'edge' ? edgeFields(declaration) : {}),
|
|
266
|
+
...(typeof declaration.icon === 'string' ? { icon: declaration.icon } : {}),
|
|
272
267
|
...(typeof declaration.description === 'string'
|
|
273
268
|
? { description: declaration.description }
|
|
274
269
|
: {}),
|
|
270
|
+
// the SDK lets a Class carry its own glyph; without this the schema declared one
|
|
271
|
+
// and every surface still drew the generic box
|
|
272
|
+
...(typeof declaration.icon === 'string' ? { icon: declaration.icon } : {}),
|
|
275
273
|
...(Object.keys(propertyMetadata).length === 0 ? {} : { propertyMetadata }),
|
|
276
274
|
...(dataDeclaration(declaration.data) ? { data: dataDeclaration(declaration.data) } : {}),
|
|
277
275
|
...(Object.keys(policies).length === 0 ? {} : { policies }),
|
|
278
276
|
}
|
|
279
277
|
}
|
|
280
278
|
|
|
281
|
-
function
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
{
|
|
292
|
-
...projectCallable(name, declaration),
|
|
293
|
-
static: declaration.static === true,
|
|
294
|
-
inheritance,
|
|
295
|
-
} satisfies IrMethod,
|
|
296
|
-
]
|
|
297
|
-
}),
|
|
298
|
-
)
|
|
279
|
+
function projectMethod(name: string, value: unknown): IrMethod {
|
|
280
|
+
const declaration = asRecord(value) ?? {}
|
|
281
|
+
return {
|
|
282
|
+
...projectFunction(name, declaration),
|
|
283
|
+
static: declaration.static === true,
|
|
284
|
+
inheritance:
|
|
285
|
+
declaration.inheritance === 'abstract' || declaration.inheritance === 'sealed'
|
|
286
|
+
? declaration.inheritance
|
|
287
|
+
: 'default',
|
|
288
|
+
}
|
|
299
289
|
}
|
|
300
290
|
|
|
301
291
|
function projectFunction(name: string, value: unknown): IrFunction {
|
|
302
|
-
return projectCallable(name, value)
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
function projectCallable(name: string, value: unknown): IrFunction {
|
|
306
292
|
const declaration = asRecord(value) ?? {}
|
|
307
|
-
const input = studioSchema(declaration.input)
|
|
308
|
-
const output = callableOutput(declaration.output)
|
|
309
293
|
const auth = callableAuth(declaration.auth)
|
|
310
294
|
return {
|
|
311
295
|
name,
|
|
312
|
-
input,
|
|
313
|
-
output,
|
|
296
|
+
input: studioSchema(declaration.input),
|
|
297
|
+
output: callableOutput(declaration.output),
|
|
314
298
|
...(typeof declaration.description === 'string'
|
|
315
299
|
? { description: declaration.description }
|
|
316
300
|
: {}),
|
|
@@ -363,99 +347,35 @@ function projectEndpoint(
|
|
|
363
347
|
): IrEndpoint {
|
|
364
348
|
const endpoint = asRecord(value) ?? {}
|
|
365
349
|
const refs = refsOf(endpoint.accepts)
|
|
366
|
-
const count =
|
|
350
|
+
const count = endpoint[cardinality]
|
|
367
351
|
return {
|
|
368
352
|
name: typeof endpoint.role === 'string' ? endpoint.role : '',
|
|
369
353
|
types: refs.map((ref) => ref.name),
|
|
370
354
|
refs,
|
|
371
|
-
...(count ===
|
|
355
|
+
...(count === '0..*' || count === '1..*' || count === '0..1' || count === '1'
|
|
356
|
+
? { cardinality: edgeCount(count) }
|
|
357
|
+
: {}),
|
|
372
358
|
}
|
|
373
359
|
}
|
|
374
360
|
|
|
375
|
-
function edgeCount(value:
|
|
361
|
+
function edgeCount(value: '0..*' | '1..*' | '0..1' | '1'): IrEndpoint['cardinality'] {
|
|
376
362
|
if (value === '0..*') return { min: 0, max: null }
|
|
377
363
|
if (value === '1..*') return { min: 1, max: null }
|
|
378
364
|
if (value === '0..1') return { min: 0, max: 1 }
|
|
379
|
-
|
|
380
|
-
return undefined
|
|
365
|
+
return { min: 1, max: 1 }
|
|
381
366
|
}
|
|
382
367
|
|
|
383
|
-
function
|
|
384
|
-
const
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
}
|
|
388
|
-
for (const [, callable] of entriesOf(root.functions)) collectCallableRefs(callable, refs)
|
|
389
|
-
for (const [, view] of entriesOf(root.views)) {
|
|
390
|
-
refs.push(...refsOf(asRecord(asRecord(view)?.target)?.definitions))
|
|
391
|
-
}
|
|
392
|
-
const core = asRecord(root.core)
|
|
393
|
-
for (const [, node] of entriesOf(core?.nodes)) addClassRef(asRecord(node)?.class, refs)
|
|
394
|
-
for (const edge of arrayOf(core?.edges)) {
|
|
395
|
-
const declaration = asRecord(edge)
|
|
396
|
-
addClassRef(declaration?.class, refs)
|
|
397
|
-
}
|
|
398
|
-
for (const [, policy] of entriesOf(root.policies)) collectPolicyRefs(policy, refs)
|
|
399
|
-
return uniqueClassRefs(refs)
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
function collectClassRefsFromClass(value: unknown): IrClassRef[] {
|
|
403
|
-
const declaration = asRecord(value) ?? {}
|
|
404
|
-
const refs = [...refsOf(declaration.extends)]
|
|
405
|
-
const endpoints = asRecord(declaration.endpoints)
|
|
406
|
-
for (const endpoint of [
|
|
407
|
-
endpoints?.source,
|
|
408
|
-
endpoints?.target,
|
|
409
|
-
...arrayOf(declaration.endpoints),
|
|
410
|
-
]) {
|
|
411
|
-
refs.push(...refsOf(asRecord(endpoint)?.accepts))
|
|
412
|
-
}
|
|
413
|
-
for (const [, property] of entriesOf(declaration.properties)) {
|
|
414
|
-
collectPathSchemaRefs(asRecord(property)?.schema, refs)
|
|
415
|
-
}
|
|
416
|
-
for (const [, callable] of entriesOf(declaration.methods)) collectCallableRefs(callable, refs)
|
|
417
|
-
for (const [, policy] of entriesOf(declaration.policies)) collectPolicyRefs(policy, refs)
|
|
418
|
-
return uniqueClassRefs(refs)
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
function collectCallableRefs(value: unknown, refs: IrClassRef[]): void {
|
|
422
|
-
const callable = asRecord(value)
|
|
423
|
-
if (callable === null) return
|
|
424
|
-
collectPathSchemaRefs(callable.input, refs)
|
|
425
|
-
const output = asRecord(callable.output)
|
|
426
|
-
collectPathSchemaRefs(output?.schema ?? output?.item, refs)
|
|
427
|
-
collectPolicyRefs(callable.policy, refs)
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
function collectPathSchemaRefs(value: unknown, refs: IrClassRef[]): void {
|
|
431
|
-
if (Array.isArray(value)) {
|
|
432
|
-
for (const item of value) collectPathSchemaRefs(item, refs)
|
|
433
|
-
return
|
|
434
|
-
}
|
|
435
|
-
const object = asRecord(value)
|
|
436
|
-
if (object === null) return
|
|
437
|
-
const annotation = asRecord(object['x-astrale-path'])
|
|
438
|
-
refs.push(...refsOf(annotation?.accepts))
|
|
439
|
-
for (const [key, child] of Object.entries(object)) {
|
|
440
|
-
if (key === 'const' || key === 'enum' || key === 'default' || key === 'examples') continue
|
|
441
|
-
collectPathSchemaRefs(child, refs)
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
function collectPolicyRefs(value: unknown, refs: IrClassRef[]): void {
|
|
446
|
-
if (Array.isArray(value)) {
|
|
447
|
-
for (const item of value) collectPolicyRefs(item, refs)
|
|
448
|
-
return
|
|
449
|
-
}
|
|
450
|
-
const object = asRecord(value)
|
|
451
|
-
if (object === null) return
|
|
452
|
-
addClassRef(object.class, refs)
|
|
453
|
-
for (const child of Object.values(object)) collectPolicyRefs(child, refs)
|
|
368
|
+
function callableOutput(value: unknown): IrCallableOutput {
|
|
369
|
+
const output = asRecord(value)
|
|
370
|
+
if (output?.mode === 'stream') return { mode: 'stream', item: studioSchema(output.item) }
|
|
371
|
+
if (output?.mode === 'binary') return { mode: 'binary' }
|
|
372
|
+
return { mode: 'value', schema: studioSchema(output?.schema) }
|
|
454
373
|
}
|
|
455
374
|
|
|
456
|
-
function
|
|
457
|
-
|
|
458
|
-
|
|
375
|
+
function callableAuth(value: unknown): IrCallableAuth | undefined {
|
|
376
|
+
return value === 'anonymous' || value === 'authenticated' || value === 'authorized'
|
|
377
|
+
? value
|
|
378
|
+
: undefined
|
|
459
379
|
}
|
|
460
380
|
|
|
461
381
|
function refsOf(value: unknown): IrClassRef[] {
|
|
@@ -466,7 +386,7 @@ function refsOf(value: unknown): IrClassRef[] {
|
|
|
466
386
|
|
|
467
387
|
function classRef(value: unknown): IrClassRef | null {
|
|
468
388
|
const ref = definitionRefOf(value)
|
|
469
|
-
return ref
|
|
389
|
+
return ref?.kind === 'class' ? { origin: ref.origin, kind: 'class', name: ref.name } : null
|
|
470
390
|
}
|
|
471
391
|
|
|
472
392
|
function definitionRefOf(value: unknown): IrSchemaRef | null {
|
|
@@ -484,29 +404,6 @@ function definitionRefOf(value: unknown): IrSchemaRef | null {
|
|
|
484
404
|
return { origin: ref.origin, kind: ref.kind, name: ref.name }
|
|
485
405
|
}
|
|
486
406
|
|
|
487
|
-
function uniqueClassRefs(refs: readonly IrClassRef[]): IrClassRef[] {
|
|
488
|
-
const seen = new Set<string>()
|
|
489
|
-
return refs.filter((ref) => {
|
|
490
|
-
const key = classRefKey(ref)
|
|
491
|
-
if (seen.has(key)) return false
|
|
492
|
-
seen.add(key)
|
|
493
|
-
return true
|
|
494
|
-
})
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
function callableOutput(value: unknown): IrCallableOutput {
|
|
498
|
-
const output = asRecord(value)
|
|
499
|
-
if (output?.mode === 'stream') return { mode: 'stream', item: studioSchema(output.item) }
|
|
500
|
-
if (output?.mode === 'binary') return { mode: 'binary' }
|
|
501
|
-
return { mode: 'value', schema: studioSchema(output?.schema) }
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
function callableAuth(value: unknown): IrCallableAuth | undefined {
|
|
505
|
-
return value === 'anonymous' || value === 'authenticated' || value === 'authorized'
|
|
506
|
-
? value
|
|
507
|
-
: undefined
|
|
508
|
-
}
|
|
509
|
-
|
|
510
407
|
function dataDeclaration(
|
|
511
408
|
value: unknown,
|
|
512
409
|
): { mediaType: string; [key: string]: unknown } | undefined {
|
|
@@ -518,8 +415,8 @@ function dataDeclaration(
|
|
|
518
415
|
|
|
519
416
|
function coreEndpoint(value: unknown, rootOrigin: string): string {
|
|
520
417
|
const endpoint = asRecord(value)
|
|
521
|
-
if (endpoint?.kind === 'domain'
|
|
522
|
-
return `/:${endpoint.origin}`
|
|
418
|
+
if (endpoint?.kind === 'domain') {
|
|
419
|
+
return `/:${typeof endpoint.origin === 'string' ? endpoint.origin : rootOrigin}`
|
|
523
420
|
}
|
|
524
421
|
const ref = definitionRefOf(value)
|
|
525
422
|
return ref?.kind === 'core' ? corePath(ref) : `/:${rootOrigin}`
|
|
@@ -529,22 +426,6 @@ function corePath(ref: Pick<IrSchemaRef, 'origin' | 'name'>): string {
|
|
|
529
426
|
return `/:${ref.origin}:core.${ref.name}`
|
|
530
427
|
}
|
|
531
428
|
|
|
532
|
-
function admitClosure(
|
|
533
|
-
value: unknown,
|
|
534
|
-
root: CanonicalDomainSchemaV1,
|
|
535
|
-
): CanonicalDomainSchemaV1[] | null {
|
|
536
|
-
if (!Array.isArray(value)) return null
|
|
537
|
-
const closure: CanonicalDomainSchemaV1[] = []
|
|
538
|
-
const origins = new Set<string>()
|
|
539
|
-
for (const candidate of value) {
|
|
540
|
-
if (!isCanonicalDomainSchemaV1(candidate) || candidate.origin === root.origin) return null
|
|
541
|
-
if (origins.has(candidate.origin)) return null
|
|
542
|
-
origins.add(candidate.origin)
|
|
543
|
-
closure.push(candidate)
|
|
544
|
-
}
|
|
545
|
-
return closure
|
|
546
|
-
}
|
|
547
|
-
|
|
548
429
|
function studioSchema(value: unknown): JsonSchema {
|
|
549
430
|
if (value === true) return {}
|
|
550
431
|
if (value === false) return { not: {} }
|
|
@@ -1,16 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* core.ts —
|
|
3
|
-
* extracted
|
|
4
|
-
* on installed deps. Never throws; a failed import or a domain with no core both
|
|
5
|
-
* become a well-formed (empty) render state.
|
|
2
|
+
* core.ts — derives a domain's genesis graph from the canonical Schema already
|
|
3
|
+
* extracted for its Studio bundle. Schema code is imported exactly once.
|
|
6
4
|
*/
|
|
7
|
-
import type { StudioCore } from '../../shared/types'
|
|
5
|
+
import type { StudioCore, StudioSchemaBundle } from '../../shared/types'
|
|
6
|
+
import type { DomainHandle } from '../domain'
|
|
8
7
|
|
|
9
|
-
import {
|
|
10
|
-
import { readSettings } from '../state/settings'
|
|
11
|
-
import { coreExtract } from './runtime'
|
|
8
|
+
import { isCanonicalDomainSchemaV1, projectCanonicalCore } from './canonical-schema'
|
|
12
9
|
|
|
13
|
-
export
|
|
10
|
+
export function buildCore(handle: DomainHandle, bundle: StudioSchemaBundle | null): StudioCore {
|
|
14
11
|
const at = new Date().toISOString()
|
|
15
12
|
const empty = (error: StudioCore['error']): StudioCore => ({
|
|
16
13
|
domain: handle.origin ?? handle.id,
|
|
@@ -20,25 +17,21 @@ export async function buildCore(handle: DomainHandle): Promise<StudioCore> {
|
|
|
20
17
|
extractedAt: at,
|
|
21
18
|
})
|
|
22
19
|
|
|
23
|
-
if (!depsInstalled
|
|
20
|
+
if (!bundle?.depsInstalled) {
|
|
24
21
|
return empty({
|
|
25
22
|
message: 'dependencies not installed — run `pnpm install` in the domain for core extraction',
|
|
26
23
|
})
|
|
27
24
|
}
|
|
25
|
+
if (!isCanonicalDomainSchemaV1(bundle.schemaRoot)) {
|
|
26
|
+
return empty(bundle.error ?? { message: 'canonical schema unavailable for core extraction' })
|
|
27
|
+
}
|
|
28
28
|
|
|
29
|
-
const
|
|
30
|
-
handle.schemaIndex,
|
|
31
|
-
handle.root,
|
|
32
|
-
readSettings(handle.root).introspectTimeoutMs,
|
|
33
|
-
)
|
|
34
|
-
// r.ok && r.core===null → the domain simply defines no core (not an error).
|
|
35
|
-
if (!r.ok) return empty(r.error ?? { message: 'core extraction failed' })
|
|
36
|
-
if (!r.core) return empty(null)
|
|
29
|
+
const core = projectCanonicalCore(bundle.schemaRoot)
|
|
37
30
|
|
|
38
31
|
return {
|
|
39
|
-
domain:
|
|
40
|
-
nodes:
|
|
41
|
-
edges:
|
|
32
|
+
domain: core.domain,
|
|
33
|
+
nodes: core.nodes,
|
|
34
|
+
edges: core.edges,
|
|
42
35
|
error: null,
|
|
43
36
|
extractedAt: at,
|
|
44
37
|
}
|