@astrale-os/cli 1.0.0-beta.3 → 1.0.0-beta.31
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 +84 -6
- package/dist/astrale.js +57586 -60753
- package/dist/public/connect-core.js +2158 -30706
- package/dist/public/keys/index.js +1016 -29541
- package/dist/public/paths/index.js +370 -28654
- package/dist/types/admin/contract.d.ts +26 -0
- package/dist/types/admin/instance/client.d.ts +20 -0
- package/dist/types/admin/instance/index.d.ts +2 -0
- package/dist/types/admin/instance/model.d.ts +6 -10
- 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 +11 -36
- package/dist/types/lib/config.d.ts +13 -59
- package/dist/types/lib/failure-debug.d.ts +1 -0
- package/dist/types/lib/idp.d.ts +25 -173
- package/dist/types/lib/instance-target.d.ts +1 -1
- package/dist/types/lib/instance.d.ts +30 -88
- package/dist/types/lib/log.d.ts +5 -3
- package/dist/types/lib/output.d.ts +1 -1
- 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 +109 -0
- package/dist/types/lib/validation.d.ts +4 -1
- package/dist/types/state/exchange-credentials.d.ts +6 -2
- package/dist/types/state/files.d.ts +2 -0
- package/dist/types/state/index.d.ts +3 -2
- package/dist/types/state/paths.d.ts +2 -0
- package/dist/types/state/session-routes.d.ts +10 -0
- package/package.json +16 -19
- package/src/__tests__/fixtures/publication.ts +20 -0
- package/src/admin/.spec/architecture.md +12 -5
- package/src/admin/__tests__/fixture.ts +25 -0
- package/src/admin/catalog/.spec/api.d.ts +1 -3
- package/src/admin/catalog/.spec/architecture.md +5 -4
- package/src/admin/catalog/__tests__/client.test.ts +143 -69
- package/src/admin/catalog/client.ts +40 -54
- package/src/admin/catalog/model.ts +3 -4
- package/src/admin/contract.ts +47 -0
- 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 +5 -9
- package/src/admin/instance/.spec/architecture.md +7 -7
- package/src/admin/instance/__tests__/client.test.ts +157 -175
- package/src/admin/instance/client.ts +39 -128
- package/src/admin/instance/index.ts +1 -2
- package/src/admin/instance/model.ts +12 -18
- package/src/commands/__tests__/admin-instance.test.ts +11 -8
- package/src/commands/__tests__/call-describe.test.ts +46 -33
- package/src/commands/__tests__/call.test.ts +34 -0
- package/src/commands/__tests__/domain-install-operation.test.ts +30 -22
- package/src/commands/__tests__/domain-install-owned.test.ts +1 -1
- package/src/commands/__tests__/domain-list.test.ts +5 -25
- package/src/commands/__tests__/install-direct.test.ts +115 -8
- package/src/commands/__tests__/install-identity-override.test.ts +6 -17
- 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__/introspect-parse.test.ts +80 -1
- package/src/commands/__tests__/logs.test.ts +250 -2
- package/src/commands/__tests__/read-commands.test.ts +119 -11
- package/src/commands/__tests__/token-ttl.test.ts +49 -4
- package/src/commands/__tests__/update.test.ts +25 -3
- package/src/commands/__tests__/view.test.ts +50 -2
- package/src/commands/auth/logout.ts +2 -1
- package/src/commands/browser.ts +29 -0
- package/src/commands/call-describe.ts +60 -68
- package/src/commands/call.ts +71 -59
- package/src/commands/domain/install.ts +73 -46
- package/src/commands/domain/list.ts +1 -1
- package/src/commands/domain/publish.ts +14 -11
- package/src/commands/domain/uninstall.ts +2 -2
- package/src/commands/get.ts +15 -16
- package/src/commands/identity/__tests__/register.test.ts +3 -3
- package/src/commands/identity/register.ts +8 -8
- 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 +53 -41
- package/src/commands/logs.ts +123 -36
- package/src/commands/mutate.ts +6 -5
- package/src/commands/query.ts +39 -36
- package/src/commands/session/analyze.ts +26 -4
- package/src/commands/token.ts +42 -27
- package/src/commands/ui/__tests__/commands.test.ts +152 -0
- package/src/commands/ui/add.ts +51 -0
- package/src/commands/ui/doctor.ts +21 -0
- package/src/commands/ui/init.ts +38 -0
- package/src/commands/ui/list.ts +26 -0
- package/src/commands/ui/preset-apply.ts +19 -0
- package/src/commands/ui/preset-list.ts +12 -0
- package/src/commands/ui/shared.ts +25 -0
- package/src/commands/update.ts +90 -48
- package/src/commands/view.ts +82 -37
- package/src/connection/.spec/api.d.ts +7 -8
- package/src/connection/.spec/architecture.md +14 -5
- 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__/auth.test.ts +1 -0
- package/src/connection/__tests__/credential.test.ts +21 -1
- package/src/connection/__tests__/errors.test.ts +27 -20
- package/src/connection/__tests__/exchange.test.ts +64 -11
- 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 +8 -4
- package/src/connection/__tests__/target.test.ts +2 -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 +110 -61
- 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 +27 -9
- package/src/errors.ts +3 -2
- package/src/graph/.spec/api.d.ts +13 -6
- package/src/graph/.spec/laws/documents.ts +7 -7
- package/src/graph/.spec/layout.ts +9 -1
- package/src/graph/__tests__/mutation.test.ts +2 -2
- package/src/graph/__tests__/query.test.ts +35 -9
- package/src/graph/class.ts +30 -0
- package/src/graph/index.ts +1 -0
- package/src/graph/query.ts +27 -40
- package/src/identity/.spec/api.d.ts +2 -2
- package/src/identity/__tests__/fixtures/registry-journey.ts +13 -1
- package/src/identity/__tests__/registration.test.ts +1 -1
- package/src/identity/__tests__/registry.test.ts +4 -0
- package/src/identity/registration.ts +3 -3
- package/src/identity/registry.ts +2 -0
- 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__/binary.test.ts +16 -1
- package/src/lib/__tests__/browser-retention.test.ts +212 -0
- package/src/lib/__tests__/command-dx.test.ts +1 -1
- package/src/lib/__tests__/config.test.ts +27 -0
- package/src/lib/__tests__/domain-publication.test.ts +10 -16
- package/src/lib/__tests__/idp.test.ts +34 -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__/log-errors.test.ts +64 -0
- package/src/lib/__tests__/skills.test.ts +834 -0
- package/src/lib/__tests__/studio-server-deps.test.ts +1 -1
- package/src/lib/__tests__/update.test.ts +264 -11
- package/src/lib/__tests__/view-assets.test.ts +25 -0
- package/src/lib/__tests__/view-external-open-intent.test.ts +150 -0
- package/src/lib/__tests__/view-external-open-origins.test.ts +27 -0
- package/src/lib/__tests__/view-open-intent.test.ts +0 -2
- package/src/lib/__tests__/view-server.test.ts +26 -3
- package/src/lib/__tests__/view-session.test.ts +2 -1
- package/src/lib/admin-instance.ts +14 -8
- package/src/lib/admin-target.ts +21 -2
- package/src/lib/binary.ts +22 -5
- package/src/lib/browser-retention.ts +210 -0
- package/src/lib/command-dx.ts +9 -9
- package/src/lib/config.ts +12 -1
- package/src/lib/domain-publication.ts +9 -7
- package/src/lib/failure-debug.ts +26 -0
- package/src/lib/idp.ts +34 -3
- package/src/lib/instance-candidates.ts +2 -2
- package/src/lib/instance-target.ts +3 -14
- package/src/lib/instance.ts +16 -11
- package/src/lib/log.ts +16 -9
- package/src/lib/output.ts +1 -1
- package/src/lib/proc.ts +7 -2
- package/src/lib/provision-instance.ts +34 -57
- package/src/lib/skills/lock.ts +117 -0
- package/src/lib/skills/sync.ts +878 -0
- package/src/lib/skills.ts +1 -52
- package/src/lib/update.ts +298 -45
- package/src/lib/validation.ts +2 -2
- package/src/lib/view/assets.ts +11 -2
- package/src/lib/view/external-open-intent.ts +41 -0
- package/src/lib/view/external-open-origins.ts +37 -0
- package/src/lib/view/host-capabilities.ts +17 -0
- package/src/lib/view/resolve.ts +1 -1
- package/src/lib/view/server.ts +18 -3
- package/src/lib/view/session.ts +2 -0
- package/src/program/.spec/api.d.ts +1 -0
- package/src/program/__tests__/program.test.ts +201 -7
- package/src/program/argv.ts +14 -0
- package/src/program/build.ts +26 -4
- package/src/program/command.ts +1 -0
- package/src/program/index.ts +1 -0
- package/src/program/registry.ts +2 -1
- package/src/setup/__tests__/instance-step.test.ts +27 -10
- package/src/setup/steps/instance.ts +22 -15
- package/src/setup/steps/skills.ts +40 -38
- package/src/state/.spec/api.d.ts +21 -2
- package/src/state/.spec/architecture.md +18 -4
- package/src/state/.spec/layout.ts +1 -0
- package/src/state/__tests__/exchange-credentials.test.ts +88 -42
- package/src/state/__tests__/files.test.ts +24 -1
- package/src/state/__tests__/fixtures/session-route-process.ts +93 -0
- package/src/state/__tests__/paths.test.ts +1 -0
- package/src/state/__tests__/session-routes.test.ts +138 -0
- package/src/state/exchange-credentials.ts +22 -9
- package/src/state/files.ts +41 -0
- package/src/state/index.ts +3 -1
- package/src/state/paths.ts +3 -0
- package/src/state/session-routes.ts +34 -0
- package/src/telemetry/__tests__/analyze-log.test.ts +38 -0
- package/src/telemetry/__tests__/recorder.test.ts +1 -0
- package/src/telemetry/__tests__/retention.test.ts +267 -0
- package/src/telemetry/__tests__/settings.test.ts +102 -0
- package/src/telemetry/__tests__/store-scan.test.ts +128 -0
- package/src/telemetry/__tests__/trigger.test.ts +33 -4
- package/src/telemetry/analyze.ts +24 -2
- package/src/telemetry/recorder.ts +15 -4
- package/src/telemetry/retention.ts +176 -0
- package/src/telemetry/session.ts +18 -12
- package/src/telemetry/settings.ts +64 -11
- package/src/telemetry/store.ts +92 -9
- package/src/telemetry/trigger.ts +16 -28
- package/src/ui/.spec/api.d.ts +14 -0
- package/src/ui/.spec/architecture.md +14 -0
- package/src/ui/.spec/laws.ts +50 -0
- package/src/ui/.spec/layout.ts +16 -0
- package/src/ui/__tests__/ui.test.ts +1742 -0
- package/src/ui/index.ts +13 -0
- package/src/ui/lock.ts +89 -0
- package/src/ui/model.ts +83 -0
- package/src/ui/operations.ts +1044 -0
- package/src/ui/project.ts +333 -0
- package/src/ui/release.ts +398 -0
- package/src/ui/runner.ts +18 -0
- package/studio/client/dist/assets/elk-api-DjmKsHXc.js +1 -0
- package/studio/client/dist/assets/index-BFVFs_8x.js +81 -0
- package/studio/client/dist/assets/index-DuB9iUdu.css +2 -0
- package/studio/client/dist/assets/rolldown-runtime-Dd_uD5pT.js +1 -0
- package/studio/client/dist/assets/schema-studio--a0kX3QU.css +1 -0
- package/studio/client/dist/assets/schema-studio-DH6oEWME.js +8 -0
- package/studio/client/dist/index.html +4 -3
- package/studio/package.json +8 -14
- 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/harness/claude/adapter.test.ts +15 -5
- package/studio/server/agent/harness/gateway/token.test.ts +1 -1
- package/studio/server/agent/harness/gateway/token.ts +3 -3
- package/studio/server/agent/prompts/anchors.test.ts +41 -32
- package/studio/server/agent/prompts/anchors.ts +23 -51
- package/studio/server/agent/prompts/system.test.ts +4 -4
- package/studio/server/agent/prompts/system.ts +5 -5
- package/studio/server/agent/routes.test.ts +1 -1
- package/studio/server/agent/run/coordinator.test.ts +1 -1
- package/studio/server/agent/run/preparation.ts +1 -1
- package/studio/server/api/canvas.ts +0 -1
- package/studio/server/api/context.ts +1 -1
- package/studio/server/api/deployment.ts +1 -1
- package/studio/server/api/views.ts +2 -2
- package/studio/server/api/workspace.ts +1 -1
- package/studio/server/api-agent-loadout.test.ts +1 -1
- package/studio/server/api.test.ts +1 -1
- package/studio/server/cache.test.ts +24 -18
- package/studio/server/cache.ts +10 -54
- package/studio/server/cli-consumers.test.ts +32 -4
- package/studio/server/cli.test.ts +2 -2
- package/studio/server/domain.test.ts +50 -68
- package/studio/server/domain.ts +82 -63
- package/studio/server/handoff/copy.ts +2 -2
- package/studio/server/index.ts +1 -1
- package/studio/server/instances/probe.test.ts +1 -1
- package/studio/server/introspect/anatomy/views/routes.ts +46 -37
- package/studio/server/introspect/anatomy/views.ts +10 -16
- package/studio/server/introspect/anatomy-extras.test.ts +29 -56
- package/studio/server/introspect/anatomy.test.ts +22 -14
- package/studio/server/introspect/anatomy.ts +6 -9
- package/studio/server/introspect/bundle.ts +0 -3
- package/studio/server/introspect/canonical-schema.test.ts +155 -424
- package/studio/server/introspect/canonical-schema.ts +248 -601
- package/studio/server/introspect/core.ts +14 -22
- package/studio/server/introspect/diff.test.ts +21 -9
- package/studio/server/introspect/diff.ts +19 -64
- package/studio/server/introspect/extractor.ts +23 -122
- package/studio/server/introspect/overlay-tsmorph.test.ts +116 -241
- package/studio/server/introspect/overlay-tsmorph.ts +0 -1
- package/studio/server/introspect/overlay.ts +3 -51
- package/studio/server/introspect/revision.test.ts +24 -28
- package/studio/server/introspect/revision.ts +11 -22
- package/studio/server/introspect/runtime.test.ts +77 -195
- package/studio/server/introspect/runtime.ts +5 -67
- package/studio/server/introspect/schema-ir-json.test.ts +81 -0
- package/studio/server/introspect/schema-ir-json.ts +39 -69
- 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/handlers.ts +116 -636
- package/studio/server/introspect/source-overlay/kernel-calls.ts +0 -3
- package/studio/server/introspect/source-overlay/spans.ts +15 -45
- package/studio/server/lifecycle.ts +4 -1
- package/studio/server/state/baseline.test.ts +14 -9
- package/studio/server/state/baseline.ts +3 -5
- package/studio/server/state/documents.test.ts +69 -0
- package/studio/server/state/documents.ts +57 -10
- package/studio/server/state/visibility.ts +1 -5
- package/studio/server/views/model.ts +3 -3
- package/studio/server/views/target.test.ts +18 -76
- package/studio/server/views/target.ts +25 -51
- package/studio/server/watch.test.ts +11 -11
- package/studio/server/watch.ts +8 -4
- package/studio/server/workspace/updates.ts +23 -1
- package/studio/server/workspace-watch.ts +2 -2
- package/studio/shared/contracts/runtime.ts +4 -0
- package/studio/shared/contracts/schema.ts +44 -131
- package/studio/shared/contracts/surface.test.ts +15 -17
- package/studio/shared/contracts/workspace.ts +5 -11
- package/studio/shared/schema/identity.test.ts +20 -44
- package/studio/shared/schema/identity.ts +9 -24
- package/studio/tsconfig.json +2 -1
- package/tsconfig.json +1 -1
- package/viewer/dist/index.html +21 -0
- package/viewer/dist/main.js +41 -62
- package/.check-workspace.cjs +0 -40
- package/src/admin/binding.ts +0 -168
- package/src/commands/__tests__/instance-create-hosts.test.ts +0 -29
- package/studio/client/dist/assets/elk-api-lwhFo7vB.js +0 -1
- package/studio/client/dist/assets/index-B-c-smo9.js +0 -8
- package/studio/client/dist/assets/index-BckHuAWk.js +0 -81
- package/studio/client/dist/assets/index-C4aNQ6dz.css +0 -1
- package/studio/client/dist/assets/index-ChOr3yP0.css +0 -1
- package/studio/server/introspect/anatomy/views/legacy.ts +0 -232
- package/studio/server/introspect/core-extractor.ts +0 -139
- package/studio/server/introspect/overlay.test.ts +0 -72
- package/studio/server/introspect/source-overlay/annotations.ts +0 -14
package/src/state/.spec/api.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { SessionRouteArtifact, SessionRouteStore } from '@astrale-os/sdk/client/session'
|
|
2
|
+
|
|
1
3
|
/** Environment values that affect CLI-owned state placement. */
|
|
2
4
|
export interface PathEnvironment {
|
|
3
5
|
readonly ASTRALE_HOME?: string
|
|
@@ -17,6 +19,7 @@ export interface Paths {
|
|
|
17
19
|
readonly idps: string
|
|
18
20
|
readonly idpSessionsDir: string
|
|
19
21
|
readonly exchangeCredentials: string
|
|
22
|
+
readonly sessionRoutes: string
|
|
20
23
|
idpDir(name: string): string
|
|
21
24
|
idpSession(identityName: string): string
|
|
22
25
|
}
|
|
@@ -35,22 +38,27 @@ export const INSTANCES_PATH: string
|
|
|
35
38
|
export const IDPS_PATH: string
|
|
36
39
|
export const IDP_SESSIONS_DIR: string
|
|
37
40
|
export const EXCHANGE_CREDENTIALS_PATH: string
|
|
41
|
+
export const SESSION_ROUTES_PATH: string
|
|
38
42
|
|
|
39
43
|
export namespace exchange {
|
|
40
44
|
interface Artifact {
|
|
41
|
-
readonly version:
|
|
45
|
+
readonly version: 2
|
|
42
46
|
readonly entries: Record<string, Entry>
|
|
43
47
|
}
|
|
44
48
|
|
|
45
49
|
interface Key {
|
|
46
50
|
readonly kernelIssuer: string
|
|
47
51
|
readonly domainIssuer: string
|
|
48
|
-
readonly
|
|
52
|
+
readonly sourceIssuer: string
|
|
53
|
+
readonly sourceSubject: string
|
|
49
54
|
}
|
|
50
55
|
|
|
51
56
|
interface Entry {
|
|
52
57
|
readonly credential: string
|
|
53
58
|
readonly expiresAt: number
|
|
59
|
+
readonly user: string
|
|
60
|
+
readonly sourceIssuer: string
|
|
61
|
+
readonly sourceSubject: string
|
|
54
62
|
}
|
|
55
63
|
}
|
|
56
64
|
|
|
@@ -118,6 +126,17 @@ export function updateIdentityStore<Value>(
|
|
|
118
126
|
|
|
119
127
|
/** Atomically replace one private CLI state file through a same-directory temporary file. */
|
|
120
128
|
export function atomicWrite(path: string, data: string): Promise<void>
|
|
129
|
+
export function atomicWriteSync(path: string, data: string): void
|
|
130
|
+
|
|
131
|
+
/** CLI filesystem representation for Kernel Client's admitted confidential route artifact. */
|
|
132
|
+
export class FileSessionRouteStore implements SessionRouteStore {
|
|
133
|
+
constructor(path?: string)
|
|
134
|
+
read(): unknown
|
|
135
|
+
write(artifact: SessionRouteArtifact): void
|
|
136
|
+
clear(): void
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export const SESSION_ROUTE_STORE: Readonly<FileSessionRouteStore>
|
|
121
140
|
|
|
122
141
|
export interface FileLockOptions {
|
|
123
142
|
readonly pollIntervalMs?: number
|
|
@@ -11,10 +11,24 @@ The identity registry decodes the legacy unversioned shape and current V1 envelo
|
|
|
11
11
|
exact bytes at `identities.json.v0.bak` before publishing V1. Invalid or newer files remain untouched
|
|
12
12
|
and fail closed. Identity orchestration owns key and IdP-session effects above this module.
|
|
13
13
|
|
|
14
|
-
The exchange registry owns a versioned `exchange.Artifact`. Each entry is keyed by the atomic
|
|
15
|
-
`(Kernel issuer, Domain issuer,
|
|
16
|
-
credential and
|
|
17
|
-
|
|
14
|
+
The exchange registry owns a versioned `exchange.Artifact`. Each V2 entry is keyed by the atomic
|
|
15
|
+
`(Kernel issuer, Domain issuer, source issuer, source subject)` identity and stores one inspected
|
|
16
|
+
Domain credential, expiry, and the exact registered Kernel User proven when that credential was
|
|
17
|
+
minted, together with the exact upstream source issuer and subject that selected the entry. This lets
|
|
18
|
+
a warm command validate and reuse the credential before any network `whoami` without accepting a
|
|
19
|
+
refresh result filed under another source identity.
|
|
20
|
+
V1 is not migrated or retained; it is discarded and replaced by the next exact exchange. The
|
|
21
|
+
registry reuses the same atomic-write and bounded file-lock primitives and maintains owner-only
|
|
22
|
+
directory and file modes.
|
|
23
|
+
|
|
24
|
+
The session-route registry is a separate representation adapter for Kernel Client's versioned,
|
|
25
|
+
bounded confidential route artifact. Kernel Client exclusively owns route keying, admission, expiry,
|
|
26
|
+
and stale/miss recovery. CLI owns only synchronous owner-private JSON I/O so a new CLI process can
|
|
27
|
+
reuse admitted routing state without an Admin-specific shortcut. Writes are atomic snapshots; a
|
|
28
|
+
lost concurrent cache update or any read/write failure is only a future cold route miss and cannot
|
|
29
|
+
change product correctness.
|
|
30
|
+
Authentication logout and identity deletion remove this artifact together with exchanged Domain
|
|
31
|
+
credentials so locally retired authority does not leave reusable destination bearers behind.
|
|
18
32
|
|
|
19
33
|
Shape decoding, migrations, and backup policy remain with each semantic registry. Commands do not
|
|
20
34
|
call these primitives directly, and Kernel Client owns no CLI filesystem state.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { afterEach, beforeEach, describe, expect, test } from 'bun:test'
|
|
2
|
-
import { mkdtemp, readFile, rm, stat } from 'node:fs/promises'
|
|
2
|
+
import { mkdir, mkdtemp, readFile, rm, stat, writeFile } from 'node:fs/promises'
|
|
3
3
|
import { tmpdir } from 'node:os'
|
|
4
4
|
import { join } from 'node:path'
|
|
5
5
|
|
|
@@ -19,11 +19,22 @@ afterEach(async () => {
|
|
|
19
19
|
|
|
20
20
|
describe('exchange credential cache', () => {
|
|
21
21
|
/** @evidence TEST-CLI-EXCHANGE-CACHE-EXACT-KEY-AND-PRIVATE-MODE */
|
|
22
|
-
test('partitions by Kernel, Domain, and
|
|
22
|
+
test('partitions by Kernel, Domain, and source identity under private filesystem modes', async () => {
|
|
23
23
|
const cache = new ExchangeCredentialCache(path)
|
|
24
24
|
const now = () => 100
|
|
25
25
|
const first = key('https://kernel-a.example', 'https://domain.example', 'user-a')
|
|
26
|
-
const
|
|
26
|
+
const partitions = [
|
|
27
|
+
first,
|
|
28
|
+
key('https://kernel-b.example', first.domainIssuer, first.sourceSubject),
|
|
29
|
+
key(first.kernelIssuer, 'https://other-domain.example', first.sourceSubject),
|
|
30
|
+
key(
|
|
31
|
+
first.kernelIssuer,
|
|
32
|
+
first.domainIssuer,
|
|
33
|
+
first.sourceSubject,
|
|
34
|
+
'https://other-source.example',
|
|
35
|
+
),
|
|
36
|
+
key(first.kernelIssuer, first.domainIssuer, 'user-b'),
|
|
37
|
+
]
|
|
27
38
|
let refreshes = 0
|
|
28
39
|
|
|
29
40
|
const resolve = (candidate: typeof first) =>
|
|
@@ -31,18 +42,16 @@ describe('exchange credential cache', () => {
|
|
|
31
42
|
candidate,
|
|
32
43
|
async () => {
|
|
33
44
|
refreshes += 1
|
|
34
|
-
return
|
|
35
|
-
credential: token(candidate, 200),
|
|
36
|
-
expiresAt: 200,
|
|
37
|
-
}
|
|
45
|
+
return entry(candidate, 200)
|
|
38
46
|
},
|
|
39
47
|
now,
|
|
40
48
|
)
|
|
41
49
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
50
|
+
for (const candidate of partitions) {
|
|
51
|
+
await expect(resolve(candidate)).resolves.toBe(token(candidate, 200))
|
|
52
|
+
await expect(resolve(candidate)).resolves.toBe(token(candidate, 200))
|
|
53
|
+
}
|
|
54
|
+
expect(refreshes).toBe(partitions.length)
|
|
46
55
|
expect((await stat(path)).mode & 0o777).toBe(0o600)
|
|
47
56
|
expect((await stat(join(directory, 'private'))).mode & 0o777).toBe(0o700)
|
|
48
57
|
})
|
|
@@ -56,10 +65,7 @@ describe('exchange credential cache', () => {
|
|
|
56
65
|
const refresh = async () => {
|
|
57
66
|
refreshes += 1
|
|
58
67
|
await new Promise((resolve) => setTimeout(resolve, 10))
|
|
59
|
-
return
|
|
60
|
-
credential: token(candidate, 200),
|
|
61
|
-
expiresAt: 200,
|
|
62
|
-
}
|
|
68
|
+
return entry(candidate, 200)
|
|
63
69
|
}
|
|
64
70
|
|
|
65
71
|
const values = await Promise.all([
|
|
@@ -77,10 +83,7 @@ describe('exchange credential cache', () => {
|
|
|
77
83
|
const candidate = key('https://kernel.example', 'https://domain.example', 'user')
|
|
78
84
|
await cache.getOrRefresh(
|
|
79
85
|
candidate,
|
|
80
|
-
async () => (
|
|
81
|
-
credential: token(candidate, 120),
|
|
82
|
-
expiresAt: 120,
|
|
83
|
-
}),
|
|
86
|
+
async () => entry(candidate, 120),
|
|
84
87
|
() => 50,
|
|
85
88
|
)
|
|
86
89
|
let refreshes = 0
|
|
@@ -88,10 +91,7 @@ describe('exchange credential cache', () => {
|
|
|
88
91
|
candidate,
|
|
89
92
|
async () => {
|
|
90
93
|
refreshes += 1
|
|
91
|
-
return
|
|
92
|
-
credential: token(candidate, 220),
|
|
93
|
-
expiresAt: 220,
|
|
94
|
-
}
|
|
94
|
+
return entry(candidate, 220)
|
|
95
95
|
},
|
|
96
96
|
() => 100,
|
|
97
97
|
)
|
|
@@ -99,19 +99,16 @@ describe('exchange credential cache', () => {
|
|
|
99
99
|
|
|
100
100
|
await expect(
|
|
101
101
|
cache.getOrRefresh(
|
|
102
|
-
key('https://other-kernel.example', candidate.domainIssuer, candidate.
|
|
103
|
-
async () => (
|
|
104
|
-
credential: token(candidate, 220),
|
|
105
|
-
expiresAt: 220,
|
|
106
|
-
}),
|
|
102
|
+
key('https://other-kernel.example', candidate.domainIssuer, candidate.sourceSubject),
|
|
103
|
+
async () => entry(candidate, 220),
|
|
107
104
|
() => 100,
|
|
108
105
|
),
|
|
109
106
|
).rejects.toThrow(/inconsistent with its cache key/i)
|
|
110
107
|
|
|
111
108
|
await expect(
|
|
112
109
|
cache.getOrRefresh(
|
|
113
|
-
key(candidate.kernelIssuer, candidate.domainIssuer, 'other-
|
|
114
|
-
async () => (
|
|
110
|
+
key(candidate.kernelIssuer, candidate.domainIssuer, 'other-source'),
|
|
111
|
+
async () => entry(candidate, 220),
|
|
115
112
|
() => 100,
|
|
116
113
|
),
|
|
117
114
|
).rejects.toThrow(/inconsistent with its cache key/i)
|
|
@@ -125,10 +122,7 @@ describe('exchange credential cache', () => {
|
|
|
125
122
|
await expect(
|
|
126
123
|
cache.getOrRefresh(
|
|
127
124
|
malformedCandidate,
|
|
128
|
-
async () => (
|
|
129
|
-
credential: token(malformedCandidate, 220, malformed),
|
|
130
|
-
expiresAt: 220,
|
|
131
|
-
}),
|
|
125
|
+
async () => entry(malformedCandidate, 220, malformed),
|
|
132
126
|
() => 100,
|
|
133
127
|
),
|
|
134
128
|
).rejects.toThrow(/inconsistent with its cache key/i)
|
|
@@ -143,10 +137,7 @@ describe('exchange credential cache', () => {
|
|
|
143
137
|
for (const candidate of [a, b]) {
|
|
144
138
|
await cache.getOrRefresh(
|
|
145
139
|
candidate,
|
|
146
|
-
async () => (
|
|
147
|
-
credential: token(candidate, 200),
|
|
148
|
-
expiresAt: 200,
|
|
149
|
-
}),
|
|
140
|
+
async () => entry(candidate, 200),
|
|
150
141
|
() => 100,
|
|
151
142
|
)
|
|
152
143
|
}
|
|
@@ -156,10 +147,60 @@ describe('exchange credential cache', () => {
|
|
|
156
147
|
await cache.clear()
|
|
157
148
|
expect(JSON.parse(await readFile(path, 'utf8')).entries).toEqual({})
|
|
158
149
|
})
|
|
150
|
+
|
|
151
|
+
test('discards the V1 cache and writes only V2 after a fresh exact exchange', async () => {
|
|
152
|
+
await mkdir(join(directory, 'private'))
|
|
153
|
+
await writeFile(
|
|
154
|
+
path,
|
|
155
|
+
JSON.stringify({
|
|
156
|
+
version: 1,
|
|
157
|
+
entries: { legacy: { credential: 'legacy', expiresAt: 999 } },
|
|
158
|
+
}),
|
|
159
|
+
)
|
|
160
|
+
const cache = new ExchangeCredentialCache(path)
|
|
161
|
+
const candidate = key('https://kernel.example', 'https://domain.example', 'source-user')
|
|
162
|
+
let refreshes = 0
|
|
163
|
+
await cache.getOrRefresh(
|
|
164
|
+
candidate,
|
|
165
|
+
async () => {
|
|
166
|
+
refreshes += 1
|
|
167
|
+
return entry(candidate, 200)
|
|
168
|
+
},
|
|
169
|
+
() => 100,
|
|
170
|
+
)
|
|
171
|
+
expect(refreshes).toBe(1)
|
|
172
|
+
const stored = JSON.parse(await readFile(path, 'utf8'))
|
|
173
|
+
expect(stored.version).toBe(2)
|
|
174
|
+
expect(JSON.stringify(stored)).not.toContain('legacy')
|
|
175
|
+
})
|
|
159
176
|
})
|
|
160
177
|
|
|
161
|
-
function key(
|
|
162
|
-
|
|
178
|
+
function key(
|
|
179
|
+
kernelIssuer: string,
|
|
180
|
+
domainIssuer: string,
|
|
181
|
+
sourceSubject: string,
|
|
182
|
+
sourceIssuer = 'https://source.example',
|
|
183
|
+
) {
|
|
184
|
+
return {
|
|
185
|
+
kernelIssuer,
|
|
186
|
+
domainIssuer,
|
|
187
|
+
sourceIssuer,
|
|
188
|
+
sourceSubject,
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function entry(
|
|
193
|
+
candidate: ReturnType<typeof key>,
|
|
194
|
+
expiresAt: number,
|
|
195
|
+
malformed?: 'outer-delegation' | 'proof-without-delegation',
|
|
196
|
+
) {
|
|
197
|
+
return {
|
|
198
|
+
credential: token(candidate, expiresAt, malformed),
|
|
199
|
+
expiresAt,
|
|
200
|
+
user: candidate.sourceSubject,
|
|
201
|
+
sourceIssuer: candidate.sourceIssuer,
|
|
202
|
+
sourceSubject: candidate.sourceSubject,
|
|
203
|
+
}
|
|
163
204
|
}
|
|
164
205
|
|
|
165
206
|
function token(
|
|
@@ -170,12 +211,17 @@ function token(
|
|
|
170
211
|
const encode = (value: unknown) => Buffer.from(JSON.stringify(value)).toString('base64url')
|
|
171
212
|
const proof = `${encode({ alg: 'EdDSA', typ: 'JWT' })}.${encode({
|
|
172
213
|
iss: candidate.kernelIssuer,
|
|
173
|
-
sub: candidate.
|
|
214
|
+
sub: candidate.sourceSubject,
|
|
174
215
|
aud: candidate.kernelIssuer,
|
|
175
216
|
exp,
|
|
176
217
|
...(malformed === 'proof-without-delegation'
|
|
177
218
|
? {}
|
|
178
|
-
: {
|
|
219
|
+
: {
|
|
220
|
+
delegation: {
|
|
221
|
+
v: 1,
|
|
222
|
+
expr: { kind: 'identity', id: candidate.sourceSubject },
|
|
223
|
+
},
|
|
224
|
+
}),
|
|
179
225
|
})}.signature`
|
|
180
226
|
return `${encode({ alg: 'EdDSA', typ: 'JWT' })}.${encode({
|
|
181
227
|
iss: candidate.domainIssuer,
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
import { tmpdir } from 'node:os'
|
|
14
14
|
import { join } from 'node:path'
|
|
15
15
|
|
|
16
|
-
import { atomicWrite, withFileLock } from '../files'
|
|
16
|
+
import { atomicWrite, atomicWriteSync, withFileLock } from '../files'
|
|
17
17
|
|
|
18
18
|
let temporaryDirectory: string
|
|
19
19
|
|
|
@@ -50,6 +50,29 @@ describe('atomicWrite', () => {
|
|
|
50
50
|
})
|
|
51
51
|
})
|
|
52
52
|
|
|
53
|
+
describe('atomicWriteSync', () => {
|
|
54
|
+
test('publishes a synced mode-0600 file without temporary residue', async () => {
|
|
55
|
+
const path = join(temporaryDirectory, 'sync', 'state.json')
|
|
56
|
+
|
|
57
|
+
atomicWriteSync(path, '{"value":1}')
|
|
58
|
+
|
|
59
|
+
expect(await readFile(path, 'utf-8')).toBe('{"value":1}')
|
|
60
|
+
expect((await stat(path)).mode & 0o777).toBe(0o600)
|
|
61
|
+
expect(await readdir(join(temporaryDirectory, 'sync'))).toEqual(['state.json'])
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
test('does not disturb the target or retain a temporary file after failure', async () => {
|
|
65
|
+
const path = join(temporaryDirectory, 'sync-target')
|
|
66
|
+
await mkdir(path)
|
|
67
|
+
await writeFile(join(path, 'retained'), 'stable')
|
|
68
|
+
|
|
69
|
+
expect(() => atomicWriteSync(path, 'replacement')).toThrow()
|
|
70
|
+
|
|
71
|
+
expect(await readFile(join(path, 'retained'), 'utf-8')).toBe('stable')
|
|
72
|
+
expect((await readdir(temporaryDirectory)).sort()).toEqual(['sync-target'])
|
|
73
|
+
})
|
|
74
|
+
})
|
|
75
|
+
|
|
53
76
|
describe('withFileLock', () => {
|
|
54
77
|
/** @evidence TEST-CLI-STATE-LOCK-SERIALIZES */
|
|
55
78
|
test('serializes contending read-modify-write transitions', async () => {
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import type { Transport } from '@astrale-os/sdk/client'
|
|
2
|
+
|
|
3
|
+
import { issuer } from '@astrale-os/sdk/auth'
|
|
4
|
+
import { call, Client } from '@astrale-os/sdk/client'
|
|
5
|
+
import { ClientSession } from '@astrale-os/sdk/client/session'
|
|
6
|
+
import { NodeId } from '@astrale-os/sdk/graph/node'
|
|
7
|
+
import { Path } from '@astrale-os/sdk/graph/path'
|
|
8
|
+
import { invocation } from '@astrale-os/sdk/invocation'
|
|
9
|
+
|
|
10
|
+
import { FileSessionRouteStore } from '../../session-routes'
|
|
11
|
+
|
|
12
|
+
const routePath = process.argv[2]
|
|
13
|
+
if (routePath === undefined) throw new Error('route artifact path is required')
|
|
14
|
+
|
|
15
|
+
const sourceEndpoint = 'https://source.kernel.test/invoke'
|
|
16
|
+
const sourceIssuer = issuer.accept('https://source.kernel.test')
|
|
17
|
+
const destinationEndpoint = 'https://destination.application.test/invoke'
|
|
18
|
+
const target = Path.id(NodeId('session-route-fresh-process'))
|
|
19
|
+
let sourceAttempts = 0
|
|
20
|
+
let destinationAttempts = 0
|
|
21
|
+
|
|
22
|
+
const route = invocation.acceptRoute({
|
|
23
|
+
endpoint: { http: destinationEndpoint },
|
|
24
|
+
via: {
|
|
25
|
+
kind: 'via',
|
|
26
|
+
issuer: sourceIssuer,
|
|
27
|
+
publication: {
|
|
28
|
+
origin: 'destination.application.test',
|
|
29
|
+
identity: {
|
|
30
|
+
issuer: 'https://destination.application.test',
|
|
31
|
+
subject: 'destination.application.test',
|
|
32
|
+
},
|
|
33
|
+
revision: `sha256:${'1'.padStart(64, '0')}`,
|
|
34
|
+
etag: `sha256:${'1'.padStart(64, '0')}`,
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
})
|
|
38
|
+
const carrier = `e30.${Buffer.from(
|
|
39
|
+
JSON.stringify({ exp: Math.floor(Date.now() / 1_000) + 600 }),
|
|
40
|
+
).toString('base64url')}.signature`
|
|
41
|
+
const sourceDispatch: Transport['dispatch'] = async () => {
|
|
42
|
+
sourceAttempts += 1
|
|
43
|
+
return {
|
|
44
|
+
kind: 'redirect',
|
|
45
|
+
invocation: { source: sourceIssuer, id: 'source-invocation' },
|
|
46
|
+
redirect: { route, credential: carrier },
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
const destinationDispatch: Transport['dispatch'] = async () => {
|
|
50
|
+
destinationAttempts += 1
|
|
51
|
+
return {
|
|
52
|
+
kind: 'value',
|
|
53
|
+
value: 'done',
|
|
54
|
+
invocation: { source: sourceIssuer, id: 'destination-invocation' },
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
const source = new Client({ transport: { dispatch: sourceDispatch } })
|
|
58
|
+
const destination = new Client({ transport: { dispatch: destinationDispatch } })
|
|
59
|
+
const session = new ClientSession({
|
|
60
|
+
kernel: sourceIssuer,
|
|
61
|
+
fetch: async () =>
|
|
62
|
+
new Response(
|
|
63
|
+
JSON.stringify({
|
|
64
|
+
protocol: 'astrale-invocation',
|
|
65
|
+
version: 1,
|
|
66
|
+
issuer: sourceIssuer,
|
|
67
|
+
endpoints: { http: sourceEndpoint },
|
|
68
|
+
}),
|
|
69
|
+
{ headers: { 'content-type': 'application/json' } },
|
|
70
|
+
),
|
|
71
|
+
pool: {
|
|
72
|
+
clientFor(
|
|
73
|
+
target: Parameters<
|
|
74
|
+
NonNullable<ConstructorParameters<typeof ClientSession>[0]['pool']>['clientFor']
|
|
75
|
+
>[0],
|
|
76
|
+
) {
|
|
77
|
+
const url = target.transport === 'auto' ? target.endpoints.http : target.url
|
|
78
|
+
return url === sourceEndpoint ? source : destination
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
auth: { ttlSeconds: 120, resolve: () => ({ credential: 'source-credential' }) },
|
|
82
|
+
routeStore: new FileSessionRouteStore(routePath),
|
|
83
|
+
policy: { maximumRouteAgeMs: 60_000 },
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
try {
|
|
87
|
+
const value = await session.call(call(target, null))
|
|
88
|
+
console.log(JSON.stringify({ value, sourceAttempts, destinationAttempts }))
|
|
89
|
+
} finally {
|
|
90
|
+
session.close()
|
|
91
|
+
source.close()
|
|
92
|
+
destination.close()
|
|
93
|
+
}
|
|
@@ -20,6 +20,7 @@ describe('state paths', () => {
|
|
|
20
20
|
expect(explicit.home).toBe('/explicit/home')
|
|
21
21
|
expect(explicit.keys).toBe('/environment/keys')
|
|
22
22
|
expect(explicit.config).toBe(join('/explicit/home', 'config.json'))
|
|
23
|
+
expect(explicit.sessionRoutes).toBe(join('/explicit/home', 'session', 'routes.json'))
|
|
23
24
|
expect(inherited.home).toBe('/environment/home')
|
|
24
25
|
expect(inherited.keys).toBe('/environment/keys')
|
|
25
26
|
expect(Object.isFrozen(explicit)).toBe(true)
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import type { SessionRouteArtifact } from '@astrale-os/sdk/client/session'
|
|
2
|
+
|
|
3
|
+
import { afterEach, beforeEach, describe, expect, test } from 'bun:test'
|
|
4
|
+
import { access, mkdir, mkdtemp, readFile, rm, stat, writeFile } from 'node:fs/promises'
|
|
5
|
+
import { tmpdir } from 'node:os'
|
|
6
|
+
import { join } from 'node:path'
|
|
7
|
+
|
|
8
|
+
import { FileSessionRouteStore } from '../session-routes'
|
|
9
|
+
|
|
10
|
+
let directory: string
|
|
11
|
+
let path: string
|
|
12
|
+
|
|
13
|
+
beforeEach(async () => {
|
|
14
|
+
directory = await mkdtemp(join(tmpdir(), 'astrale-session-routes-'))
|
|
15
|
+
path = join(directory, 'private', 'routes.json')
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
afterEach(async () => {
|
|
19
|
+
await rm(directory, { recursive: true, force: true })
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
describe('session route file store', () => {
|
|
23
|
+
test('round-trips the Kernel-owned artifact under owner-private filesystem modes', async () => {
|
|
24
|
+
const artifact: SessionRouteArtifact = { version: 1, entries: {} }
|
|
25
|
+
const store = new FileSessionRouteStore(path)
|
|
26
|
+
|
|
27
|
+
store.write(artifact)
|
|
28
|
+
|
|
29
|
+
expect(store.read()).toEqual(artifact)
|
|
30
|
+
expect((await stat(path)).mode & 0o777).toBe(0o600)
|
|
31
|
+
expect((await stat(join(directory, 'private'))).mode & 0o777).toBe(0o700)
|
|
32
|
+
expect(await readFile(path, 'utf8')).toBe('{"version":1,"entries":{}}\n')
|
|
33
|
+
|
|
34
|
+
store.clear()
|
|
35
|
+
await expect(access(path)).rejects.toThrow()
|
|
36
|
+
expect(() => store.clear()).not.toThrow()
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
test('leaves malformed representation recovery to Kernel Client as a cold miss', async () => {
|
|
40
|
+
await mkdir(join(directory, 'private'))
|
|
41
|
+
await writeFile(path, '{invalid')
|
|
42
|
+
const fixture = join(import.meta.dir, 'fixtures', 'session-route-process.ts')
|
|
43
|
+
|
|
44
|
+
expect(await runFixture(fixture, path)).toEqual({
|
|
45
|
+
value: 'done',
|
|
46
|
+
sourceAttempts: 1,
|
|
47
|
+
destinationAttempts: 1,
|
|
48
|
+
})
|
|
49
|
+
expect(JSON.parse(await readFile(path, 'utf8'))).toMatchObject({ version: 1 })
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
test('reuses Kernel-admitted routing state in a separate operating-system process', async () => {
|
|
53
|
+
const routePath = join(directory, 'private', 'routes.json')
|
|
54
|
+
const fixture = join(import.meta.dir, 'fixtures', 'session-route-process.ts')
|
|
55
|
+
|
|
56
|
+
const first = await runFixture(fixture, routePath)
|
|
57
|
+
const second = await runFixture(fixture, routePath)
|
|
58
|
+
|
|
59
|
+
expect(first).toEqual({ value: 'done', sourceAttempts: 1, destinationAttempts: 1 })
|
|
60
|
+
expect(second).toEqual({ value: 'done', sourceAttempts: 0, destinationAttempts: 1 })
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
test('publishes only complete owner-private artifacts under concurrent process writers', async () => {
|
|
64
|
+
const routePath = join(directory, 'private', 'routes.json')
|
|
65
|
+
const fixture = join(import.meta.dir, 'fixtures', 'session-route-process.ts')
|
|
66
|
+
|
|
67
|
+
const results = await Promise.all(
|
|
68
|
+
Array.from({ length: 8 }, () => runFixture(fixture, routePath)),
|
|
69
|
+
)
|
|
70
|
+
expect(results).toHaveLength(8)
|
|
71
|
+
expect(results.every((result) => result.destinationAttempts === 1)).toBe(true)
|
|
72
|
+
expect(JSON.parse(await readFile(routePath, 'utf8'))).toMatchObject({ version: 1 })
|
|
73
|
+
expect((await stat(routePath)).mode & 0o777).toBe(0o600)
|
|
74
|
+
expect(await runFixture(fixture, routePath)).toEqual({
|
|
75
|
+
value: 'done',
|
|
76
|
+
sourceAttempts: 0,
|
|
77
|
+
destinationAttempts: 1,
|
|
78
|
+
})
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
test('auth logout clears every persisted bearer cache through the real CLI command', async () => {
|
|
82
|
+
const routePath = join(directory, 'session', 'routes.json')
|
|
83
|
+
const exchangePath = join(directory, 'exchange', 'credentials.json')
|
|
84
|
+
await mkdir(join(directory, 'session'), { recursive: true })
|
|
85
|
+
await mkdir(join(directory, 'exchange'), { recursive: true })
|
|
86
|
+
await writeFile(routePath, '{"version":1,"entries":{"confidential":{}}}\n')
|
|
87
|
+
await writeFile(
|
|
88
|
+
exchangePath,
|
|
89
|
+
'{"version":2,"entries":{"confidential":{"credential":"bearer"}}}\n',
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
const child = Bun.spawn(
|
|
93
|
+
[
|
|
94
|
+
process.execPath,
|
|
95
|
+
join(import.meta.dir, '../../..', 'bin', 'astrale.ts'),
|
|
96
|
+
'auth',
|
|
97
|
+
'logout',
|
|
98
|
+
'--all',
|
|
99
|
+
'--json',
|
|
100
|
+
],
|
|
101
|
+
{
|
|
102
|
+
env: { ...process.env, ASTRALE_HOME: directory },
|
|
103
|
+
stdout: 'pipe',
|
|
104
|
+
stderr: 'pipe',
|
|
105
|
+
},
|
|
106
|
+
)
|
|
107
|
+
const [exitCode, stdout, stderr] = await Promise.all([
|
|
108
|
+
child.exited,
|
|
109
|
+
new Response(child.stdout).text(),
|
|
110
|
+
new Response(child.stderr).text(),
|
|
111
|
+
])
|
|
112
|
+
|
|
113
|
+
expect(exitCode, stderr).toBe(0)
|
|
114
|
+
expect(JSON.parse(stdout)).toEqual({ cleared: [] })
|
|
115
|
+
await expect(access(routePath)).rejects.toThrow()
|
|
116
|
+
expect(JSON.parse(await readFile(exchangePath, 'utf8'))).toEqual({ version: 2, entries: {} })
|
|
117
|
+
})
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
interface FixtureResult {
|
|
121
|
+
readonly value: string
|
|
122
|
+
readonly sourceAttempts: number
|
|
123
|
+
readonly destinationAttempts: number
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
async function runFixture(fixture: string, routePath: string): Promise<FixtureResult> {
|
|
127
|
+
const child = Bun.spawn([process.execPath, fixture, routePath], {
|
|
128
|
+
stdout: 'pipe',
|
|
129
|
+
stderr: 'pipe',
|
|
130
|
+
})
|
|
131
|
+
const [exitCode, stdout, stderr] = await Promise.all([
|
|
132
|
+
child.exited,
|
|
133
|
+
new Response(child.stdout).text(),
|
|
134
|
+
new Response(child.stderr).text(),
|
|
135
|
+
])
|
|
136
|
+
expect(exitCode, stderr).toBe(0)
|
|
137
|
+
return JSON.parse(stdout) as FixtureResult
|
|
138
|
+
}
|