@astrale-os/cli 0.8.0-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/README.md +4 -1
- package/dist/astrale.js +62968 -41240
- 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 +30 -8
- 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-D0cBetPW.js +1 -0
- package/studio/client/dist/assets/elk-worker.min-C9JGDOE-.js +6312 -0
- package/studio/client/dist/assets/index-BQnJ5sgd.css +1 -0
- package/studio/client/dist/assets/index-ChOr3yP0.css +1 -0
- package/studio/client/dist/assets/index-Dspir4w7.js +81 -0
- package/studio/client/dist/assets/index-bVD2KJgz.js +8 -0
- package/studio/client/dist/index.html +2 -2
- 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/studio/tsconfig.json +23 -0
- package/tsconfig.json +1 -1
- package/viewer/dist/main.js +65 -39
- package/viewer/tsconfig.json +13 -0
- 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-BaqEuIQJ.js +0 -109
- package/studio/client/dist/assets/index-jRdExahh.css +0 -1
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { defineLaw } from '@astrale-os/spec/authoring'
|
|
2
|
+
|
|
3
|
+
export const CLI_CONNECTION_TARGET = defineLaw({
|
|
4
|
+
id: 'CLI-CONNECTION-TARGET',
|
|
5
|
+
statement:
|
|
6
|
+
'Explicit URL, explicit instance, active bookmark, managed lookup, and Admin target selection preserve the existing precedence while returning the exact invocation URL and issuer separately.',
|
|
7
|
+
tests: [
|
|
8
|
+
{
|
|
9
|
+
file: '__tests__/target.test.ts',
|
|
10
|
+
id: 'TEST-CLI-CONNECTION-SELECTS-EXACT-TARGET',
|
|
11
|
+
},
|
|
12
|
+
],
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
export const CLI_CONNECTION_DOMAIN_EXCHANGE = defineLaw({
|
|
16
|
+
id: 'CLI-CONNECTION-DOMAIN-EXCHANGE',
|
|
17
|
+
statement:
|
|
18
|
+
'A target with an explicit Domain issuer resolves the source credential, authenticates whoami at the exact Kernel, requests explicit self attenuation for that User and Domain audience, posts the resulting envelope to the discovered standard endpoint, and returns only a Domain token bound back to the same Kernel; there is no inferred issuer or legacy broker fallback.',
|
|
19
|
+
tests: [
|
|
20
|
+
{
|
|
21
|
+
file: '__tests__/exchange.test.ts',
|
|
22
|
+
id: 'TEST-CLI-EXCHANGE-WHOAMI-DELEGATE-EXCHANGE-CACHE',
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
file: '__tests__/exchange.test.ts',
|
|
26
|
+
id: 'TEST-CLI-EXCHANGE-NO-LEGACY-FALLBACK',
|
|
27
|
+
},
|
|
28
|
+
],
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
export const CLI_CONNECTION_HOP_CREDENTIAL = defineLaw({
|
|
32
|
+
id: 'CLI-CONNECTION-HOP-CREDENTIAL',
|
|
33
|
+
statement:
|
|
34
|
+
'A source hop resolves a fresh credential only when its session-pinned issuer equals the selected source issuer; a destination hop requires the same selected resolver and returns a fresh delegation for the witnessed Publication issuer, never the source credential, with its TTL bounded strictly inside any source JWT expiry.',
|
|
35
|
+
tests: [
|
|
36
|
+
{
|
|
37
|
+
file: '__tests__/credential.test.ts',
|
|
38
|
+
id: 'TEST-CLI-CONNECTION-RESOLVES-CREDENTIAL-PER-HOP',
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
file: '__tests__/credential.test.ts',
|
|
42
|
+
id: 'TEST-CLI-CONNECTION-DELEGATES-VIA-SOURCE-AUTH',
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
file: '__tests__/credential.test.ts',
|
|
46
|
+
id: 'TEST-CLI-CONNECTION-REJECTS-HOP-SOURCE-ISSUER-MISMATCH',
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
file: '__tests__/credential.test.ts',
|
|
50
|
+
id: 'TEST-CLI-CONNECTION-BOUNDS-DELEGATION-TO-SOURCE-EXPIRY',
|
|
51
|
+
},
|
|
52
|
+
],
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
export const CLI_CONNECTION_EXPLICIT_ANONYMOUS = defineLaw({
|
|
56
|
+
id: 'CLI-CONNECTION-EXPLICIT-ANONYMOUS',
|
|
57
|
+
statement:
|
|
58
|
+
'An explicit anonymous selection suppresses ambient, bookmark-default, and local credentials by omitting the Host credential capability; it is rejected before connection construction when combined with --as or --creds.',
|
|
59
|
+
tests: [
|
|
60
|
+
{
|
|
61
|
+
file: '__tests__/credential.test.ts',
|
|
62
|
+
id: 'TEST-CLI-CONNECTION-OMITS-EXPLICIT-ANONYMOUS-CREDENTIAL',
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
file: '__tests__/session.test.ts',
|
|
66
|
+
id: 'TEST-CLI-CONNECTION-REJECTS-ANONYMOUS-CREDENTIAL-CONFLICT',
|
|
67
|
+
},
|
|
68
|
+
],
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
export const CLI_CONNECTION_SOURCE_ISSUER = defineLaw({
|
|
72
|
+
id: 'CLI-CONNECTION-SOURCE-ISSUER',
|
|
73
|
+
statement:
|
|
74
|
+
'CLI constructs ClientSession once with sourceIssuer equal to the selected ConnectionTarget issuer independently from its invocation URL, so path-multiplexed Kernels do not substitute root Publication discovery; every credential hop repeats that pin.',
|
|
75
|
+
tests: [
|
|
76
|
+
{
|
|
77
|
+
file: '__tests__/session.test.ts',
|
|
78
|
+
id: 'TEST-CLI-CONNECTION-PINS-SOURCE-ISSUER',
|
|
79
|
+
},
|
|
80
|
+
],
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
export const CLI_CONNECTION_ISSUER_DISCOVERY = defineLaw({
|
|
84
|
+
id: 'CLI-CONNECTION-ISSUER-DISCOVERY',
|
|
85
|
+
statement:
|
|
86
|
+
'When invocation URL and issuer differ, liveness probes resolve OIDC metadata at the pinned issuer and reject metadata that declares any other issuer before fetching its keys.',
|
|
87
|
+
tests: [
|
|
88
|
+
{
|
|
89
|
+
file: '../lib/__tests__/meta.test.ts',
|
|
90
|
+
id: 'TEST-CLI-CONNECTION-PROBES-PINNED-ISSUER',
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
file: '../lib/__tests__/meta.test.ts',
|
|
94
|
+
id: 'TEST-CLI-CONNECTION-REJECTS-DISCOVERY-ISSUER-MISMATCH',
|
|
95
|
+
},
|
|
96
|
+
],
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
export const CLI_AUTH_REGISTRATION_TARGET = defineLaw({
|
|
100
|
+
id: 'CLI-AUTH-REGISTRATION-TARGET',
|
|
101
|
+
statement:
|
|
102
|
+
'Key identity registration lookup uses the same target key for bookmark, managed, Admin, and direct URL calls that identity registration stores.',
|
|
103
|
+
tests: [
|
|
104
|
+
{
|
|
105
|
+
file: '__tests__/auth.test.ts',
|
|
106
|
+
id: 'TEST-CLI-AUTH-USES-DIRECT-URL-REGISTRATION',
|
|
107
|
+
},
|
|
108
|
+
],
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
export const CLI_SELF_AUTHENTICATED_PRINCIPAL = defineLaw({
|
|
112
|
+
id: 'CLI-SELF-AUTHENTICATED-PRINCIPAL',
|
|
113
|
+
statement:
|
|
114
|
+
'@self resolves only from authenticated Identity.whoami on the selected target, so delegated carriers use their effective principal and never an unverified JWT subject or stale local registration.',
|
|
115
|
+
tests: [
|
|
116
|
+
{
|
|
117
|
+
file: '__tests__/self.test.ts',
|
|
118
|
+
id: 'TEST-CLI-SELF-USES-AUTHENTICATED-EFFECTIVE-PRINCIPAL',
|
|
119
|
+
},
|
|
120
|
+
],
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
export const CLI_CONNECTION_TERMINAL_CLOSE = defineLaw({
|
|
124
|
+
id: 'CLI-CONNECTION-TERMINAL-CLOSE',
|
|
125
|
+
statement:
|
|
126
|
+
'The Client Session and direct source-Auth client close exactly once after action success, failure, or cancellation.',
|
|
127
|
+
tests: [
|
|
128
|
+
{
|
|
129
|
+
file: '__tests__/session.test.ts',
|
|
130
|
+
id: 'TEST-CLI-CONNECTION-CLOSES-OWNED-CLIENTS',
|
|
131
|
+
},
|
|
132
|
+
],
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
export const CLI_CONNECTION_TIMEOUT = defineLaw({
|
|
136
|
+
id: 'CLI-CONNECTION-TIMEOUT',
|
|
137
|
+
statement:
|
|
138
|
+
'The CLI accepts only a positive integer timeout before constructing a Client Session and applies it to both source-Auth and Session operations.',
|
|
139
|
+
tests: [
|
|
140
|
+
{
|
|
141
|
+
file: '__tests__/session.test.ts',
|
|
142
|
+
id: 'TEST-CLI-CONNECTION-REJECTS-INVALID-TIMEOUT-BEFORE-OPEN',
|
|
143
|
+
},
|
|
144
|
+
],
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
export const CLI_CONNECTION_CA_SCOPE = defineLaw({
|
|
148
|
+
id: 'CLI-CONNECTION-CA-SCOPE',
|
|
149
|
+
statement:
|
|
150
|
+
'A selected bookmark CA file changes only the Fetch capability owned by this connection and leaves non-HTTPS requests on the injected fallback Fetch.',
|
|
151
|
+
tests: [
|
|
152
|
+
{
|
|
153
|
+
file: '__tests__/ca-fetch.test.ts',
|
|
154
|
+
id: 'TEST-CLI-CONNECTION-SCOPES-CUSTOM-CA-TO-HTTPS-FETCH',
|
|
155
|
+
},
|
|
156
|
+
],
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
export const CLI_CONNECTION_CALL_SHAPE = defineLaw({
|
|
160
|
+
id: 'CLI-CONNECTION-CALL-SHAPE',
|
|
161
|
+
statement:
|
|
162
|
+
'Caller-authored path text and input enter Client as exactly one public Call; the CLI retains no optional callable hint, caller ETag, or second dispatch representation.',
|
|
163
|
+
tests: [
|
|
164
|
+
{
|
|
165
|
+
file: '__tests__/call.test.ts',
|
|
166
|
+
id: 'TEST-CLI-CONNECTION-CREATES-ONE-CANONICAL-CALL',
|
|
167
|
+
},
|
|
168
|
+
],
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
export const CLI_CONNECTION_PUBLIC_SEMANTIC_REASON = defineLaw({
|
|
172
|
+
id: 'CLI-CONNECTION-PUBLIC-SEMANTIC-REASON',
|
|
173
|
+
statement:
|
|
174
|
+
'Machine-mode Kernel command failures preserve the admitted ResponseError code and public semantic reason so scripts can distinguish conflicts such as revision guards and required data migrations without parsing prose.',
|
|
175
|
+
tests: [
|
|
176
|
+
{
|
|
177
|
+
file: '__tests__/errors.test.ts',
|
|
178
|
+
id: 'TEST-CLI-CONNECTION-PRESERVES-PUBLIC-SEMANTIC-REASON',
|
|
179
|
+
},
|
|
180
|
+
],
|
|
181
|
+
})
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { defineLayout } from '@astrale-os/spec/authoring'
|
|
2
|
+
|
|
3
|
+
export default defineLayout({
|
|
4
|
+
entries: [
|
|
5
|
+
'__tests__/',
|
|
6
|
+
'auth.ts',
|
|
7
|
+
'call.ts',
|
|
8
|
+
'command.ts',
|
|
9
|
+
'credential.ts',
|
|
10
|
+
'errors.ts',
|
|
11
|
+
'exchange.ts',
|
|
12
|
+
'index.ts',
|
|
13
|
+
'self.ts',
|
|
14
|
+
'session.ts',
|
|
15
|
+
'target.ts',
|
|
16
|
+
'tsconfig.json',
|
|
17
|
+
],
|
|
18
|
+
exact: true,
|
|
19
|
+
})
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { describe, expect, test } from 'bun:test'
|
|
2
2
|
|
|
3
|
+
import type { Identity } from '../../identity/index'
|
|
3
4
|
import type { AstraleConfig } from '../../lib/config'
|
|
4
|
-
import type { Identity } from '../../lib/identity'
|
|
5
5
|
|
|
6
6
|
import { resolveKeyIdentityAuthOptions } from '../auth'
|
|
7
7
|
|
|
@@ -10,7 +10,7 @@ const config: AstraleConfig = {
|
|
|
10
10
|
admin: {
|
|
11
11
|
name: 'admin',
|
|
12
12
|
url: 'https://admin.eu.astrale.ai/api',
|
|
13
|
-
|
|
13
|
+
kernelIssuer: 'https://admin.eu.astrale.ai/api',
|
|
14
14
|
},
|
|
15
15
|
telemetry: { enabled: true },
|
|
16
16
|
}
|
|
@@ -75,4 +75,28 @@ describe('resolveKeyIdentityAuthOptions', () => {
|
|
|
75
75
|
audience: 'https://prod.example.com',
|
|
76
76
|
})
|
|
77
77
|
})
|
|
78
|
+
|
|
79
|
+
/** @evidence TEST-CLI-AUTH-USES-DIRECT-URL-REGISTRATION */
|
|
80
|
+
test('uses a registration stored for a direct URL target', () => {
|
|
81
|
+
const url = 'https://kernel.example/invoke'
|
|
82
|
+
const identity: Identity = {
|
|
83
|
+
subject: 'alice',
|
|
84
|
+
source: 'key',
|
|
85
|
+
mode: 'local',
|
|
86
|
+
createdAt: '2026-01-01T00:00:00.000Z',
|
|
87
|
+
registrations: {
|
|
88
|
+
[url]: {
|
|
89
|
+
iss: 'https://kernel.example/identity/alice',
|
|
90
|
+
sub: 'alice-node',
|
|
91
|
+
registeredAt: '2026-01-01T00:00:00.000Z',
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
expect(resolveKeyIdentityAuthOptions(identity, config, 'https://kernel.example', url)).toEqual({
|
|
97
|
+
issuer: 'https://kernel.example/identity/alice',
|
|
98
|
+
subject: 'alice-node',
|
|
99
|
+
audience: 'https://kernel.example',
|
|
100
|
+
})
|
|
101
|
+
})
|
|
78
102
|
})
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { afterEach, describe, expect, test } from 'bun:test'
|
|
2
|
+
import { Buffer } from 'node:buffer'
|
|
3
|
+
import { readFile } from 'node:fs/promises'
|
|
4
|
+
import { fileURLToPath } from 'node:url'
|
|
5
|
+
|
|
6
|
+
import { fetchWithCaFile } from '../../lib/ca-fetch'
|
|
7
|
+
|
|
8
|
+
const certificateFile = fileURLToPath(new URL('fixtures/localhost-cert.pem', import.meta.url))
|
|
9
|
+
// Public test-only key material is encoded so repository secret scanners do not mistake it for a credential.
|
|
10
|
+
const keyFile = fileURLToPath(new URL('fixtures/localhost-key.base64', import.meta.url))
|
|
11
|
+
let server: Bun.Server<unknown> | undefined
|
|
12
|
+
|
|
13
|
+
afterEach(async () => {
|
|
14
|
+
if (server !== undefined) {
|
|
15
|
+
await server.stop(true)
|
|
16
|
+
}
|
|
17
|
+
server = undefined
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
describe('connection CA fetch', () => {
|
|
21
|
+
/** @evidence TEST-CLI-CONNECTION-SCOPES-CUSTOM-CA-TO-HTTPS-FETCH */
|
|
22
|
+
test('trusts the selected HTTPS CA and leaves plain HTTP on the fallback Fetch', async () => {
|
|
23
|
+
const [certificate, encodedKey] = await Promise.all([
|
|
24
|
+
readFile(certificateFile),
|
|
25
|
+
readFile(keyFile, 'utf8'),
|
|
26
|
+
])
|
|
27
|
+
const key = Buffer.from(encodedKey.trim(), 'base64')
|
|
28
|
+
server = Bun.serve({
|
|
29
|
+
hostname: '127.0.0.1',
|
|
30
|
+
port: 0,
|
|
31
|
+
tls: { cert: certificate, key },
|
|
32
|
+
fetch: () => new Response('trusted'),
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
const requests: Array<{ input: RequestInfo | URL; init?: RequestInit }> = []
|
|
36
|
+
const fallback = (async (input: RequestInfo | URL, init?: RequestInit) => {
|
|
37
|
+
requests.push({ input, init })
|
|
38
|
+
return new Response('fallback')
|
|
39
|
+
}) as typeof fetch
|
|
40
|
+
const scoped = fetchWithCaFile(certificateFile, fallback)
|
|
41
|
+
const init = { method: 'POST', body: 'payload' } satisfies RequestInit
|
|
42
|
+
|
|
43
|
+
const httpsUrl = `https://127.0.0.1:${server.port}/invoke`
|
|
44
|
+
const httpsResponse = await scoped(httpsUrl)
|
|
45
|
+
expect(await httpsResponse.text()).toBe('trusted')
|
|
46
|
+
expect(httpsResponse.url).toBe(httpsUrl)
|
|
47
|
+
expect(httpsResponse.redirected).toBe(false)
|
|
48
|
+
await expect(
|
|
49
|
+
scoped('http://localhost:8080/invoke', init).then((value) => value.text()),
|
|
50
|
+
).resolves.toBe('fallback')
|
|
51
|
+
expect(requests).toEqual([{ input: 'http://localhost:8080/invoke', init }])
|
|
52
|
+
})
|
|
53
|
+
})
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test'
|
|
2
|
+
|
|
3
|
+
import { createPathCall } from '../call'
|
|
4
|
+
|
|
5
|
+
describe('createPathCall', () => {
|
|
6
|
+
/** @evidence TEST-CLI-CONNECTION-CREATES-ONE-CANONICAL-CALL */
|
|
7
|
+
test('creates one path Call without legacy routing hints', () => {
|
|
8
|
+
expect(
|
|
9
|
+
JSON.parse(
|
|
10
|
+
JSON.stringify(createPathCall('/:kernel.astrale.ai:function.query', { value: 1 })),
|
|
11
|
+
),
|
|
12
|
+
).toEqual({
|
|
13
|
+
target: '/:kernel.astrale.ai:function.query',
|
|
14
|
+
input: { value: 1 },
|
|
15
|
+
})
|
|
16
|
+
})
|
|
17
|
+
})
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import type { SessionAuth } from '@astrale-os/kernel-client/session'
|
|
2
|
+
|
|
3
|
+
import { issuer, type IssuerId } from '@astrale-os/sdk/auth'
|
|
4
|
+
import { Path } from '@astrale-os/sdk/graph/path'
|
|
5
|
+
import { describe, expect, test } from 'bun:test'
|
|
6
|
+
|
|
7
|
+
import type { AstraleConfig } from '../../lib/config'
|
|
8
|
+
|
|
9
|
+
import { createCliCredential, createConnectionCredential } from '../credential'
|
|
10
|
+
|
|
11
|
+
const SOURCE = issuer.accept('https://kernel.example')
|
|
12
|
+
const TARGET_CALL = Object.freeze({
|
|
13
|
+
target: Path.parse('/:example.dev:function.call').raw,
|
|
14
|
+
input: { value: 1 },
|
|
15
|
+
}) satisfies Parameters<SessionAuth['resolve']>[0]
|
|
16
|
+
const config: AstraleConfig = {
|
|
17
|
+
issuer: 'https://cli.example',
|
|
18
|
+
admin: { name: 'admin', url: SOURCE, kernelIssuer: SOURCE },
|
|
19
|
+
telemetry: { enabled: false },
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
describe('connection credential', () => {
|
|
23
|
+
/** @evidence TEST-CLI-CONNECTION-RESOLVES-SOURCE-AUTH-BEFORE-ROUTING */
|
|
24
|
+
test('resolves source authority without destination knowledge', async () => {
|
|
25
|
+
const audiences: IssuerId[] = []
|
|
26
|
+
const calls: unknown[] = []
|
|
27
|
+
const auth = createConnectionCredential(SOURCE, {
|
|
28
|
+
async resolve(audience) {
|
|
29
|
+
audiences.push(audience)
|
|
30
|
+
return `source:${audience}`
|
|
31
|
+
},
|
|
32
|
+
})
|
|
33
|
+
const signal = new AbortController().signal
|
|
34
|
+
|
|
35
|
+
calls.push(TARGET_CALL)
|
|
36
|
+
await expect(auth.resolve(TARGET_CALL, signal)).resolves.toEqual({
|
|
37
|
+
credential: `source:${SOURCE}`,
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
expect(auth.ttlSeconds).toBe(60)
|
|
41
|
+
expect(audiences).toEqual([SOURCE])
|
|
42
|
+
expect(calls).toEqual([TARGET_CALL])
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
/** @evidence TEST-CLI-CONNECTION-BOUNDS-REMOTE-CARRIER-TO-SOURCE-LIFETIME */
|
|
46
|
+
test('bounds destination delegation to the current source credential lifetime', async () => {
|
|
47
|
+
const expiresAt = Math.ceil(Date.now() / 1_000) + 120
|
|
48
|
+
const auth = createConnectionCredential(SOURCE, {
|
|
49
|
+
async resolve() {
|
|
50
|
+
return token(expiresAt)
|
|
51
|
+
},
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
const resolved = await auth.resolve(TARGET_CALL, new AbortController().signal)
|
|
55
|
+
expect(resolved.credential).toBe(token(expiresAt))
|
|
56
|
+
expect(resolved.delegate?.ttlSeconds).toBeGreaterThan(0)
|
|
57
|
+
expect(resolved.delegate?.ttlSeconds).toBeLessThan(120)
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
/** @evidence TEST-CLI-CONNECTION-USES-RAW-SOURCE-CREDENTIAL */
|
|
61
|
+
test('binds explicit CLI credentials to source-Kernel auth only', async () => {
|
|
62
|
+
const auth = createCliCredential(
|
|
63
|
+
{ url: `${SOURCE}/invoke`, kernelIssuer: SOURCE, slug: 'source' },
|
|
64
|
+
{ creds: 'raw-source-token' },
|
|
65
|
+
config,
|
|
66
|
+
)
|
|
67
|
+
if (auth === undefined) throw new Error('expected authenticated credential')
|
|
68
|
+
|
|
69
|
+
await expect(auth.resolve(TARGET_CALL, new AbortController().signal)).resolves.toEqual({
|
|
70
|
+
credential: 'raw-source-token',
|
|
71
|
+
})
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
/** @evidence TEST-CLI-CONNECTION-OMITS-EXPLICIT-ANONYMOUS-CREDENTIAL */
|
|
75
|
+
test('omits the auth capability for an explicit anonymous session', () => {
|
|
76
|
+
const auth = createCliCredential(
|
|
77
|
+
{ url: `${SOURCE}/invoke`, kernelIssuer: SOURCE, defaultIdentity: 'ambient-default' },
|
|
78
|
+
{ anonymous: true },
|
|
79
|
+
config,
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
expect(auth).toBeUndefined()
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
/** @evidence TEST-CLI-CONNECTION-PROPAGATES-AUTH-CANCELLATION */
|
|
86
|
+
test('passes the live Session operation signal to source credential resolution', async () => {
|
|
87
|
+
const controller = new AbortController()
|
|
88
|
+
const reason = new Error('cancelled')
|
|
89
|
+
const auth = createConnectionCredential(SOURCE, {
|
|
90
|
+
async resolve(_audience, signal) {
|
|
91
|
+
expect(signal).toBe(controller.signal)
|
|
92
|
+
throw signal.reason
|
|
93
|
+
},
|
|
94
|
+
})
|
|
95
|
+
controller.abort(reason)
|
|
96
|
+
|
|
97
|
+
await expect(auth.resolve(TARGET_CALL, controller.signal)).rejects.toBe(reason)
|
|
98
|
+
})
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
function token(exp: number): string {
|
|
102
|
+
const encode = (value: unknown) => Buffer.from(JSON.stringify(value)).toString('base64url')
|
|
103
|
+
return `${encode({ alg: 'EdDSA', typ: 'JWT' })}.${encode({
|
|
104
|
+
iss: SOURCE,
|
|
105
|
+
sub: 'principal',
|
|
106
|
+
aud: SOURCE,
|
|
107
|
+
exp,
|
|
108
|
+
})}.signature`
|
|
109
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { ResponseError } from '@astrale-os/kernel-client'
|
|
2
|
+
import { describe, expect, test } from 'bun:test'
|
|
3
|
+
|
|
4
|
+
import { formatKernelError, stripMethodSuffix } from '../errors'
|
|
5
|
+
|
|
6
|
+
const CONFLICT = 4001
|
|
7
|
+
|
|
8
|
+
describe('stripMethodSuffix', () => {
|
|
9
|
+
test('strips ::listChildren from path', () => {
|
|
10
|
+
expect(stripMethodSuffix('Path not found: "/nonexistent::listChildren"')).toBe(
|
|
11
|
+
'Path not found: "/nonexistent"',
|
|
12
|
+
)
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
test('strips ::get from path', () => {
|
|
16
|
+
expect(stripMethodSuffix('Path not found: "/some/path::get"')).toBe(
|
|
17
|
+
'Path not found: "/some/path"',
|
|
18
|
+
)
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
test('strips any method name', () => {
|
|
22
|
+
expect(stripMethodSuffix('"/kernel.astrale.ai/Root::describe"')).toBe(
|
|
23
|
+
'"/kernel.astrale.ai/Root"',
|
|
24
|
+
)
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
test('does not strip from non-path strings', () => {
|
|
28
|
+
expect(stripMethodSuffix('regular::text')).toBe('regular::text')
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
test('handles multiple paths in one message', () => {
|
|
32
|
+
expect(stripMethodSuffix('"/a/b::get" and "/c/d::list"')).toBe('"/a/b" and "/c/d"')
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
test('preserves messages without method suffixes', () => {
|
|
36
|
+
expect(stripMethodSuffix('No path found')).toBe('No path found')
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
test('does not strip single colon (not a method dispatch)', () => {
|
|
40
|
+
expect(stripMethodSuffix('"/a/b:notMethod"')).toBe('"/a/b:notMethod"')
|
|
41
|
+
})
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
describe('formatKernelError', () => {
|
|
45
|
+
/** @evidence TEST-CLI-CONNECTION-PRESERVES-PUBLIC-SEMANTIC-REASON */
|
|
46
|
+
test('preserves a Kernel-admitted semantic reason in machine output', async () => {
|
|
47
|
+
const writes: string[] = []
|
|
48
|
+
const original = process.stderr.write
|
|
49
|
+
process.stderr.write = ((chunk: string | Uint8Array) => {
|
|
50
|
+
writes.push(typeof chunk === 'string' ? chunk : new TextDecoder().decode(chunk))
|
|
51
|
+
return true
|
|
52
|
+
}) as typeof process.stderr.write
|
|
53
|
+
try {
|
|
54
|
+
await formatKernelError(
|
|
55
|
+
new ResponseError(CONFLICT, 'Schema change requires migration.', {
|
|
56
|
+
code: 'DATA_MIGRATION_REQUIRED',
|
|
57
|
+
details: {
|
|
58
|
+
requirements: [
|
|
59
|
+
{
|
|
60
|
+
operation: 'validate-existing',
|
|
61
|
+
reason: 'invalid-existing',
|
|
62
|
+
subject: {
|
|
63
|
+
kind: 'definition',
|
|
64
|
+
ref: { origin: 'x.test', kind: 'class', name: 'X' },
|
|
65
|
+
},
|
|
66
|
+
work: { observedFacts: 1 },
|
|
67
|
+
},
|
|
68
|
+
],
|
|
69
|
+
},
|
|
70
|
+
}),
|
|
71
|
+
true,
|
|
72
|
+
)
|
|
73
|
+
} finally {
|
|
74
|
+
process.stderr.write = original
|
|
75
|
+
}
|
|
76
|
+
expect(writes).toHaveLength(1)
|
|
77
|
+
expect(JSON.parse(writes[0]!)).toEqual({
|
|
78
|
+
error: 'RESPONSE_ERROR',
|
|
79
|
+
code: CONFLICT,
|
|
80
|
+
message: 'Schema change requires migration.',
|
|
81
|
+
reason: {
|
|
82
|
+
code: 'DATA_MIGRATION_REQUIRED',
|
|
83
|
+
details: {
|
|
84
|
+
requirements: [
|
|
85
|
+
{
|
|
86
|
+
operation: 'validate-existing',
|
|
87
|
+
reason: 'invalid-existing',
|
|
88
|
+
subject: { kind: 'definition', ref: { origin: 'x.test', kind: 'class', name: 'X' } },
|
|
89
|
+
work: { observedFacts: 1 },
|
|
90
|
+
},
|
|
91
|
+
],
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
})
|
|
95
|
+
})
|
|
96
|
+
})
|