@astrale-os/cli 1.0.0-beta.1 → 1.0.0-beta.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.
- package/README.md +7 -11
- package/dist/astrale.js +3737 -3523
- package/dist/public/connect-core.js +396 -392
- package/dist/public/keys/index.js +371 -378
- package/dist/public/paths/index.js +371 -378
- package/dist/types/lib/idp.d.ts +6 -0
- package/dist/types/lib/meta.d.ts +2 -2
- package/package.json +6 -4
- package/src/admin/binding.ts +168 -0
- package/src/admin/catalog/__tests__/client.test.ts +2 -2
- package/src/admin/catalog/client.ts +4 -9
- package/src/admin/instance/__tests__/client.test.ts +2 -2
- package/src/admin/instance/client.ts +6 -11
- package/src/commands/__tests__/auth-login.test.ts +54 -1
- package/src/commands/__tests__/call.test.ts +5 -1
- package/src/commands/__tests__/domain-install-operation.test.ts +23 -7
- package/src/commands/__tests__/domain-install-owned.test.ts +5 -1
- package/src/commands/__tests__/domain-list.test.ts +83 -4
- package/src/commands/__tests__/install-identity-override.test.ts +30 -19
- package/src/commands/__tests__/studio-descriptor.test.ts +22 -0
- package/src/commands/__tests__/update.test.ts +23 -16
- package/src/commands/domain/install.ts +11 -15
- package/src/commands/domain/list.ts +17 -12
- package/src/commands/studio.ts +20 -0
- package/src/commands/update.ts +9 -5
- package/src/connection/__tests__/errors.test.ts +16 -12
- package/src/connection/__tests__/exchange.test.ts +6 -5
- package/src/connection/exchange.ts +5 -4
- package/src/lib/__tests__/domain-publication.test.ts +144 -0
- package/src/lib/__tests__/idp-session.test.ts +22 -0
- package/src/lib/__tests__/idp.test.ts +2 -0
- package/src/lib/__tests__/instance.test.ts +6 -0
- package/src/lib/__tests__/studio-server-deps.test.ts +53 -16
- package/src/lib/__tests__/update.test.ts +12 -0
- package/src/lib/domain-publication.ts +102 -0
- package/src/lib/idp.ts +5 -1
- package/src/lib/instance.ts +6 -1
- package/src/lib/login-flow.ts +13 -3
- package/src/lib/meta.ts +2 -2
- package/src/lib/update.ts +4 -4
- package/src/program/__tests__/program.test.ts +1 -1
- package/studio/client/dist/assets/{elk-api-D2xgMJvi.js → elk-api-lwhFo7vB.js} +1 -1
- package/studio/client/dist/assets/index-B-c-smo9.js +8 -0
- package/studio/client/dist/assets/index-BckHuAWk.js +81 -0
- package/studio/client/dist/assets/index-C4aNQ6dz.css +1 -0
- package/studio/client/dist/index.html +2 -2
- package/studio/package.json +3 -1
- package/studio/server/agent/bridge/stdio.ts +8 -1
- package/studio/server/agent/conversation.test.ts +10 -5
- package/studio/server/agent/conversation.ts +56 -8
- package/studio/server/agent/harness/codex/loadout.ts +18 -21
- package/studio/server/agent/harness/gateway/config.ts +20 -18
- package/studio/server/agent/harness/selection.ts +9 -1
- package/studio/server/agent/prompts/system.ts +3 -2
- package/studio/server/agent/prompts/turn.ts +13 -4
- package/studio/server/agent/run/completion.ts +2 -2
- package/studio/server/agent/run/preparation.ts +6 -5
- package/studio/server/agent/run/transcript.test.ts +35 -1
- package/studio/server/agent/run/transcript.ts +169 -3
- package/studio/server/agent/run/usage.test.ts +24 -0
- package/studio/server/agent/run/usage.ts +26 -1
- package/studio/server/api/canvas.ts +44 -0
- package/studio/server/api/comments.ts +85 -0
- package/studio/server/api/context.ts +61 -0
- package/studio/server/api/deployment.ts +39 -0
- package/studio/server/api/documents.ts +55 -0
- package/studio/server/api/domain.ts +59 -0
- package/studio/server/api/http.ts +45 -0
- package/studio/server/api/project.ts +57 -0
- package/studio/server/api/schema.ts +12 -0
- package/studio/server/api/views.ts +57 -0
- package/studio/server/api/workspace.ts +60 -0
- package/studio/server/api.test.ts +147 -0
- package/studio/server/api.ts +20 -422
- package/studio/server/cache.test.ts +111 -0
- package/studio/server/cache.ts +163 -9
- package/studio/server/cli-consumers.test.ts +494 -0
- package/studio/server/cli.test.ts +88 -0
- package/studio/server/cli.ts +240 -0
- package/studio/server/client-package.ts +25 -1
- package/studio/server/domain.test.ts +30 -2
- package/studio/server/domain.ts +14 -2
- package/studio/server/environment/dotenv-preview.test.ts +39 -0
- package/studio/server/environment/dotenv-preview.ts +27 -0
- package/studio/server/environment/files.test.ts +53 -0
- package/studio/server/{state/env.ts → environment/files.ts} +7 -48
- package/studio/server/{state → handoff}/copy.ts +21 -6
- package/studio/server/handoff/service.test.ts +22 -0
- package/studio/server/{state/handoff.ts → handoff/service.ts} +21 -14
- package/studio/server/instances/active.ts +72 -0
- package/studio/server/instances/deploy-record.test.ts +31 -0
- package/studio/server/instances/deploy-record.ts +37 -0
- package/studio/server/instances/deploy.ts +56 -0
- package/studio/server/{state/instance.test.ts → instances/probe.test.ts} +25 -21
- package/studio/server/instances/probe.ts +67 -0
- package/studio/server/instances/status.test.ts +12 -0
- package/studio/server/instances/status.ts +49 -0
- package/studio/server/introspect/anatomy/client-tree.ts +57 -0
- package/studio/server/introspect/anatomy/env-fields.ts +66 -0
- package/studio/server/introspect/anatomy/schema-definition.ts +53 -0
- package/studio/server/introspect/anatomy/source.ts +181 -0
- package/studio/server/introspect/anatomy/views/legacy.ts +232 -0
- package/studio/server/introspect/anatomy/views/routes.ts +105 -0
- package/studio/server/introspect/anatomy/views.ts +58 -0
- package/studio/server/introspect/anatomy-extras.test.ts +91 -4
- package/studio/server/introspect/anatomy-extras.ts +7 -726
- package/studio/server/introspect/anatomy.ts +46 -19
- package/studio/server/introspect/bundle.ts +15 -6
- package/studio/server/introspect/canonical-schema.test.ts +79 -0
- package/studio/server/introspect/canonical-schema.ts +80 -18
- package/studio/server/introspect/config-preview.test.ts +32 -0
- package/studio/server/introspect/config-preview.ts +119 -0
- package/studio/server/introspect/diff.test.ts +7 -11
- package/studio/server/introspect/diff.ts +28 -37
- package/studio/server/introspect/extractor.ts +8 -6
- package/studio/server/introspect/hash.ts +5 -2
- package/studio/server/introspect/overlay-tsmorph.ts +7 -1305
- package/studio/server/introspect/revision.test.ts +54 -0
- package/studio/server/introspect/revision.ts +49 -0
- package/studio/server/introspect/runtime.test.ts +63 -5
- package/studio/server/introspect/runtime.ts +49 -3
- package/studio/server/introspect/schema-ir-json.ts +181 -0
- package/studio/server/introspect/source-overlay/annotations.ts +14 -0
- package/studio/server/introspect/source-overlay/handlers.ts +681 -0
- package/studio/server/introspect/source-overlay/kernel-calls.ts +49 -0
- package/studio/server/introspect/source-overlay/project.ts +248 -0
- package/studio/server/introspect/source-overlay/spans.ts +360 -0
- package/studio/server/json.ts +46 -0
- package/studio/server/lifecycle.ts +5 -1
- package/studio/server/sse.test.ts +26 -0
- package/studio/server/sse.ts +4 -1
- package/studio/server/state/baseline.test.ts +212 -1
- package/studio/server/state/baseline.ts +145 -29
- package/studio/server/state/comments.test.ts +66 -1
- package/studio/server/state/comments.ts +173 -10
- package/studio/server/state/context.ts +34 -2
- package/studio/server/state/documents.ts +42 -1
- package/studio/server/state/integrations.ts +27 -1
- package/studio/server/state/layout.test.ts +34 -0
- package/studio/server/state/layout.ts +44 -17
- package/studio/server/state/settings.ts +29 -22
- package/studio/server/state/store.test.ts +122 -0
- package/studio/server/state/store.ts +66 -17
- package/studio/server/state/visibility.ts +22 -7
- package/studio/server/views/model.test.ts +57 -0
- package/studio/server/views/model.ts +66 -0
- package/studio/server/views/runtime.ts +40 -0
- package/studio/server/views/selection-repository.test.ts +31 -0
- package/studio/server/views/selection-repository.ts +60 -0
- package/studio/server/views/session.test.ts +73 -0
- package/studio/server/views/session.ts +166 -0
- package/studio/server/{state/views.test.ts → views/target.test.ts} +8 -159
- package/studio/server/views/target.ts +248 -0
- package/studio/server/watch.ts +1 -1
- package/studio/server/workspace/catalog.test.ts +17 -0
- package/studio/server/workspace/catalog.ts +43 -0
- package/studio/server/{state → workspace}/create.test.ts +1 -1
- package/studio/server/{state → workspace}/create.ts +1 -1
- package/studio/server/{state → workspace}/git.ts +1 -1
- package/studio/server/workspace/updates.ts +83 -0
- package/studio/server/workspace-state.ts +1 -1
- package/studio/shared/contracts/README.md +16 -0
- package/studio/shared/contracts/agent.ts +270 -0
- package/studio/shared/contracts/runtime.ts +50 -0
- package/studio/shared/contracts/schema.ts +462 -0
- package/studio/shared/contracts/surface.test.ts +319 -0
- package/studio/shared/contracts/workspace.ts +274 -0
- package/studio/shared/layout.test.ts +116 -0
- package/studio/shared/schema/identity.test.ts +58 -0
- package/studio/shared/schema/identity.ts +82 -0
- package/studio/shared/types.ts +11 -1099
- package/studio/tsconfig.json +1 -1
- package/viewer/dist/main.js +35 -35
- package/studio/client/dist/assets/index-BMdnsIJA.css +0 -1
- package/studio/client/dist/assets/index-D-vRV8w7.js +0 -8
- package/studio/client/dist/assets/index-LGSWRrk8.js +0 -81
- package/studio/server/state/catalog.ts +0 -117
- package/studio/server/state/instance.ts +0 -280
- package/studio/server/state/updates.ts +0 -63
- package/studio/server/state/views.ts +0 -535
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test'
|
|
2
|
+
|
|
3
|
+
import { admittedBundleRevisionFromSdk } from './revision'
|
|
4
|
+
|
|
5
|
+
const REVISION = `sha256:${'a'.repeat(64)}` as const
|
|
6
|
+
|
|
7
|
+
describe('installed Bundle revision', () => {
|
|
8
|
+
test('uses bundle admission before asking the cohort schema owner for its revision', () => {
|
|
9
|
+
const calls: string[] = []
|
|
10
|
+
const root = { format: 'astrale.dsl', version: 'v1', origin: 'notes.example.dev' }
|
|
11
|
+
const input = { wire: true }
|
|
12
|
+
const sdk = {
|
|
13
|
+
bundle: {
|
|
14
|
+
accept(value: unknown) {
|
|
15
|
+
calls.push('bundle.accept')
|
|
16
|
+
expect(value).toBe(input)
|
|
17
|
+
return { root, closure: [] }
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
schema: {
|
|
21
|
+
revision(value: unknown) {
|
|
22
|
+
calls.push('schema.revision')
|
|
23
|
+
expect(value).toBe(root)
|
|
24
|
+
return REVISION
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
expect(admittedBundleRevisionFromSdk(sdk, input)).toBe(REVISION)
|
|
30
|
+
expect(calls).toEqual(['bundle.accept', 'schema.revision'])
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
test('fails closed when bundle admission or the returned revision is invalid', () => {
|
|
34
|
+
expect(
|
|
35
|
+
admittedBundleRevisionFromSdk(
|
|
36
|
+
{ bundle: { accept: () => ({ root: {} }) }, schema: { revision: () => 'sha-short' } },
|
|
37
|
+
{},
|
|
38
|
+
),
|
|
39
|
+
).toBeNull()
|
|
40
|
+
expect(
|
|
41
|
+
admittedBundleRevisionFromSdk(
|
|
42
|
+
{
|
|
43
|
+
bundle: {
|
|
44
|
+
accept: () => {
|
|
45
|
+
throw new Error('invalid bundle')
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
schema: { revision: () => REVISION },
|
|
49
|
+
},
|
|
50
|
+
{},
|
|
51
|
+
),
|
|
52
|
+
).toBeNull()
|
|
53
|
+
})
|
|
54
|
+
})
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { SchemaRevision } from '../../shared/types'
|
|
2
|
+
|
|
3
|
+
import { isSchemaRevision } from '../../shared/types'
|
|
4
|
+
|
|
5
|
+
type AnyRecord = Record<string, unknown>
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Cross the installed-bundle JSON boundary through one exact SDK cohort, then
|
|
9
|
+
* ask the DSL owner for the root revision. Invalid/unavailable bundles return
|
|
10
|
+
* null so Studio reports unknown drift instead of inventing an identity.
|
|
11
|
+
*/
|
|
12
|
+
export function admittedBundleRevisionFromSdk(
|
|
13
|
+
sdkModule: Record<string, unknown>,
|
|
14
|
+
input: unknown,
|
|
15
|
+
): SchemaRevision | null {
|
|
16
|
+
const bundleApi = asRecord(sdkModule.bundle)
|
|
17
|
+
const schemaApi = asRecord(sdkModule.schema)
|
|
18
|
+
if (typeof bundleApi?.accept !== 'function' || typeof schemaApi?.revision !== 'function') {
|
|
19
|
+
return null
|
|
20
|
+
}
|
|
21
|
+
try {
|
|
22
|
+
const accepted = asRecord(Reflect.apply(bundleApi.accept, bundleApi, [input]))
|
|
23
|
+
if (!accepted || !Object.prototype.hasOwnProperty.call(accepted, 'root')) return null
|
|
24
|
+
const revision = Reflect.apply(schemaApi.revision, schemaApi, [accepted.root])
|
|
25
|
+
return isSchemaRevision(revision) ? revision : null
|
|
26
|
+
} catch {
|
|
27
|
+
return null
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export async function admittedBundleRevision(
|
|
32
|
+
domainRoot: string,
|
|
33
|
+
input: unknown,
|
|
34
|
+
): Promise<SchemaRevision | null> {
|
|
35
|
+
try {
|
|
36
|
+
const sdkModule: Record<string, unknown> = await import(
|
|
37
|
+
Bun.resolveSync('@astrale-os/sdk/schema', domainRoot)
|
|
38
|
+
)
|
|
39
|
+
return admittedBundleRevisionFromSdk(sdkModule, input)
|
|
40
|
+
} catch {
|
|
41
|
+
return null
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function asRecord(value: unknown): AnyRecord | null {
|
|
46
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value)
|
|
47
|
+
? (value as AnyRecord)
|
|
48
|
+
: null
|
|
49
|
+
}
|
|
@@ -6,7 +6,8 @@ import { join } from 'node:path'
|
|
|
6
6
|
import type { DomainHandle } from '../domain'
|
|
7
7
|
|
|
8
8
|
import { buildBundle } from './bundle'
|
|
9
|
-
import {
|
|
9
|
+
import { renderFingerprintOf } from './hash'
|
|
10
|
+
import { admittedBundleRevision } from './revision'
|
|
10
11
|
import { coreExtract, runtimeExtract } from './runtime'
|
|
11
12
|
|
|
12
13
|
const roots: string[] = []
|
|
@@ -40,7 +41,7 @@ function currentFixture(): DomainHandle {
|
|
|
40
41
|
schemaIndex,
|
|
41
42
|
`
|
|
42
43
|
import {
|
|
43
|
-
defineSchema, domain, edge, edgeClass, fn, node, nodeClass,
|
|
44
|
+
bundle, defineSchema, domain, edge, edgeClass, fn, node, nodeClass,
|
|
44
45
|
nodeInterface, output, property, view,
|
|
45
46
|
} from '@astrale-os/sdk/schema'
|
|
46
47
|
|
|
@@ -84,6 +85,7 @@ export const schema = defineSchema('documents.runtime.test', {
|
|
|
84
85
|
edges: [edge(welcome, owned_by, domain(), {})],
|
|
85
86
|
},
|
|
86
87
|
})
|
|
88
|
+
export const installedBundle = bundle.create(schema)
|
|
87
89
|
`,
|
|
88
90
|
)
|
|
89
91
|
// Canonical core extraction must not import the effectful composition entry.
|
|
@@ -105,6 +107,8 @@ describe('current SDK schema extractor', () => {
|
|
|
105
107
|
const result = await runtimeExtract(handle.schemaIndex, handle.root)
|
|
106
108
|
|
|
107
109
|
expect(result.ok).toBe(true)
|
|
110
|
+
expect(result.schemaMode).toBe('canonical-admitted')
|
|
111
|
+
expect(result.revision).toMatch(/^sha256:[0-9a-f]{64}$/)
|
|
108
112
|
expect(result.root).toMatchObject({
|
|
109
113
|
format: 'astrale.dsl',
|
|
110
114
|
version: 'v1',
|
|
@@ -133,15 +137,48 @@ describe('current SDK schema extractor', () => {
|
|
|
133
137
|
})
|
|
134
138
|
})
|
|
135
139
|
|
|
136
|
-
test('
|
|
140
|
+
test('transports the DSL revision and fingerprints the root only for rendering', async () => {
|
|
137
141
|
const handle = currentFixture()
|
|
138
142
|
const extracted = await runtimeExtract(handle.schemaIndex, handle.root)
|
|
139
143
|
expect(extracted.ok).toBe(true)
|
|
144
|
+
if (extracted.revision === null) throw new Error('expected an admitted schema revision')
|
|
140
145
|
|
|
141
146
|
const bundle = await buildBundle(handle)
|
|
142
147
|
expect(bundle.schemaRoot).toEqual(extracted.root)
|
|
143
|
-
expect(bundle.
|
|
144
|
-
expect(bundle.
|
|
148
|
+
expect(bundle.schemaMode).toBe('canonical-admitted')
|
|
149
|
+
expect(bundle.schemaRevision).toBe(extracted.revision)
|
|
150
|
+
expect(bundle.renderFingerprint).toBe(renderFingerprintOf(extracted.root))
|
|
151
|
+
expect(bundle.renderFingerprint).not.toBe(renderFingerprintOf(extracted.ir))
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
test('re-admits installed Bundle JSON before comparing its DSL revision', async () => {
|
|
155
|
+
const handle = currentFixture()
|
|
156
|
+
const extracted = await runtimeExtract(handle.schemaIndex, handle.root)
|
|
157
|
+
if (extracted.revision === null) throw new Error('expected an admitted local schema revision')
|
|
158
|
+
const fixture = (await import(handle.schemaIndex)) as { installedBundle: unknown }
|
|
159
|
+
const installedWire = JSON.parse(JSON.stringify(fixture.installedBundle))
|
|
160
|
+
|
|
161
|
+
expect(await admittedBundleRevision(handle.root, installedWire)).toBe(extracted.revision)
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
test('renders a V1-shaped but unadmitted root only as a structural preview', async () => {
|
|
165
|
+
const root = fixtureRoot('canonical-preview')
|
|
166
|
+
linkCurrentSdk(root)
|
|
167
|
+
const schemaIndex = join(root, 'schema', 'index.ts')
|
|
168
|
+
writeFileSync(
|
|
169
|
+
schemaIndex,
|
|
170
|
+
`export const schema = {
|
|
171
|
+
format: 'astrale.dsl', version: 'v1', origin: 'not an origin',
|
|
172
|
+
dependencies: [], types: {}, interfaces: {}, classes: {}, functions: {},
|
|
173
|
+
policies: {}, views: {}, core: { nodes: {}, edges: [] },
|
|
174
|
+
}\n`,
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
const result = await runtimeExtract(schemaIndex, root)
|
|
178
|
+
expect(result.ok).toBe(true)
|
|
179
|
+
expect(result.schemaMode).toBe('canonical-preview')
|
|
180
|
+
expect(result.revision).toBeNull()
|
|
181
|
+
expect(result.root).toMatchObject({ format: 'astrale.dsl', version: 'v1' })
|
|
145
182
|
})
|
|
146
183
|
|
|
147
184
|
test('extracts canonical core without importing implementation.ts', async () => {
|
|
@@ -172,6 +209,27 @@ describe('current SDK schema extractor', () => {
|
|
|
172
209
|
})
|
|
173
210
|
})
|
|
174
211
|
|
|
212
|
+
test('runtime extractor preserves the legacy compiled-IR envelope', async () => {
|
|
213
|
+
const root = fixtureRoot('legacy-runtime')
|
|
214
|
+
const schemaIndex = join(root, 'schema', 'index.ts')
|
|
215
|
+
writeFileSync(
|
|
216
|
+
schemaIndex,
|
|
217
|
+
`export const D = { $: { ir: {
|
|
218
|
+
version: 'legacy', domain: 'legacy.runtime.test', types: {}, interfaces: {},
|
|
219
|
+
classes: {}, imports: {}, functions: {},
|
|
220
|
+
} } }\n`,
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
const result = await runtimeExtract(schemaIndex, root)
|
|
224
|
+
expect(result).toMatchObject({
|
|
225
|
+
ok: true,
|
|
226
|
+
schemaMode: 'legacy',
|
|
227
|
+
revision: null,
|
|
228
|
+
root: null,
|
|
229
|
+
ir: { domain: 'legacy.runtime.test', functions: {} },
|
|
230
|
+
})
|
|
231
|
+
})
|
|
232
|
+
|
|
175
233
|
test('core extractor preserves the legacy defineCore fallback', async () => {
|
|
176
234
|
const root = fixtureRoot('legacy-core')
|
|
177
235
|
const schemaIndex = join(root, 'schema', 'index.ts')
|
|
@@ -5,7 +5,15 @@
|
|
|
5
5
|
* render IR plus the raw canonical root (when present), or an error render-state
|
|
6
6
|
* — never throws.
|
|
7
7
|
*/
|
|
8
|
-
import type {
|
|
8
|
+
import type {
|
|
9
|
+
IrInterface,
|
|
10
|
+
SchemaIR,
|
|
11
|
+
SchemaRevision,
|
|
12
|
+
StudioCore,
|
|
13
|
+
StudioSchemaBundle,
|
|
14
|
+
} from '../../shared/types'
|
|
15
|
+
|
|
16
|
+
import { isSchemaRevision } from '../../shared/types'
|
|
9
17
|
|
|
10
18
|
const EXTRACTOR = new URL('./extractor.ts', import.meta.url).pathname
|
|
11
19
|
const CORE_EXTRACTOR = new URL('./core-extractor.ts', import.meta.url).pathname
|
|
@@ -13,8 +21,11 @@ const CORE_EXTRACTOR = new URL('./core-extractor.ts', import.meta.url).pathname
|
|
|
13
21
|
export interface RuntimeExtractResult {
|
|
14
22
|
ok: boolean
|
|
15
23
|
ir: SchemaIR | null
|
|
16
|
-
/**
|
|
24
|
+
/** Portable canonical V1 document. Null for legacy domains and failures. */
|
|
17
25
|
root: unknown | null
|
|
26
|
+
schemaMode: StudioSchemaBundle['schemaMode']
|
|
27
|
+
/** Present only when the Domain cohort SDK admitted `root`. */
|
|
28
|
+
revision: SchemaRevision | null
|
|
18
29
|
/** member bodies of imported (kernel + cross-domain) interfaces, by name */
|
|
19
30
|
importedInterfaces?: Record<string, IrInterface>
|
|
20
31
|
error?: { message: string }
|
|
@@ -42,6 +53,8 @@ export async function runtimeExtract(
|
|
|
42
53
|
ok: false,
|
|
43
54
|
ir: null,
|
|
44
55
|
root: null,
|
|
56
|
+
schemaMode: 'unavailable',
|
|
57
|
+
revision: null,
|
|
45
58
|
error: { message: err.trim() || 'extractor produced no output' },
|
|
46
59
|
}
|
|
47
60
|
}
|
|
@@ -51,16 +64,49 @@ export async function runtimeExtract(
|
|
|
51
64
|
ok: false,
|
|
52
65
|
ir: null,
|
|
53
66
|
root: null,
|
|
67
|
+
schemaMode: 'unavailable',
|
|
68
|
+
revision: null,
|
|
54
69
|
error: parsed.error ?? { message: 'extraction failed' },
|
|
55
70
|
}
|
|
71
|
+
const schemaMode = parsed.schemaMode
|
|
72
|
+
const revision = parsed.revision
|
|
73
|
+
const validMode =
|
|
74
|
+
schemaMode === 'canonical-admitted' ||
|
|
75
|
+
schemaMode === 'canonical-preview' ||
|
|
76
|
+
schemaMode === 'legacy'
|
|
77
|
+
const validRevision =
|
|
78
|
+
schemaMode === 'canonical-admitted' ? isSchemaRevision(revision) : revision == null
|
|
79
|
+
const validRoot =
|
|
80
|
+
schemaMode === 'legacy'
|
|
81
|
+
? parsed.root == null
|
|
82
|
+
: parsed.root !== null && parsed.root !== undefined
|
|
83
|
+
if (!validMode || !validRevision || !validRoot) {
|
|
84
|
+
return {
|
|
85
|
+
ok: false,
|
|
86
|
+
ir: null,
|
|
87
|
+
root: null,
|
|
88
|
+
schemaMode: 'unavailable',
|
|
89
|
+
revision: null,
|
|
90
|
+
error: { message: 'extractor produced an invalid schema admission envelope' },
|
|
91
|
+
}
|
|
92
|
+
}
|
|
56
93
|
return {
|
|
57
94
|
ok: true,
|
|
58
95
|
ir: parsed.ir as SchemaIR,
|
|
59
96
|
root: parsed.root ?? null,
|
|
97
|
+
schemaMode,
|
|
98
|
+
revision: revision ?? null,
|
|
60
99
|
importedInterfaces: (parsed.importedInterfaces ?? {}) as Record<string, IrInterface>,
|
|
61
100
|
}
|
|
62
101
|
} catch (e: any) {
|
|
63
|
-
return {
|
|
102
|
+
return {
|
|
103
|
+
ok: false,
|
|
104
|
+
ir: null,
|
|
105
|
+
root: null,
|
|
106
|
+
schemaMode: 'unavailable',
|
|
107
|
+
revision: null,
|
|
108
|
+
error: { message: String(e?.message ?? e) },
|
|
109
|
+
}
|
|
64
110
|
}
|
|
65
111
|
}
|
|
66
112
|
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/** Structural admission for persisted Studio render IR. SDK admission is separate. */
|
|
2
|
+
|
|
3
|
+
import type {
|
|
4
|
+
IrFunction,
|
|
5
|
+
IrInterface,
|
|
6
|
+
IrMethod,
|
|
7
|
+
IrSchemaRef,
|
|
8
|
+
JsonSchema,
|
|
9
|
+
SchemaIR,
|
|
10
|
+
} from '../../shared/types'
|
|
11
|
+
|
|
12
|
+
import { asJsonRecord, asStringArray } from '../json'
|
|
13
|
+
|
|
14
|
+
function optional<T>(value: unknown, guard: (candidate: unknown) => candidate is T): boolean {
|
|
15
|
+
return value === undefined || guard(value)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function recordOf(value: unknown, guard: (candidate: unknown) => boolean): boolean {
|
|
19
|
+
const record = asJsonRecord(value)
|
|
20
|
+
return !!record && Object.values(record).every(guard)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function isJsonSchema(value: unknown): value is JsonSchema {
|
|
24
|
+
return !!asJsonRecord(value)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function isSchemaRef(value: unknown): value is IrSchemaRef {
|
|
28
|
+
const record = asJsonRecord(value)
|
|
29
|
+
return (
|
|
30
|
+
typeof record?.origin === 'string' &&
|
|
31
|
+
typeof record.name === 'string' &&
|
|
32
|
+
['type', 'interface', 'class', 'function', 'policy', 'view', 'core'].includes(
|
|
33
|
+
String(record.kind),
|
|
34
|
+
)
|
|
35
|
+
)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function isSchemaRefArray(value: unknown): value is IrSchemaRef[] {
|
|
39
|
+
return Array.isArray(value) && value.every(isSchemaRef)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function isOutput(value: unknown): boolean {
|
|
43
|
+
const record = asJsonRecord(value)
|
|
44
|
+
if (!record) return false
|
|
45
|
+
if (record.mode === 'binary') return true
|
|
46
|
+
if (record.mode === 'value') return isJsonSchema(record.schema)
|
|
47
|
+
if (record.mode === 'stream') return isJsonSchema(record.item)
|
|
48
|
+
return false
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function isMethod(value: unknown): value is IrMethod {
|
|
52
|
+
const record = asJsonRecord(value)
|
|
53
|
+
return (
|
|
54
|
+
typeof record?.name === 'string' &&
|
|
55
|
+
recordOf(record.params, isJsonSchema) &&
|
|
56
|
+
isJsonSchema(record.returns) &&
|
|
57
|
+
typeof record.static === 'boolean' &&
|
|
58
|
+
['default', 'abstract', 'sealed'].includes(String(record.inheritance)) &&
|
|
59
|
+
optional(record.input, isJsonSchema) &&
|
|
60
|
+
(record.requiredParams === undefined || asStringArray(record.requiredParams) !== undefined) &&
|
|
61
|
+
(record.output === undefined || isOutput(record.output))
|
|
62
|
+
)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function isEndpoint(value: unknown): boolean {
|
|
66
|
+
const record = asJsonRecord(value)
|
|
67
|
+
if (!record || typeof record.name !== 'string' || !asStringArray(record.types)) return false
|
|
68
|
+
if (record.refs !== undefined && !isSchemaRefArray(record.refs)) return false
|
|
69
|
+
if (record.cardinality !== undefined) {
|
|
70
|
+
const cardinality = asJsonRecord(record.cardinality)
|
|
71
|
+
if (
|
|
72
|
+
!cardinality ||
|
|
73
|
+
typeof cardinality.min !== 'number' ||
|
|
74
|
+
!Number.isFinite(cardinality.min) ||
|
|
75
|
+
(cardinality.max !== null &&
|
|
76
|
+
(typeof cardinality.max !== 'number' || !Number.isFinite(cardinality.max)))
|
|
77
|
+
) {
|
|
78
|
+
return false
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return true
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function isInterface(value: unknown): value is IrInterface {
|
|
85
|
+
const record = asJsonRecord(value)
|
|
86
|
+
return (
|
|
87
|
+
record?.type === 'interface' &&
|
|
88
|
+
typeof record.name === 'string' &&
|
|
89
|
+
recordOf(record.properties, isJsonSchema) &&
|
|
90
|
+
recordOf(record.methods, isMethod) &&
|
|
91
|
+
(record.required === undefined || asStringArray(record.required) !== undefined) &&
|
|
92
|
+
(record.extends === undefined || asStringArray(record.extends) !== undefined) &&
|
|
93
|
+
(record.extendsRefs === undefined || isSchemaRefArray(record.extendsRefs)) &&
|
|
94
|
+
(record.endpoints === undefined ||
|
|
95
|
+
(Array.isArray(record.endpoints) && record.endpoints.every(isEndpoint)))
|
|
96
|
+
)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function isClass(value: unknown): boolean {
|
|
100
|
+
const record = asJsonRecord(value)
|
|
101
|
+
return (
|
|
102
|
+
(record?.type === 'node' || record?.type === 'edge') &&
|
|
103
|
+
typeof record.name === 'string' &&
|
|
104
|
+
recordOf(record.properties, isJsonSchema) &&
|
|
105
|
+
recordOf(record.methods, isMethod) &&
|
|
106
|
+
(record.required === undefined || asStringArray(record.required) !== undefined) &&
|
|
107
|
+
(record.implements === undefined || asStringArray(record.implements) !== undefined) &&
|
|
108
|
+
(record.implementsRefs === undefined || isSchemaRefArray(record.implementsRefs)) &&
|
|
109
|
+
(record.endpoints === undefined ||
|
|
110
|
+
(Array.isArray(record.endpoints) && record.endpoints.every(isEndpoint)))
|
|
111
|
+
)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function isFunction(value: unknown): value is IrFunction {
|
|
115
|
+
const record = asJsonRecord(value)
|
|
116
|
+
return (
|
|
117
|
+
typeof record?.name === 'string' &&
|
|
118
|
+
isJsonSchema(record.input) &&
|
|
119
|
+
recordOf(record.params, isJsonSchema) &&
|
|
120
|
+
(record.requiredParams === undefined || asStringArray(record.requiredParams) !== undefined) &&
|
|
121
|
+
isOutput(record.output) &&
|
|
122
|
+
isJsonSchema(record.returns) &&
|
|
123
|
+
record.static === true &&
|
|
124
|
+
record.inheritance === 'default'
|
|
125
|
+
)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function isImport(value: unknown): boolean {
|
|
129
|
+
const record = asJsonRecord(value)
|
|
130
|
+
return (
|
|
131
|
+
typeof record?.origin === 'string' &&
|
|
132
|
+
(record.definition === 'class' || record.definition === 'interface') &&
|
|
133
|
+
optional(record.ref, isSchemaRef)
|
|
134
|
+
)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function isView(value: unknown): boolean {
|
|
138
|
+
const record = asJsonRecord(value)
|
|
139
|
+
const target = asJsonRecord(record?.target)
|
|
140
|
+
return (
|
|
141
|
+
typeof record?.name === 'string' &&
|
|
142
|
+
['required', 'optional', 'public'].includes(String(record.auth)) &&
|
|
143
|
+
!!target &&
|
|
144
|
+
(target.kind === 'domain' ||
|
|
145
|
+
(target.kind === 'definition' && isSchemaRefArray(target.definitions)))
|
|
146
|
+
)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export function decodeSchemaIR(value: unknown): SchemaIR | undefined {
|
|
150
|
+
const record = asJsonRecord(value)
|
|
151
|
+
if (
|
|
152
|
+
!record ||
|
|
153
|
+
typeof record.version !== 'string' ||
|
|
154
|
+
typeof record.domain !== 'string' ||
|
|
155
|
+
!recordOf(record.types, isJsonSchema) ||
|
|
156
|
+
!recordOf(record.interfaces, isInterface) ||
|
|
157
|
+
!recordOf(record.classes, isClass) ||
|
|
158
|
+
!recordOf(record.imports, isImport) ||
|
|
159
|
+
!recordOf(record.functions, isFunction) ||
|
|
160
|
+
(record.importsByKey !== undefined && !recordOf(record.importsByKey, isImport)) ||
|
|
161
|
+
(record.importedInterfacesByKey !== undefined &&
|
|
162
|
+
!recordOf(record.importedInterfacesByKey, isInterface)) ||
|
|
163
|
+
(record.views !== undefined && !recordOf(record.views, isView)) ||
|
|
164
|
+
(record.policies !== undefined && !asJsonRecord(record.policies)) ||
|
|
165
|
+
(record.dependencies !== undefined &&
|
|
166
|
+
(!Array.isArray(record.dependencies) ||
|
|
167
|
+
!record.dependencies.every((dependency) => {
|
|
168
|
+
const item = asJsonRecord(dependency)
|
|
169
|
+
return typeof item?.origin === 'string' && typeof item.revision === 'string'
|
|
170
|
+
})))
|
|
171
|
+
) {
|
|
172
|
+
return undefined
|
|
173
|
+
}
|
|
174
|
+
return record as unknown as SchemaIR
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export function decodeIrInterfaceRecord(value: unknown): Record<string, IrInterface> | undefined {
|
|
178
|
+
const record = asJsonRecord(value)
|
|
179
|
+
if (!record || !Object.values(record).every(isInterface)) return undefined
|
|
180
|
+
return record as unknown as Record<string, IrInterface>
|
|
181
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compatibility owner for Studio schema annotations.
|
|
3
|
+
*
|
|
4
|
+
* The current Kernel admits enum updates against their exact Value Schema, so
|
|
5
|
+
* there are no source-derived annotations today. The serialized overlay field
|
|
6
|
+
* remains consumed by the client and shared contract.
|
|
7
|
+
*/
|
|
8
|
+
import type { SchemaAnnotation, SchemaIR } from '../../../shared/types'
|
|
9
|
+
|
|
10
|
+
export function buildSchemaAnnotations(_args: { ir: SchemaIR | null }): SchemaAnnotation[] {
|
|
11
|
+
// The current Kernel admits updated values against their exact Value Schema;
|
|
12
|
+
// enum properties therefore need no Studio-specific compatibility warning.
|
|
13
|
+
return []
|
|
14
|
+
}
|