@astrale-os/cli 1.0.0-beta.0 → 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 +3769 -3549
- 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 +5 -3
- package/src/admin/instance/client.ts +9 -27
- 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 +73 -0
- 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 +39 -13
- 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
package/dist/types/lib/idp.d.ts
CHANGED
|
@@ -162,6 +162,8 @@ export declare const IdpSessionSchema: z.ZodObject<{
|
|
|
162
162
|
idp: z.ZodString;
|
|
163
163
|
issuer: z.ZodString;
|
|
164
164
|
subject: z.ZodString;
|
|
165
|
+
/** OAuth client that issued this session's refresh token. */
|
|
166
|
+
clientId: z.ZodOptional<z.ZodString>;
|
|
165
167
|
audience: z.ZodOptional<z.ZodString>;
|
|
166
168
|
organizationId: z.ZodOptional<z.ZodString>;
|
|
167
169
|
access_token: z.ZodString;
|
|
@@ -194,6 +196,8 @@ export declare const IdpSessionSchema: z.ZodObject<{
|
|
|
194
196
|
idp: z.ZodString;
|
|
195
197
|
issuer: z.ZodString;
|
|
196
198
|
subject: z.ZodString;
|
|
199
|
+
/** OAuth client that issued this session's refresh token. */
|
|
200
|
+
clientId: z.ZodOptional<z.ZodString>;
|
|
197
201
|
audience: z.ZodOptional<z.ZodString>;
|
|
198
202
|
organizationId: z.ZodOptional<z.ZodString>;
|
|
199
203
|
access_token: z.ZodString;
|
|
@@ -226,6 +230,8 @@ export declare const IdpSessionSchema: z.ZodObject<{
|
|
|
226
230
|
idp: z.ZodString;
|
|
227
231
|
issuer: z.ZodString;
|
|
228
232
|
subject: z.ZodString;
|
|
233
|
+
/** OAuth client that issued this session's refresh token. */
|
|
234
|
+
clientId: z.ZodOptional<z.ZodString>;
|
|
229
235
|
audience: z.ZodOptional<z.ZodString>;
|
|
230
236
|
organizationId: z.ZodOptional<z.ZodString>;
|
|
231
237
|
access_token: z.ZodString;
|
package/dist/types/lib/meta.d.ts
CHANGED
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
* `jwks_uri`) and JWKS at `/.well-known/jwks.json`. These helpers probe
|
|
7
7
|
* both to verify the issuer is alive and publishes at least one key.
|
|
8
8
|
*
|
|
9
|
-
* Domain
|
|
10
|
-
* outside this
|
|
9
|
+
* Domain deployment readiness uses the canonical Domain Publication; that is
|
|
10
|
+
* outside this issuer-connectivity helper.
|
|
11
11
|
*/
|
|
12
12
|
type FetchLike = (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
|
|
13
13
|
export type DiscoveryDocument = {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@astrale-os/cli",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.2",
|
|
4
4
|
"description": "Astrale CLI — connect to existing Astrale kernels",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"astrale",
|
|
@@ -62,9 +62,10 @@
|
|
|
62
62
|
"zod": "^3.25.0"
|
|
63
63
|
},
|
|
64
64
|
"devDependencies": {
|
|
65
|
-
"@astrale-os/kernel-client": ">=0.6.0-beta.
|
|
65
|
+
"@astrale-os/kernel-client": ">=0.6.0-beta.4 <1.0.0",
|
|
66
|
+
"@astrale-os/kernel-protocol": ">=0.5.0-beta.3 <1.0.0",
|
|
66
67
|
"@astrale-os/ox": ">=0.1.0 <1.0.0",
|
|
67
|
-
"@astrale-os/sdk": ">=0.5.0-beta.
|
|
68
|
+
"@astrale-os/sdk": ">=0.5.0-beta.4 <1.0.0",
|
|
68
69
|
"@astrale-os/shell": ">=0.3.8-beta.2 <1.0.0",
|
|
69
70
|
"@astrale/commitlint-config": "npm:@jsr/astrale__commitlint-config@~2.0.1",
|
|
70
71
|
"@commitlint/cli": "~20.3.1",
|
|
@@ -107,7 +108,8 @@
|
|
|
107
108
|
"lint:fix": "oxlint --fix .",
|
|
108
109
|
"format": "pnpm exec oxfmt --write .",
|
|
109
110
|
"format:check": "pnpm exec oxfmt --check .",
|
|
110
|
-
"test": "bun test src && node --test scripts/*.test.mjs",
|
|
111
|
+
"test": "bun test src studio/client/src studio/server studio/shared && node --test scripts/*.test.mjs",
|
|
112
|
+
"test:studio": "pnpm --dir studio test",
|
|
111
113
|
"test:watch": "bun test --watch src"
|
|
112
114
|
}
|
|
113
115
|
}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import type { Input, Result } from '@astrale-os/kernel-client'
|
|
2
|
+
import type {
|
|
3
|
+
ClientSession,
|
|
4
|
+
SessionRequestOptions,
|
|
5
|
+
SessionSnapshot,
|
|
6
|
+
} from '@astrale-os/kernel-client/session'
|
|
7
|
+
import type {
|
|
8
|
+
Definition,
|
|
9
|
+
Domain as RichDomain,
|
|
10
|
+
InstanceMethod,
|
|
11
|
+
StaticMethod,
|
|
12
|
+
} from '@astrale-os/sdk/domain'
|
|
13
|
+
import type { PathLike } from '@astrale-os/sdk/graph/path'
|
|
14
|
+
import type { Key } from '@astrale-os/sdk/schema'
|
|
15
|
+
|
|
16
|
+
import { call } from '@astrale-os/kernel-client'
|
|
17
|
+
import * as sessionModule from '@astrale-os/kernel-client/session'
|
|
18
|
+
import { Domain } from '@astrale-os/sdk/domain'
|
|
19
|
+
import { Path } from '@astrale-os/sdk/graph/path'
|
|
20
|
+
|
|
21
|
+
const ADMIN_ORIGIN = 'admin.astrale.ai'
|
|
22
|
+
|
|
23
|
+
type DynamicCallable =
|
|
24
|
+
| (Definition<'function'> & { readonly path: NonNullable<Definition<'function'>['path']> })
|
|
25
|
+
| StaticMethod
|
|
26
|
+
| InstanceMethod
|
|
27
|
+
|
|
28
|
+
export interface AdminBindingOperations {
|
|
29
|
+
readonly installation: unknown
|
|
30
|
+
readonly publication: SessionSnapshot['publication']
|
|
31
|
+
invoke(
|
|
32
|
+
callable: DynamicCallable,
|
|
33
|
+
receiverOrInput: PathLike | Input,
|
|
34
|
+
inputOrOptions?: Input | SessionRequestOptions,
|
|
35
|
+
options?: SessionRequestOptions,
|
|
36
|
+
): Promise<unknown>
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export type AdminBinding = RichDomain & {
|
|
40
|
+
readonly $: RichDomain['$'] & AdminBindingOperations
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
interface CompatibleCallableReference {
|
|
44
|
+
readonly origin: typeof ADMIN_ORIGIN
|
|
45
|
+
readonly revision: SessionSnapshot['publication']['schema']['revision']
|
|
46
|
+
readonly callable: Key.Callable
|
|
47
|
+
readonly target: PathLike
|
|
48
|
+
readonly outputMode: DynamicCallable['definition']['output']['mode']
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
interface CompatibleSession {
|
|
52
|
+
readonly installation?: (origin: typeof ADMIN_ORIGIN) => Promise<unknown>
|
|
53
|
+
readonly installed?: (origin: typeof ADMIN_ORIGIN) => Promise<unknown>
|
|
54
|
+
readonly invoke?: (
|
|
55
|
+
reference: CompatibleCallableReference,
|
|
56
|
+
input: Input,
|
|
57
|
+
options?: SessionRequestOptions,
|
|
58
|
+
) => Promise<unknown>
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
type LegacyDispatchBound = (
|
|
62
|
+
session: ClientSession,
|
|
63
|
+
request: ReturnType<typeof call>,
|
|
64
|
+
expected: {
|
|
65
|
+
readonly schema: { readonly revision: CompatibleCallableReference['revision'] }
|
|
66
|
+
readonly publication: {
|
|
67
|
+
readonly origin: SessionSnapshot['publication']['origin']
|
|
68
|
+
readonly identity: SessionSnapshot['publication']['identity']
|
|
69
|
+
readonly revision: CompatibleCallableReference['revision']
|
|
70
|
+
readonly etag: SessionSnapshot['publication']['etag']
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
options?: SessionRequestOptions,
|
|
74
|
+
) => Promise<Result>
|
|
75
|
+
|
|
76
|
+
/** Bind the Admin revision advertised by this exact Session snapshot. */
|
|
77
|
+
export async function bindAdmin(session: ClientSession): Promise<AdminBinding> {
|
|
78
|
+
const snapshot = await session.snapshot()
|
|
79
|
+
if (
|
|
80
|
+
snapshot.publication.origin !== ADMIN_ORIGIN ||
|
|
81
|
+
snapshot.bundle.root.origin !== ADMIN_ORIGIN
|
|
82
|
+
) {
|
|
83
|
+
throw new TypeError('Configured Admin target does not serve the Admin Domain.')
|
|
84
|
+
}
|
|
85
|
+
const compatible = session as unknown as CompatibleSession
|
|
86
|
+
const resolveInstallation = compatible.installation ?? compatible.installed
|
|
87
|
+
if (resolveInstallation === undefined) {
|
|
88
|
+
throw new TypeError('Client Session cannot resolve an installed Admin Domain.')
|
|
89
|
+
}
|
|
90
|
+
const installation = await resolveInstallation.call(session, ADMIN_ORIGIN)
|
|
91
|
+
const domain = Domain.fromSchema(snapshot.bundle.root)
|
|
92
|
+
const invoke = ((
|
|
93
|
+
callable: DynamicCallable,
|
|
94
|
+
receiverOrInput: PathLike | Input,
|
|
95
|
+
inputOrOptions?: Input | SessionRequestOptions,
|
|
96
|
+
options?: SessionRequestOptions,
|
|
97
|
+
) => {
|
|
98
|
+
const target =
|
|
99
|
+
'on' in callable ? callable.on(Path.from(receiverOrInput as PathLike)) : callable.path
|
|
100
|
+
const reference: CompatibleCallableReference = Object.freeze({
|
|
101
|
+
origin: ADMIN_ORIGIN,
|
|
102
|
+
revision: snapshot.publication.schema.revision,
|
|
103
|
+
callable: callable.key as Key.Callable,
|
|
104
|
+
target,
|
|
105
|
+
outputMode: callable.definition.output.mode,
|
|
106
|
+
})
|
|
107
|
+
return invokeCompatible(
|
|
108
|
+
session,
|
|
109
|
+
snapshot,
|
|
110
|
+
reference,
|
|
111
|
+
('on' in callable ? inputOrOptions : receiverOrInput) as Input,
|
|
112
|
+
('on' in callable ? options : inputOrOptions) as SessionRequestOptions | undefined,
|
|
113
|
+
)
|
|
114
|
+
}) as AdminBindingOperations['invoke']
|
|
115
|
+
|
|
116
|
+
return Object.freeze({
|
|
117
|
+
...domain,
|
|
118
|
+
$: Object.freeze({
|
|
119
|
+
...domain.$,
|
|
120
|
+
installation,
|
|
121
|
+
publication: snapshot.publication,
|
|
122
|
+
invoke,
|
|
123
|
+
}),
|
|
124
|
+
}) as AdminBinding
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async function invokeCompatible(
|
|
128
|
+
session: ClientSession,
|
|
129
|
+
snapshot: SessionSnapshot,
|
|
130
|
+
reference: CompatibleCallableReference,
|
|
131
|
+
input: Input,
|
|
132
|
+
options: SessionRequestOptions | undefined,
|
|
133
|
+
): Promise<unknown> {
|
|
134
|
+
const current = session as unknown as CompatibleSession
|
|
135
|
+
if (current.invoke !== undefined) return current.invoke(reference, input, options)
|
|
136
|
+
|
|
137
|
+
const dispatchBound = (sessionModule as unknown as { dispatchBound?: LegacyDispatchBound })
|
|
138
|
+
.dispatchBound
|
|
139
|
+
if (dispatchBound === undefined) {
|
|
140
|
+
throw new TypeError('Client Session cannot invoke an exact Admin publication.')
|
|
141
|
+
}
|
|
142
|
+
const result = await dispatchBound(
|
|
143
|
+
session,
|
|
144
|
+
call(reference.target, input),
|
|
145
|
+
{
|
|
146
|
+
schema: { revision: reference.revision },
|
|
147
|
+
publication: {
|
|
148
|
+
origin: snapshot.publication.origin,
|
|
149
|
+
identity: snapshot.publication.identity,
|
|
150
|
+
revision: snapshot.publication.schema.revision,
|
|
151
|
+
etag: snapshot.publication.etag,
|
|
152
|
+
},
|
|
153
|
+
},
|
|
154
|
+
options,
|
|
155
|
+
)
|
|
156
|
+
return requireOutput(reference.outputMode, result)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function requireOutput(
|
|
160
|
+
expected: CompatibleCallableReference['outputMode'],
|
|
161
|
+
result: Result,
|
|
162
|
+
): unknown {
|
|
163
|
+
if (result.kind !== expected) {
|
|
164
|
+
if (result.kind === 'stream') void result.stream.cancel(`Admin callable declared ${expected}.`)
|
|
165
|
+
throw new TypeError(`Admin callable declared ${expected} output but returned ${result.kind}.`)
|
|
166
|
+
}
|
|
167
|
+
return result.kind === 'stream' ? result.stream : result.value
|
|
168
|
+
}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type { DomainBinding } from '@astrale-os/kernel-client/domain'
|
|
2
1
|
import type { Node } from '@astrale-os/sdk/graph/node'
|
|
3
2
|
import type { QueryAST } from '@astrale-os/sdk/query'
|
|
4
3
|
|
|
@@ -7,6 +6,7 @@ import { NodeId } from '@astrale-os/sdk/graph/node'
|
|
|
7
6
|
import { Path } from '@astrale-os/sdk/graph/path'
|
|
8
7
|
import { describe, expect, mock, test } from 'bun:test'
|
|
9
8
|
|
|
9
|
+
import type { AdminBinding } from '../../binding'
|
|
10
10
|
import type { AdminGraphApi } from '../../graph'
|
|
11
11
|
|
|
12
12
|
import { connectAdminCatalog } from '../client'
|
|
@@ -61,7 +61,7 @@ function fixture(input: {
|
|
|
61
61
|
core: { nodes: { fleet: { path: Path.id(NodeId('fleet')) } } },
|
|
62
62
|
invoke,
|
|
63
63
|
},
|
|
64
|
-
} as unknown as
|
|
64
|
+
} as unknown as AdminBinding
|
|
65
65
|
const query = mock(async (_ast: QueryAST) => ({
|
|
66
66
|
result: {
|
|
67
67
|
kind: 'nodes' as const,
|
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
import type { DomainBinding } from '@astrale-os/kernel-client/domain'
|
|
2
1
|
import type { ClientSession } from '@astrale-os/kernel-client/session'
|
|
3
2
|
import type { Node } from '@astrale-os/sdk/graph/node'
|
|
4
3
|
|
|
5
|
-
import { bind } from '@astrale-os/kernel-client/domain'
|
|
6
4
|
import { Path } from '@astrale-os/sdk/graph/path'
|
|
7
5
|
import { Query } from '@astrale-os/sdk/query'
|
|
8
6
|
|
|
7
|
+
import { bindAdmin, type AdminBinding } from '../binding'
|
|
9
8
|
import { readAllNodes, type AdminGraphApi } from '../graph'
|
|
10
9
|
import {
|
|
11
10
|
AdminDomainNotFoundError,
|
|
@@ -37,7 +36,7 @@ export interface AdminCatalogApi {
|
|
|
37
36
|
}
|
|
38
37
|
|
|
39
38
|
export interface AdminCatalogDependencies {
|
|
40
|
-
readonly bind?: (session: ClientSession) => Promise<
|
|
39
|
+
readonly bind?: (session: ClientSession) => Promise<AdminBinding>
|
|
41
40
|
readonly operationId?: (kind: 'publish' | 'configure-default') => string
|
|
42
41
|
}
|
|
43
42
|
|
|
@@ -46,7 +45,7 @@ export async function connectAdminCatalog(
|
|
|
46
45
|
context: AdminCatalogContext,
|
|
47
46
|
dependencies: AdminCatalogDependencies = {},
|
|
48
47
|
): Promise<AdminCatalogApi> {
|
|
49
|
-
const binding = await (dependencies.bind ??
|
|
48
|
+
const binding = await (dependencies.bind ?? bindAdmin)(context.session)
|
|
50
49
|
if (binding.$.publication?.origin !== ADMIN_ORIGIN || binding.$.origin !== ADMIN_ORIGIN) {
|
|
51
50
|
throw new TypeError('Configured Admin target does not serve the Admin Domain.')
|
|
52
51
|
}
|
|
@@ -145,11 +144,7 @@ export async function connectAdminCatalog(
|
|
|
145
144
|
})
|
|
146
145
|
}
|
|
147
146
|
|
|
148
|
-
|
|
149
|
-
return bind(session, await session.installed(ADMIN_ORIGIN))
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
type DynamicDefinition = ReturnType<DomainBinding['$']['class']>
|
|
147
|
+
type DynamicDefinition = ReturnType<AdminBinding['$']['class']>
|
|
153
148
|
|
|
154
149
|
function domainFromNode(
|
|
155
150
|
definition: DynamicDefinition,
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type { DomainBinding } from '@astrale-os/kernel-client/domain'
|
|
2
1
|
import type { Node } from '@astrale-os/sdk/graph/node'
|
|
3
2
|
import type { QueryAST } from '@astrale-os/sdk/query'
|
|
4
3
|
|
|
@@ -7,6 +6,7 @@ import { NodeId } from '@astrale-os/sdk/graph/node'
|
|
|
7
6
|
import { Path } from '@astrale-os/sdk/graph/path'
|
|
8
7
|
import { describe, expect, mock, test } from 'bun:test'
|
|
9
8
|
|
|
9
|
+
import type { AdminBinding } from '../../binding'
|
|
10
10
|
import type { AdminGraphApi } from '../../graph'
|
|
11
11
|
|
|
12
12
|
import { connectAdminInstances } from '../client'
|
|
@@ -78,7 +78,7 @@ function fixture(input: {
|
|
|
78
78
|
core: { nodes: { fleet: { path: Path.id(NodeId('fleet')) } } },
|
|
79
79
|
invoke,
|
|
80
80
|
},
|
|
81
|
-
} as unknown as
|
|
81
|
+
} as unknown as AdminBinding
|
|
82
82
|
const query = mock(async (ast: QueryAST) => {
|
|
83
83
|
const name = sourceName(ast)
|
|
84
84
|
if (name === 'Instance') return graphResult(input.instances ?? [])
|
|
@@ -153,7 +153,7 @@ describe('V2 Admin Instance adapter', () => {
|
|
|
153
153
|
)
|
|
154
154
|
})
|
|
155
155
|
|
|
156
|
-
test('
|
|
156
|
+
test('delegates default placement to Fleet without reading Host inventory', async () => {
|
|
157
157
|
const created = {
|
|
158
158
|
id: '@instance-node',
|
|
159
159
|
slug: 'demo',
|
|
@@ -179,6 +179,8 @@ describe('V2 Admin Instance adapter', () => {
|
|
|
179
179
|
Path.id(NodeId('fleet')),
|
|
180
180
|
{ operationId: 'cli.instance.create:test', slug: 'demo' },
|
|
181
181
|
)
|
|
182
|
+
expect(contract.graph.query).not.toHaveBeenCalled()
|
|
183
|
+
expect(contract.graph.neighbors).not.toHaveBeenCalled()
|
|
182
184
|
})
|
|
183
185
|
|
|
184
186
|
test('resolves --host-id to an exact Host receiver and rejects the reserved Admin Host', async () => {
|
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
import type { DomainBinding } from '@astrale-os/kernel-client/domain'
|
|
2
1
|
import type { ClientSession } from '@astrale-os/kernel-client/session'
|
|
3
2
|
import type { Node } from '@astrale-os/sdk/graph/node'
|
|
4
3
|
|
|
5
|
-
import { bind } from '@astrale-os/kernel-client/domain'
|
|
6
4
|
import { Path } from '@astrale-os/sdk/graph/path'
|
|
7
5
|
import { Query } from '@astrale-os/sdk/query'
|
|
8
6
|
|
|
7
|
+
import { bindAdmin, type AdminBinding } from '../binding'
|
|
9
8
|
import { readAllNodes, type AdminGraphApi } from '../graph'
|
|
10
9
|
import {
|
|
11
10
|
AdminHostNotFoundError,
|
|
@@ -43,7 +42,7 @@ export interface AdminInstanceApi {
|
|
|
43
42
|
}
|
|
44
43
|
|
|
45
44
|
export interface AdminInstanceDependencies {
|
|
46
|
-
readonly bind?: (session: ClientSession) => Promise<
|
|
45
|
+
readonly bind?: (session: ClientSession) => Promise<AdminBinding>
|
|
47
46
|
readonly operationId?: (kind: 'create' | 'status' | 'delete' | 'install-domain') => string
|
|
48
47
|
}
|
|
49
48
|
|
|
@@ -55,7 +54,7 @@ export async function connectAdminInstances(
|
|
|
55
54
|
context: AdminInstanceContext,
|
|
56
55
|
dependencies: AdminInstanceDependencies = {},
|
|
57
56
|
): Promise<AdminInstanceApi> {
|
|
58
|
-
const binding = await (dependencies.bind ??
|
|
57
|
+
const binding = await (dependencies.bind ?? bindAdmin)(context.session)
|
|
59
58
|
if (binding.$.publication?.origin !== ADMIN_ORIGIN || binding.$.origin !== ADMIN_ORIGIN) {
|
|
60
59
|
throw new TypeError('Configured Admin target does not serve the Admin Domain.')
|
|
61
60
|
}
|
|
@@ -119,15 +118,9 @@ export async function connectAdminInstances(
|
|
|
119
118
|
)
|
|
120
119
|
}
|
|
121
120
|
|
|
122
|
-
//
|
|
123
|
-
//
|
|
124
|
-
//
|
|
125
|
-
const inventory = await hostInventory(context.graph, binding, Host, fleet)
|
|
126
|
-
const eligible = inventory.hosts.filter(
|
|
127
|
-
(host) => host.state === 'ready' && host.path !== inventory.reserved,
|
|
128
|
-
)
|
|
129
|
-
if (eligible.length > 1) throw hostSelectionRequired(eligible.map((host) => host.id))
|
|
130
|
-
|
|
121
|
+
// Default placement belongs to Admin. Avoid a redundant Host graph read:
|
|
122
|
+
// it requires broader graph authority than this Fleet operation and can
|
|
123
|
+
// deny an otherwise authorized caller before the server selects a Host.
|
|
131
124
|
return instanceFromSummary(
|
|
132
125
|
await binding.$.invoke(Fleet.$.method('createInstance') as never, fleet, input as never),
|
|
133
126
|
)
|
|
@@ -149,10 +142,6 @@ export async function connectAdminInstances(
|
|
|
149
142
|
})
|
|
150
143
|
}
|
|
151
144
|
|
|
152
|
-
async function bindInstalledAdmin(session: ClientSession): Promise<DomainBinding> {
|
|
153
|
-
return bind(session, await session.installed(ADMIN_ORIGIN))
|
|
154
|
-
}
|
|
155
|
-
|
|
156
145
|
interface HostInventoryItem {
|
|
157
146
|
readonly id: string
|
|
158
147
|
readonly nodeId: string
|
|
@@ -162,8 +151,8 @@ interface HostInventoryItem {
|
|
|
162
151
|
|
|
163
152
|
async function hostInventory(
|
|
164
153
|
graph: AdminGraphApi,
|
|
165
|
-
binding:
|
|
166
|
-
Host: ReturnType<
|
|
154
|
+
binding: AdminBinding,
|
|
155
|
+
Host: ReturnType<AdminBinding['$']['class']>,
|
|
167
156
|
fleet: Path,
|
|
168
157
|
): Promise<{ readonly hosts: readonly HostInventoryItem[]; readonly reserved?: string }> {
|
|
169
158
|
const [nodes, reservedPage] = await Promise.all([
|
|
@@ -200,14 +189,7 @@ async function hostInventory(
|
|
|
200
189
|
})
|
|
201
190
|
}
|
|
202
191
|
|
|
203
|
-
|
|
204
|
-
return new Error(
|
|
205
|
-
`alphaCreate could not choose a host: ${ids.length} ready hosts are assigned (${[...ids].sort().join(', ')}). ` +
|
|
206
|
-
'Specify host_id once multi-host placement is enabled.',
|
|
207
|
-
)
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
type DynamicDefinition = ReturnType<DomainBinding['$']['class']>
|
|
192
|
+
type DynamicDefinition = ReturnType<AdminBinding['$']['class']>
|
|
211
193
|
|
|
212
194
|
function instanceFromSummary(input: unknown): InstanceInfo {
|
|
213
195
|
const value = record(input, 'Admin Instance summary')
|
|
@@ -81,11 +81,56 @@ describe('auth login command', () => {
|
|
|
81
81
|
expect(result.exitCode).toBe(0)
|
|
82
82
|
const session = JSON.parse(await readFile(join(tmp, 'idp-sessions', 'alice.json'), 'utf-8'))
|
|
83
83
|
expect(session.audience).toBe('https://kernel.example.com')
|
|
84
|
+
expect(session.clientId).toBe('client_123')
|
|
84
85
|
expect(session.subject).toBe('user_123')
|
|
85
86
|
} finally {
|
|
86
87
|
await server.stop()
|
|
87
88
|
}
|
|
88
89
|
})
|
|
90
|
+
|
|
91
|
+
test('persists an explicit client id and reuses it for a named login', async () => {
|
|
92
|
+
const server = oauthServer({
|
|
93
|
+
accessToken: unsignedJwt({
|
|
94
|
+
iss: 'http://127.0.0.1',
|
|
95
|
+
sub: 'user_123',
|
|
96
|
+
exp: 1893456000,
|
|
97
|
+
}),
|
|
98
|
+
})
|
|
99
|
+
try {
|
|
100
|
+
await writeIdpConfig(server.url)
|
|
101
|
+
|
|
102
|
+
const explicit = await runAuthLogin(
|
|
103
|
+
'--idp',
|
|
104
|
+
'test',
|
|
105
|
+
'--name',
|
|
106
|
+
'alice',
|
|
107
|
+
'--client-id',
|
|
108
|
+
'client_override',
|
|
109
|
+
'--client-credentials',
|
|
110
|
+
'--client-secret-env',
|
|
111
|
+
'TEST_CLIENT_SECRET',
|
|
112
|
+
'--raw',
|
|
113
|
+
)
|
|
114
|
+
const reused = await runAuthLogin(
|
|
115
|
+
'--idp',
|
|
116
|
+
'test',
|
|
117
|
+
'--name',
|
|
118
|
+
'alice',
|
|
119
|
+
'--client-credentials',
|
|
120
|
+
'--client-secret-env',
|
|
121
|
+
'TEST_CLIENT_SECRET',
|
|
122
|
+
'--raw',
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
expect(explicit.exitCode).toBe(0)
|
|
126
|
+
expect(reused.exitCode).toBe(0)
|
|
127
|
+
expect(server.clientIds()).toEqual(['client_override', 'client_override'])
|
|
128
|
+
const session = JSON.parse(await readFile(join(tmp, 'idp-sessions', 'alice.json'), 'utf-8'))
|
|
129
|
+
expect(session.clientId).toBe('client_override')
|
|
130
|
+
} finally {
|
|
131
|
+
await server.stop()
|
|
132
|
+
}
|
|
133
|
+
})
|
|
89
134
|
})
|
|
90
135
|
|
|
91
136
|
async function writeIdpConfig(baseUrl: string): Promise<void> {
|
|
@@ -131,12 +176,19 @@ async function writeIdpConfig(baseUrl: string): Promise<void> {
|
|
|
131
176
|
)
|
|
132
177
|
}
|
|
133
178
|
|
|
134
|
-
function oauthServer(args: { accessToken: string }): {
|
|
179
|
+
function oauthServer(args: { accessToken: string }): {
|
|
180
|
+
url: string
|
|
181
|
+
clientIds: () => Array<string | null>
|
|
182
|
+
stop: () => Promise<void>
|
|
183
|
+
} {
|
|
184
|
+
const clientIds: Array<string | null> = []
|
|
135
185
|
const server = Bun.serve({
|
|
136
186
|
port: 0,
|
|
137
187
|
fetch: async (request) => {
|
|
138
188
|
const url = new URL(request.url)
|
|
139
189
|
if (url.pathname !== '/token') return new Response('not found', { status: 404 })
|
|
190
|
+
const form = new URLSearchParams(await request.text())
|
|
191
|
+
clientIds.push(form.get('client_id'))
|
|
140
192
|
return Response.json({
|
|
141
193
|
access_token: args.accessToken,
|
|
142
194
|
token_type: 'Bearer',
|
|
@@ -146,6 +198,7 @@ function oauthServer(args: { accessToken: string }): { url: string; stop: () =>
|
|
|
146
198
|
})
|
|
147
199
|
return {
|
|
148
200
|
url: `http://${server.hostname}:${server.port}`,
|
|
201
|
+
clientIds: () => clientIds,
|
|
149
202
|
stop: () => server.stop(true),
|
|
150
203
|
}
|
|
151
204
|
}
|
|
@@ -14,7 +14,11 @@ describe('call command result lifetime', () => {
|
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
const sessionStream = Object.assign(stream(), { async cancel() {} })
|
|
17
|
-
const materialized = await materializeCallResult({
|
|
17
|
+
const materialized = await materializeCallResult({
|
|
18
|
+
kind: 'stream',
|
|
19
|
+
invocation: { source: 'https://kernel.test' as never, id: 'test-stream' },
|
|
20
|
+
stream: sessionStream,
|
|
21
|
+
})
|
|
18
22
|
sessionOpen = false
|
|
19
23
|
|
|
20
24
|
expect(materialized).toEqual({ kind: 'stream', values: ['first', 'second'] })
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import { defineDomain } from '@astrale-os/sdk'
|
|
2
|
+
import { issuer } from '@astrale-os/sdk/auth'
|
|
3
|
+
import { createDeployment } from '@astrale-os/sdk/deployment'
|
|
4
|
+
import { defineSchema } from '@astrale-os/sdk/schema/v1'
|
|
1
5
|
import { afterEach, describe, expect, mock, test } from 'bun:test'
|
|
2
6
|
|
|
3
7
|
import { installDirect } from '../domain/install'
|
|
@@ -7,6 +11,22 @@ const RETRY = '139137b5-af47-47ce-92b2-b64a2b0c63d7'
|
|
|
7
11
|
|
|
8
12
|
const originalFetch = globalThis.fetch
|
|
9
13
|
|
|
14
|
+
const schema = defineSchema('crm.test', {})
|
|
15
|
+
const definition = defineDomain({
|
|
16
|
+
schema,
|
|
17
|
+
handlers: { functions: {}, classes: {}, interfaces: {} },
|
|
18
|
+
})
|
|
19
|
+
const deployed = createDeployment({
|
|
20
|
+
definition,
|
|
21
|
+
issuer: issuer.accept('https://crm.test'),
|
|
22
|
+
bundleHref: 'https://crm.test/domain.bundle.json',
|
|
23
|
+
bindings: { callables: [] },
|
|
24
|
+
}).publication
|
|
25
|
+
|
|
26
|
+
function publicationResponse(): Response {
|
|
27
|
+
return Response.json(deployed)
|
|
28
|
+
}
|
|
29
|
+
|
|
10
30
|
class ExitError extends Error {}
|
|
11
31
|
|
|
12
32
|
afterEach(() => {
|
|
@@ -15,9 +35,7 @@ afterEach(() => {
|
|
|
15
35
|
|
|
16
36
|
describe('direct domain install operation identity', () => {
|
|
17
37
|
test('generates one operation before transport and exposes it for recovery', async () => {
|
|
18
|
-
globalThis.fetch = mock(async () =>
|
|
19
|
-
Response.json({ domainName: 'crm.test' }),
|
|
20
|
-
) as unknown as typeof fetch
|
|
38
|
+
globalThis.fetch = mock(async () => publicationResponse()) as unknown as typeof fetch
|
|
21
39
|
const create = mock(() => GENERATED)
|
|
22
40
|
const requests: unknown[] = []
|
|
23
41
|
let recovery: unknown
|
|
@@ -54,9 +72,7 @@ describe('direct domain install operation identity', () => {
|
|
|
54
72
|
})
|
|
55
73
|
|
|
56
74
|
test('accepts an explicit operation only as the exact retry identity', async () => {
|
|
57
|
-
globalThis.fetch = mock(async () =>
|
|
58
|
-
Response.json({ domainName: 'crm.test' }),
|
|
59
|
-
) as unknown as typeof fetch
|
|
75
|
+
globalThis.fetch = mock(async () => publicationResponse()) as unknown as typeof fetch
|
|
60
76
|
const create = mock(() => GENERATED)
|
|
61
77
|
const accepted: string[] = []
|
|
62
78
|
const requests: unknown[] = []
|
|
@@ -95,7 +111,7 @@ describe('direct domain install operation identity', () => {
|
|
|
95
111
|
test('rejects a non-UUID operation before metadata or Kernel transport', async () => {
|
|
96
112
|
const originalExit = process.exit
|
|
97
113
|
const originalWrite = process.stderr.write
|
|
98
|
-
const fetch = mock(async () =>
|
|
114
|
+
const fetch = mock(async () => publicationResponse())
|
|
99
115
|
const run = mock(async () => undefined)
|
|
100
116
|
globalThis.fetch = fetch as unknown as typeof globalThis.fetch
|
|
101
117
|
process.exit = (() => {
|
|
@@ -11,6 +11,10 @@ class ExitError extends Error {
|
|
|
11
11
|
|
|
12
12
|
const calls: Array<{ path: string; params: unknown }> = []
|
|
13
13
|
let inventory: OwnedInstanceInfo[] = []
|
|
14
|
+
const TEST_INVOCATION = {
|
|
15
|
+
source: 'https://kernel.test',
|
|
16
|
+
id: 'domain-install-owned',
|
|
17
|
+
} as ConstructorParameters<typeof ResponseError>[2]
|
|
14
18
|
|
|
15
19
|
const hostCall = mock(async (call: { target: string; input: unknown }): Promise<unknown> => {
|
|
16
20
|
const path = call.target
|
|
@@ -137,7 +141,7 @@ describe('admin domain install owner boundary', () => {
|
|
|
137
141
|
createdAt: '2026-08-20T00:00:00.000Z',
|
|
138
142
|
updatedAt: '2026-08-20T00:00:00.000Z',
|
|
139
143
|
}
|
|
140
|
-
const failure = new ResponseError(5001, 'Schema operation is not supported.', {
|
|
144
|
+
const failure = new ResponseError(5001, 'Schema operation is not supported.', TEST_INVOCATION, {
|
|
141
145
|
code: 'SCHEMA_UPGRADE_INCOMPATIBLE',
|
|
142
146
|
details: {
|
|
143
147
|
phase: 'upgrade',
|