@astrale-os/cli 0.8.1-alpha.0 → 0.8.1-alpha.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/dist/astrale.js +62959 -41238
- package/dist/public/connect-core.js +55030 -0
- package/dist/public/keys/index.js +45631 -0
- package/dist/public/paths/index.js +43990 -0
- package/dist/types/admin/instance/model.d.ts +40 -0
- package/dist/types/connect-core.d.ts +22 -0
- package/dist/types/connection/auth.d.ts +33 -0
- package/dist/types/errors.d.ts +23 -0
- package/dist/types/identity/registry.d.ts +25 -0
- package/dist/types/keys/algorithm.d.ts +6 -0
- package/dist/types/keys/credential.d.ts +21 -0
- package/dist/types/keys/index.d.ts +4 -0
- package/dist/types/keys/pair.d.ts +34 -0
- package/dist/types/lib/admin-target.d.ts +74 -0
- package/dist/types/lib/ca-fetch.d.ts +2 -0
- package/dist/types/lib/config.d.ts +71 -0
- package/dist/types/lib/format.d.ts +1 -0
- package/dist/types/lib/idp-session.d.ts +33 -0
- package/dist/types/lib/idp.d.ts +397 -0
- package/dist/types/lib/instance-target.d.ts +36 -0
- package/dist/types/lib/instance.d.ts +186 -0
- package/dist/types/lib/log.d.ts +30 -0
- package/dist/types/lib/login-flow.d.ts +49 -0
- package/dist/types/lib/meta.d.ts +36 -0
- package/dist/types/lib/output.d.ts +83 -0
- package/dist/types/lib/table.d.ts +18 -0
- package/dist/types/lib/validation.d.ts +11 -0
- package/dist/types/paths/index.d.ts +2 -0
- package/dist/types/state/exchange-credentials.d.ts +25 -0
- package/dist/types/state/files.d.ts +9 -0
- package/dist/types/state/identities.d.ts +42 -0
- package/dist/types/state/index.d.ts +8 -0
- package/dist/types/state/paths.d.ts +32 -0
- package/package.json +22 -7
- package/src/admin/.spec/api.d.ts +3 -0
- package/src/admin/.spec/architecture.md +15 -0
- package/src/admin/.spec/icon.svg +3 -0
- package/src/admin/.spec/layout.ts +6 -0
- package/src/admin/catalog/.spec/api.d.ts +65 -0
- package/src/admin/catalog/.spec/architecture.md +6 -0
- package/src/admin/catalog/.spec/icon.svg +3 -0
- package/src/admin/catalog/.spec/layout.ts +6 -0
- package/src/admin/catalog/__tests__/client.test.ts +171 -0
- package/src/admin/catalog/client.ts +214 -0
- package/src/admin/catalog/index.ts +13 -0
- package/src/admin/catalog/model.ts +42 -0
- package/src/admin/catalog/tsconfig.json +8 -0
- package/src/admin/graph/.spec/api.d.ts +28 -0
- package/src/admin/graph/.spec/architecture.md +5 -0
- package/src/admin/graph/.spec/icon.svg +3 -0
- package/src/admin/graph/.spec/layout.ts +3 -0
- package/src/admin/graph/index.ts +6 -0
- package/src/admin/graph/nodes.ts +56 -0
- package/src/admin/index.ts +2 -0
- package/src/admin/instance/.spec/api.d.ts +66 -0
- package/src/admin/instance/.spec/architecture.md +10 -0
- package/src/admin/instance/.spec/icon.svg +3 -0
- package/src/admin/instance/.spec/layout.ts +6 -0
- package/src/admin/instance/__tests__/client.test.ts +289 -0
- package/src/admin/instance/client.ts +298 -0
- package/src/admin/instance/index.ts +16 -0
- package/src/admin/instance/model.ts +65 -0
- package/src/admin/instance/tsconfig.json +8 -0
- package/src/commands/__tests__/admin-instance.test.ts +3 -36
- package/src/commands/__tests__/call.test.ts +21 -68
- package/src/commands/__tests__/domain-install-owned.test.ts +26 -27
- package/src/commands/__tests__/domain-list.test.ts +2 -6
- package/src/commands/__tests__/logs.test.ts +50 -98
- package/src/commands/__tests__/ls.test.ts +21 -17
- package/src/commands/__tests__/read-commands.test.ts +171 -101
- package/src/commands/__tests__/view.test.ts +100 -51
- package/src/commands/admin/status.ts +3 -3
- package/src/commands/admin/use.ts +29 -6
- package/src/commands/auth/login.ts +1 -1
- package/src/commands/auth/logout.ts +4 -2
- package/src/commands/auth/status.ts +2 -2
- package/src/commands/auth/token.ts +2 -2
- package/src/commands/browser.ts +1 -1
- package/src/commands/call.ts +55 -91
- package/src/commands/describe.ts +52 -147
- package/src/commands/domain/install.ts +81 -50
- package/src/commands/domain/list.ts +4 -8
- package/src/commands/domain/publish.ts +9 -31
- package/src/commands/get.ts +32 -65
- package/src/commands/identity/__tests__/register.test.ts +93 -0
- package/src/commands/identity/create.ts +2 -2
- package/src/commands/identity/delete.ts +2 -2
- package/src/commands/identity/export.ts +7 -33
- package/src/commands/identity/import.ts +21 -70
- package/src/commands/identity/list.ts +2 -2
- package/src/commands/identity/register.ts +148 -117
- package/src/commands/identity/sync.ts +2 -2
- package/src/commands/identity/unsync.ts +2 -2
- package/src/commands/identity/use.ts +2 -2
- package/src/commands/identity/whoami.ts +2 -2
- package/src/commands/idp/add.ts +1 -1
- package/src/commands/idp/list.ts +1 -1
- package/src/commands/idp/refresh.ts +1 -1
- package/src/commands/idp/remove.ts +2 -2
- package/src/commands/idp/show.ts +1 -1
- package/src/commands/instance/active.ts +2 -2
- package/src/commands/instance/bookmark.ts +8 -2
- package/src/commands/instance/create.ts +1 -1
- package/src/commands/instance/delete.ts +4 -10
- package/src/commands/instance/forget.ts +1 -1
- package/src/commands/instance/list.ts +3 -3
- package/src/commands/instance/status.ts +5 -7
- package/src/commands/instance/use.ts +3 -3
- package/src/commands/logs.ts +152 -279
- package/src/commands/ls.ts +78 -204
- package/src/commands/mutate.ts +70 -139
- package/src/commands/query.ts +100 -278
- package/src/commands/session/analyze.ts +1 -1
- package/src/commands/session/list.ts +1 -1
- package/src/commands/setup.ts +1 -1
- package/src/commands/status.ts +1 -1
- package/src/commands/studio.ts +1 -1
- package/src/commands/token.ts +26 -21
- package/src/commands/update.ts +1 -1
- package/src/commands/use.ts +2 -3
- package/src/commands/view-serve.ts +1 -1
- package/src/commands/view.ts +67 -109
- package/src/connect-core.test.ts +38 -0
- package/src/connect-core.ts +42 -0
- package/src/connection/.spec/api.d.ts +111 -0
- package/src/connection/.spec/architecture.md +41 -0
- package/src/connection/.spec/capabilities/connection.ts +13 -0
- package/src/connection/.spec/examples/call.ts +7 -0
- package/src/connection/.spec/flows/command.ts +20 -0
- package/src/connection/.spec/flows/session.ts +61 -0
- package/src/connection/.spec/laws/connection.ts +181 -0
- package/src/connection/.spec/layout.ts +19 -0
- package/src/{kernel → connection}/__tests__/auth.test.ts +26 -2
- package/src/connection/__tests__/ca-fetch.test.ts +53 -0
- package/src/connection/__tests__/call.test.ts +17 -0
- package/src/connection/__tests__/credential.test.ts +109 -0
- package/src/connection/__tests__/errors.test.ts +96 -0
- package/src/connection/__tests__/exchange.test.ts +224 -0
- package/src/connection/__tests__/fixtures/localhost-cert.pem +19 -0
- package/src/connection/__tests__/fixtures/localhost-key.base64 +1 -0
- package/src/connection/__tests__/managed.test.ts +39 -0
- package/src/connection/__tests__/self.test.ts +51 -0
- package/src/connection/__tests__/session.test.ts +140 -0
- package/src/connection/__tests__/target.test.ts +93 -0
- package/src/{kernel → connection}/auth.ts +8 -8
- package/src/connection/call.ts +7 -0
- package/src/{kernel/run.ts → connection/command.ts} +22 -13
- package/src/connection/credential.ts +114 -0
- package/src/{kernel → connection}/errors.ts +32 -9
- package/src/connection/exchange.ts +274 -0
- package/src/connection/index.ts +8 -0
- package/src/connection/self.ts +120 -0
- package/src/connection/session.ts +176 -0
- package/src/connection/target.ts +111 -0
- package/src/connection/tsconfig.json +6 -0
- package/src/graph/.spec/api.d.ts +34 -0
- package/src/graph/.spec/laws/documents.ts +24 -0
- package/src/graph/.spec/laws/projection.ts +13 -0
- package/src/graph/.spec/layout.ts +6 -0
- package/src/graph/__tests__/mutation.test.ts +43 -0
- package/src/graph/__tests__/projection.test.ts +22 -0
- package/src/graph/__tests__/query.test.ts +154 -0
- package/src/graph/index.ts +3 -0
- package/src/graph/mutation.ts +18 -0
- package/src/graph/projection.ts +21 -0
- package/src/graph/query.ts +108 -0
- package/src/graph/tsconfig.json +6 -0
- package/src/identity/.history/ard/bootstrap-root-transfer.md +57 -0
- package/src/identity/.history/ard/domain-mediated-registration.md +36 -0
- package/src/identity/.spec/api.d.ts +124 -0
- package/src/identity/.spec/architecture.md +33 -0
- package/src/identity/.spec/capabilities/identity.ts +7 -0
- package/src/identity/.spec/flows/bootstrap-root.ts +79 -0
- package/src/identity/.spec/flows/domain-mediated-registration.ts +51 -0
- package/src/identity/.spec/flows/import.ts +29 -0
- package/src/identity/.spec/laws/identity.ts +56 -0
- package/src/identity/.spec/layout.ts +13 -0
- package/src/identity/.spec/schemas/identity-export-v1.schema.json +25 -0
- package/src/identity/__tests__/fixtures/registry-journey.ts +53 -0
- package/src/identity/__tests__/registration.test.ts +64 -0
- package/src/identity/__tests__/registry.test.ts +70 -0
- package/src/identity/__tests__/transfer.test.ts +198 -0
- package/src/identity/index.ts +23 -0
- package/src/identity/registration.ts +72 -0
- package/src/identity/registry.ts +172 -0
- package/src/identity/transfer.ts +187 -0
- package/src/identity/tsconfig.json +6 -0
- package/src/index.ts +1 -0
- package/src/keys/.spec/api.d.ts +74 -0
- package/src/keys/.spec/architecture.md +13 -0
- package/src/keys/.spec/laws/keys.ts +43 -0
- package/src/keys/.spec/layout.ts +6 -0
- package/src/keys/__tests__/algorithm.test.ts +39 -0
- package/src/keys/__tests__/keys.test.ts +168 -0
- package/src/keys/algorithm.ts +33 -0
- package/src/keys/credential.ts +112 -0
- package/src/keys/index.ts +14 -0
- package/src/keys/pair.ts +219 -0
- package/src/keys/tsconfig.json +6 -0
- package/src/lib/__tests__/admin-target.test.ts +67 -7
- package/src/lib/__tests__/binary.test.ts +2 -3
- package/src/lib/__tests__/command-dx.test.ts +2 -2
- package/src/lib/__tests__/config.test.ts +3 -2
- package/src/lib/__tests__/idp.test.ts +4 -6
- package/src/lib/__tests__/instance-target.test.ts +7 -4
- package/src/lib/__tests__/local-status.test.ts +1 -1
- package/src/lib/__tests__/log.test.ts +31 -0
- package/src/lib/__tests__/meta.test.ts +55 -0
- package/src/lib/__tests__/sdk-deps.test.ts +1 -1
- package/src/lib/__tests__/self.test.ts +8 -230
- package/src/lib/__tests__/use-target.test.ts +1 -1
- package/src/lib/__tests__/view-open-intent.test.ts +78 -104
- package/src/lib/__tests__/view-session.test.ts +99 -0
- package/src/lib/admin-domain.ts +39 -25
- package/src/lib/admin-instance.ts +38 -43
- package/src/lib/admin-target.ts +67 -13
- package/src/lib/binary.ts +13 -34
- package/src/lib/browser.ts +1 -1
- package/src/{kernel → lib}/ca-fetch.ts +35 -27
- package/src/lib/config.ts +1 -1
- package/src/lib/idp-session.ts +1 -1
- package/src/lib/idp.ts +3 -8
- package/src/lib/instance-target.ts +12 -8
- package/src/lib/instance.ts +34 -2
- package/src/lib/local-status.ts +2 -2
- package/src/lib/log.ts +3 -1
- package/src/lib/login-flow.ts +32 -5
- package/src/lib/meta.ts +15 -11
- package/src/lib/provision-instance.ts +4 -12
- package/src/lib/self.ts +5 -147
- package/src/lib/update.ts +1 -1
- package/src/lib/use-target.ts +1 -1
- package/src/lib/validation.ts +2 -2
- package/src/lib/view/open-intent.ts +7 -37
- package/src/lib/view/port-allocation.ts +1 -1
- package/src/lib/view/resolve.ts +52 -45
- package/src/lib/view/server.ts +16 -33
- package/src/lib/view/session.ts +51 -22
- package/src/paths/.spec/api.d.ts +13 -0
- package/src/paths/.spec/architecture.md +5 -0
- package/src/paths/.spec/layout.ts +3 -0
- package/src/paths/index.ts +13 -0
- package/src/program/.spec/api.d.ts +42 -0
- package/src/program/.spec/architecture.md +18 -0
- package/src/program/.spec/laws/program.ts +29 -0
- package/src/program/.spec/layout.ts +14 -0
- package/src/program/__tests__/program.test.ts +359 -0
- package/src/program/build.ts +190 -0
- package/src/program/command.ts +42 -0
- package/src/program/index.ts +2 -0
- package/src/program/options.ts +40 -0
- package/src/{registry.ts → program/registry.ts} +5 -1
- package/src/program/tsconfig.json +6 -0
- package/src/setup/steps/admin.ts +18 -3
- package/src/setup/steps/instance.ts +1 -1
- package/src/state/.spec/api.d.ts +133 -0
- package/src/state/.spec/architecture.md +31 -0
- package/src/state/.spec/capabilities/state.ts +7 -0
- package/src/state/.spec/flows/identity-update.ts +22 -0
- package/src/state/.spec/laws/state.ts +73 -0
- package/src/state/.spec/layout.ts +14 -0
- package/src/state/__tests__/exchange-credentials.test.ts +188 -0
- package/src/state/__tests__/files.test.ts +113 -0
- package/src/state/__tests__/fixtures/identity-transition.ts +37 -0
- package/src/state/__tests__/identities.test.ts +142 -0
- package/src/state/__tests__/paths.test.ts +27 -0
- package/src/state/exchange-credentials.ts +219 -0
- package/src/state/files.ts +129 -0
- package/src/state/identities.ts +236 -0
- package/src/state/index.ts +29 -0
- package/src/state/paths.ts +61 -0
- package/src/state/tsconfig.json +6 -0
- package/src/telemetry/__tests__/redact.test.ts +3 -3
- package/src/telemetry/__tests__/trigger.test.ts +1 -1
- package/src/telemetry/settings.ts +1 -1
- package/src/telemetry/store.ts +1 -1
- package/src/test-utils.ts +1 -1
- package/studio/client/dist/assets/{elk-api-DVw5WDTH.js → elk-api-D0cBetPW.js} +1 -1
- package/studio/client/dist/assets/index-Dspir4w7.js +81 -0
- package/studio/client/dist/assets/{index-Dl709Ra1.js → index-bVD2KJgz.js} +4 -4
- package/studio/client/dist/index.html +1 -1
- package/studio/package.json +10 -1
- package/studio/server/introspect/extractor.ts +1 -1
- package/studio/server/state/views.test.ts +3 -7
- package/studio/server/state/views.ts +23 -30
- package/tsconfig.json +1 -1
- package/viewer/dist/main.js +65 -39
- package/src/command.ts +0 -42
- package/src/commands/__tests__/help-contract.test.ts +0 -164
- package/src/kernel/__tests__/client-owned-lookup.test.ts +0 -51
- package/src/kernel/__tests__/errors.test.ts +0 -43
- package/src/kernel/__tests__/expand.test.ts +0 -123
- package/src/kernel/client.ts +0 -214
- package/src/kernel/expand.ts +0 -192
- package/src/kernel/graph.ts +0 -96
- package/src/kernel/index.ts +0 -29
- package/src/kernel/options.ts +0 -22
- package/src/kernel/types.ts +0 -14
- package/src/lib/__tests__/domain-identity.test.ts +0 -60
- package/src/lib/__tests__/fs-atomic.test.ts +0 -104
- package/src/lib/__tests__/identity.test.ts +0 -79
- package/src/lib/__tests__/keys.test.ts +0 -129
- package/src/lib/domain-identity.ts +0 -49
- package/src/lib/env.ts +0 -49
- package/src/lib/fs-atomic.ts +0 -126
- package/src/lib/identity.ts +0 -256
- package/src/lib/keys.ts +0 -294
- package/src/lib/paths.ts +0 -11
- package/src/program.ts +0 -213
- package/studio/client/dist/assets/index-CTbkDp3z.js +0 -81
package/src/kernel/expand.ts
DELETED
|
@@ -1,192 +0,0 @@
|
|
|
1
|
-
import type { KernelCommandOpts } from './types'
|
|
2
|
-
|
|
3
|
-
/** CLI bridge for @self resolution and stale-registration error hints. */
|
|
4
|
-
import { readConfig } from '../lib/config'
|
|
5
|
-
import { getDefault, getIdentity, setRegistration } from '../lib/identity'
|
|
6
|
-
import { decodeTokenClaims, readIdpSession } from '../lib/idp'
|
|
7
|
-
import { resolveInstanceTarget } from '../lib/instance-target'
|
|
8
|
-
import { fileExists, keypairPaths } from '../lib/keys'
|
|
9
|
-
import { KEYS_DIR } from '../lib/paths'
|
|
10
|
-
import {
|
|
11
|
-
containsSelfRef,
|
|
12
|
-
expandSelfReferences,
|
|
13
|
-
resolveSelfNodeId,
|
|
14
|
-
selfRefusalError,
|
|
15
|
-
type SelfResolverContext,
|
|
16
|
-
type SelfResolution,
|
|
17
|
-
} from '../lib/self'
|
|
18
|
-
import { lookupImplicitOwnedInstance, withKernelClient } from './client'
|
|
19
|
-
|
|
20
|
-
/** Metadata attached to errors so the NotFoundError path can hint at stale `@self` expansions. */
|
|
21
|
-
export type SelfExpansionMeta = {
|
|
22
|
-
original: string
|
|
23
|
-
expanded: string
|
|
24
|
-
selfId: string
|
|
25
|
-
identity?: string
|
|
26
|
-
slug?: string
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/** Build the same target/signing context `withKernelClient` will use. */
|
|
30
|
-
export async function buildSelfContext(opts: KernelCommandOpts): Promise<SelfResolverContext> {
|
|
31
|
-
const config = await readConfig()
|
|
32
|
-
// Mirror withKernelClient's slug logic: --url without -i ⇒ no slug.
|
|
33
|
-
let slug: string | undefined
|
|
34
|
-
let defaultIdentity: string | undefined
|
|
35
|
-
if (opts.url && !opts.instance) {
|
|
36
|
-
slug = undefined
|
|
37
|
-
} else {
|
|
38
|
-
const resolved = await resolveInstanceTarget(
|
|
39
|
-
opts.instance ? { source: 'name', name: opts.instance } : { source: 'active' },
|
|
40
|
-
{
|
|
41
|
-
config,
|
|
42
|
-
admin: {},
|
|
43
|
-
managed: (instanceSlug) => lookupImplicitOwnedInstance(instanceSlug, opts),
|
|
44
|
-
},
|
|
45
|
-
)
|
|
46
|
-
slug = resolved.name
|
|
47
|
-
defaultIdentity = resolved.defaultIdentity
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// Mirror resolveCredential's instance-signed branch: when targeting a
|
|
51
|
-
// child for which the CLI generated a dedicated keypair, the call signs
|
|
52
|
-
// as the instance itself, not as a user identity.
|
|
53
|
-
let instanceSigned = false
|
|
54
|
-
if (!opts.creds && !opts.as && !defaultIdentity && slug && slug !== 'manager') {
|
|
55
|
-
const { privatePath } = keypairPaths(slug, KEYS_DIR)
|
|
56
|
-
instanceSigned = await fileExists(privatePath)
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// Identity lookup failures should surface as real CLI errors, not @self refusals.
|
|
60
|
-
let identity: SelfResolverContext['identity']
|
|
61
|
-
if (!opts.creds) {
|
|
62
|
-
const identityName = opts.as ?? defaultIdentity
|
|
63
|
-
if (identityName) {
|
|
64
|
-
const i = await getIdentity(identityName)
|
|
65
|
-
identity = { ...i, name: identityName }
|
|
66
|
-
} else {
|
|
67
|
-
identity = await getDefault()
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
let idpSubject: string | undefined
|
|
72
|
-
if (identity && (identity.source ?? 'key') === 'idp') {
|
|
73
|
-
instanceSigned = false
|
|
74
|
-
const session = await readIdpSession(identity.name)
|
|
75
|
-
const claims = decodeTokenClaims(session?.id_token ?? session?.access_token)
|
|
76
|
-
if (typeof claims?.sub === 'string' && claims.sub.trim().length > 0) {
|
|
77
|
-
idpSubject = claims.sub
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
return { identity, instanceSlug: slug, credsJwt: opts.creds, instanceSigned, idpSubject }
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* Run `fn`; if it throws a `NotFoundError`, stamp expansion metadata onto the
|
|
86
|
-
* error so `formatKernelError` can append the stale-registration hint. The
|
|
87
|
-
* error is re-thrown either way.
|
|
88
|
-
*/
|
|
89
|
-
export async function withSelfHint<T>(
|
|
90
|
-
fn: () => Promise<T>,
|
|
91
|
-
meta: SelfExpansionMeta | undefined,
|
|
92
|
-
): Promise<T> {
|
|
93
|
-
if (!meta) return fn()
|
|
94
|
-
try {
|
|
95
|
-
return await fn()
|
|
96
|
-
} catch (err) {
|
|
97
|
-
if (err instanceof Error && err.name === 'NotFoundError') {
|
|
98
|
-
;(err as Error & { expandedFromSelf?: SelfExpansionMeta }).expandedFromSelf = meta
|
|
99
|
-
}
|
|
100
|
-
throw err
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
/** Convenience: resolve the nodeId once, or throw the typed refusal. */
|
|
105
|
-
export function resolveOrThrow(selfCtx: SelfResolverContext): string {
|
|
106
|
-
const r: SelfResolution = resolveSelfNodeId(selfCtx)
|
|
107
|
-
if ('reason' in r) throw selfRefusalError(r)
|
|
108
|
-
return r.id
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
export type ResolveSelfIdLazyDeps = {
|
|
112
|
-
whoami?: (opts: KernelCommandOpts) => Promise<{ id?: unknown; kernelUrl: string }>
|
|
113
|
-
setRegistration?: typeof setRegistration
|
|
114
|
-
now?: () => Date
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
/**
|
|
118
|
-
* Resolve @self for IdP identities through one whoami refresh, then cache the
|
|
119
|
-
* current node id. Non-IdP refusals stay typed and local.
|
|
120
|
-
*/
|
|
121
|
-
export async function resolveSelfIdLazy(
|
|
122
|
-
selfCtx: SelfResolverContext,
|
|
123
|
-
opts: KernelCommandOpts,
|
|
124
|
-
deps: ResolveSelfIdLazyDeps = {},
|
|
125
|
-
): Promise<string> {
|
|
126
|
-
const r: SelfResolution = resolveSelfNodeId(selfCtx)
|
|
127
|
-
const cachedId = 'reason' in r ? undefined : r.id
|
|
128
|
-
const isIdp = (selfCtx.identity?.source ?? 'key') === 'idp'
|
|
129
|
-
|
|
130
|
-
if (isIdp && selfCtx.instanceSlug && selfCtx.identity) {
|
|
131
|
-
let lookup: { id?: unknown; kernelUrl: string }
|
|
132
|
-
try {
|
|
133
|
-
lookup = await (deps.whoami ?? whoamiSelfId)(opts)
|
|
134
|
-
} catch {
|
|
135
|
-
if (cachedId) return cachedId
|
|
136
|
-
if ('reason' in r) throw selfRefusalError(r)
|
|
137
|
-
throw selfRefusalError({ reason: 'idp-no-sub', identityName: selfCtx.identity.name })
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
const resolvedId =
|
|
141
|
-
typeof lookup.id === 'string' && lookup.id.trim().length > 0 ? lookup.id : undefined
|
|
142
|
-
if (!resolvedId) {
|
|
143
|
-
if (cachedId) return cachedId
|
|
144
|
-
if ('reason' in r) throw selfRefusalError(r)
|
|
145
|
-
throw selfRefusalError({ reason: 'idp-no-sub', identityName: selfCtx.identity.name })
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
const cached = selfCtx.identity.registrations?.[selfCtx.instanceSlug]
|
|
149
|
-
if (cached?.sub !== resolvedId || cached?.iss !== lookup.kernelUrl) {
|
|
150
|
-
await (deps.setRegistration ?? setRegistration)(selfCtx.identity.name, selfCtx.instanceSlug, {
|
|
151
|
-
iss: lookup.kernelUrl,
|
|
152
|
-
sub: resolvedId,
|
|
153
|
-
registeredAt: (deps.now ?? (() => new Date()))().toISOString(),
|
|
154
|
-
})
|
|
155
|
-
}
|
|
156
|
-
return resolvedId
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
if (!('reason' in r)) return r.id
|
|
160
|
-
throw selfRefusalError(r)
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
async function whoamiSelfId(opts: KernelCommandOpts): Promise<{ id?: unknown; kernelUrl: string }> {
|
|
164
|
-
let kernelUrl = ''
|
|
165
|
-
const me = await withKernelClient(opts, (ctx) => {
|
|
166
|
-
kernelUrl = ctx.url
|
|
167
|
-
return ctx.client.as(ctx.credential).auth.whoami()
|
|
168
|
-
})
|
|
169
|
-
return { id: me.id, kernelUrl }
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
/** Expand @self in path-like commands and return metadata for NotFound hints. */
|
|
173
|
-
export async function expandSelfInPath(
|
|
174
|
-
path: string,
|
|
175
|
-
opts: KernelCommandOpts,
|
|
176
|
-
): Promise<{ path: string; meta: SelfExpansionMeta | undefined }> {
|
|
177
|
-
if (!containsSelfRef(path)) return { path, meta: undefined }
|
|
178
|
-
const selfCtx = await buildSelfContext(opts)
|
|
179
|
-
const id = await resolveSelfIdLazy(selfCtx, opts)
|
|
180
|
-
const expanded = expandSelfReferences(path, id)
|
|
181
|
-
if (expanded === path) return { path, meta: undefined }
|
|
182
|
-
return {
|
|
183
|
-
path: expanded,
|
|
184
|
-
meta: {
|
|
185
|
-
original: path,
|
|
186
|
-
expanded,
|
|
187
|
-
selfId: id,
|
|
188
|
-
identity: selfCtx.identity?.name,
|
|
189
|
-
slug: selfCtx.instanceSlug,
|
|
190
|
-
},
|
|
191
|
-
}
|
|
192
|
-
}
|
package/src/kernel/graph.ts
DELETED
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
GetResultWire,
|
|
3
|
-
GraphApi,
|
|
4
|
-
GraphNodeWire,
|
|
5
|
-
MutationResultWire,
|
|
6
|
-
PatchInput,
|
|
7
|
-
QueryASTInput,
|
|
8
|
-
} from '@astrale-os/kernel-client/graph'
|
|
9
|
-
|
|
10
|
-
import { createGraph, rawOf } from '@astrale-os/kernel-client/graph'
|
|
11
|
-
|
|
12
|
-
import type { ClientContext } from './client'
|
|
13
|
-
|
|
14
|
-
export type {
|
|
15
|
-
GetResultWire,
|
|
16
|
-
GraphApi,
|
|
17
|
-
GraphNodeWire,
|
|
18
|
-
MutationResultWire,
|
|
19
|
-
PatchInput,
|
|
20
|
-
QueryASTInput,
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/** A node row as it crosses the graph doors — the shared item shape for `get`/`ls`/`describe`. */
|
|
24
|
-
export type GraphNode = GraphNodeWire
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Bind the graph sugar (`function.get` / `function.mutate` + the read/write
|
|
28
|
-
* helpers) over a CLI client session. The sugar is pure over a call capability;
|
|
29
|
-
* we hand it `ClientSession.call` — the same surface every command already uses.
|
|
30
|
-
*/
|
|
31
|
-
export function bindGraph(ctx: ClientContext): GraphApi {
|
|
32
|
-
return createGraph((method, params) => ctx.client.call(method, params))
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Split a `function.get` page into addressed root and descendants. Root matches
|
|
37
|
-
* path/id first, then falls back to the shallowest node in the page.
|
|
38
|
-
*/
|
|
39
|
-
export function splitRoot(
|
|
40
|
-
nodes: readonly GraphNode[],
|
|
41
|
-
rootPath: string,
|
|
42
|
-
): { root: GraphNode | undefined; children: GraphNode[] } {
|
|
43
|
-
const raw = rawOf(rootPath)
|
|
44
|
-
const idForm = rootPath.startsWith('@') ? rootPath.slice(1) : undefined
|
|
45
|
-
|
|
46
|
-
let idx = nodes.findIndex(
|
|
47
|
-
(n) => rawOf(n.path) === raw || (idForm !== undefined && n.id === idForm),
|
|
48
|
-
)
|
|
49
|
-
if (idx < 0 && nodes.length > 0) {
|
|
50
|
-
let min = Number.POSITIVE_INFINITY
|
|
51
|
-
nodes.forEach((n, i) => {
|
|
52
|
-
const depth = rawOf(n.path).split('/').length
|
|
53
|
-
if (depth < min) {
|
|
54
|
-
min = depth
|
|
55
|
-
idx = i
|
|
56
|
-
}
|
|
57
|
-
})
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
const root = idx >= 0 ? nodes[idx] : undefined
|
|
61
|
-
const children = idx >= 0 ? nodes.filter((_, i) => i !== idx) : [...nodes]
|
|
62
|
-
return { root, children }
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/** The children cursor of a single-root `function.get` result, if the page overflowed. */
|
|
66
|
-
export function childrenCursor(result: GetResultWire): string | undefined {
|
|
67
|
-
const next = result.next
|
|
68
|
-
if (!next) return undefined
|
|
69
|
-
return Object.values(next)[0]?.children
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Strip a fully-qualified prop key to its leaf name. Node props travel under
|
|
74
|
-
* qualified keys (`<domain>:class.X.property.name` / `<domain>:interface.I.property.name`);
|
|
75
|
-
* this recovers `name`.
|
|
76
|
-
*/
|
|
77
|
-
export function unqualifyKey(key: string): string {
|
|
78
|
-
const dot = key.lastIndexOf('.property.')
|
|
79
|
-
if (dot >= 0) return key.slice(dot + '.property.'.length)
|
|
80
|
-
const slash = key.lastIndexOf('/')
|
|
81
|
-
return slash >= 0 ? key.slice(slash + 1) : key
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/** Read a node prop by its unqualified leaf name (exact key wins, else a qualified match). */
|
|
85
|
-
export function nodeProp(
|
|
86
|
-
node: { props: Record<string, unknown> } | null | undefined,
|
|
87
|
-
name: string,
|
|
88
|
-
): unknown {
|
|
89
|
-
const props = node?.props
|
|
90
|
-
if (!props) return undefined
|
|
91
|
-
if (name in props) return props[name]
|
|
92
|
-
for (const [k, v] of Object.entries(props)) {
|
|
93
|
-
if (unqualifyKey(k) === name) return v
|
|
94
|
-
}
|
|
95
|
-
return undefined
|
|
96
|
-
}
|
package/src/kernel/index.ts
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
export {
|
|
2
|
-
withKernelClient,
|
|
3
|
-
withAdminKernelClient,
|
|
4
|
-
resolveKernelTarget,
|
|
5
|
-
type ClientContext,
|
|
6
|
-
type ResolvedKernelTarget,
|
|
7
|
-
} from './client'
|
|
8
|
-
export { resolveCredential } from './auth'
|
|
9
|
-
export { formatKernelError } from './errors'
|
|
10
|
-
export {
|
|
11
|
-
buildSelfContext,
|
|
12
|
-
expandSelfInPath,
|
|
13
|
-
withSelfHint,
|
|
14
|
-
resolveOrThrow,
|
|
15
|
-
resolveSelfIdLazy,
|
|
16
|
-
type SelfExpansionMeta,
|
|
17
|
-
} from './expand'
|
|
18
|
-
export { runKernelCommand } from './run'
|
|
19
|
-
export { bindGraph, splitRoot, childrenCursor, nodeProp, unqualifyKey } from './graph'
|
|
20
|
-
export type {
|
|
21
|
-
GetResultWire,
|
|
22
|
-
GraphApi,
|
|
23
|
-
GraphNode,
|
|
24
|
-
GraphNodeWire,
|
|
25
|
-
MutationResultWire,
|
|
26
|
-
PatchInput,
|
|
27
|
-
QueryASTInput,
|
|
28
|
-
} from './graph'
|
|
29
|
-
export type { KernelCommandOpts, CallCommandOpts } from './types'
|
package/src/kernel/options.ts
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import type { CommandOption } from '../command'
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Flags consumed by `withKernelClient` (and therefore valid on any command
|
|
5
|
-
* that talks to a kernel). Spread these into a command's `options` to expose
|
|
6
|
-
* the same `--url / --instance / --timeout / --as / --creds / --debug`
|
|
7
|
-
* surface as `astrale call`.
|
|
8
|
-
*/
|
|
9
|
-
export const KERNEL_PASSTHROUGH_OPTIONS: CommandOption[] = [
|
|
10
|
-
{
|
|
11
|
-
flags: '--url <url>',
|
|
12
|
-
description: 'Target a kernel URL directly (overrides instance resolution)',
|
|
13
|
-
},
|
|
14
|
-
{
|
|
15
|
-
flags: '-i, --instance <name>',
|
|
16
|
-
description: 'Target a specific instance (overrides active)',
|
|
17
|
-
},
|
|
18
|
-
{ flags: '--timeout <ms>', description: 'Request timeout in ms (default: 30000)' },
|
|
19
|
-
{ flags: '--as <identity>', description: 'Call as a specific identity' },
|
|
20
|
-
{ flags: '--creds <token>', description: 'Use a pre-signed credential (e.g. delegation token)' },
|
|
21
|
-
{ flags: '--debug', description: 'Print full error diagnostics on failure' },
|
|
22
|
-
]
|
package/src/kernel/types.ts
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import type { OutputOpts } from '../lib/output'
|
|
2
|
-
|
|
3
|
-
export type KernelCommandOpts = OutputOpts & {
|
|
4
|
-
url?: string
|
|
5
|
-
instance?: string
|
|
6
|
-
timeout?: string
|
|
7
|
-
as?: string
|
|
8
|
-
creds?: string
|
|
9
|
-
debug?: boolean
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export type CallCommandOpts = KernelCommandOpts & {
|
|
13
|
-
data?: string
|
|
14
|
-
}
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `inferAlg` regression tests.
|
|
3
|
-
*
|
|
4
|
-
* (The historical `collectFunctionSubs` adversarial suite — 15 cases that
|
|
5
|
-
* pinned the raw-spec edge-walker against both wire forms / both member
|
|
6
|
-
* namespaces — was removed when the CLI converged onto the kernel's
|
|
7
|
-
* canonical resolver. Subs derivation now runs the IDENTICAL
|
|
8
|
-
* `Graph.fromWire` → `deserializeDomainFromGraph` → `collectFunctionSubs`
|
|
9
|
-
* pipeline the kernel uses at install validation, so drift is impossible
|
|
10
|
-
* by construction. The contract is pinned by
|
|
11
|
-
* `kernel/core/__tests__/domain/resolve-callables-agreement.test.ts`.)
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
import { describe, expect, test } from 'bun:test'
|
|
15
|
-
|
|
16
|
-
import { AstraleError } from '../../errors'
|
|
17
|
-
import { inferAlg } from '../domain-identity'
|
|
18
|
-
|
|
19
|
-
describe('inferAlg', () => {
|
|
20
|
-
// Adversarial: older CLI releases generated ES256 keys without
|
|
21
|
-
// stamping `alg`. Every fresh-machine `astrale domain install` then
|
|
22
|
-
// crashed with "alg is required when jwk.alg is not present" because the
|
|
23
|
-
// CLI read `privateJwk.alg as string` directly and handed `undefined` to
|
|
24
|
-
// jose's importJWK. The fix sets `alg` at keygen time AND falls back to
|
|
25
|
-
// inferring from `crv`/`kty` for already-issued keys (META_TRACE #34).
|
|
26
|
-
|
|
27
|
-
test('returns explicit alg when present', () => {
|
|
28
|
-
expect(inferAlg({ alg: 'ES256' })).toBe('ES256')
|
|
29
|
-
expect(inferAlg({ alg: 'EdDSA' })).toBe('EdDSA')
|
|
30
|
-
})
|
|
31
|
-
|
|
32
|
-
test('infers ES256 from P-256 EC keys (crv + kty) when alg is missing', () => {
|
|
33
|
-
expect(inferAlg({ kty: 'EC', crv: 'P-256' })).toBe('ES256')
|
|
34
|
-
})
|
|
35
|
-
|
|
36
|
-
test('infers EdDSA from Ed25519 OKP keys (crv + kty) when alg is missing', () => {
|
|
37
|
-
expect(inferAlg({ kty: 'OKP', crv: 'Ed25519' })).toBe('EdDSA')
|
|
38
|
-
})
|
|
39
|
-
|
|
40
|
-
test('explicit alg wins over inferable shape (no second-guessing)', () => {
|
|
41
|
-
// If a file says ES256 but has Ed25519 components, that's a broken
|
|
42
|
-
// file — don't silently override. assertKeyPairConsistent will catch
|
|
43
|
-
// the mismatch downstream.
|
|
44
|
-
expect(inferAlg({ alg: 'ES256', kty: 'OKP', crv: 'Ed25519' })).toBe('ES256')
|
|
45
|
-
})
|
|
46
|
-
|
|
47
|
-
test('throws AstraleError with hint when no alg/crv/kty resolves', () => {
|
|
48
|
-
expect(() => inferAlg({})).toThrow(AstraleError)
|
|
49
|
-
expect(() => inferAlg({ kty: 'EC' })).toThrow(AstraleError)
|
|
50
|
-
expect(() => inferAlg({ kty: 'EC', crv: 'P-384' })).toThrow(AstraleError)
|
|
51
|
-
})
|
|
52
|
-
|
|
53
|
-
test('error message includes keyPath when provided', () => {
|
|
54
|
-
expect(() => inferAlg({}, '/path/to/broken.jwk')).toThrow(/\/path\/to\/broken\.jwk/)
|
|
55
|
-
})
|
|
56
|
-
|
|
57
|
-
test('treats empty-string alg as missing', () => {
|
|
58
|
-
expect(inferAlg({ alg: '', kty: 'EC', crv: 'P-256' })).toBe('ES256')
|
|
59
|
-
})
|
|
60
|
-
})
|
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
import { afterEach, beforeEach, describe, expect, test } from 'bun:test'
|
|
2
|
-
import { mkdtemp, readdir, readFile, rm, stat, utimes, writeFile } from 'node:fs/promises'
|
|
3
|
-
import { tmpdir } from 'node:os'
|
|
4
|
-
import { join } from 'node:path'
|
|
5
|
-
|
|
6
|
-
import { atomicWrite, withFileLock } from '../fs-atomic'
|
|
7
|
-
|
|
8
|
-
let tmp: string
|
|
9
|
-
|
|
10
|
-
beforeEach(async () => {
|
|
11
|
-
tmp = await mkdtemp(join(tmpdir(), 'astrale-fs-atomic-'))
|
|
12
|
-
})
|
|
13
|
-
|
|
14
|
-
afterEach(async () => {
|
|
15
|
-
await rm(tmp, { recursive: true, force: true })
|
|
16
|
-
})
|
|
17
|
-
|
|
18
|
-
describe('atomicWrite', () => {
|
|
19
|
-
test('writes the content with mode 0600 and leaves no tmp files behind', async () => {
|
|
20
|
-
const path = join(tmp, 'out.json')
|
|
21
|
-
|
|
22
|
-
await atomicWrite(path, '{"a":1}')
|
|
23
|
-
|
|
24
|
-
expect(await readFile(path, 'utf-8')).toBe('{"a":1}')
|
|
25
|
-
expect(((await stat(path)).mode & 0o777).toString(8)).toBe('600')
|
|
26
|
-
expect(await readdir(tmp)).toEqual(['out.json'])
|
|
27
|
-
})
|
|
28
|
-
})
|
|
29
|
-
|
|
30
|
-
describe('withFileLock', () => {
|
|
31
|
-
test('serializes concurrent read-modify-write cycles', async () => {
|
|
32
|
-
const lockPath = join(tmp, 'counter.lock')
|
|
33
|
-
const counterPath = join(tmp, 'counter')
|
|
34
|
-
await writeFile(counterPath, '0')
|
|
35
|
-
|
|
36
|
-
const bump = () =>
|
|
37
|
-
withFileLock(
|
|
38
|
-
lockPath,
|
|
39
|
-
async () => {
|
|
40
|
-
const value = Number(await readFile(counterPath, 'utf-8'))
|
|
41
|
-
await new Promise((r) => setTimeout(r, 2))
|
|
42
|
-
await writeFile(counterPath, String(value + 1))
|
|
43
|
-
},
|
|
44
|
-
{ pollIntervalMs: 2 },
|
|
45
|
-
)
|
|
46
|
-
await Promise.all(Array.from({ length: 20 }, bump))
|
|
47
|
-
|
|
48
|
-
expect(await readFile(counterPath, 'utf-8')).toBe('20')
|
|
49
|
-
})
|
|
50
|
-
|
|
51
|
-
test('takes over a lock whose mtime is stale', async () => {
|
|
52
|
-
const lockPath = join(tmp, 'stale.lock')
|
|
53
|
-
await writeFile(lockPath, JSON.stringify({ pid: process.pid }))
|
|
54
|
-
const past = new Date(Date.now() - 60_000)
|
|
55
|
-
await utimes(lockPath, past, past)
|
|
56
|
-
|
|
57
|
-
const ran = await withFileLock(lockPath, async () => 'ran', { pollIntervalMs: 5 })
|
|
58
|
-
|
|
59
|
-
expect(ran).toBe('ran')
|
|
60
|
-
})
|
|
61
|
-
|
|
62
|
-
test('takes over a fresh lock held by a dead pid', async () => {
|
|
63
|
-
const lockPath = join(tmp, 'dead.lock')
|
|
64
|
-
await writeFile(lockPath, JSON.stringify({ pid: 99999999 }))
|
|
65
|
-
|
|
66
|
-
const ran = await withFileLock(lockPath, async () => 'ran', { pollIntervalMs: 5 })
|
|
67
|
-
|
|
68
|
-
expect(ran).toBe('ran')
|
|
69
|
-
})
|
|
70
|
-
|
|
71
|
-
test('releases the lock when fn throws', async () => {
|
|
72
|
-
const lockPath = join(tmp, 'throw.lock')
|
|
73
|
-
|
|
74
|
-
await expect(
|
|
75
|
-
withFileLock(lockPath, async () => {
|
|
76
|
-
throw new Error('boom')
|
|
77
|
-
}),
|
|
78
|
-
).rejects.toThrow('boom')
|
|
79
|
-
|
|
80
|
-
const ran = await withFileLock(lockPath, async () => 'ran', { timeoutMs: 200 })
|
|
81
|
-
expect(ran).toBe('ran')
|
|
82
|
-
})
|
|
83
|
-
|
|
84
|
-
test('creates a missing parent directory', async () => {
|
|
85
|
-
const lockPath = join(tmp, 'nested', 'deep', 'a.lock')
|
|
86
|
-
|
|
87
|
-
const ran = await withFileLock(lockPath, async () => 'ran')
|
|
88
|
-
|
|
89
|
-
expect(ran).toBe('ran')
|
|
90
|
-
})
|
|
91
|
-
|
|
92
|
-
test('times out while a live holder keeps the lock', async () => {
|
|
93
|
-
const lockPath = join(tmp, 'held.lock')
|
|
94
|
-
await writeFile(lockPath, JSON.stringify({ pid: process.pid }))
|
|
95
|
-
|
|
96
|
-
await expect(
|
|
97
|
-
withFileLock(lockPath, async () => 'ran', {
|
|
98
|
-
pollIntervalMs: 20,
|
|
99
|
-
staleAfterMs: 60_000,
|
|
100
|
-
timeoutMs: 150,
|
|
101
|
-
}),
|
|
102
|
-
).rejects.toThrow('Timed out')
|
|
103
|
-
})
|
|
104
|
-
})
|
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from 'bun:test'
|
|
2
|
-
|
|
3
|
-
import { IdentityStoreSchema } from '../identity'
|
|
4
|
-
|
|
5
|
-
describe('IdentityStoreSchema', () => {
|
|
6
|
-
test('parses valid store', () => {
|
|
7
|
-
const result = IdentityStoreSchema.parse({
|
|
8
|
-
default: 'manager',
|
|
9
|
-
identities: {
|
|
10
|
-
manager: { subject: 'manager', createdAt: '2024-01-01T00:00:00Z' },
|
|
11
|
-
},
|
|
12
|
-
})
|
|
13
|
-
expect(result.default).toBe('manager')
|
|
14
|
-
expect(result.identities.manager.subject).toBe('manager')
|
|
15
|
-
})
|
|
16
|
-
|
|
17
|
-
test('parses multiple identities', () => {
|
|
18
|
-
const result = IdentityStoreSchema.parse({
|
|
19
|
-
default: 'admin',
|
|
20
|
-
identities: {
|
|
21
|
-
admin: {
|
|
22
|
-
subject: 'admin',
|
|
23
|
-
createdAt: '2024-01-01T00:00:00Z',
|
|
24
|
-
issuer: 'https://admin.example.com',
|
|
25
|
-
},
|
|
26
|
-
user: { subject: 'user-principal', createdAt: '2024-06-15T12:00:00Z' },
|
|
27
|
-
},
|
|
28
|
-
})
|
|
29
|
-
expect(Object.keys(result.identities)).toHaveLength(2)
|
|
30
|
-
expect(result.identities.admin.issuer).toBe('https://admin.example.com')
|
|
31
|
-
expect(result.identities.user.subject).toBe('user-principal')
|
|
32
|
-
})
|
|
33
|
-
|
|
34
|
-
test('parses IdP-backed identities without key metadata', () => {
|
|
35
|
-
const result = IdentityStoreSchema.parse({
|
|
36
|
-
default: 'alice',
|
|
37
|
-
identities: {
|
|
38
|
-
alice: {
|
|
39
|
-
source: 'idp',
|
|
40
|
-
subject: 'user_123',
|
|
41
|
-
idp: 'workos',
|
|
42
|
-
issuer: 'https://example.authkit.app',
|
|
43
|
-
audience: 'https://api.example.com',
|
|
44
|
-
createdAt: '2024-01-01T00:00:00Z',
|
|
45
|
-
claims: { email: 'alice@example.com' },
|
|
46
|
-
},
|
|
47
|
-
},
|
|
48
|
-
})
|
|
49
|
-
|
|
50
|
-
expect(result.identities.alice.source).toBe('idp')
|
|
51
|
-
expect(result.identities.alice.kid).toBeUndefined()
|
|
52
|
-
expect(result.identities.alice.idp).toBe('workos')
|
|
53
|
-
})
|
|
54
|
-
|
|
55
|
-
test('rejects missing default field', () => {
|
|
56
|
-
expect(() =>
|
|
57
|
-
IdentityStoreSchema.parse({
|
|
58
|
-
identities: { manager: { subject: 'manager', createdAt: '2024-01-01' } },
|
|
59
|
-
}),
|
|
60
|
-
).toThrow()
|
|
61
|
-
})
|
|
62
|
-
|
|
63
|
-
test('rejects identity missing subject', () => {
|
|
64
|
-
expect(() =>
|
|
65
|
-
IdentityStoreSchema.parse({
|
|
66
|
-
default: 'manager',
|
|
67
|
-
identities: { manager: { createdAt: '2024-01-01' } },
|
|
68
|
-
}),
|
|
69
|
-
).toThrow()
|
|
70
|
-
})
|
|
71
|
-
|
|
72
|
-
test('accepts empty identities record', () => {
|
|
73
|
-
const result = IdentityStoreSchema.parse({
|
|
74
|
-
default: 'nobody',
|
|
75
|
-
identities: {},
|
|
76
|
-
})
|
|
77
|
-
expect(Object.keys(result.identities)).toHaveLength(0)
|
|
78
|
-
})
|
|
79
|
-
})
|