@astrale-os/cli 1.0.0-beta.27 → 1.0.0-beta.29
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 +59 -3
- package/dist/astrale.js +1765 -1057
- package/dist/public/connect-core.js +86 -17
- package/dist/public/keys/index.js +69 -2
- package/dist/public/paths/index.js +67 -0
- package/dist/types/connection/session.d.ts +2 -2
- package/dist/types/lib/config.d.ts +6 -0
- package/dist/types/state/exchange-credentials.d.ts +6 -2
- package/dist/types/state/files.d.ts +2 -0
- package/dist/types/state/index.d.ts +3 -2
- package/dist/types/state/paths.d.ts +2 -0
- package/dist/types/state/session-routes.d.ts +10 -0
- package/package.json +2 -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/auth/logout.ts +2 -1
- package/src/commands/browser.ts +29 -0
- 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/connection/.spec/architecture.md +9 -1
- package/src/connection/__tests__/auth.test.ts +1 -0
- package/src/connection/__tests__/credential.test.ts +1 -0
- package/src/connection/__tests__/exchange.test.ts +34 -10
- package/src/connection/__tests__/session.test.ts +5 -1
- package/src/connection/__tests__/target.test.ts +1 -0
- package/src/connection/exchange.ts +63 -38
- package/src/connection/session.ts +8 -1
- package/src/identity/__tests__/fixtures/registry-journey.ts +13 -1
- package/src/identity/__tests__/registry.test.ts +4 -0
- package/src/identity/registry.ts +2 -0
- package/src/lib/__tests__/browser-retention.test.ts +212 -0
- package/src/lib/__tests__/config.test.ts +27 -0
- package/src/lib/__tests__/skills.test.ts +8 -2
- package/src/lib/browser-retention.ts +210 -0
- package/src/lib/config.ts +12 -1
- package/src/program/__tests__/program.test.ts +1 -1
- package/src/state/.spec/api.d.ts +21 -2
- package/src/state/.spec/architecture.md +18 -4
- package/src/state/.spec/layout.ts +1 -0
- package/src/state/__tests__/exchange-credentials.test.ts +88 -42
- package/src/state/__tests__/files.test.ts +24 -1
- package/src/state/__tests__/fixtures/session-route-process.ts +93 -0
- package/src/state/__tests__/paths.test.ts +1 -0
- package/src/state/__tests__/session-routes.test.ts +138 -0
- package/src/state/exchange-credentials.ts +22 -9
- package/src/state/files.ts +41 -0
- package/src/state/index.ts +3 -1
- package/src/state/paths.ts +3 -0
- package/src/state/session-routes.ts +34 -0
- package/src/telemetry/__tests__/analyze-log.test.ts +38 -0
- package/src/telemetry/__tests__/retention.test.ts +267 -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/analyze.ts +24 -2
- package/src/telemetry/recorder.ts +6 -3
- package/src/telemetry/retention.ts +176 -0
- package/src/telemetry/session.ts +18 -12
- package/src/telemetry/settings.ts +64 -11
- package/src/telemetry/store.ts +92 -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/package.json +1 -1
- 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/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/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/viewer/dist/main.js +28 -28
- package/studio/client/dist/assets/index-C0FAnwNw.css +0 -2
- package/studio/client/dist/assets/index-CpBed0V1.js +0 -81
- package/studio/client/dist/assets/schema-studio-BilUNb6Z.css +0 -1
- package/studio/client/dist/assets/schema-studio-DJm9guI8.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,12 +1,12 @@
|
|
|
1
1
|
<!doctype html>
|
|
2
|
-
<html lang="en"
|
|
2
|
+
<html lang="en">
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<title>Domain Studio</title>
|
|
7
|
-
<script type="module" crossorigin src="/assets/index-
|
|
7
|
+
<script type="module" crossorigin src="/assets/index-BFVFs_8x.js"></script>
|
|
8
8
|
<link rel="modulepreload" crossorigin href="/assets/rolldown-runtime-Dd_uD5pT.js">
|
|
9
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
9
|
+
<link rel="stylesheet" crossorigin href="/assets/index-DuB9iUdu.css">
|
|
10
10
|
</head>
|
|
11
11
|
<body>
|
|
12
12
|
<div id="root"></div>
|
package/studio/package.json
CHANGED
|
@@ -41,7 +41,20 @@ const ir: SchemaIR = {
|
|
|
41
41
|
functions: {
|
|
42
42
|
inspect: {
|
|
43
43
|
name: 'inspect',
|
|
44
|
-
input: {
|
|
44
|
+
input: {
|
|
45
|
+
type: 'object',
|
|
46
|
+
properties: {
|
|
47
|
+
cursor: {
|
|
48
|
+
$ref: 'https://schemas.astrale.ai/graph/1/node-path',
|
|
49
|
+
'x-astrale-path': {
|
|
50
|
+
target: 'node',
|
|
51
|
+
cardinality: 'one',
|
|
52
|
+
accepts: [{ origin: 'example.dev', kind: 'class', name: 'Item' }],
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
required: [],
|
|
57
|
+
},
|
|
45
58
|
output: { mode: 'stream', item: { type: 'integer' } },
|
|
46
59
|
auth: 'authenticated',
|
|
47
60
|
},
|
|
@@ -63,8 +76,8 @@ describe('anchor descriptions', () => {
|
|
|
63
76
|
})
|
|
64
77
|
|
|
65
78
|
test('describes standalone Functions with output mode and authentication', () => {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
)
|
|
79
|
+
const description = describeAnchor('function.inspect', ir, undefined)
|
|
80
|
+
expect(description).toContain('inspect(cursor:→node?)')
|
|
81
|
+
expect(description).toContain('stream<int> [authenticated]')
|
|
69
82
|
})
|
|
70
83
|
})
|
|
@@ -14,6 +14,8 @@ import type {
|
|
|
14
14
|
SchemaOverlay,
|
|
15
15
|
} from '../../../shared/types'
|
|
16
16
|
|
|
17
|
+
import { isNodePathSchema } from '../../../shared/types'
|
|
18
|
+
|
|
17
19
|
/** Terse JSON-Schema type label (mirrors the client's format.tsx describe/typeLabel). */
|
|
18
20
|
function propType(s: JsonSchema | undefined, optionalOverride?: boolean): string {
|
|
19
21
|
if (!s) return 'any'
|
|
@@ -22,8 +24,7 @@ function propType(s: JsonSchema | undefined, optionalOverride?: boolean): string
|
|
|
22
24
|
const base = Array.isArray(t) ? t.find((x) => x !== 'null') : t
|
|
23
25
|
let label: string
|
|
24
26
|
if (s.enum) label = `enum(${s.enum.map(String).join('|')})`
|
|
25
|
-
else if (s
|
|
26
|
-
else if (s.$dataRef) label = '→data'
|
|
27
|
+
else if (isNodePathSchema(s)) label = '→node'
|
|
27
28
|
else if (base === 'array') label = `${propType(s.items)}[]`
|
|
28
29
|
else if (base === 'object') label = `{${Object.keys(s.properties ?? {}).join(',')}}`
|
|
29
30
|
else if (base === 'integer') label = 'int'
|
|
@@ -2,12 +2,11 @@ import { expect, test } from 'bun:test'
|
|
|
2
2
|
|
|
3
3
|
import { buildSystemPrompt } from './system'
|
|
4
4
|
|
|
5
|
-
test('embedded agents receive the current SDK
|
|
5
|
+
test('embedded agents receive the current SDK and frontend skill contracts', () => {
|
|
6
6
|
const prompt = buildSystemPrompt({ bridge: false })
|
|
7
7
|
|
|
8
8
|
expect(prompt).toContain('modular Actions and Workflows')
|
|
9
|
-
expect(prompt).toContain('
|
|
9
|
+
expect(prompt).toContain('astrale-frontend-design')
|
|
10
10
|
expect(prompt).toContain('Runtime/Application entries')
|
|
11
|
-
expect(prompt).toContain('do not restore defineDomain or Interface-era layouts')
|
|
12
11
|
expect(prompt).not.toContain('schema/ runtime/ views/')
|
|
13
12
|
})
|
|
@@ -25,12 +25,12 @@ export function buildSystemPrompt(options: { bridge: boolean }): string {
|
|
|
25
25
|
'Operating rules (when a thread genuinely calls for a code change):',
|
|
26
26
|
'- Read the skills under `.agents/skills/` FIRST and follow them: **astrale-domain**',
|
|
27
27
|
' (schema modeling, handlers, views, deploy/install) before editing schema/handlers,',
|
|
28
|
-
'
|
|
29
|
-
' the
|
|
28
|
+
' **astrale-frontend-design** before editing a Domain UI, and **astrale-cli** when you',
|
|
29
|
+
' run the `astrale` CLI. Treat those current skills and the project itself as authoritative.',
|
|
30
30
|
'- Prefer the project’s existing semantic modules over inventing new structure. Current',
|
|
31
31
|
' SDK projects author Schema definitions, modular Actions and Workflows, a Runtime,',
|
|
32
32
|
' and an Application. Wire modules through their semantic SDK facades and explicit',
|
|
33
|
-
' Runtime/Application entries
|
|
33
|
+
' Runtime/Application entries.',
|
|
34
34
|
'- The studio re-renders automatically as you save files — never start, build, or refresh',
|
|
35
35
|
' anything for the UI to update.',
|
|
36
36
|
'- Sanity-check schema/handler edits with `pnpm typecheck` (or `tsgo --noEmit`).',
|
|
@@ -94,7 +94,7 @@ export async function prepareRun(
|
|
|
94
94
|
bareResume && !firstTurn
|
|
95
95
|
? buildResumePrompt()
|
|
96
96
|
: buildTurnPrompt({
|
|
97
|
-
origin: bundle?.
|
|
97
|
+
origin: bundle?.ir?.domain ?? handle.origin ?? domainId,
|
|
98
98
|
root,
|
|
99
99
|
renderFingerprint,
|
|
100
100
|
schemaRevision: bundle?.schemaRevision,
|
|
@@ -43,7 +43,7 @@ export async function handleContextRoute(context: DomainRouteContext): Promise<R
|
|
|
43
43
|
const storedContext = readContext(root)
|
|
44
44
|
const openComments = readComments(root).comments.filter((comment) => comment.status === 'open')
|
|
45
45
|
const markdown = buildCopyMarkdown({
|
|
46
|
-
origin: bundle?.
|
|
46
|
+
origin: bundle?.ir?.domain ?? handle.origin ?? id,
|
|
47
47
|
root,
|
|
48
48
|
renderFingerprint: bundle?.renderFingerprint ?? '',
|
|
49
49
|
schemaRevision: bundle?.schemaRevision,
|
|
@@ -24,7 +24,7 @@ export async function handleViewRoute(context: DomainRouteContext): Promise<Resp
|
|
|
24
24
|
const view = anatomy?.views.find((candidate) => candidate.slug === slug)
|
|
25
25
|
if (!view) return notFound()
|
|
26
26
|
const bundle = await getBundle(id)
|
|
27
|
-
const origin = bundle?.
|
|
27
|
+
const origin = bundle?.ir?.domain || anatomy?.overview.origin
|
|
28
28
|
if (!origin) return badRequest('domain origin is unavailable')
|
|
29
29
|
return json(
|
|
30
30
|
await getViewRuntime(root, origin, view, bundle, readSettings(root).viewProbeTimeoutMs),
|
|
@@ -39,7 +39,7 @@ export async function handleViewRoute(context: DomainRouteContext): Promise<Resp
|
|
|
39
39
|
const view = anatomy?.views.find((candidate) => candidate.slug === slug)
|
|
40
40
|
if (!view) return notFound()
|
|
41
41
|
const bundle = await getBundle(id)
|
|
42
|
-
const origin = bundle?.
|
|
42
|
+
const origin = bundle?.ir?.domain || anatomy?.overview.origin
|
|
43
43
|
if (!origin) return badRequest('domain origin is unavailable')
|
|
44
44
|
return json(
|
|
45
45
|
await launchViewSession(
|
|
@@ -18,7 +18,7 @@ export async function handleWorkspaceRoute(
|
|
|
18
18
|
const bundle = await getBundle(handle.id)
|
|
19
19
|
return {
|
|
20
20
|
id: handle.id,
|
|
21
|
-
origin: bundle?.
|
|
21
|
+
origin: bundle?.ir?.domain || handle.origin || handle.id,
|
|
22
22
|
path: handle.root,
|
|
23
23
|
schemaDir: handle.schemaDirName,
|
|
24
24
|
depsInstalled: depsInstalled(handle.root),
|
|
@@ -4,9 +4,9 @@ import { decodeBundleCacheEntry } from './cache'
|
|
|
4
4
|
|
|
5
5
|
function entry(): Record<string, unknown> {
|
|
6
6
|
return {
|
|
7
|
-
version:
|
|
7
|
+
version: 6,
|
|
8
8
|
key: 'cache-key',
|
|
9
|
-
futureEntryField: { version:
|
|
9
|
+
futureEntryField: { version: 6 },
|
|
10
10
|
bundle: {
|
|
11
11
|
domainId: 'notes',
|
|
12
12
|
renderFingerprint: 'render-hash',
|
|
@@ -27,13 +27,8 @@ function entry(): Record<string, unknown> {
|
|
|
27
27
|
core: {},
|
|
28
28
|
},
|
|
29
29
|
overlay: {
|
|
30
|
-
origin: 'notes.example.dev',
|
|
31
|
-
requires: [],
|
|
32
|
-
crossDomainImports: [],
|
|
33
|
-
mixins: [],
|
|
34
30
|
handlerLinks: [],
|
|
35
31
|
sourceSpans: {},
|
|
36
|
-
annotations: [],
|
|
37
32
|
},
|
|
38
33
|
schemaRoot: {
|
|
39
34
|
format: 'astrale.dsl',
|
|
@@ -51,7 +46,7 @@ test('bundle cache admits known structure while ignoring future fields', () => {
|
|
|
51
46
|
const decoded = decodeBundleCacheEntry(entry())
|
|
52
47
|
|
|
53
48
|
expect(decoded).toMatchObject({
|
|
54
|
-
version:
|
|
49
|
+
version: 6,
|
|
55
50
|
key: 'cache-key',
|
|
56
51
|
bundle: { domainId: 'notes', schemaMode: 'canonical-preview' },
|
|
57
52
|
})
|
package/studio/server/cache.ts
CHANGED
|
@@ -17,16 +17,15 @@ import { buildBundle } from './introspect/bundle'
|
|
|
17
17
|
import { isCanonicalDomainSchemaV1 } from './introspect/canonical-schema'
|
|
18
18
|
import { buildCore } from './introspect/core'
|
|
19
19
|
import { decodeSchemaIR } from './introspect/schema-ir-json'
|
|
20
|
-
import { asBoolean, asFiniteNumber, asJsonRecord, asString
|
|
20
|
+
import { asBoolean, asFiniteNumber, asJsonRecord, asString } from './json'
|
|
21
21
|
import { hashAnatomyFiles } from './state/baseline'
|
|
22
22
|
import { readJson, writeJson } from './state/store'
|
|
23
23
|
|
|
24
24
|
const bundles = new Map<string, StudioSchemaBundle>()
|
|
25
25
|
const anatomies = new Map<string, Promise<DomainAnatomy>>()
|
|
26
|
-
const cores = new Map<string, StudioCore>()
|
|
27
26
|
|
|
28
27
|
const BUNDLE_CACHE_FILE = '.cache/schema-bundle.json'
|
|
29
|
-
const BUNDLE_CACHE_VERSION =
|
|
28
|
+
const BUNDLE_CACHE_VERSION = 6
|
|
30
29
|
const LOCKFILES = ['bun.lock', 'pnpm-lock.yaml', 'package-lock.json', 'yarn.lock']
|
|
31
30
|
const TOOL_INPUTS = [
|
|
32
31
|
'cache.ts',
|
|
@@ -37,7 +36,6 @@ const TOOL_INPUTS = [
|
|
|
37
36
|
'introspect/canonical-schema.ts',
|
|
38
37
|
'introspect/overlay.ts',
|
|
39
38
|
'introspect/overlay-tsmorph.ts',
|
|
40
|
-
'introspect/source-overlay/annotations.ts',
|
|
41
39
|
'introspect/source-overlay/handlers.ts',
|
|
42
40
|
'introspect/source-overlay/kernel-calls.ts',
|
|
43
41
|
'introspect/source-overlay/project.ts',
|
|
@@ -53,18 +51,6 @@ interface BundleCacheEntry {
|
|
|
53
51
|
bundle: StudioSchemaBundle
|
|
54
52
|
}
|
|
55
53
|
|
|
56
|
-
function isCrossDomainImport(value: unknown): boolean {
|
|
57
|
-
const record = asJsonRecord(value)
|
|
58
|
-
const ref = asJsonRecord(record?.ref)
|
|
59
|
-
return (
|
|
60
|
-
typeof record?.name === 'string' &&
|
|
61
|
-
typeof record.origin === 'string' &&
|
|
62
|
-
ref?.kind === 'class' &&
|
|
63
|
-
typeof ref.origin === 'string' &&
|
|
64
|
-
typeof ref.name === 'string'
|
|
65
|
-
)
|
|
66
|
-
}
|
|
67
|
-
|
|
68
54
|
function isHandlerLink(value: unknown): boolean {
|
|
69
55
|
const record = asJsonRecord(value)
|
|
70
56
|
return (
|
|
@@ -87,37 +73,15 @@ function isSourceSpan(value: unknown): boolean {
|
|
|
87
73
|
)
|
|
88
74
|
}
|
|
89
75
|
|
|
90
|
-
function isAnnotation(value: unknown): boolean {
|
|
91
|
-
const record = asJsonRecord(value)
|
|
92
|
-
return (
|
|
93
|
-
typeof record?.target === 'string' &&
|
|
94
|
-
(record.severity === 'warn' || record.severity === 'info') &&
|
|
95
|
-
(record.code === 'COMPILE_ERROR' || record.code === 'EDGE_PROP_TYPE_MISSING') &&
|
|
96
|
-
typeof record.message === 'string'
|
|
97
|
-
)
|
|
98
|
-
}
|
|
99
|
-
|
|
100
76
|
function decodeOverlay(value: unknown): SchemaOverlay | undefined {
|
|
101
77
|
const record = asJsonRecord(value)
|
|
102
|
-
const requires = asStringArray(record?.requires)
|
|
103
|
-
const crossDomainImports = record?.crossDomainImports
|
|
104
|
-
const mixins = record?.mixins
|
|
105
78
|
const handlerLinks = record?.handlerLinks
|
|
106
79
|
const sourceSpans = asJsonRecord(record?.sourceSpans)
|
|
107
|
-
const annotations = record?.annotations
|
|
108
80
|
if (
|
|
109
|
-
typeof record?.origin !== 'string' ||
|
|
110
|
-
!requires ||
|
|
111
|
-
!Array.isArray(crossDomainImports) ||
|
|
112
|
-
!crossDomainImports.every(isCrossDomainImport) ||
|
|
113
|
-
!Array.isArray(mixins) ||
|
|
114
|
-
!mixins.every(isCrossDomainImport) ||
|
|
115
81
|
!Array.isArray(handlerLinks) ||
|
|
116
82
|
!handlerLinks.every(isHandlerLink) ||
|
|
117
83
|
!sourceSpans ||
|
|
118
|
-
!Object.values(sourceSpans).every(isSourceSpan)
|
|
119
|
-
!Array.isArray(annotations) ||
|
|
120
|
-
!annotations.every(isAnnotation)
|
|
84
|
+
!Object.values(sourceSpans).every(isSourceSpan)
|
|
121
85
|
) {
|
|
122
86
|
return undefined
|
|
123
87
|
}
|
|
@@ -285,10 +249,7 @@ export async function getAnatomy(id: string, rebuild = false): Promise<DomainAna
|
|
|
285
249
|
export async function getCore(id: string, rebuild = false): Promise<StudioCore | null> {
|
|
286
250
|
const h = getDomain(id)
|
|
287
251
|
if (!h) return null
|
|
288
|
-
|
|
289
|
-
const c = await buildCore(h)
|
|
290
|
-
cores.set(id, c)
|
|
291
|
-
return c
|
|
252
|
+
return buildCore(h, await getBundle(id, rebuild))
|
|
292
253
|
}
|
|
293
254
|
|
|
294
255
|
export function invalidate(id: string, what: 'schema' | 'anatomy' | 'all'): void {
|
|
@@ -298,6 +259,4 @@ export function invalidate(id: string, what: 'schema' | 'anatomy' | 'all'): void
|
|
|
298
259
|
const domain = getDomain(id)
|
|
299
260
|
if (domain) invalidateClientPackage(domain.root)
|
|
300
261
|
}
|
|
301
|
-
// Core is derived from the canonical Schema and belongs to the anatomy set.
|
|
302
|
-
if (what !== 'schema') cores.delete(id)
|
|
303
262
|
}
|
|
@@ -84,7 +84,7 @@ export function buildCopyMarkdown(parts: CopyParts): string {
|
|
|
84
84
|
lines.push(`- **Render fingerprint:** \`${renderFingerprint}\``)
|
|
85
85
|
lines.push('')
|
|
86
86
|
lines.push(
|
|
87
|
-
'Load the **astrale-domain** skill,
|
|
87
|
+
'Load the **astrale-domain** skill and, for UI work, **astrale-frontend-design**. Use the context/threads below, ' +
|
|
88
88
|
'read any listed context documents if relevant, edit code on disk, and reply by appending one `{role:"author"}` entry per open thread ' +
|
|
89
89
|
'(merge by `id`). Resolve a thread by adding a closing note plus `"status":"closed"`; ' +
|
|
90
90
|
'a thread whose last entry is not yours will be resent.',
|
|
@@ -1,108 +1,103 @@
|
|
|
1
|
+
import * as sdk from '@astrale-os/sdk/schema'
|
|
2
|
+
import {
|
|
3
|
+
core,
|
|
4
|
+
defineSchema,
|
|
5
|
+
edgeClass,
|
|
6
|
+
func,
|
|
7
|
+
method,
|
|
8
|
+
nodeClass,
|
|
9
|
+
output,
|
|
10
|
+
property,
|
|
11
|
+
valueSchema,
|
|
12
|
+
view,
|
|
13
|
+
} from '@astrale-os/sdk/schema'
|
|
1
14
|
import { describe, expect, test } from 'bun:test'
|
|
2
15
|
|
|
3
16
|
import {
|
|
4
|
-
|
|
5
|
-
closureFromSdk,
|
|
17
|
+
extractCanonicalSchemaFromSdk,
|
|
6
18
|
findCanonicalDomainSchemaExport,
|
|
7
19
|
isCanonicalDomainSchemaV1,
|
|
8
20
|
projectCanonicalCore,
|
|
9
|
-
projectCanonicalSchema,
|
|
10
21
|
type CanonicalDomainSchemaV1,
|
|
11
22
|
} from './canonical-schema'
|
|
12
23
|
|
|
13
|
-
const
|
|
14
|
-
const
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
methods: {},
|
|
26
|
-
},
|
|
27
|
-
},
|
|
28
|
-
} satisfies CanonicalDomainSchemaV1
|
|
24
|
+
const string = valueSchema<string>()({ type: 'string' })
|
|
25
|
+
const boolean = valueSchema<boolean>()({ type: 'boolean' })
|
|
26
|
+
const rename = method({
|
|
27
|
+
input: valueSchema<{ title: string }>()({
|
|
28
|
+
type: 'object',
|
|
29
|
+
properties: { title: { type: 'string' } },
|
|
30
|
+
required: ['title'],
|
|
31
|
+
additionalProperties: false,
|
|
32
|
+
}),
|
|
33
|
+
output: boolean,
|
|
34
|
+
auth: 'authorized',
|
|
35
|
+
})
|
|
29
36
|
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
},
|
|
49
|
-
output: { mode: 'value', schema: { type: 'boolean' } },
|
|
50
|
-
auth: 'authorized',
|
|
51
|
-
static: false,
|
|
52
|
-
inheritance: 'default',
|
|
53
|
-
},
|
|
54
|
-
},
|
|
55
|
-
},
|
|
56
|
-
links: {
|
|
57
|
-
kind: 'edge',
|
|
58
|
-
orientation: 'directed',
|
|
59
|
-
endpoints: {
|
|
60
|
-
source: { role: 'source', accepts: [localRef], outgoing: '1' },
|
|
61
|
-
target: { role: 'target', accepts: [baseRef], incoming: '0..*' },
|
|
62
|
-
},
|
|
63
|
-
properties: {},
|
|
64
|
-
methods: {},
|
|
65
|
-
},
|
|
66
|
-
},
|
|
37
|
+
const Named = nodeClass({ properties: { title: string } })
|
|
38
|
+
const dependency = defineSchema('shared.example.dev', { classes: { Named } })
|
|
39
|
+
const Document = nodeClass({
|
|
40
|
+
description: 'A document.',
|
|
41
|
+
icon: '<svg />',
|
|
42
|
+
extends: [Named],
|
|
43
|
+
properties: { slug: property(string, { description: 'Stable slug.' }) },
|
|
44
|
+
methods: { rename },
|
|
45
|
+
})
|
|
46
|
+
const links = edgeClass.directed({
|
|
47
|
+
source: { as: 'source', accepts: [Document], outgoing: '1' },
|
|
48
|
+
target: { as: 'target', accepts: [Named], incoming: '0..*' },
|
|
49
|
+
})
|
|
50
|
+
const primary = core.node(Document, { slug: 'primary', title: 'Primary' })
|
|
51
|
+
const primaryLink = core.edge(primary, links, primary, {})
|
|
52
|
+
const schema = defineSchema('docs.example.dev', {
|
|
53
|
+
dependencies: { shared: dependency },
|
|
54
|
+
classes: { Document, links },
|
|
67
55
|
functions: {
|
|
68
|
-
search: {
|
|
69
|
-
input: { type: 'object', properties: { text: { type: 'string' } }, required: ['text'] },
|
|
70
|
-
output: { mode: 'stream', item: { type: 'string' } },
|
|
71
|
-
auth: 'authenticated',
|
|
72
|
-
},
|
|
73
|
-
},
|
|
74
|
-
views: {
|
|
75
|
-
documents: {
|
|
76
|
-
target: { kind: 'definition', definitions: [localRef] },
|
|
77
|
-
},
|
|
56
|
+
search: func({ input: string, output: output.stream(string), auth: 'authenticated' }),
|
|
78
57
|
},
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
source: { origin: 'docs.example.dev', kind: 'core', name: 'primary' },
|
|
87
|
-
target: { kind: 'domain', origin: 'docs.example.dev' },
|
|
88
|
-
},
|
|
89
|
-
],
|
|
90
|
-
},
|
|
91
|
-
} satisfies CanonicalDomainSchemaV1
|
|
58
|
+
views: { documents: view({ target: Document }) },
|
|
59
|
+
core: { nodes: { primary }, edges: [primaryLink] },
|
|
60
|
+
})
|
|
61
|
+
const namedRef = { origin: dependency.origin, kind: 'class', name: 'Named' } as const
|
|
62
|
+
const documentRef = { origin: schema.origin, kind: 'class', name: 'Document' } as const
|
|
63
|
+
|
|
64
|
+
const sdkModule = sdk
|
|
92
65
|
|
|
93
66
|
describe('canonical Schema projection', () => {
|
|
94
|
-
test('recognizes and
|
|
95
|
-
expect(isCanonicalDomainSchemaV1(
|
|
96
|
-
expect(
|
|
97
|
-
|
|
67
|
+
test('recognizes exported V1 roots and preserves an invalid root as a preview', () => {
|
|
68
|
+
expect(isCanonicalDomainSchemaV1(schema)).toBe(true)
|
|
69
|
+
expect(findCanonicalDomainSchemaExport({ other: dependency, schema })).toBe(schema)
|
|
70
|
+
|
|
71
|
+
const invalid = {
|
|
72
|
+
format: 'astrale.dsl',
|
|
73
|
+
version: 'v1',
|
|
74
|
+
origin: 'not an origin',
|
|
75
|
+
dependencies: {},
|
|
76
|
+
classes: {},
|
|
77
|
+
functions: {},
|
|
78
|
+
policies: {},
|
|
79
|
+
views: {},
|
|
80
|
+
core: { nodes: {}, edges: [] },
|
|
81
|
+
} as CanonicalDomainSchemaV1
|
|
82
|
+
expect(extractCanonicalSchemaFromSdk(sdkModule, invalid)).toMatchObject({
|
|
83
|
+
status: 'preview',
|
|
84
|
+
revision: null,
|
|
85
|
+
ir: { domain: 'not an origin' },
|
|
86
|
+
})
|
|
98
87
|
})
|
|
99
88
|
|
|
100
|
-
test('
|
|
101
|
-
const
|
|
102
|
-
expect(
|
|
103
|
-
|
|
104
|
-
|
|
89
|
+
test('uses the resolved Domain for Classes, dependency footprint, callables, and Views', () => {
|
|
90
|
+
const extraction = extractCanonicalSchemaFromSdk(sdkModule, schema)
|
|
91
|
+
expect(extraction.status).toBe('admitted')
|
|
92
|
+
expect(extraction.revision).toMatch(/^sha256:[0-9a-f]{64}$/)
|
|
93
|
+
expect(extraction.ir.classes.Document).toMatchObject({
|
|
94
|
+
icon: '<svg />',
|
|
95
|
+
extendsRefs: [namedRef],
|
|
96
|
+
properties: { slug: { type: 'string' } },
|
|
105
97
|
required: ['slug'],
|
|
98
|
+
propertyMetadata: {
|
|
99
|
+
slug: { required: true, visibility: 'public', description: 'Stable slug.' },
|
|
100
|
+
},
|
|
106
101
|
methods: {
|
|
107
102
|
rename: {
|
|
108
103
|
input: { required: ['title'] },
|
|
@@ -111,69 +106,100 @@ describe('canonical Schema projection', () => {
|
|
|
111
106
|
},
|
|
112
107
|
},
|
|
113
108
|
})
|
|
114
|
-
expect(ir.importsByKey['shared.example.dev:class.Named']).toEqual({
|
|
115
|
-
origin:
|
|
116
|
-
ref:
|
|
109
|
+
expect(extraction.ir.importsByKey['shared.example.dev:class.Named']).toEqual({
|
|
110
|
+
origin: namedRef.origin,
|
|
111
|
+
ref: namedRef,
|
|
117
112
|
key: 'shared.example.dev:class.Named',
|
|
118
113
|
})
|
|
119
|
-
expect(
|
|
120
|
-
|
|
114
|
+
expect(
|
|
115
|
+
extraction.ir.importedClassesByKey['shared.example.dev:class.Named']?.properties.title,
|
|
116
|
+
).toEqual({ type: 'string' })
|
|
117
|
+
expect(extraction.ir.classes.links.endpoints).toEqual([
|
|
118
|
+
{
|
|
119
|
+
name: 'source',
|
|
120
|
+
types: ['Document'],
|
|
121
|
+
refs: [documentRef],
|
|
122
|
+
cardinality: { min: 1, max: 1 },
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
name: 'target',
|
|
126
|
+
types: ['Named'],
|
|
127
|
+
refs: [namedRef],
|
|
128
|
+
cardinality: { min: 0, max: null },
|
|
129
|
+
},
|
|
130
|
+
])
|
|
131
|
+
expect(extraction.ir.functions.search).toMatchObject({
|
|
132
|
+
auth: 'authenticated',
|
|
133
|
+
output: { mode: 'stream' },
|
|
134
|
+
})
|
|
135
|
+
expect(extraction.ir.views.documents.target).toEqual({
|
|
136
|
+
kind: 'definition',
|
|
137
|
+
definitions: [documentRef],
|
|
121
138
|
})
|
|
122
|
-
expect(ir.
|
|
123
|
-
{
|
|
124
|
-
{ name: 'target', types: ['Named'], refs: [baseRef], cardinality: { min: 0, max: null } },
|
|
139
|
+
expect(extraction.ir.dependencies).toEqual([
|
|
140
|
+
{ origin: dependency.origin, revision: sdk.schema.revision(dependency) },
|
|
125
141
|
])
|
|
126
|
-
expect(ir.functions.search).toMatchObject({ auth: 'authenticated', output: { mode: 'stream' } })
|
|
127
|
-
expect(ir.views?.documents.target).toEqual({ kind: 'definition', definitions: [localRef] })
|
|
128
|
-
expect(ir.views?.documents).not.toHaveProperty('auth')
|
|
129
142
|
})
|
|
130
143
|
|
|
131
|
-
test('projects
|
|
132
|
-
|
|
133
|
-
|
|
144
|
+
test('projects Core from the same admitted root without a second Schema import', () => {
|
|
145
|
+
const extraction = extractCanonicalSchemaFromSdk(sdkModule, schema)
|
|
146
|
+
expect(projectCanonicalCore(extraction.root)).toEqual({
|
|
147
|
+
domain: schema.origin,
|
|
134
148
|
nodes: [
|
|
135
149
|
{
|
|
136
150
|
path: '/:docs.example.dev:core.primary',
|
|
137
151
|
className: 'Document',
|
|
138
|
-
data: {
|
|
152
|
+
data: {
|
|
153
|
+
'docs.example.dev:class.Document.property.slug': 'primary',
|
|
154
|
+
'shared.example.dev:class.Named.property.title': 'Primary',
|
|
155
|
+
},
|
|
139
156
|
},
|
|
140
157
|
],
|
|
141
158
|
edges: [
|
|
142
159
|
{
|
|
143
160
|
from: '/:docs.example.dev:core.primary',
|
|
144
|
-
to: '/:docs.example.dev',
|
|
161
|
+
to: '/:docs.example.dev:core.primary',
|
|
145
162
|
edgeName: 'links',
|
|
163
|
+
data: {},
|
|
146
164
|
},
|
|
147
165
|
],
|
|
148
166
|
})
|
|
149
167
|
})
|
|
150
168
|
|
|
151
|
-
test('
|
|
152
|
-
const
|
|
153
|
-
const
|
|
154
|
-
|
|
155
|
-
bundle: {
|
|
169
|
+
test('delegates admission, resolution, and dependency reachability to the DSL', () => {
|
|
170
|
+
const calls: string[] = []
|
|
171
|
+
const wrapped = {
|
|
172
|
+
...sdk,
|
|
173
|
+
bundle: {
|
|
174
|
+
...sdk.bundle,
|
|
175
|
+
create(value: typeof schema) {
|
|
176
|
+
calls.push('bundle.create')
|
|
177
|
+
return sdk.bundle.create(value)
|
|
178
|
+
},
|
|
179
|
+
accept(value: unknown) {
|
|
180
|
+
calls.push('bundle.accept')
|
|
181
|
+
return sdk.bundle.accept(value)
|
|
182
|
+
},
|
|
183
|
+
},
|
|
156
184
|
schema: {
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
185
|
+
...sdk.schema,
|
|
186
|
+
resolve(value: typeof schema) {
|
|
187
|
+
calls.push('schema.resolve')
|
|
188
|
+
return sdk.schema.resolve(value)
|
|
189
|
+
},
|
|
190
|
+
compareDependencyMeaning(source: typeof schema, target: typeof dependency) {
|
|
191
|
+
calls.push('schema.compareDependencyMeaning')
|
|
192
|
+
return sdk.schema.compareDependencyMeaning(source, target)
|
|
193
|
+
},
|
|
160
194
|
},
|
|
161
|
-
}
|
|
162
|
-
expect(closureFromSdk(sdk, accepted)).toEqual([dependency])
|
|
163
|
-
expect(admitCanonicalSchemaFromSdk(sdk, accepted)).toEqual({
|
|
164
|
-
status: 'admitted',
|
|
165
|
-
root: accepted,
|
|
166
|
-
closure: [dependency],
|
|
167
|
-
revision,
|
|
168
|
-
})
|
|
169
|
-
})
|
|
195
|
+
} as unknown as typeof sdk
|
|
170
196
|
|
|
171
|
-
|
|
172
|
-
expect(
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
197
|
+
expect(extractCanonicalSchemaFromSdk(wrapped, schema).status).toBe('admitted')
|
|
198
|
+
expect(calls).toEqual([
|
|
199
|
+
'bundle.create',
|
|
200
|
+
'bundle.accept',
|
|
201
|
+
'schema.resolve',
|
|
202
|
+
'schema.compareDependencyMeaning',
|
|
203
|
+
])
|
|
178
204
|
})
|
|
179
205
|
})
|