@astrale-os/cli 1.0.0-beta.4 → 1.0.0-beta.6

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.
Files changed (173) hide show
  1. package/dist/astrale.js +55649 -59388
  2. package/dist/public/connect-core.js +1972 -26831
  3. package/dist/public/keys/index.js +1754 -40686
  4. package/dist/public/paths/index.js +859 -39511
  5. package/dist/types/admin/instance/model.d.ts +3 -4
  6. package/dist/types/errors.d.ts +1 -1
  7. package/dist/types/lib/idp.d.ts +1 -0
  8. package/dist/types/lib/instance-target.d.ts +1 -1
  9. package/dist/types/lib/log.d.ts +2 -2
  10. package/dist/types/lib/update.d.ts +30 -0
  11. package/package.json +4 -6
  12. package/src/__tests__/fixtures/publication.ts +20 -0
  13. package/src/admin/__tests__/binding.test.ts +14 -21
  14. package/src/admin/__tests__/fixture.ts +101 -0
  15. package/src/admin/binding.ts +86 -6
  16. package/src/admin/catalog/.spec/api.d.ts +2 -2
  17. package/src/admin/catalog/__tests__/client.test.ts +20 -49
  18. package/src/admin/catalog/client.ts +35 -26
  19. package/src/admin/catalog/model.ts +3 -4
  20. package/src/admin/graph/.spec/api.d.ts +4 -10
  21. package/src/admin/graph/nodes.ts +1 -1
  22. package/src/admin/instance/.spec/api.d.ts +2 -2
  23. package/src/admin/instance/__tests__/client.test.ts +58 -83
  24. package/src/admin/instance/client.ts +49 -39
  25. package/src/admin/instance/model.ts +9 -8
  26. package/src/commands/__tests__/call-describe.test.ts +46 -33
  27. package/src/commands/__tests__/domain-install-operation.test.ts +3 -14
  28. package/src/commands/__tests__/domain-install-owned.test.ts +1 -1
  29. package/src/commands/__tests__/domain-list.test.ts +5 -25
  30. package/src/commands/__tests__/install-identity-override.test.ts +3 -14
  31. package/src/commands/__tests__/read-commands.test.ts +35 -10
  32. package/src/commands/__tests__/update.test.ts +25 -3
  33. package/src/commands/call-describe.ts +59 -67
  34. package/src/commands/call.ts +41 -54
  35. package/src/commands/domain/install.ts +2 -2
  36. package/src/commands/domain/uninstall.ts +2 -2
  37. package/src/commands/get.ts +13 -14
  38. package/src/commands/identity/__tests__/register.test.ts +3 -3
  39. package/src/commands/identity/register.ts +5 -4
  40. package/src/commands/instance/delete.ts +5 -3
  41. package/src/commands/instance/status.ts +1 -9
  42. package/src/commands/introspect.ts +4 -11
  43. package/src/commands/logs.ts +24 -25
  44. package/src/commands/mutate.ts +6 -5
  45. package/src/commands/query.ts +27 -30
  46. package/src/commands/token.ts +8 -7
  47. package/src/commands/update.ts +31 -11
  48. package/src/commands/view.ts +4 -3
  49. package/src/connection/.spec/api.d.ts +7 -8
  50. package/src/connection/.spec/architecture.md +5 -4
  51. package/src/connection/.spec/flows/session.ts +2 -2
  52. package/src/connection/.spec/laws/connection.ts +6 -2
  53. package/src/connection/.spec/layout.ts +1 -0
  54. package/src/connection/__tests__/credential.test.ts +1 -1
  55. package/src/connection/__tests__/errors.test.ts +27 -20
  56. package/src/connection/__tests__/exchange.test.ts +30 -1
  57. package/src/connection/__tests__/failure-fixtures.ts +39 -0
  58. package/src/connection/__tests__/failure-safety.test.ts +75 -0
  59. package/src/connection/__tests__/self.test.ts +31 -11
  60. package/src/connection/__tests__/session.test.ts +3 -3
  61. package/src/connection/call.ts +1 -1
  62. package/src/connection/credential.ts +2 -2
  63. package/src/connection/errors.ts +1 -355
  64. package/src/connection/exchange.ts +47 -23
  65. package/src/connection/failure/classify.ts +110 -0
  66. package/src/connection/failure/debug.ts +26 -0
  67. package/src/connection/failure/index.ts +18 -0
  68. package/src/connection/failure/model.ts +28 -0
  69. package/src/connection/failure/render.ts +116 -0
  70. package/src/connection/reasons.ts +16 -0
  71. package/src/connection/self.ts +39 -21
  72. package/src/connection/session.ts +8 -8
  73. package/src/errors.ts +3 -2
  74. package/src/graph/.spec/api.d.ts +12 -5
  75. package/src/graph/.spec/layout.ts +9 -1
  76. package/src/graph/__tests__/mutation.test.ts +2 -2
  77. package/src/graph/__tests__/query.test.ts +25 -6
  78. package/src/graph/class.ts +30 -0
  79. package/src/graph/index.ts +1 -0
  80. package/src/graph/query.ts +21 -32
  81. package/src/identity/.spec/api.d.ts +2 -2
  82. package/src/identity/__tests__/registration.test.ts +1 -1
  83. package/src/identity/registration.ts +3 -3
  84. package/src/lib/__tests__/domain-publication.test.ts +4 -15
  85. package/src/lib/__tests__/idp.test.ts +25 -0
  86. package/src/lib/__tests__/instance-target.test.ts +6 -39
  87. package/src/lib/__tests__/log-errors.test.ts +53 -0
  88. package/src/lib/__tests__/studio-server-deps.test.ts +1 -1
  89. package/src/lib/__tests__/update.test.ts +101 -8
  90. package/src/lib/__tests__/view-open-intent.test.ts +0 -2
  91. package/src/lib/__tests__/view-server.test.ts +1 -1
  92. package/src/lib/__tests__/view-session.test.ts +1 -1
  93. package/src/lib/command-dx.ts +1 -4
  94. package/src/lib/domain-publication.ts +1 -1
  95. package/src/lib/idp.ts +29 -1
  96. package/src/lib/instance-target.ts +3 -14
  97. package/src/lib/log.ts +11 -6
  98. package/src/lib/update.ts +120 -26
  99. package/src/lib/validation.ts +2 -2
  100. package/src/lib/view/resolve.ts +1 -1
  101. package/src/program/__tests__/program.test.ts +1 -1
  102. package/studio/client/dist/assets/{elk-api-lwhFo7vB.js → elk-api-Di4OXGNH.js} +1 -1
  103. package/studio/client/dist/assets/index-BLm6J11H.js +81 -0
  104. package/studio/client/dist/assets/index-BiGR8Oo9.css +1 -0
  105. package/studio/client/dist/assets/index-DTjvkOUX.js +8 -0
  106. package/studio/client/dist/index.html +2 -2
  107. package/studio/package.json +2 -9
  108. package/studio/server/agent/ask.test.ts +1 -1
  109. package/studio/server/agent/bridge/grant.test.ts +1 -1
  110. package/studio/server/agent/bridge/routes.test.ts +1 -1
  111. package/studio/server/agent/harness/claude/adapter.test.ts +15 -5
  112. package/studio/server/agent/prompts/anchors.test.ts +32 -36
  113. package/studio/server/agent/prompts/anchors.ts +20 -49
  114. package/studio/server/agent/prompts/system.test.ts +3 -2
  115. package/studio/server/agent/prompts/system.ts +3 -3
  116. package/studio/server/agent/routes.test.ts +1 -1
  117. package/studio/server/agent/run/coordinator.test.ts +1 -1
  118. package/studio/server/api/canvas.ts +0 -1
  119. package/studio/server/api-agent-loadout.test.ts +1 -1
  120. package/studio/server/api.test.ts +1 -1
  121. package/studio/server/cache.test.ts +23 -12
  122. package/studio/server/cache.ts +12 -15
  123. package/studio/server/domain.test.ts +50 -68
  124. package/studio/server/domain.ts +82 -63
  125. package/studio/server/handoff/copy.ts +1 -1
  126. package/studio/server/index.ts +1 -1
  127. package/studio/server/instances/probe.test.ts +1 -1
  128. package/studio/server/introspect/anatomy/views/routes.ts +46 -37
  129. package/studio/server/introspect/anatomy/views.ts +9 -14
  130. package/studio/server/introspect/anatomy-extras.test.ts +26 -42
  131. package/studio/server/introspect/anatomy.test.ts +22 -14
  132. package/studio/server/introspect/anatomy.ts +6 -9
  133. package/studio/server/introspect/bundle.ts +0 -3
  134. package/studio/server/introspect/canonical-schema.test.ts +94 -389
  135. package/studio/server/introspect/canonical-schema.ts +227 -457
  136. package/studio/server/introspect/core-extractor.ts +18 -127
  137. package/studio/server/introspect/core.ts +1 -2
  138. package/studio/server/introspect/diff.test.ts +11 -7
  139. package/studio/server/introspect/diff.ts +18 -55
  140. package/studio/server/introspect/extractor.ts +22 -117
  141. package/studio/server/introspect/overlay-tsmorph.test.ts +119 -240
  142. package/studio/server/introspect/overlay.test.ts +26 -54
  143. package/studio/server/introspect/overlay.ts +10 -37
  144. package/studio/server/introspect/revision.test.ts +1 -1
  145. package/studio/server/introspect/revision.ts +1 -1
  146. package/studio/server/introspect/runtime.test.ts +65 -183
  147. package/studio/server/introspect/runtime.ts +8 -25
  148. package/studio/server/introspect/schema-ir-json.test.ts +70 -0
  149. package/studio/server/introspect/schema-ir-json.ts +38 -68
  150. package/studio/server/introspect/schema-refs.test.ts +43 -88
  151. package/studio/server/introspect/schema-refs.ts +14 -49
  152. package/studio/server/introspect/source-overlay/handlers.ts +116 -636
  153. package/studio/server/introspect/source-overlay/kernel-calls.ts +0 -3
  154. package/studio/server/introspect/source-overlay/spans.ts +15 -45
  155. package/studio/server/state/baseline.test.ts +14 -9
  156. package/studio/server/state/baseline.ts +3 -5
  157. package/studio/server/state/visibility.ts +1 -5
  158. package/studio/server/views/model.ts +3 -3
  159. package/studio/server/views/target.test.ts +17 -74
  160. package/studio/server/views/target.ts +24 -50
  161. package/studio/server/watch.test.ts +11 -11
  162. package/studio/server/watch.ts +8 -4
  163. package/studio/server/workspace-watch.ts +2 -2
  164. package/studio/shared/contracts/schema.ts +38 -107
  165. package/studio/shared/contracts/surface.test.ts +13 -15
  166. package/studio/shared/contracts/workspace.ts +2 -11
  167. package/studio/shared/schema/identity.test.ts +20 -44
  168. package/studio/shared/schema/identity.ts +9 -23
  169. package/viewer/dist/main.js +40 -61
  170. package/studio/client/dist/assets/index-B-c-smo9.js +0 -8
  171. package/studio/client/dist/assets/index-BckHuAWk.js +0 -81
  172. package/studio/client/dist/assets/index-C4aNQ6dz.css +0 -1
  173. package/studio/server/introspect/anatomy/views/legacy.ts +0 -232
