@astrale-os/cli 1.0.0-beta.0 → 1.0.0-beta.10
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 +23 -12
- package/dist/astrale.js +51494 -56307
- package/dist/public/connect-core.js +2028 -26846
- package/dist/public/keys/index.js +1755 -40694
- package/dist/public/paths/index.js +859 -39518
- package/dist/types/admin/binding.d.ts +19 -0
- package/dist/types/admin/instance/client.d.ts +22 -0
- package/dist/types/admin/instance/index.d.ts +2 -0
- package/dist/types/admin/instance/model.d.ts +5 -12
- package/dist/types/connection/auth.d.ts +3 -0
- package/dist/types/connection/call.d.ts +3 -0
- package/dist/types/connection/command.d.ts +27 -0
- package/dist/types/connection/credential.d.ts +14 -0
- package/dist/types/connection/errors.d.ts +1 -0
- package/dist/types/connection/exchange.d.ts +9 -0
- package/dist/types/connection/failure/classify.d.ts +2 -0
- package/dist/types/connection/failure/index.d.ts +5 -0
- package/dist/types/connection/failure/model.d.ts +25 -0
- package/dist/types/connection/failure/render.d.ts +3 -0
- package/dist/types/connection/index.d.ts +8 -0
- package/dist/types/connection/reasons.d.ts +37 -0
- package/dist/types/connection/self.d.ts +37 -0
- package/dist/types/connection/session.d.ts +31 -0
- package/dist/types/connection/target.d.ts +38 -0
- package/dist/types/errors.d.ts +1 -1
- package/dist/types/lib/admin-instance.d.ts +17 -0
- package/dist/types/lib/admin-target.d.ts +6 -2
- package/dist/types/lib/failure-debug.d.ts +1 -0
- package/dist/types/lib/idp.d.ts +7 -0
- package/dist/types/lib/instance-target.d.ts +1 -1
- package/dist/types/lib/instance.d.ts +10 -2
- package/dist/types/lib/log.d.ts +5 -3
- package/dist/types/lib/meta.d.ts +2 -2
- package/dist/types/lib/proc.d.ts +43 -0
- package/dist/types/lib/self.d.ts +9 -0
- package/dist/types/lib/update.d.ts +131 -0
- package/package.json +8 -10
- package/src/__tests__/fixtures/publication.ts +20 -0
- package/src/admin/__tests__/binding.test.ts +31 -0
- package/src/admin/__tests__/fixture.ts +101 -0
- package/src/admin/binding.ts +98 -0
- package/src/admin/catalog/.spec/api.d.ts +2 -2
- package/src/admin/catalog/__tests__/client.test.ts +20 -49
- package/src/admin/catalog/client.ts +36 -32
- package/src/admin/catalog/model.ts +3 -4
- package/src/admin/graph/.spec/api.d.ts +4 -10
- package/src/admin/graph/nodes.ts +1 -1
- package/src/admin/instance/.spec/api.d.ts +2 -2
- package/src/admin/instance/__tests__/client.test.ts +40 -163
- package/src/admin/instance/client.ts +35 -137
- package/src/admin/instance/index.ts +1 -2
- package/src/admin/instance/model.ts +11 -20
- package/src/commands/__tests__/admin-instance.test.ts +11 -8
- package/src/commands/__tests__/auth-login.test.ts +54 -1
- package/src/commands/__tests__/call-describe.test.ts +46 -33
- package/src/commands/__tests__/call.test.ts +5 -1
- package/src/commands/__tests__/domain-install-operation.test.ts +12 -7
- package/src/commands/__tests__/domain-install-owned.test.ts +6 -2
- package/src/commands/__tests__/domain-list.test.ts +63 -4
- package/src/commands/__tests__/install-identity-override.test.ts +19 -19
- package/src/commands/__tests__/instance-list-rows.test.ts +1 -1
- package/src/commands/__tests__/instance-status.test.ts +139 -0
- package/src/commands/__tests__/read-commands.test.ts +35 -10
- package/src/commands/__tests__/studio-descriptor.test.ts +22 -0
- package/src/commands/__tests__/update.test.ts +95 -0
- package/src/commands/call-describe.ts +60 -68
- package/src/commands/call.ts +43 -56
- package/src/commands/domain/install.ts +13 -17
- package/src/commands/domain/list.ts +18 -13
- package/src/commands/domain/publish.ts +14 -11
- package/src/commands/domain/uninstall.ts +2 -2
- package/src/commands/get.ts +14 -15
- package/src/commands/identity/__tests__/register.test.ts +3 -3
- package/src/commands/identity/register.ts +5 -4
- package/src/commands/instance/create.ts +8 -16
- package/src/commands/instance/delete.ts +12 -9
- package/src/commands/instance/list.ts +2 -2
- package/src/commands/instance/status.ts +66 -20
- package/src/commands/instance/use.ts +16 -12
- package/src/commands/introspect.ts +9 -16
- package/src/commands/logs.ts +24 -25
- package/src/commands/mutate.ts +6 -5
- package/src/commands/query.ts +27 -30
- package/src/commands/studio.ts +20 -0
- package/src/commands/token.ts +8 -7
- package/src/commands/update.ts +68 -22
- package/src/commands/view.ts +7 -6
- package/src/connection/.spec/api.d.ts +7 -8
- package/src/connection/.spec/architecture.md +5 -4
- package/src/connection/.spec/flows/session.ts +2 -2
- package/src/connection/.spec/laws/connection.ts +6 -2
- package/src/connection/.spec/layout.ts +1 -0
- package/src/connection/__tests__/credential.test.ts +20 -1
- package/src/connection/__tests__/errors.test.ts +43 -32
- package/src/connection/__tests__/exchange.test.ts +36 -6
- package/src/connection/__tests__/failure-fixtures.ts +39 -0
- package/src/connection/__tests__/failure-safety.test.ts +75 -0
- package/src/connection/__tests__/self.test.ts +31 -11
- package/src/connection/__tests__/session.test.ts +3 -3
- package/src/connection/__tests__/target.test.ts +1 -0
- package/src/connection/auth.ts +13 -0
- package/src/connection/call.ts +1 -1
- package/src/connection/credential.ts +2 -2
- package/src/connection/errors.ts +1 -355
- package/src/connection/exchange.ts +52 -27
- package/src/connection/failure/classify.ts +110 -0
- package/src/connection/failure/index.ts +18 -0
- package/src/connection/failure/model.ts +28 -0
- package/src/connection/failure/render.ts +116 -0
- package/src/connection/reasons.ts +16 -0
- package/src/connection/self.ts +39 -21
- package/src/connection/session.ts +19 -8
- package/src/errors.ts +3 -2
- package/src/graph/.spec/api.d.ts +12 -5
- package/src/graph/.spec/layout.ts +9 -1
- package/src/graph/__tests__/mutation.test.ts +2 -2
- package/src/graph/__tests__/query.test.ts +25 -6
- package/src/graph/class.ts +30 -0
- package/src/graph/index.ts +1 -0
- package/src/graph/query.ts +21 -32
- package/src/identity/.spec/api.d.ts +2 -2
- package/src/identity/__tests__/registration.test.ts +1 -1
- package/src/identity/registration.ts +3 -3
- package/src/keys/__tests__/keys.test.ts +4 -0
- package/src/keys/credential.ts +1 -0
- package/src/lib/__tests__/admin-target.test.ts +12 -0
- package/src/lib/__tests__/command-dx.test.ts +1 -1
- package/src/lib/__tests__/domain-publication.test.ts +138 -0
- package/src/lib/__tests__/idp-session.test.ts +22 -0
- package/src/lib/__tests__/idp.test.ts +36 -4
- package/src/lib/__tests__/instance-candidates.test.ts +1 -3
- package/src/lib/__tests__/instance-target.test.ts +7 -39
- package/src/lib/__tests__/instance.test.ts +6 -0
- package/src/lib/__tests__/log-errors.test.ts +64 -0
- package/src/lib/__tests__/studio-server-deps.test.ts +53 -16
- package/src/lib/__tests__/update.test.ts +113 -8
- package/src/lib/__tests__/view-open-intent.test.ts +0 -2
- package/src/lib/__tests__/view-server.test.ts +1 -1
- package/src/lib/__tests__/view-session.test.ts +1 -1
- package/src/lib/admin-instance.ts +14 -8
- package/src/lib/admin-target.ts +21 -2
- package/src/lib/command-dx.ts +9 -9
- package/src/lib/domain-publication.ts +104 -0
- package/src/lib/failure-debug.ts +26 -0
- package/src/lib/idp.ts +39 -4
- package/src/lib/instance-candidates.ts +2 -2
- package/src/lib/instance-target.ts +3 -14
- package/src/lib/instance.ts +22 -12
- package/src/lib/log.ts +16 -9
- package/src/lib/login-flow.ts +13 -3
- package/src/lib/meta.ts +2 -2
- package/src/lib/provision-instance.ts +34 -57
- package/src/lib/update.ts +124 -30
- package/src/lib/validation.ts +2 -2
- package/src/lib/view/resolve.ts +1 -1
- package/src/program/__tests__/program.test.ts +20 -4
- package/src/program/build.ts +2 -2
- package/src/setup/__tests__/instance-step.test.ts +27 -10
- package/src/setup/steps/instance.ts +22 -15
- package/studio/client/dist/assets/{elk-api-D2xgMJvi.js → elk-api-ByzoDZWH.js} +1 -1
- package/studio/client/dist/assets/index-B0RCgUOT.js +8 -0
- package/studio/client/dist/assets/index-BiGR8Oo9.css +1 -0
- package/studio/client/dist/assets/index-N8J8EKlB.js +81 -0
- package/studio/client/dist/index.html +2 -2
- package/studio/package.json +5 -10
- package/studio/server/agent/ask.test.ts +1 -1
- package/studio/server/agent/bridge/grant.test.ts +1 -1
- package/studio/server/agent/bridge/routes.test.ts +1 -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/claude/adapter.test.ts +15 -5
- 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/anchors.test.ts +32 -36
- package/studio/server/agent/prompts/anchors.ts +20 -49
- package/studio/server/agent/prompts/system.test.ts +3 -2
- package/studio/server/agent/prompts/system.ts +6 -5
- package/studio/server/agent/prompts/turn.ts +13 -4
- package/studio/server/agent/routes.test.ts +1 -1
- package/studio/server/agent/run/completion.ts +2 -2
- package/studio/server/agent/run/coordinator.test.ts +1 -1
- 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 +43 -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-agent-loadout.test.ts +1 -1
- package/studio/server/api.test.ts +147 -0
- package/studio/server/api.ts +20 -422
- package/studio/server/cache.test.ts +122 -0
- package/studio/server/cache.ts +162 -11
- 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 +50 -40
- package/studio/server/domain.ts +83 -52
- 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/index.ts +1 -1
- 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/routes.ts +114 -0
- package/studio/server/introspect/anatomy/views.ts +53 -0
- package/studio/server/introspect/anatomy-extras.test.ts +106 -35
- package/studio/server/introspect/anatomy-extras.ts +7 -726
- package/studio/server/introspect/anatomy.test.ts +22 -14
- package/studio/server/introspect/anatomy.ts +52 -28
- package/studio/server/introspect/bundle.ts +15 -9
- package/studio/server/introspect/canonical-schema.test.ts +93 -309
- package/studio/server/introspect/canonical-schema.ts +257 -425
- package/studio/server/introspect/config-preview.test.ts +32 -0
- package/studio/server/introspect/config-preview.ts +119 -0
- package/studio/server/introspect/core-extractor.ts +18 -127
- package/studio/server/introspect/core.ts +1 -2
- package/studio/server/introspect/diff.test.ts +18 -18
- package/studio/server/introspect/diff.ts +44 -90
- package/studio/server/introspect/extractor.ts +23 -116
- package/studio/server/introspect/hash.ts +5 -2
- package/studio/server/introspect/overlay-tsmorph.test.ts +119 -240
- package/studio/server/introspect/overlay-tsmorph.ts +7 -1305
- package/studio/server/introspect/overlay.test.ts +26 -54
- package/studio/server/introspect/overlay.ts +10 -37
- package/studio/server/introspect/revision.test.ts +54 -0
- package/studio/server/introspect/revision.ts +49 -0
- package/studio/server/introspect/runtime.test.ts +83 -143
- package/studio/server/introspect/runtime.ts +40 -11
- package/studio/server/introspect/schema-ir-json.test.ts +70 -0
- package/studio/server/introspect/schema-ir-json.ts +151 -0
- package/studio/server/introspect/schema-refs.test.ts +43 -88
- package/studio/server/introspect/schema-refs.ts +14 -49
- package/studio/server/introspect/source-overlay/annotations.ts +14 -0
- package/studio/server/introspect/source-overlay/handlers.ts +161 -0
- package/studio/server/introspect/source-overlay/kernel-calls.ts +46 -0
- package/studio/server/introspect/source-overlay/project.ts +248 -0
- package/studio/server/introspect/source-overlay/spans.ts +330 -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 +223 -7
- package/studio/server/state/baseline.ts +148 -34
- 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 -11
- 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/views/target.test.ts +78 -0
- package/studio/server/views/target.ts +222 -0
- package/studio/server/watch.test.ts +11 -11
- package/studio/server/watch.ts +9 -5
- 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/server/workspace-watch.ts +2 -2
- 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 +393 -0
- package/studio/shared/contracts/surface.test.ts +317 -0
- package/studio/shared/contracts/workspace.ts +265 -0
- package/studio/shared/layout.test.ts +116 -0
- package/studio/shared/schema/identity.test.ts +34 -0
- package/studio/shared/schema/identity.ts +68 -0
- package/studio/shared/types.ts +11 -1099
- package/studio/tsconfig.json +1 -1
- package/viewer/dist/main.js +40 -61
- package/.check-workspace.cjs +0 -40
- package/src/commands/__tests__/instance-create-hosts.test.ts +0 -29
- 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.test.ts +0 -286
- package/studio/server/state/views.ts +0 -535
|
@@ -1,14 +1,16 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type { ClientSession } from '@astrale-os/kernel-client/session'
|
|
3
|
-
import type { Node } from '@astrale-os/sdk/graph/node'
|
|
1
|
+
import type { ClientSession } from '@astrale-os/sdk/client/session'
|
|
4
2
|
|
|
5
|
-
import { bind } from '@astrale-os/kernel-client/domain'
|
|
6
3
|
import { Path } from '@astrale-os/sdk/graph/path'
|
|
7
|
-
import { Query } from '@astrale-os/sdk/query'
|
|
8
4
|
|
|
9
|
-
import { readAllNodes, type AdminGraphApi } from '../graph'
|
|
10
5
|
import {
|
|
11
|
-
|
|
6
|
+
bindAdmin,
|
|
7
|
+
invokeAdminMethod,
|
|
8
|
+
requireAdminBinding,
|
|
9
|
+
requireAdminClass,
|
|
10
|
+
requireAdminCore,
|
|
11
|
+
type AdminBinding,
|
|
12
|
+
} from '../binding'
|
|
13
|
+
import {
|
|
12
14
|
AdminInstanceNotFoundError,
|
|
13
15
|
findOwnedInstance,
|
|
14
16
|
type DomainInstallReceipt,
|
|
@@ -17,33 +19,20 @@ import {
|
|
|
17
19
|
type OwnedInstanceInfo,
|
|
18
20
|
} from './model'
|
|
19
21
|
|
|
20
|
-
const ADMIN_ORIGIN = 'admin.astrale.ai'
|
|
21
|
-
const PAGE_SIZE = 256
|
|
22
|
-
const MAXIMUM_INSTANCES = 10_000
|
|
23
|
-
const MAXIMUM_PAGES = Math.ceil(MAXIMUM_INSTANCES / PAGE_SIZE) + 1
|
|
24
|
-
|
|
25
|
-
const HostRef = Object.freeze({ origin: ADMIN_ORIGIN, kind: 'class' as const, name: 'Host' })
|
|
26
|
-
const fleetReservesAdminHost = Object.freeze({
|
|
27
|
-
origin: ADMIN_ORIGIN,
|
|
28
|
-
kind: 'class' as const,
|
|
29
|
-
name: 'fleet_reserves_admin_host',
|
|
30
|
-
})
|
|
31
|
-
|
|
32
22
|
export interface AdminInstanceContext {
|
|
33
23
|
readonly session: ClientSession
|
|
34
|
-
readonly graph: AdminGraphApi
|
|
35
24
|
}
|
|
36
25
|
|
|
37
26
|
export interface AdminInstanceApi {
|
|
38
27
|
list(): Promise<OwnedInstanceInfo[]>
|
|
39
|
-
create(slug: string
|
|
28
|
+
create(slug: string): Promise<InstanceInfo>
|
|
40
29
|
status(identifier: string): Promise<InstanceInfo>
|
|
41
30
|
delete(identifier: string): Promise<InstanceInfo>
|
|
42
31
|
installDomain(identifier: string, domain: string): Promise<DomainInstallReceipt>
|
|
43
32
|
}
|
|
44
33
|
|
|
45
34
|
export interface AdminInstanceDependencies {
|
|
46
|
-
readonly bind?: (session: ClientSession) => Promise<
|
|
35
|
+
readonly bind?: (session: ClientSession) => Promise<AdminBinding>
|
|
47
36
|
readonly operationId?: (kind: 'create' | 'status' | 'delete' | 'install-domain') => string
|
|
48
37
|
}
|
|
49
38
|
|
|
@@ -55,24 +44,22 @@ export async function connectAdminInstances(
|
|
|
55
44
|
context: AdminInstanceContext,
|
|
56
45
|
dependencies: AdminInstanceDependencies = {},
|
|
57
46
|
): Promise<AdminInstanceApi> {
|
|
58
|
-
const binding = await (dependencies.bind ??
|
|
59
|
-
if (binding.$.publication?.origin !== ADMIN_ORIGIN || binding.$.origin !== ADMIN_ORIGIN) {
|
|
60
|
-
throw new TypeError('Configured Admin target does not serve the Admin Domain.')
|
|
61
|
-
}
|
|
47
|
+
const binding = requireAdminBinding(await (dependencies.bind ?? bindAdmin)(context.session))
|
|
62
48
|
|
|
63
|
-
const Instance = binding
|
|
64
|
-
const
|
|
65
|
-
const
|
|
66
|
-
const fleet = binding.$.core.nodes.fleet?.path
|
|
67
|
-
if (fleet === undefined) throw new TypeError('Admin Domain has no singleton Fleet receiver.')
|
|
49
|
+
const Instance = requireAdminClass(binding, 'Instance', 'node')
|
|
50
|
+
const Fleet = requireAdminClass(binding, 'Fleet', 'node')
|
|
51
|
+
const fleet = requireAdminCore(binding, 'fleet')
|
|
68
52
|
|
|
69
53
|
const operationId = dependencies.operationId ?? defaultOperationId
|
|
70
54
|
|
|
71
55
|
const list = async (): Promise<OwnedInstanceInfo[]> => {
|
|
72
|
-
const result: unknown = await
|
|
73
|
-
|
|
56
|
+
const result: unknown = await invokeAdminMethod(
|
|
57
|
+
context.session,
|
|
58
|
+
binding,
|
|
59
|
+
Fleet,
|
|
60
|
+
'listInstances',
|
|
74
61
|
fleet,
|
|
75
|
-
{}
|
|
62
|
+
{},
|
|
76
63
|
)
|
|
77
64
|
if (!Array.isArray(result)) throw new TypeError('Admin Instance inventory is invalid.')
|
|
78
65
|
return result.map((value) => instanceFromSummary(value) as OwnedInstanceInfo)
|
|
@@ -89,126 +76,48 @@ export async function connectAdminInstances(
|
|
|
89
76
|
identifier: string,
|
|
90
77
|
): Promise<InstanceInfo> => {
|
|
91
78
|
const instance = await requireInstance(identifier)
|
|
92
|
-
const output = await
|
|
93
|
-
|
|
79
|
+
const output = await invokeAdminMethod(
|
|
80
|
+
context.session,
|
|
81
|
+
binding,
|
|
82
|
+
Instance,
|
|
83
|
+
method,
|
|
94
84
|
Path.parse(instance.id),
|
|
95
|
-
{ operationId: operationId(method) }
|
|
85
|
+
{ operationId: operationId(method) },
|
|
96
86
|
)
|
|
97
87
|
return instanceFromSummary(output)
|
|
98
88
|
}
|
|
99
89
|
|
|
100
90
|
return Object.freeze({
|
|
101
91
|
list,
|
|
102
|
-
async create(slug: string
|
|
92
|
+
async create(slug: string) {
|
|
103
93
|
const input = Object.freeze({
|
|
104
94
|
operationId: operationId('create'),
|
|
105
95
|
slug,
|
|
106
96
|
})
|
|
107
|
-
if (hostId !== undefined) {
|
|
108
|
-
const selected = await hostInventory(context.graph, binding, Host, fleet)
|
|
109
|
-
const host = selected.hosts.find(
|
|
110
|
-
(item) => item.id === hostId && item.state === 'ready' && item.path !== selected.reserved,
|
|
111
|
-
)
|
|
112
|
-
if (host === undefined) throw new AdminHostNotFoundError(hostId)
|
|
113
|
-
return instanceFromSummary(
|
|
114
|
-
await binding.$.invoke(
|
|
115
|
-
Host.$.method('createInstance') as never,
|
|
116
|
-
Path.parse(host.path),
|
|
117
|
-
input as never,
|
|
118
|
-
),
|
|
119
|
-
)
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
// Preserve the existing interactive multi-Host picker without accepting a
|
|
123
|
-
// doomed operation first. Graph read policy exposes only caller-usable
|
|
124
|
-
// Hosts; the reserved Admin Host is excluded explicitly.
|
|
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
|
-
|
|
131
97
|
return instanceFromSummary(
|
|
132
|
-
await binding
|
|
98
|
+
await invokeAdminMethod(context.session, binding, Fleet, 'createInstance', fleet, input),
|
|
133
99
|
)
|
|
134
100
|
},
|
|
135
101
|
status: (identifier: string) => invokeInstance('status', identifier),
|
|
136
102
|
delete: (identifier: string) => invokeInstance('delete', identifier),
|
|
137
103
|
async installDomain(identifier: string, domain: string): Promise<DomainInstallReceipt> {
|
|
138
104
|
const instance = await requireInstance(identifier)
|
|
139
|
-
const output = await
|
|
140
|
-
|
|
105
|
+
const output = await invokeAdminMethod(
|
|
106
|
+
context.session,
|
|
107
|
+
binding,
|
|
108
|
+
Instance,
|
|
109
|
+
'installDomain',
|
|
141
110
|
Path.parse(instance.id),
|
|
142
111
|
{
|
|
143
112
|
operationId: operationId('install-domain'),
|
|
144
113
|
domain: Path.parse(domain).raw,
|
|
145
|
-
}
|
|
114
|
+
},
|
|
146
115
|
)
|
|
147
116
|
return domainInstallReceipt(output)
|
|
148
117
|
},
|
|
149
118
|
})
|
|
150
119
|
}
|
|
151
120
|
|
|
152
|
-
async function bindInstalledAdmin(session: ClientSession): Promise<DomainBinding> {
|
|
153
|
-
return bind(session, await session.installed(ADMIN_ORIGIN))
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
interface HostInventoryItem {
|
|
157
|
-
readonly id: string
|
|
158
|
-
readonly nodeId: string
|
|
159
|
-
readonly path: string
|
|
160
|
-
readonly state: string
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
async function hostInventory(
|
|
164
|
-
graph: AdminGraphApi,
|
|
165
|
-
binding: DomainBinding,
|
|
166
|
-
Host: ReturnType<DomainBinding['$']['class']>,
|
|
167
|
-
fleet: Path,
|
|
168
|
-
): Promise<{ readonly hosts: readonly HostInventoryItem[]; readonly reserved?: string }> {
|
|
169
|
-
const [nodes, reservedPage] = await Promise.all([
|
|
170
|
-
readAllNodes(
|
|
171
|
-
graph,
|
|
172
|
-
Query.from({ kind: 'node', definitions: [HostRef] }).select({
|
|
173
|
-
kind: 'nodes',
|
|
174
|
-
projection: { kind: 'value' },
|
|
175
|
-
}),
|
|
176
|
-
{
|
|
177
|
-
label: 'Admin Host inventory',
|
|
178
|
-
maximum: MAXIMUM_INSTANCES,
|
|
179
|
-
maximumPages: MAXIMUM_PAGES,
|
|
180
|
-
},
|
|
181
|
-
),
|
|
182
|
-
graph.neighbors(fleet, fleetReservesAdminHost, {
|
|
183
|
-
direction: 'outgoing',
|
|
184
|
-
page: { size: 1 },
|
|
185
|
-
}),
|
|
186
|
-
])
|
|
187
|
-
const reserved = reservedPage.first === null ? undefined : Path.id(reservedPage.first.id).raw
|
|
188
|
-
return Object.freeze({
|
|
189
|
-
hosts: Object.freeze(
|
|
190
|
-
nodes.map((node) =>
|
|
191
|
-
Object.freeze({
|
|
192
|
-
id: requiredStringProperty(Host, node, 'id'),
|
|
193
|
-
nodeId: String(node.id),
|
|
194
|
-
path: Path.id(node.id).raw,
|
|
195
|
-
state: requiredStringProperty(Host, node, 'state'),
|
|
196
|
-
}),
|
|
197
|
-
),
|
|
198
|
-
),
|
|
199
|
-
...(reserved === undefined ? {} : { reserved }),
|
|
200
|
-
})
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
function hostSelectionRequired(ids: readonly string[]): Error {
|
|
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']>
|
|
211
|
-
|
|
212
121
|
function instanceFromSummary(input: unknown): InstanceInfo {
|
|
213
122
|
const value = record(input, 'Admin Instance summary')
|
|
214
123
|
const failure = value.failure === undefined ? undefined : record(value.failure, 'Admin failure')
|
|
@@ -217,10 +126,6 @@ function instanceFromSummary(input: unknown): InstanceInfo {
|
|
|
217
126
|
slug: requiredString(value.slug, 'Admin Instance slug'),
|
|
218
127
|
url: optionalStringValue(value.url) ?? '',
|
|
219
128
|
state: instanceState(value.state),
|
|
220
|
-
...(value.hostId === undefined
|
|
221
|
-
? {}
|
|
222
|
-
: { hostId: requiredString(value.hostId, 'Admin Host id') }),
|
|
223
|
-
...(value.region === undefined ? {} : { region: requiredString(value.region, 'Admin region') }),
|
|
224
129
|
...(value.phase === undefined
|
|
225
130
|
? {}
|
|
226
131
|
: { phase: requiredString(value.phase, 'Admin Instance phase') }),
|
|
@@ -256,13 +161,6 @@ function domainInstallReceipt(input: unknown): DomainInstallReceipt {
|
|
|
256
161
|
})
|
|
257
162
|
}
|
|
258
163
|
|
|
259
|
-
function requiredStringProperty(definition: DynamicDefinition, node: Node, name: string): string {
|
|
260
|
-
return requiredString(
|
|
261
|
-
node.props[definition.$.property(name).key],
|
|
262
|
-
`Admin ${definition.name}.${name}`,
|
|
263
|
-
)
|
|
264
|
-
}
|
|
265
|
-
|
|
266
164
|
function instanceState(input: unknown): InstanceState {
|
|
267
165
|
if (
|
|
268
166
|
input === 'provisioning' ||
|
|
@@ -5,10 +5,9 @@ export {
|
|
|
5
5
|
type AdminInstanceDependencies,
|
|
6
6
|
} from './client'
|
|
7
7
|
export {
|
|
8
|
-
AdminHostNotFoundError,
|
|
9
8
|
AdminInstanceNotFoundError,
|
|
10
9
|
findOwnedInstance,
|
|
11
|
-
|
|
10
|
+
formatInstanceState,
|
|
12
11
|
type DomainInstallReceipt,
|
|
13
12
|
type InstanceInfo,
|
|
14
13
|
type InstanceState,
|
|
@@ -5,9 +5,7 @@ export interface InstanceInfo {
|
|
|
5
5
|
readonly id: string
|
|
6
6
|
readonly slug: string
|
|
7
7
|
readonly url: string
|
|
8
|
-
readonly
|
|
9
|
-
readonly region?: string
|
|
10
|
-
readonly state?: InstanceState
|
|
8
|
+
readonly state: InstanceState
|
|
11
9
|
readonly phase?: string
|
|
12
10
|
readonly error?: string | null
|
|
13
11
|
readonly createdAt?: string
|
|
@@ -28,19 +26,13 @@ export interface DomainInstallReceipt {
|
|
|
28
26
|
readonly error?: string
|
|
29
27
|
}
|
|
30
28
|
|
|
31
|
-
export class AdminInstanceNotFoundError extends
|
|
32
|
-
readonly name = 'NotFoundError'
|
|
33
|
-
|
|
34
|
-
constructor(readonly identifier: string) {
|
|
35
|
-
super(`No owned Admin Instance matches ${JSON.stringify(identifier)}.`)
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export class AdminHostNotFoundError extends Error {
|
|
40
|
-
readonly name = 'NotFoundError'
|
|
41
|
-
|
|
29
|
+
export class AdminInstanceNotFoundError extends AstraleError {
|
|
42
30
|
constructor(readonly identifier: string) {
|
|
43
|
-
super(
|
|
31
|
+
super(
|
|
32
|
+
'INSTANCE_NOT_FOUND',
|
|
33
|
+
`No owned Admin Instance matches ${JSON.stringify(identifier)}.`,
|
|
34
|
+
'Run `astrale instance list` to see your instances.',
|
|
35
|
+
)
|
|
44
36
|
}
|
|
45
37
|
}
|
|
46
38
|
|
|
@@ -57,9 +49,8 @@ export function findOwnedInstance(
|
|
|
57
49
|
)
|
|
58
50
|
}
|
|
59
51
|
|
|
60
|
-
/** Human-readable
|
|
61
|
-
export function
|
|
62
|
-
return
|
|
63
|
-
.filter(Boolean)
|
|
64
|
-
.join(' · ')
|
|
52
|
+
/** Human-readable non-ready lifecycle state for one Instance. */
|
|
53
|
+
export function formatInstanceState(info: InstanceInfo): string {
|
|
54
|
+
return info.state === 'ready' ? '' : `${info.state}${info.phase ? ` (${info.phase})` : ''}`
|
|
65
55
|
}
|
|
56
|
+
import { AstraleError } from '../../errors'
|
|
@@ -4,7 +4,10 @@ import type { IdentityStore } from '../../identity/index'
|
|
|
4
4
|
|
|
5
5
|
import { AuthError } from '../../errors'
|
|
6
6
|
import { findOwnedInstance, type OwnedInstanceInfo } from '../../lib/admin-instance'
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
assertInstanceCreateIdentity,
|
|
9
|
+
selectInstanceCreateIdentity,
|
|
10
|
+
} from '../../lib/provision-instance'
|
|
8
11
|
|
|
9
12
|
describe('admin-backed instance commands', () => {
|
|
10
13
|
test('findOwnedInstance matches owner inventory by slug or stable node id', () => {
|
|
@@ -14,8 +17,6 @@ describe('admin-backed instance commands', () => {
|
|
|
14
17
|
url: 'https://demo.eu.astrale.ai',
|
|
15
18
|
state: 'failed',
|
|
16
19
|
organizationId: 'org_123',
|
|
17
|
-
hostId: 'host-1',
|
|
18
|
-
region: 'eu',
|
|
19
20
|
phase: 'installing:default-domains',
|
|
20
21
|
error: 'postInstall failed',
|
|
21
22
|
createdAt: '2026-07-16T00:00:00.000Z',
|
|
@@ -39,8 +40,8 @@ describe('admin-backed instance commands', () => {
|
|
|
39
40
|
},
|
|
40
41
|
}
|
|
41
42
|
|
|
42
|
-
expect(() =>
|
|
43
|
-
expect(() =>
|
|
43
|
+
expect(() => assertInstanceCreateIdentity(store)).toThrow(AuthError)
|
|
44
|
+
expect(() => assertInstanceCreateIdentity(store)).toThrow('WorkOS login required')
|
|
44
45
|
})
|
|
45
46
|
|
|
46
47
|
test('instance create preflight accepts IdP-backed identities', () => {
|
|
@@ -58,7 +59,8 @@ describe('admin-backed instance commands', () => {
|
|
|
58
59
|
},
|
|
59
60
|
}
|
|
60
61
|
|
|
61
|
-
expect(() =>
|
|
62
|
+
expect(() => assertInstanceCreateIdentity(store)).not.toThrow()
|
|
63
|
+
expect(selectInstanceCreateIdentity(store)).toBe('bryan')
|
|
62
64
|
})
|
|
63
65
|
|
|
64
66
|
test('--as selection must also be IdP-backed', () => {
|
|
@@ -82,7 +84,8 @@ describe('admin-backed instance commands', () => {
|
|
|
82
84
|
},
|
|
83
85
|
}
|
|
84
86
|
|
|
85
|
-
expect(() =>
|
|
86
|
-
expect(() =>
|
|
87
|
+
expect(() => assertInstanceCreateIdentity(store, { as: 'local' })).toThrow(AuthError)
|
|
88
|
+
expect(() => assertInstanceCreateIdentity(store, { as: 'workos' })).not.toThrow()
|
|
89
|
+
expect(selectInstanceCreateIdentity(store, { as: 'workos' })).toBe('workos')
|
|
87
90
|
})
|
|
88
91
|
})
|
|
@@ -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
|
}
|
|
@@ -1,34 +1,47 @@
|
|
|
1
1
|
import { Path } from '@astrale-os/sdk/graph/path'
|
|
2
|
+
import { bundle, defineSchema, func, method, nodeClass } from '@astrale-os/sdk/schema'
|
|
2
3
|
import { describe, expect, test } from 'bun:test'
|
|
3
4
|
|
|
4
|
-
import {
|
|
5
|
+
import { describeCallableFromBundle } from '../call-describe'
|
|
5
6
|
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
output: { mode: 'value' },
|
|
15
|
-
},
|
|
16
|
-
},
|
|
17
|
-
},
|
|
7
|
+
const createInstance = method({
|
|
8
|
+
auth: 'authenticated',
|
|
9
|
+
description: 'Create one child Instance.',
|
|
10
|
+
input: {
|
|
11
|
+
type: 'object',
|
|
12
|
+
properties: { operationId: { type: 'string' }, slug: { type: 'string' } },
|
|
13
|
+
required: ['operationId', 'slug'],
|
|
14
|
+
additionalProperties: false,
|
|
18
15
|
},
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
16
|
+
output: { type: 'object', additionalProperties: true },
|
|
17
|
+
static: true,
|
|
18
|
+
})
|
|
19
|
+
const inspectInstance = method({
|
|
20
|
+
auth: 'authenticated',
|
|
21
|
+
description: 'Inspect one child Instance.',
|
|
22
|
+
input: { type: 'object', properties: {}, additionalProperties: false },
|
|
23
|
+
output: { type: 'object', additionalProperties: true },
|
|
24
|
+
})
|
|
25
|
+
const journal = func({
|
|
26
|
+
auth: 'authenticated',
|
|
27
|
+
description: 'Read the authorized Kernel journal.',
|
|
28
|
+
input: { type: 'object', properties: {}, additionalProperties: false },
|
|
29
|
+
output: { type: 'object', additionalProperties: true },
|
|
30
|
+
})
|
|
31
|
+
const source = defineSchema('host.astrale.ai', {
|
|
32
|
+
classes: {
|
|
33
|
+
Manager: nodeClass({ methods: { createInstance } }),
|
|
34
|
+
Instance: nodeClass({ methods: { inspectInstance } }),
|
|
24
35
|
},
|
|
25
|
-
}
|
|
36
|
+
functions: { journal },
|
|
37
|
+
})
|
|
38
|
+
const installed = bundle.create(source)
|
|
26
39
|
|
|
27
|
-
describe('
|
|
28
|
-
test('reads a static Class method from the Domain
|
|
29
|
-
const described =
|
|
40
|
+
describe('describeCallableFromBundle', () => {
|
|
41
|
+
test('reads a static Class method from the resolved Domain', () => {
|
|
42
|
+
const described = describeCallableFromBundle(
|
|
30
43
|
Path.parse('/:host.astrale.ai:class.Manager:createInstance'),
|
|
31
|
-
|
|
44
|
+
installed,
|
|
32
45
|
)
|
|
33
46
|
expect(described).toMatchObject({
|
|
34
47
|
origin: 'host.astrale.ai',
|
|
@@ -36,30 +49,30 @@ describe('describeCallableFromSchema', () => {
|
|
|
36
49
|
method: 'createInstance',
|
|
37
50
|
dispatch: 'static',
|
|
38
51
|
description: 'Create one child Instance.',
|
|
39
|
-
auth: '
|
|
52
|
+
auth: 'authenticated',
|
|
40
53
|
})
|
|
41
54
|
expect(described?.input).toMatchObject({ required: ['operationId', 'slug'] })
|
|
42
55
|
})
|
|
43
56
|
|
|
44
|
-
test('finds
|
|
57
|
+
test('finds one instance method when the receiver Path is not a Class projection', () => {
|
|
45
58
|
expect(
|
|
46
|
-
|
|
47
|
-
Path.parse('/:host.astrale.ai:core.manager::
|
|
48
|
-
|
|
59
|
+
describeCallableFromBundle(
|
|
60
|
+
Path.parse('/:host.astrale.ai:core.manager::inspectInstance'),
|
|
61
|
+
installed,
|
|
49
62
|
),
|
|
50
63
|
).toMatchObject({
|
|
51
64
|
origin: 'host.astrale.ai',
|
|
52
|
-
class: '
|
|
53
|
-
method: '
|
|
65
|
+
class: 'Instance',
|
|
66
|
+
method: 'inspectInstance',
|
|
54
67
|
dispatch: 'instance',
|
|
55
68
|
})
|
|
56
69
|
})
|
|
57
70
|
|
|
58
|
-
test('reads a standalone Function from the Domain
|
|
71
|
+
test('reads a standalone Function from the resolved Domain', () => {
|
|
59
72
|
expect(
|
|
60
|
-
|
|
73
|
+
describeCallableFromBundle(Path.parse('/:host.astrale.ai:function.journal'), installed),
|
|
61
74
|
).toMatchObject({
|
|
62
|
-
origin: '
|
|
75
|
+
origin: 'host.astrale.ai',
|
|
63
76
|
function: 'journal',
|
|
64
77
|
description: 'Read the authorized Kernel journal.',
|
|
65
78
|
})
|
|
@@ -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,5 +1,7 @@
|
|
|
1
|
+
import { defineSchema } from '@astrale-os/sdk/schema'
|
|
1
2
|
import { afterEach, describe, expect, mock, test } from 'bun:test'
|
|
2
3
|
|
|
4
|
+
import { releaseFor } from '../../__tests__/fixtures/publication'
|
|
3
5
|
import { installDirect } from '../domain/install'
|
|
4
6
|
|
|
5
7
|
const GENERATED = '4a4c9a18-50f6-4d84-a7b7-2d83e3e45dc8'
|
|
@@ -7,6 +9,13 @@ const RETRY = '139137b5-af47-47ce-92b2-b64a2b0c63d7'
|
|
|
7
9
|
|
|
8
10
|
const originalFetch = globalThis.fetch
|
|
9
11
|
|
|
12
|
+
const schema = defineSchema('crm.test', {})
|
|
13
|
+
const deployed = releaseFor(schema, 'https://crm.test').publication
|
|
14
|
+
|
|
15
|
+
function publicationResponse(): Response {
|
|
16
|
+
return Response.json(deployed)
|
|
17
|
+
}
|
|
18
|
+
|
|
10
19
|
class ExitError extends Error {}
|
|
11
20
|
|
|
12
21
|
afterEach(() => {
|
|
@@ -15,9 +24,7 @@ afterEach(() => {
|
|
|
15
24
|
|
|
16
25
|
describe('direct domain install operation identity', () => {
|
|
17
26
|
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
|
|
27
|
+
globalThis.fetch = mock(async () => publicationResponse()) as unknown as typeof fetch
|
|
21
28
|
const create = mock(() => GENERATED)
|
|
22
29
|
const requests: unknown[] = []
|
|
23
30
|
let recovery: unknown
|
|
@@ -54,9 +61,7 @@ describe('direct domain install operation identity', () => {
|
|
|
54
61
|
})
|
|
55
62
|
|
|
56
63
|
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
|
|
64
|
+
globalThis.fetch = mock(async () => publicationResponse()) as unknown as typeof fetch
|
|
60
65
|
const create = mock(() => GENERATED)
|
|
61
66
|
const accepted: string[] = []
|
|
62
67
|
const requests: unknown[] = []
|
|
@@ -95,7 +100,7 @@ describe('direct domain install operation identity', () => {
|
|
|
95
100
|
test('rejects a non-UUID operation before metadata or Kernel transport', async () => {
|
|
96
101
|
const originalExit = process.exit
|
|
97
102
|
const originalWrite = process.stderr.write
|
|
98
|
-
const fetch = mock(async () =>
|
|
103
|
+
const fetch = mock(async () => publicationResponse())
|
|
99
104
|
const run = mock(async () => undefined)
|
|
100
105
|
globalThis.fetch = fetch as unknown as typeof globalThis.fetch
|
|
101
106
|
process.exit = (() => {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ResponseError } from '@astrale-os/
|
|
1
|
+
import { ResponseError } from '@astrale-os/sdk/client'
|
|
2
2
|
import { afterEach, beforeEach, describe, expect, mock, test } from 'bun:test'
|
|
3
3
|
|
|
4
4
|
import type { OwnedInstanceInfo } from '../../lib/admin-instance'
|
|
@@ -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',
|