@@ -1,34 +1,47 @@
1
1
  import { Path } from '@astrale-os/sdk/graph/path'
2
+ import { bundle, defineSchema, func, method, nodeClass } from '@astrale-os/sdk/schema'
2
3
  import { describe, expect, test } from 'bun:test'
3
4
 
4
- import { describeCallableFromSchema } from '../call-describe'
5
+ import { describeCallableFromBundle } from '../call-describe'
5
6
 
6
- const schema = {
7
- classes: {
8
- Manager: {
9
- methods: {
10
- createInstance: {
11
- auth: 'authorized',
12
- description: 'Create one child Instance.',
13
- input: { type: 'object', required: ['operationId', 'slug'] },
14
- output: { mode: 'value' },
15
- },
16
- },
17
- },
7
+ const createInstance = method({
8
+ auth: 'authenticated',
9
+ description: 'Create one child Instance.',
10
+ input: {
11
+ type: 'object',
12
+ properties: { operationId: { type: 'string' }, slug: { type: 'string' } },
13
+ required: ['operationId', 'slug'],
14
+ additionalProperties: false,
18
15
  },
19
- functions: {
20
- journal: {
21
- description: 'Read the authorized Kernel journal.',
22
- input: { type: 'object' },
23
- },
16
+ output: { type: 'object', additionalProperties: true },
17
+ static: true,
18
+ })
19
+ const inspectInstance = method({
20
+ auth: 'authenticated',
21
+ description: 'Inspect one child Instance.',
22
+ input: { type: 'object', properties: {}, additionalProperties: false },
23
+ output: { type: 'object', additionalProperties: true },
24
+ })
25
+ const journal = func({
26
+ auth: 'authenticated',
27
+ description: 'Read the authorized Kernel journal.',
28
+ input: { type: 'object', properties: {}, additionalProperties: false },
29
+ output: { type: 'object', additionalProperties: true },
30
+ })
31
+ const source = defineSchema('host.astrale.ai', {
32
+ classes: {
33
+ Manager: nodeClass({ methods: { createInstance } }),
34
+ Instance: nodeClass({ methods: { inspectInstance } }),
24
35
  },
25
- }
36
+ functions: { journal },
37
+ })
38
+ const installed = bundle.create(source)
26
39
 
27
- describe('describeCallableFromSchema', () => {
28
- test('reads a static Class method from the Domain schema', () => {
29
- const described = describeCallableFromSchema(
40
+ describe('describeCallableFromBundle', () => {
41
+ test('reads a static Class method from the resolved Domain', () => {
42
+ const described = describeCallableFromBundle(
30
43
  Path.parse('/:host.astrale.ai:class.Manager:createInstance'),
31
- schema,
44
+ installed,
32
45
  )
33
46
  expect(described).toMatchObject({
34
47
  origin: 'host.astrale.ai',
@@ -36,30 +49,30 @@ describe('describeCallableFromSchema', () => {
36
49
  method: 'createInstance',
37
50
  dispatch: 'static',
38
51
  description: 'Create one child Instance.',
39
- auth: 'authorized',
52
+ auth: 'authenticated',
40
53
  })
41
54
  expect(described?.input).toMatchObject({ required: ['operationId', 'slug'] })
42
55
  })
43
56
 
44
- test('finds an instance method when the receiver Path is not a Class projection', () => {
57
+ test('finds one instance method when the receiver Path is not a Class projection', () => {
45
58
  expect(
46
- describeCallableFromSchema(
47
- Path.parse('/:host.astrale.ai:core.manager::createInstance'),
48
- schema,
59
+ describeCallableFromBundle(
60
+ Path.parse('/:host.astrale.ai:core.manager::inspectInstance'),
61
+ installed,
49
62
  ),
50
63
  ).toMatchObject({
51
64
  origin: 'host.astrale.ai',
52
- class: 'Manager',
53
- method: 'createInstance',
65
+ class: 'Instance',
66
+ method: 'inspectInstance',
54
67
  dispatch: 'instance',
55
68
  })
56
69
  })
57
70
 
58
- test('reads a standalone Function from the Domain schema', () => {
71
+ test('reads a standalone Function from the resolved Domain', () => {
59
72
  expect(
60
- describeCallableFromSchema(Path.parse('/:kernel.astrale.ai:function.journal'), schema),
73
+ describeCallableFromBundle(Path.parse('/:host.astrale.ai:function.journal'), installed),
61
74
  ).toMatchObject({
62
- origin: 'kernel.astrale.ai',
75
+ origin: 'host.astrale.ai',
63
76
  function: 'journal',
64
77
  description: 'Read the authorized Kernel journal.',
65
78
  })
@@ -1,9 +1,7 @@
1
- import { defineDomain } from '@astrale-os/sdk'
2
- import { issuer } from '@astrale-os/sdk/auth'
3
- import { createDeployment } from '@astrale-os/sdk/deployment'
4
- import { defineSchema } from '@astrale-os/sdk/schema/v1'
1
+ import { defineSchema } from '@astrale-os/sdk/schema'
5
2
  import { afterEach, describe, expect, mock, test } from 'bun:test'
6
3
 
4
+ import { releaseFor } from '../../__tests__/fixtures/publication'
7
5
  import { installDirect } from '../domain/install'
8
6
 
9
7
  const GENERATED = '4a4c9a18-50f6-4d84-a7b7-2d83e3e45dc8'
@@ -12,16 +10,7 @@ const RETRY = '139137b5-af47-47ce-92b2-b64a2b0c63d7'
12
10
  const originalFetch = globalThis.fetch
13
11
 
14
12
  const schema = defineSchema('crm.test', {})
15
- const definition = defineDomain({
16
- schema,
17
- handlers: { functions: {}, classes: {}, interfaces: {} },
18
- })
19
- const deployed = createDeployment({
20
- definition,
21
- issuer: issuer.accept('https://crm.test'),
22
- bundleHref: 'https://crm.test/domain.bundle.json',
23
- bindings: { callables: [] },
24
- }).publication
13
+ const deployed = releaseFor(schema, 'https://crm.test').publication
25
14
 
26
15
  function publicationResponse(): Response {
27
16
  return Response.json(deployed)
@@ -1,4 +1,4 @@
1
- import { ResponseError } from '@astrale-os/kernel-client'
1
+ import { ResponseError } from '@astrale-os/sdk/client'
2
2
  import { afterEach, beforeEach, describe, expect, mock, test } from 'bun:test'
3
3
 
4
4
  import type { OwnedInstanceInfo } from '../../lib/admin-instance'
@@ -1,11 +1,9 @@
1
- import { defineDomain } from '@astrale-os/sdk'
2
- import { issuer } from '@astrale-os/sdk/auth'
3
- import { createDeployment } from '@astrale-os/sdk/deployment'
4
- import { defineSchema } from '@astrale-os/sdk/schema/v1'
1
+ import { defineSchema, schema as schemaApi } from '@astrale-os/sdk/schema'
5
2
  import { afterAll, describe, expect, test } from 'bun:test'
6
3
 
7
4
  import type { DomainInfo } from '../../lib/admin-domain'
8
5
 
6
+ import { releaseFor } from '../../__tests__/fixtures/publication'
9
7
  import { byDefaultThenName, domainProjection, probe, type DomainRow } from '../domain/list'
10
8
 
11
9
  const strip = (s: string): string => s.replace(/\[[0-9;]*m/g, '')
@@ -85,16 +83,7 @@ describe('domain list — canonical Publication check', () => {
85
83
 
86
84
  test('reports the admitted schema revision in machine data', async () => {
87
85
  const schema = defineSchema('catalog-check.example.dev', {})
88
- const definition = defineDomain({
89
- schema,
90
- handlers: { functions: {}, classes: {}, interfaces: {} },
91
- })
92
- const deployed = createDeployment({
93
- definition,
94
- issuer: issuer.accept('https://catalog-check.example.dev'),
95
- bundleHref: 'https://catalog-check.example.dev/domain.bundle.json',
96
- bindings: { callables: [] },
97
- }).publication
86
+ const deployed = releaseFor(schema, 'https://catalog-check.example.dev').publication
98
87
  const server = Bun.serve({
99
88
  port: 0,
100
89
  fetch(request) {
@@ -114,23 +103,14 @@ describe('domain list — canonical Publication check', () => {
114
103
  ),
115
104
  ).resolves.toMatchObject({
116
105
  reachable: true,
117
- schemaRevision: definition.domain.$.revision,
106
+ schemaRevision: schemaApi.revision(schema),
118
107
  checkError: null,
119
108
  })
120
109
  })
121
110
 
122
111
  test('rejects a Publication whose origin differs from the catalog entry', async () => {
123
112
  const schema = defineSchema('deployed.example.dev', {})
124
- const definition = defineDomain({
125
- schema,
126
- handlers: { functions: {}, classes: {}, interfaces: {} },
127
- })
128
- const deployed = createDeployment({
129
- definition,
130
- issuer: issuer.accept('https://deployed.example.dev'),
131
- bundleHref: 'https://deployed.example.dev/domain.bundle.json',
132
- bindings: { callables: [] },
133
- }).publication
113
+ const deployed = releaseFor(schema, 'https://deployed.example.dev').publication
134
114
  const server = Bun.serve({
135
115
  port: 0,
136
116
  fetch: () => Response.json(deployed),
@@ -1,9 +1,7 @@
1
- import { defineDomain } from '@astrale-os/sdk'
2
- import { issuer } from '@astrale-os/sdk/auth'
3
- import { createDeployment } from '@astrale-os/sdk/deployment'
4
- import { defineSchema } from '@astrale-os/sdk/schema/v1'
1
+ import { defineSchema } from '@astrale-os/sdk/schema'
5
2
  import { afterAll, describe, expect, test } from 'bun:test'
6
3
 
4
+ import { releaseFor } from '../../__tests__/fixtures/publication'
7
5
  import { domainRefFromTarget, isIdentityOverride, probeDeclaredOrigin } from '../domain/install'
8
6
 
9
7
  describe('admin-path target classification', () => {
@@ -47,16 +45,7 @@ describe('declared-origin Publication probe', () => {
47
45
 
48
46
  test('reads origin from a well-formed canonical Publication', async () => {
49
47
  const schema = defineSchema('crm.acme.dev', {})
50
- const definition = defineDomain({
51
- schema,
52
- handlers: { functions: {}, classes: {}, interfaces: {} },
53
- })
54
- const deployed = createDeployment({
55
- definition,
56
- issuer: issuer.accept('https://crm.acme.dev'),
57
- bundleHref: 'https://crm.acme.dev/domain.bundle.json',
58
- bindings: { callables: [] },
59
- }).publication
48
+ const deployed = releaseFor(schema, 'https://crm.acme.dev').publication
60
49
  const url = servePublication((req) =>
61
50
  new URL(req.url).pathname === '/.well-known/astrale/domain.json'
62
51
  ? Response.json(deployed)
@@ -36,6 +36,8 @@ let mutations: unknown[] = []
36
36
  let queryResult: unknown
37
37
  let getResult: unknown
38
38
  let mutationResult: unknown
39
+ let selfFailure: unknown
40
+ let selfContexts: unknown[] = []
39
41
 
40
42
  const queryMock = mock(async (ast: unknown, options?: unknown) => {
41
43
  queryCalls.push({ ast, options })
@@ -49,7 +51,9 @@ const mutateMock = mock(async (mutation: unknown) => {
49
51
  mutations.push(mutation)
50
52
  return mutationResult
51
53
  })
52
- const expandSelfInPathMock = mock(async (path: string) => {
54
+ const expandSelfInPathMock = mock(async (path: string, context: unknown) => {
55
+ selfContexts.push(context)
56
+ if (selfFailure !== undefined) throw selfFailure
53
57
  if (path === '@self') {
54
58
  return {
55
59
  path: '@expanded-self',
@@ -59,10 +63,11 @@ const expandSelfInPathMock = mock(async (path: string) => {
59
63
  return { path, meta: undefined }
60
64
  })
61
65
  const withSelfHintMock = mock(async (action: () => Promise<unknown>) => action())
66
+ const commandContext = {
67
+ graph: { query: queryMock, getOrThrow: getOrThrowMock, mutate: mutateMock },
68
+ }
62
69
  const runKernelCommandMock = mock(async (run: RunOpts) => {
63
- const result = await run.fn({
64
- graph: { query: queryMock, getOrThrow: getOrThrowMock, mutate: mutateMock },
65
- })
70
+ const result = await run.fn(commandContext)
66
71
  await run.format?.(result, run.opts, true)
67
72
  })
68
73
 
@@ -88,6 +93,8 @@ beforeEach(() => {
88
93
  }
89
94
  getResult = undefined
90
95
  mutationResult = { createdNodes: {} }
96
+ selfFailure = undefined
97
+ selfContexts = []
91
98
  queryMock.mockClear()
92
99
  getOrThrowMock.mockClear()
93
100
  mutateMock.mockClear()
@@ -161,7 +168,7 @@ describe('query command', () => {
161
168
  ])
162
169
  })
163
170
 
164
- test('authors a Definition source through the same canonical Query V6 call', async () => {
171
+ test('authors a Class source through the same canonical Query V6 call', async () => {
165
172
  const { queryCommand } = await import('../query')
166
173
 
167
174
  await queryCommand([], {
@@ -179,8 +186,8 @@ describe('query command', () => {
179
186
  kind: 'node',
180
187
  terms: [
181
188
  {
182
- kind: 'definition',
183
- definition: { origin: 'issues.astrale.ai', kind: 'class', name: 'Issue' },
189
+ kind: 'class',
190
+ class: { origin: 'issues.astrale.ai', kind: 'class', name: 'Issue' },
184
191
  },
185
192
  ],
186
193
  binding: 'n0',
@@ -193,6 +200,17 @@ describe('query command', () => {
193
200
  ])
194
201
  })
195
202
 
203
+ test('resolves @self inside the command session and never relabels transport failure as input', async () => {
204
+ const { queryCommand } = await import('../query')
205
+ await queryCommand(['@self'], { json: true, limit: '2' })
206
+ expect(selfContexts).toEqual([commandContext])
207
+
208
+ const aggregate = new AggregateError([], '')
209
+ selfFailure = aggregate
210
+ await expect(queryCommand(['@self'], { json: true, limit: '2' })).rejects.toBe(aggregate)
211
+ expect(errors.join('\n')).not.toContain('INVALID_INPUT')
212
+ })
213
+
196
214
  /** @evidence TEST-CLI-QUERY-REJECTS-V1-BEFORE-CONNECTION */
197
215
  test('rejects a legacy AST before opening the command connection', async () => {
198
216
  const { queryCommand } = await import('../query')
@@ -215,7 +233,7 @@ describe('get command', () => {
215
233
  const { getCommand } = await import('../get')
216
234
  getResult = {
217
235
  id: 'note-1',
218
- class: '/:notes.example.dev:class.Note',
236
+ class: 'notes.example.dev:class.Note',
219
237
  props: { 'notes.example.dev:class.Note.property.title': 'Hello' },
220
238
  }
221
239
 
@@ -226,6 +244,13 @@ describe('get command', () => {
226
244
  expect(stdout).not.toContain('__labels')
227
245
  expect(stdout).not.toContain('classId')
228
246
  })
247
+
248
+ test('resolves @self through the same point-read command context', async () => {
249
+ const { getCommand } = await import('../get')
250
+ await getCommand('@self', { json: true })
251
+ expect(selfContexts).toEqual([commandContext])
252
+ expect(getTargets).toEqual(['@expanded-self'])
253
+ })
229
254
  })
230
255
 
231
256
  describe('mutate command', () => {
@@ -242,7 +267,7 @@ describe('mutate command', () => {
242
267
  {
243
268
  op: 'node.create',
244
269
  as: 'note',
245
- class: '/:notes.example.dev:class.Note',
270
+ class: 'notes.example.dev:class.Note',
246
271
  props: {},
247
272
  },
248
273
  ],
@@ -258,7 +283,7 @@ describe('mutate command', () => {
258
283
  {
259
284
  op: 'node.create',
260
285
  as: 'note',
261
- class: '/:notes.example.dev:class.Note',
286
+ class: 'notes.example.dev:class.Note',
262
287
  props: {},
263
288
  },
264
289
  ],
@@ -35,9 +35,11 @@ describe('CLI update staleness', () => {
35
35
  const result = await cliStale(
36
36
  { channel: 'canary' },
37
37
  {
38
- update: async () => {
39
- throw new Error('package managed')
40
- },
38
+ update: async () => ({
39
+ status: 'managed',
40
+ currentVersion: '1.0.0-beta.0',
41
+ executable: '/opt/homebrew/bin/node',
42
+ }),
41
43
  fetchPackageVersion: async ({ channel }) => {
42
44
  expect(channel).toBe('canary')
43
45
  return '1.0.0-canary.0'
@@ -52,6 +54,26 @@ describe('CLI update staleness', () => {
52
54
  })
53
55
  })
54
56
 
57
+ test('does not misclassify a script update failure as package-managed', async () => {
58
+ const fetchPackageVersion = mock(async () => '9.9.9')
59
+ const result = await cliStale(
60
+ {},
61
+ {
62
+ update: async () => {
63
+ throw new Error('release endpoint unavailable')
64
+ },
65
+ fetchPackageVersion,
66
+ },
67
+ )
68
+
69
+ expect(result).toMatchObject({
70
+ stale: false,
71
+ managed: false,
72
+ error: 'release endpoint unavailable',
73
+ })
74
+ expect(fetchPackageVersion).not.toHaveBeenCalled()
75
+ })
76
+
55
77
  test('maps the default to beta and the stable channel to npm latest', async () => {
56
78
  const originalFetch = globalThis.fetch
57
79
  const fetchMock = mock(async (input: RequestInfo | URL) => {
@@ -1,8 +1,9 @@
1
1
  import type { Path } from '@astrale-os/sdk/graph/path'
2
+ import type { ResolvedFunction, ResolvedMethod } from '@astrale-os/sdk/schema'
2
3
 
3
- import { AstraleError } from '../errors'
4
+ import { bundle, schema } from '@astrale-os/sdk/schema'
4
5
 
5
- type JsonRecord = Record<string, unknown>
6
+ import { AstraleError } from '../errors'
6
7
 
7
8
  export interface CallableDescription {
8
9
  readonly path: string
@@ -18,40 +19,40 @@ export interface CallableDescription {
18
19
  readonly candidates?: readonly CallableDescription[]
19
20
  }
20
21
 
21
- /** Resolve one callable's input/output from an installed Domain schema document. */
22
- export function describeCallableFromSchema(
22
+ /** Resolve one callable from an admitted installed Domain bundle. */
23
+ export function describeCallableFromBundle(
23
24
  path: Path,
24
- schema: unknown,
25
+ input: unknown,
25
26
  ): CallableDescription | undefined {
26
27
  if (path.ast.anchor.kind !== 'domain') return undefined
27
28
  const origin = path.ast.anchor.origin
28
29
  const last = path.ast.steps.at(-1)
29
- const document = asRecord(schema)
30
- if (document === undefined || last === undefined) return undefined
30
+ if (last === undefined) return undefined
31
+ const domain = schema.resolve(bundle.accept(input).root)
32
+ if (domain.origin !== origin) return undefined
31
33
 
32
34
  if (last.kind === 'method') {
33
- const owner = path.ast.steps.at(-2)
34
- if (
35
- owner?.kind === 'projection' &&
36
- (owner.projection.kind === 'class' || owner.projection.kind === 'interface')
37
- ) {
38
- const bag = asRecord(
39
- owner.projection.kind === 'class' ? document.classes : document.interfaces,
35
+ const ownerStep = path.ast.steps.at(-2)
36
+ if (ownerStep?.kind === 'projection' && ownerStep.projection.kind === 'class') {
37
+ const owner = domain.classes[ownerStep.projection.name]
38
+ if (owner?.kind !== 'node') return undefined
39
+ const method = namedMethod(
40
+ last.dispatch === 'static' ? owner.static.methods : owner.methods,
41
+ last.name,
42
+ last.dispatch,
40
43
  )
41
- const definition = asRecord(bag?.[owner.projection.name])
42
- const method = asRecord(asRecord(definition?.methods)?.[last.name])
43
- if (method !== undefined) {
44
- return Object.freeze({
45
- path: path.raw,
46
- origin,
47
- class: owner.projection.name,
48
- method: last.name,
49
- dispatch: last.dispatch,
50
- ...callableFields(method),
51
- })
52
- }
44
+ return method === undefined
45
+ ? undefined
46
+ : description(path, origin, owner.ref.name, method, last.dispatch)
53
47
  }
54
- const matches = findNamedMethods(document, origin, path.raw, last.name, last.dispatch)
48
+
49
+ const matches = Object.values(domain.classes).flatMap((owner) => {
50
+ if (owner.kind !== 'node') return []
51
+ const method = namedMethod(owner.methods, last.name, last.dispatch)
52
+ return method === undefined
53
+ ? []
54
+ : [description(path, origin, owner.ref.name, method, last.dispatch)]
55
+ })
55
56
  if (matches.length === 1) return matches[0]
56
57
  if (matches.length > 1) {
57
58
  return Object.freeze({
@@ -66,13 +67,13 @@ export function describeCallableFromSchema(
66
67
  }
67
68
 
68
69
  if (last.kind === 'projection' && last.projection.kind === 'function') {
69
- const fn = asRecord(asRecord(document.functions)?.[last.projection.name])
70
- if (fn === undefined) return undefined
70
+ const callable = domain.functions[last.projection.name]
71
+ if (callable === undefined) return undefined
71
72
  return Object.freeze({
72
73
  path: path.raw,
73
74
  origin,
74
- function: last.projection.name,
75
- ...callableFields(fn),
75
+ function: callable.ref.name,
76
+ ...callableFields(callable),
76
77
  })
77
78
  }
78
79
  return undefined
@@ -86,46 +87,37 @@ export function missingCallableDescription(path: string): AstraleError {
86
87
  )
87
88
  }
88
89
 
89
- function findNamedMethods(
90
- schema: JsonRecord,
90
+ function namedMethod(
91
+ methods: Iterable<ResolvedMethod>,
92
+ name: string,
93
+ dispatch: 'static' | 'instance',
94
+ ): ResolvedMethod | undefined {
95
+ const expectedStatic = dispatch === 'static'
96
+ return [...methods].find((method) => method.name === name && method.static === expectedStatic)
97
+ }
98
+
99
+ function description(
100
+ path: Path,
91
101
  origin: string,
92
- path: string,
93
- method: string,
102
+ className: string,
103
+ callable: ResolvedMethod,
94
104
  dispatch: 'static' | 'instance',
95
- ): CallableDescription[] {
96
- const matches: CallableDescription[] = []
97
- for (const bag of [schema.classes, schema.interfaces]) {
98
- const definitions = asRecord(bag)
99
- if (definitions === undefined) continue
100
- for (const [className, definition] of Object.entries(definitions)) {
101
- const methodDef = asRecord(asRecord(asRecord(definition)?.methods)?.[method])
102
- if (methodDef === undefined) continue
103
- matches.push(
104
- Object.freeze({
105
- path,
106
- origin,
107
- class: className,
108
- method,
109
- dispatch,
110
- ...callableFields(methodDef),
111
- }),
112
- )
113
- }
114
- }
115
- return matches
105
+ ): CallableDescription {
106
+ return Object.freeze({
107
+ path: path.raw,
108
+ origin,
109
+ class: className,
110
+ method: callable.name,
111
+ dispatch,
112
+ ...callableFields(callable),
113
+ })
116
114
  }
117
115
 
118
- function callableFields(value: JsonRecord): Partial<CallableDescription> {
116
+ function callableFields(callable: ResolvedFunction | ResolvedMethod): Partial<CallableDescription> {
119
117
  return {
120
- ...(typeof value.description === 'string' ? { description: value.description } : {}),
121
- ...(value.auth === undefined ? {} : { auth: value.auth }),
122
- ...(value.input === undefined ? {} : { input: value.input }),
123
- ...(value.output === undefined ? {} : { output: value.output }),
118
+ ...(callable.description === undefined ? {} : { description: callable.description }),
119
+ auth: callable.auth,
120
+ input: callable.input,
121
+ output: callable.output,
124
122
  }
125
123
  }
126
-
127
- function asRecord(input: unknown): JsonRecord | undefined {
128
- return input !== null && typeof input === 'object' && !Array.isArray(input)
129
- ? (input as JsonRecord)
130
- : undefined
131
- }