@astrale-os/cli 1.0.0-beta.3 → 1.0.0-beta.30

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 (343) hide show
  1. package/README.md +80 -6
  2. package/dist/astrale.js +55471 -58674
  3. package/dist/public/connect-core.js +2158 -30706
  4. package/dist/public/keys/index.js +1016 -29541
  5. package/dist/public/paths/index.js +370 -28654
  6. package/dist/types/admin/contract.d.ts +26 -0
  7. package/dist/types/admin/instance/client.d.ts +20 -0
  8. package/dist/types/admin/instance/index.d.ts +2 -0
  9. package/dist/types/admin/instance/model.d.ts +6 -10
  10. package/dist/types/connection/auth.d.ts +3 -0
  11. package/dist/types/connection/call.d.ts +3 -0
  12. package/dist/types/connection/command.d.ts +27 -0
  13. package/dist/types/connection/credential.d.ts +14 -0
  14. package/dist/types/connection/errors.d.ts +1 -0
  15. package/dist/types/connection/exchange.d.ts +9 -0
  16. package/dist/types/connection/failure/classify.d.ts +2 -0
  17. package/dist/types/connection/failure/index.d.ts +5 -0
  18. package/dist/types/connection/failure/model.d.ts +25 -0
  19. package/dist/types/connection/failure/render.d.ts +3 -0
  20. package/dist/types/connection/index.d.ts +8 -0
  21. package/dist/types/connection/reasons.d.ts +37 -0
  22. package/dist/types/connection/self.d.ts +37 -0
  23. package/dist/types/connection/session.d.ts +31 -0
  24. package/dist/types/connection/target.d.ts +38 -0
  25. package/dist/types/errors.d.ts +1 -1
  26. package/dist/types/lib/admin-instance.d.ts +17 -0
  27. package/dist/types/lib/admin-target.d.ts +11 -36
  28. package/dist/types/lib/config.d.ts +13 -59
  29. package/dist/types/lib/failure-debug.d.ts +1 -0
  30. package/dist/types/lib/idp.d.ts +25 -173
  31. package/dist/types/lib/instance-target.d.ts +1 -1
  32. package/dist/types/lib/instance.d.ts +30 -88
  33. package/dist/types/lib/log.d.ts +5 -3
  34. package/dist/types/lib/output.d.ts +1 -1
  35. package/dist/types/lib/proc.d.ts +43 -0
  36. package/dist/types/lib/self.d.ts +9 -0
  37. package/dist/types/lib/update.d.ts +109 -0
  38. package/dist/types/lib/validation.d.ts +4 -1
  39. package/dist/types/state/exchange-credentials.d.ts +6 -2
  40. package/dist/types/state/files.d.ts +2 -0
  41. package/dist/types/state/index.d.ts +3 -2
  42. package/dist/types/state/paths.d.ts +2 -0
  43. package/dist/types/state/session-routes.d.ts +10 -0
  44. package/package.json +16 -19
  45. package/src/__tests__/fixtures/publication.ts +20 -0
  46. package/src/admin/.spec/architecture.md +12 -5
  47. package/src/admin/__tests__/fixture.ts +25 -0
  48. package/src/admin/catalog/.spec/api.d.ts +1 -3
  49. package/src/admin/catalog/.spec/architecture.md +5 -4
  50. package/src/admin/catalog/__tests__/client.test.ts +143 -69
  51. package/src/admin/catalog/client.ts +40 -54
  52. package/src/admin/catalog/model.ts +3 -4
  53. package/src/admin/contract.ts +47 -0
  54. package/src/admin/graph/.spec/api.d.ts +4 -10
  55. package/src/admin/graph/nodes.ts +1 -1
  56. package/src/admin/instance/.spec/api.d.ts +5 -9
  57. package/src/admin/instance/.spec/architecture.md +7 -7
  58. package/src/admin/instance/__tests__/client.test.ts +157 -175
  59. package/src/admin/instance/client.ts +39 -128
  60. package/src/admin/instance/index.ts +1 -2
  61. package/src/admin/instance/model.ts +12 -18
  62. package/src/commands/__tests__/admin-instance.test.ts +11 -8
  63. package/src/commands/__tests__/call-describe.test.ts +46 -33
  64. package/src/commands/__tests__/call.test.ts +34 -0
  65. package/src/commands/__tests__/domain-install-operation.test.ts +30 -22
  66. package/src/commands/__tests__/domain-install-owned.test.ts +1 -1
  67. package/src/commands/__tests__/domain-list.test.ts +5 -25
  68. package/src/commands/__tests__/install-direct.test.ts +115 -8
  69. package/src/commands/__tests__/install-identity-override.test.ts +6 -17
  70. package/src/commands/__tests__/instance-list-rows.test.ts +1 -1
  71. package/src/commands/__tests__/instance-status.test.ts +139 -0
  72. package/src/commands/__tests__/introspect-parse.test.ts +80 -1
  73. package/src/commands/__tests__/logs.test.ts +250 -2
  74. package/src/commands/__tests__/read-commands.test.ts +118 -10
  75. package/src/commands/__tests__/token-ttl.test.ts +49 -4
  76. package/src/commands/__tests__/update.test.ts +25 -3
  77. package/src/commands/__tests__/view.test.ts +50 -2
  78. package/src/commands/auth/logout.ts +2 -1
  79. package/src/commands/browser.ts +29 -0
  80. package/src/commands/call-describe.ts +60 -68
  81. package/src/commands/call.ts +71 -59
  82. package/src/commands/domain/install.ts +73 -46
  83. package/src/commands/domain/list.ts +1 -1
  84. package/src/commands/domain/publish.ts +14 -11
  85. package/src/commands/domain/uninstall.ts +2 -2
  86. package/src/commands/get.ts +15 -16
  87. package/src/commands/identity/__tests__/register.test.ts +3 -3
  88. package/src/commands/identity/register.ts +8 -8
  89. package/src/commands/instance/create.ts +8 -16
  90. package/src/commands/instance/delete.ts +12 -9
  91. package/src/commands/instance/list.ts +2 -2
  92. package/src/commands/instance/status.ts +66 -20
  93. package/src/commands/instance/use.ts +16 -12
  94. package/src/commands/introspect.ts +53 -41
  95. package/src/commands/logs.ts +123 -36
  96. package/src/commands/mutate.ts +6 -5
  97. package/src/commands/query.ts +35 -32
  98. package/src/commands/session/analyze.ts +26 -4
  99. package/src/commands/token.ts +42 -27
  100. package/src/commands/ui/__tests__/commands.test.ts +152 -0
  101. package/src/commands/ui/add.ts +51 -0
  102. package/src/commands/ui/doctor.ts +21 -0
  103. package/src/commands/ui/init.ts +38 -0
  104. package/src/commands/ui/list.ts +26 -0
  105. package/src/commands/ui/preset-apply.ts +19 -0
  106. package/src/commands/ui/preset-list.ts +12 -0
  107. package/src/commands/ui/shared.ts +25 -0
  108. package/src/commands/update.ts +89 -48
  109. package/src/commands/view.ts +82 -37
  110. package/src/connection/.spec/api.d.ts +7 -8
  111. package/src/connection/.spec/architecture.md +14 -5
  112. package/src/connection/.spec/flows/session.ts +2 -2
  113. package/src/connection/.spec/laws/connection.ts +6 -2
  114. package/src/connection/.spec/layout.ts +1 -0
  115. package/src/connection/__tests__/auth.test.ts +1 -0
  116. package/src/connection/__tests__/credential.test.ts +21 -1
  117. package/src/connection/__tests__/errors.test.ts +27 -20
  118. package/src/connection/__tests__/exchange.test.ts +64 -11
  119. package/src/connection/__tests__/failure-fixtures.ts +39 -0
  120. package/src/connection/__tests__/failure-safety.test.ts +75 -0
  121. package/src/connection/__tests__/self.test.ts +31 -11
  122. package/src/connection/__tests__/session.test.ts +8 -4
  123. package/src/connection/__tests__/target.test.ts +2 -0
  124. package/src/connection/auth.ts +13 -0
  125. package/src/connection/call.ts +1 -1
  126. package/src/connection/credential.ts +2 -2
  127. package/src/connection/errors.ts +1 -355
  128. package/src/connection/exchange.ts +110 -61
  129. package/src/connection/failure/classify.ts +110 -0
  130. package/src/connection/failure/index.ts +18 -0
  131. package/src/connection/failure/model.ts +28 -0
  132. package/src/connection/failure/render.ts +116 -0
  133. package/src/connection/reasons.ts +16 -0
  134. package/src/connection/self.ts +39 -21
  135. package/src/connection/session.ts +27 -9
  136. package/src/errors.ts +3 -2
  137. package/src/graph/.spec/api.d.ts +12 -5
  138. package/src/graph/.spec/layout.ts +9 -1
  139. package/src/graph/__tests__/mutation.test.ts +2 -2
  140. package/src/graph/__tests__/query.test.ts +25 -6
  141. package/src/graph/class.ts +30 -0
  142. package/src/graph/index.ts +1 -0
  143. package/src/graph/query.ts +21 -32
  144. package/src/identity/.spec/api.d.ts +2 -2
  145. package/src/identity/__tests__/fixtures/registry-journey.ts +13 -1
  146. package/src/identity/__tests__/registration.test.ts +1 -1
  147. package/src/identity/__tests__/registry.test.ts +4 -0
  148. package/src/identity/registration.ts +3 -3
  149. package/src/identity/registry.ts +2 -0
  150. package/src/keys/__tests__/keys.test.ts +4 -0
  151. package/src/keys/credential.ts +1 -0
  152. package/src/lib/__tests__/admin-target.test.ts +12 -0
  153. package/src/lib/__tests__/binary.test.ts +16 -1
  154. package/src/lib/__tests__/browser-retention.test.ts +212 -0
  155. package/src/lib/__tests__/command-dx.test.ts +1 -1
  156. package/src/lib/__tests__/config.test.ts +27 -0
  157. package/src/lib/__tests__/domain-publication.test.ts +10 -16
  158. package/src/lib/__tests__/idp.test.ts +34 -4
  159. package/src/lib/__tests__/instance-candidates.test.ts +1 -3
  160. package/src/lib/__tests__/instance-target.test.ts +7 -39
  161. package/src/lib/__tests__/log-errors.test.ts +64 -0
  162. package/src/lib/__tests__/skills.test.ts +809 -0
  163. package/src/lib/__tests__/studio-server-deps.test.ts +1 -1
  164. package/src/lib/__tests__/update.test.ts +264 -11
  165. package/src/lib/__tests__/view-assets.test.ts +25 -0
  166. package/src/lib/__tests__/view-external-open-intent.test.ts +150 -0
  167. package/src/lib/__tests__/view-external-open-origins.test.ts +27 -0
  168. package/src/lib/__tests__/view-open-intent.test.ts +0 -2
  169. package/src/lib/__tests__/view-server.test.ts +26 -3
  170. package/src/lib/__tests__/view-session.test.ts +2 -1
  171. package/src/lib/admin-instance.ts +14 -8
  172. package/src/lib/admin-target.ts +21 -2
  173. package/src/lib/binary.ts +22 -5
  174. package/src/lib/browser-retention.ts +210 -0
  175. package/src/lib/command-dx.ts +9 -9
  176. package/src/lib/config.ts +12 -1
  177. package/src/lib/domain-publication.ts +9 -7
  178. package/src/lib/failure-debug.ts +26 -0
  179. package/src/lib/idp.ts +34 -3
  180. package/src/lib/instance-candidates.ts +2 -2
  181. package/src/lib/instance-target.ts +3 -14
  182. package/src/lib/instance.ts +16 -11
  183. package/src/lib/log.ts +16 -9
  184. package/src/lib/output.ts +1 -1
  185. package/src/lib/proc.ts +7 -2
  186. package/src/lib/provision-instance.ts +34 -57
  187. package/src/lib/skills/lock.ts +117 -0
  188. package/src/lib/skills/sync.ts +814 -0
  189. package/src/lib/skills.ts +1 -52
  190. package/src/lib/update.ts +298 -45
  191. package/src/lib/validation.ts +2 -2
  192. package/src/lib/view/assets.ts +11 -2
  193. package/src/lib/view/external-open-intent.ts +41 -0
  194. package/src/lib/view/external-open-origins.ts +37 -0
  195. package/src/lib/view/host-capabilities.ts +17 -0
  196. package/src/lib/view/resolve.ts +1 -1
  197. package/src/lib/view/server.ts +18 -3
  198. package/src/lib/view/session.ts +2 -0
  199. package/src/program/.spec/api.d.ts +1 -0
  200. package/src/program/__tests__/program.test.ts +163 -6
  201. package/src/program/argv.ts +14 -0
  202. package/src/program/build.ts +26 -4
  203. package/src/program/command.ts +1 -0
  204. package/src/program/index.ts +1 -0
  205. package/src/program/registry.ts +2 -1
  206. package/src/setup/__tests__/instance-step.test.ts +27 -10
  207. package/src/setup/steps/instance.ts +22 -15
  208. package/src/setup/steps/skills.ts +40 -38
  209. package/src/state/.spec/api.d.ts +21 -2
  210. package/src/state/.spec/architecture.md +18 -4
  211. package/src/state/.spec/layout.ts +1 -0
  212. package/src/state/__tests__/exchange-credentials.test.ts +88 -42
  213. package/src/state/__tests__/files.test.ts +24 -1
  214. package/src/state/__tests__/fixtures/session-route-process.ts +93 -0
  215. package/src/state/__tests__/paths.test.ts +1 -0
  216. package/src/state/__tests__/session-routes.test.ts +138 -0
  217. package/src/state/exchange-credentials.ts +22 -9
  218. package/src/state/files.ts +41 -0
  219. package/src/state/index.ts +3 -1
  220. package/src/state/paths.ts +3 -0
  221. package/src/state/session-routes.ts +34 -0
  222. package/src/telemetry/__tests__/analyze-log.test.ts +38 -0
  223. package/src/telemetry/__tests__/recorder.test.ts +1 -0
  224. package/src/telemetry/__tests__/retention.test.ts +267 -0
  225. package/src/telemetry/__tests__/settings.test.ts +102 -0
  226. package/src/telemetry/__tests__/store-scan.test.ts +128 -0
  227. package/src/telemetry/__tests__/trigger.test.ts +33 -4
  228. package/src/telemetry/analyze.ts +24 -2
  229. package/src/telemetry/recorder.ts +15 -4
  230. package/src/telemetry/retention.ts +176 -0
  231. package/src/telemetry/session.ts +18 -12
  232. package/src/telemetry/settings.ts +64 -11
  233. package/src/telemetry/store.ts +92 -9
  234. package/src/telemetry/trigger.ts +16 -28
  235. package/src/ui/.spec/api.d.ts +14 -0
  236. package/src/ui/.spec/architecture.md +14 -0
  237. package/src/ui/.spec/laws.ts +50 -0
  238. package/src/ui/.spec/layout.ts +16 -0
  239. package/src/ui/__tests__/ui.test.ts +1742 -0
  240. package/src/ui/index.ts +13 -0
  241. package/src/ui/lock.ts +89 -0
  242. package/src/ui/model.ts +83 -0
  243. package/src/ui/operations.ts +1044 -0
  244. package/src/ui/project.ts +333 -0
  245. package/src/ui/release.ts +398 -0
  246. package/src/ui/runner.ts +18 -0
  247. package/studio/client/dist/assets/elk-api-DjmKsHXc.js +1 -0
  248. package/studio/client/dist/assets/index-BFVFs_8x.js +81 -0
  249. package/studio/client/dist/assets/index-DuB9iUdu.css +2 -0
  250. package/studio/client/dist/assets/rolldown-runtime-Dd_uD5pT.js +1 -0
  251. package/studio/client/dist/assets/schema-studio--a0kX3QU.css +1 -0
  252. package/studio/client/dist/assets/schema-studio-DH6oEWME.js +8 -0
  253. package/studio/client/dist/index.html +4 -3
  254. package/studio/package.json +8 -14
  255. package/studio/server/agent/ask.test.ts +1 -1
  256. package/studio/server/agent/bridge/grant.test.ts +1 -1
  257. package/studio/server/agent/bridge/routes.test.ts +1 -1
  258. package/studio/server/agent/harness/claude/adapter.test.ts +15 -5
  259. package/studio/server/agent/harness/gateway/token.test.ts +1 -1
  260. package/studio/server/agent/harness/gateway/token.ts +3 -3
  261. package/studio/server/agent/prompts/anchors.test.ts +41 -32
  262. package/studio/server/agent/prompts/anchors.ts +23 -51
  263. package/studio/server/agent/prompts/system.test.ts +4 -4
  264. package/studio/server/agent/prompts/system.ts +5 -5
  265. package/studio/server/agent/routes.test.ts +1 -1
  266. package/studio/server/agent/run/coordinator.test.ts +1 -1
  267. package/studio/server/agent/run/preparation.ts +1 -1
  268. package/studio/server/api/canvas.ts +0 -1
  269. package/studio/server/api/context.ts +1 -1
  270. package/studio/server/api/deployment.ts +1 -1
  271. package/studio/server/api/views.ts +2 -2
  272. package/studio/server/api/workspace.ts +1 -1
  273. package/studio/server/api-agent-loadout.test.ts +1 -1
  274. package/studio/server/api.test.ts +1 -1
  275. package/studio/server/cache.test.ts +24 -18
  276. package/studio/server/cache.ts +10 -54
  277. package/studio/server/cli-consumers.test.ts +31 -3
  278. package/studio/server/domain.test.ts +50 -68
  279. package/studio/server/domain.ts +82 -63
  280. package/studio/server/handoff/copy.ts +2 -2
  281. package/studio/server/index.ts +1 -1
  282. package/studio/server/instances/probe.test.ts +1 -1
  283. package/studio/server/introspect/anatomy/views/routes.ts +46 -37
  284. package/studio/server/introspect/anatomy/views.ts +10 -16
  285. package/studio/server/introspect/anatomy-extras.test.ts +29 -56
  286. package/studio/server/introspect/anatomy.test.ts +22 -14
  287. package/studio/server/introspect/anatomy.ts +6 -9
  288. package/studio/server/introspect/bundle.ts +0 -3
  289. package/studio/server/introspect/canonical-schema.test.ts +155 -424
  290. package/studio/server/introspect/canonical-schema.ts +248 -601
  291. package/studio/server/introspect/core.ts +14 -22
  292. package/studio/server/introspect/diff.test.ts +21 -9
  293. package/studio/server/introspect/diff.ts +19 -64
  294. package/studio/server/introspect/extractor.ts +23 -122
  295. package/studio/server/introspect/overlay-tsmorph.test.ts +116 -241
  296. package/studio/server/introspect/overlay-tsmorph.ts +0 -1
  297. package/studio/server/introspect/overlay.ts +3 -51
  298. package/studio/server/introspect/revision.test.ts +24 -28
  299. package/studio/server/introspect/revision.ts +11 -22
  300. package/studio/server/introspect/runtime.test.ts +77 -195
  301. package/studio/server/introspect/runtime.ts +5 -67
  302. package/studio/server/introspect/schema-ir-json.test.ts +81 -0
  303. package/studio/server/introspect/schema-ir-json.ts +39 -69
  304. package/studio/server/introspect/schema-refs.test.ts +43 -88
  305. package/studio/server/introspect/schema-refs.ts +14 -49
  306. package/studio/server/introspect/source-overlay/handlers.ts +116 -636
  307. package/studio/server/introspect/source-overlay/kernel-calls.ts +0 -3
  308. package/studio/server/introspect/source-overlay/spans.ts +15 -45
  309. package/studio/server/lifecycle.ts +4 -1
  310. package/studio/server/state/baseline.test.ts +14 -9
  311. package/studio/server/state/baseline.ts +3 -5
  312. package/studio/server/state/documents.test.ts +69 -0
  313. package/studio/server/state/documents.ts +57 -10
  314. package/studio/server/state/visibility.ts +1 -5
  315. package/studio/server/views/model.ts +3 -3
  316. package/studio/server/views/target.test.ts +18 -76
  317. package/studio/server/views/target.ts +24 -50
  318. package/studio/server/watch.test.ts +11 -11
  319. package/studio/server/watch.ts +8 -4
  320. package/studio/server/workspace/updates.ts +23 -1
  321. package/studio/server/workspace-watch.ts +2 -2
  322. package/studio/shared/contracts/runtime.ts +4 -0
  323. package/studio/shared/contracts/schema.ts +44 -131
  324. package/studio/shared/contracts/surface.test.ts +15 -17
  325. package/studio/shared/contracts/workspace.ts +5 -11
  326. package/studio/shared/schema/identity.test.ts +20 -44
  327. package/studio/shared/schema/identity.ts +9 -24
  328. package/studio/tsconfig.json +2 -1
  329. package/tsconfig.json +1 -1
  330. package/viewer/dist/index.html +21 -0
  331. package/viewer/dist/main.js +41 -62
  332. package/.check-workspace.cjs +0 -40
  333. package/src/admin/binding.ts +0 -168
  334. package/src/commands/__tests__/instance-create-hosts.test.ts +0 -29
  335. package/studio/client/dist/assets/elk-api-lwhFo7vB.js +0 -1
  336. package/studio/client/dist/assets/index-B-c-smo9.js +0 -8
  337. package/studio/client/dist/assets/index-BckHuAWk.js +0 -81
  338. package/studio/client/dist/assets/index-C4aNQ6dz.css +0 -1
  339. package/studio/client/dist/assets/index-ChOr3yP0.css +0 -1
  340. package/studio/server/introspect/anatomy/views/legacy.ts +0 -232
  341. package/studio/server/introspect/core-extractor.ts +0 -139
  342. package/studio/server/introspect/overlay.test.ts +0 -72
  343. package/studio/server/introspect/source-overlay/annotations.ts +0 -14
@@ -1,21 +1,16 @@
1
1
  import { Path } from '@astrale-os/sdk/graph/path'
2
- import { syscalls } from '@astrale-os/sdk/schema/kernel'
2
+ import { K } from '@astrale-os/sdk/schema'
3
3
  import chalk from 'chalk'
4
4
 
5
5
  import type { ConnectionContext, KernelCommandOpts } from '../connection'
6
6
  import type { Column, ListProjection } from '../lib/output'
7
7
  import type { CommandDefinition } from '../program/index'
8
8
 
9
- import {
10
- createPathCall,
11
- expandSelfInPath,
12
- runKernelCommand,
13
- withClientSession,
14
- } from '../connection'
15
- import { failClosed } from '../lib/log'
9
+ import { createPathCall, expandSelfInPath, runKernelCommand } from '../connection'
10
+ import { failInput } from '../lib/log'
16
11
  import { isMachine, output, presentList } from '../lib/output'
17
12
 
18
- const JOURNAL_PATH = Path.project(syscalls.journal.ref).raw
13
+ const JOURNAL_PATH = Path.project(K.functions.journal.ref).raw
19
14
  const DEFAULT_LIMIT = 200
20
15
  const FOLLOW_INTERVAL_MS = 2_000
21
16
 
@@ -38,10 +33,21 @@ export interface JournalRecord {
38
33
  readonly occurredAt?: string
39
34
  readonly committedAt?: string
40
35
  readonly principal?: string
36
+ readonly correlation?: JournalCorrelation
41
37
  readonly correlationId?: string
42
38
  readonly causationId?: string
43
39
  }
44
40
 
41
+ export interface JournalCorrelation {
42
+ readonly operationId?: string
43
+ readonly parentOperationId?: string
44
+ readonly invocationId?: string
45
+ readonly rootInvocationId?: string
46
+ readonly parentInvocationId?: string
47
+ readonly traceId?: string
48
+ readonly spanId?: string
49
+ }
50
+
45
51
  export interface JournalPage {
46
52
  readonly records: readonly JournalRecord[]
47
53
  readonly cursor?: string
@@ -127,7 +133,7 @@ async function runOnce(opts: LogsOpts): Promise<void> {
127
133
  await runKernelCommand({
128
134
  opts,
129
135
  label: 'Kernel journal',
130
- fn: (context) => fetchPage(context, opts),
136
+ fn: async (context) => fetchPage(context, await resolveLogsOpts(opts, context)),
131
137
  format: (page, format) => {
132
138
  if (isMachine(format) || format.format !== undefined) output(page, format)
133
139
  else {
@@ -138,15 +144,33 @@ async function runOnce(opts: LogsOpts): Promise<void> {
138
144
  })
139
145
  }
140
146
 
141
- async function follow(opts: LogsOpts): Promise<void> {
142
- await withClientSession(opts, async (context) => {
143
- let cursor = opts.cursor
144
- for (;;) {
145
- const page = await fetchPage(context, { ...opts, cursor })
146
- for (const record of page.records) printRecord(record)
147
- cursor = page.cursor ?? cursor
148
- await new Promise((resolve) => setTimeout(resolve, FOLLOW_INTERVAL_MS))
149
- }
147
+ type FollowDependencies = {
148
+ readonly run: typeof runKernelCommand
149
+ readonly pause: (milliseconds: number) => Promise<void>
150
+ }
151
+
152
+ /** Follow one admitted journal stream. Dependencies are explicit so routing is proven at the command boundary. */
153
+ export async function followLogs(
154
+ opts: LogsOpts,
155
+ dependencies: FollowDependencies = {
156
+ run: runKernelCommand,
157
+ pause: (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds)),
158
+ },
159
+ ): Promise<void> {
160
+ validateLogsOpts(opts)
161
+ await dependencies.run({
162
+ opts,
163
+ label: 'Kernel journal',
164
+ fn: async (context): Promise<never> => {
165
+ const resolved = await resolveLogsOpts(opts, context)
166
+ let cursor = resolved.cursor
167
+ for (;;) {
168
+ const page = await fetchPage(context, { ...resolved, cursor })
169
+ for (const record of page.records) printRecord(record, opts)
170
+ cursor = page.cursor ?? cursor
171
+ await dependencies.pause(FOLLOW_INTERVAL_MS)
172
+ }
173
+ },
150
174
  })
151
175
  }
152
176
 
@@ -169,12 +193,28 @@ function journalProjection(records: JournalRecord[]): ListProjection {
169
193
  }
170
194
  }
171
195
 
172
- function printRecord(record: JournalRecord): void {
196
+ function printRecord(record: JournalRecord, opts: LogsOpts): void {
197
+ if (isMachine(opts) || opts.format === 'json') {
198
+ process.stdout.write(formatFollowRecord(record))
199
+ return
200
+ }
173
201
  process.stdout.write(
174
202
  `${chalk.dim(String(record.sequence).padStart(6))} ${chalk.dim(record.timestamp)} ${chalk.cyan(record.topic)} ${chalk.dim(record.principal ?? '')}\n`,
175
203
  )
176
204
  }
177
205
 
206
+ /** Machine follow is an NDJSON stream: one complete admitted record per line. */
207
+ export function formatFollowRecord(record: JournalRecord): string {
208
+ return `${JSON.stringify(record)}\n`
209
+ }
210
+
211
+ export function validateLogsOpts(opts: LogsOpts): void {
212
+ buildJournalInput(opts)
213
+ if (opts.follow && opts.format === 'yaml' && !opts.json && !opts.raw) {
214
+ throw new TypeError('--follow does not support YAML; use --json for an NDJSON stream')
215
+ }
216
+ }
217
+
178
218
  function acceptRecord(input: unknown, index: number): JournalRecord {
179
219
  if (
180
220
  !isRecord(input) ||
@@ -188,10 +228,17 @@ function acceptRecord(input: unknown, index: number): JournalRecord {
188
228
  if (timestamp === undefined) {
189
229
  throw new TypeError(`Kernel journal record ${index} is missing occurredAt/timestamp`)
190
230
  }
191
- const correlation = isRecord(input.correlation) ? input.correlation : undefined
192
- const correlationId =
193
- optionalText(input.correlationId, index, 'correlationId') ??
194
- optionalText(correlation?.invocationId, index, 'correlation.invocationId')
231
+ const correlation = acceptCorrelation(input.correlation, index)
232
+ const legacyCorrelationId = optionalIdentifier(input.correlationId, index, 'correlationId')
233
+ const structuredCorrelationId = correlation?.invocationId
234
+ if (
235
+ legacyCorrelationId !== undefined &&
236
+ structuredCorrelationId !== undefined &&
237
+ legacyCorrelationId !== structuredCorrelationId
238
+ ) {
239
+ throw new TypeError(`Kernel journal record ${index} has conflicting correlation identifiers`)
240
+ }
241
+ const correlationId = structuredCorrelationId ?? legacyCorrelationId
195
242
  const principal = optionalText(input.principal, index, 'principal')
196
243
  return Object.freeze({
197
244
  sequence: input.sequence as number,
@@ -203,13 +250,56 @@ function acceptRecord(input: unknown, index: number): JournalRecord {
203
250
  ? {}
204
251
  : { committedAt: input.committedAt as string }),
205
252
  ...(principal === undefined ? {} : { principal }),
253
+ ...(correlation === undefined ? {} : { correlation }),
206
254
  ...(correlationId === undefined ? {} : { correlationId }),
207
- ...(optionalText(input.causationId, index, 'causationId') === undefined
255
+ ...(optionalIdentifier(input.causationId, index, 'causationId') === undefined
208
256
  ? {}
209
257
  : { causationId: input.causationId as string }),
210
258
  })
211
259
  }
212
260
 
261
+ const correlationFields = [
262
+ 'operationId',
263
+ 'parentOperationId',
264
+ 'invocationId',
265
+ 'rootInvocationId',
266
+ 'parentInvocationId',
267
+ 'traceId',
268
+ 'spanId',
269
+ ] as const
270
+
271
+ function acceptCorrelation(input: unknown, index: number): JournalCorrelation | undefined {
272
+ if (input === undefined) return undefined
273
+ if (!isRecord(input)) {
274
+ throw new TypeError(`Kernel journal record ${index}.correlation must be an object`)
275
+ }
276
+ const unknown = Object.keys(input).find(
277
+ (field) => !correlationFields.includes(field as (typeof correlationFields)[number]),
278
+ )
279
+ if (unknown !== undefined) {
280
+ throw new TypeError(`Kernel journal record ${index}.correlation.${unknown} is unsupported`)
281
+ }
282
+ return Object.freeze(
283
+ Object.fromEntries(
284
+ correlationFields.flatMap((field) => {
285
+ const value = optionalIdentifier(input[field], index, `correlation.${field}`)
286
+ return value === undefined ? [] : [[field, value]]
287
+ }),
288
+ ),
289
+ )
290
+ }
291
+
292
+ function optionalIdentifier(input: unknown, index: number, field: string): string | undefined {
293
+ const value = optionalText(input, index, field)
294
+ if (value === undefined) return undefined
295
+ if (value.trim() === '' || new TextEncoder().encode(value).byteLength > 256) {
296
+ throw new TypeError(
297
+ `Kernel journal record ${index}.${field} must be non-empty and at most 256 UTF-8 bytes`,
298
+ )
299
+ }
300
+ return value
301
+ }
302
+
213
303
  function optionalText(input: unknown, index: number, field: string): string | undefined {
214
304
  if (input === undefined) return undefined
215
305
  if (typeof input !== 'string') {
@@ -227,11 +317,10 @@ function positiveInteger(flag: string, raw: string): number {
227
317
  return value
228
318
  }
229
319
 
230
- async function prepareLogsOpts(opts: LogsOpts): Promise<LogsOpts> {
231
- buildJournalInput(opts)
320
+ async function resolveLogsOpts(opts: LogsOpts, context: ConnectionContext): Promise<LogsOpts> {
232
321
  const principal = nonEmpty(opts.principal)
233
322
  if (principal !== '@self') return opts
234
- const { path } = await expandSelfInPath('@self', opts)
323
+ const { path } = await expandSelfInPath('@self', context)
235
324
  return { ...opts, principal: path.startsWith('@') ? path.slice(1) : path }
236
325
  }
237
326
 
@@ -252,10 +341,8 @@ Behavior:
252
341
  Calls the public Kernel journal syscall and emits its { records, cursor }
253
342
  page. Topic selection is exact or prefix-based; cursors and timestamps are
254
343
  opaque strings owned by the journal backend. --follow reuses one Client Session
255
- and advances only with the returned cursor.
256
-
257
- Historical event-glob lowering and the application-specific services-domain
258
- log buffer are not part of the Kernel V2 journal contract.
344
+ and advances only with the returned cursor. With --json, follow output is
345
+ NDJSON with one complete admitted record per line; YAML follow is unsupported.
259
346
 
260
347
  Examples:
261
348
  $ astrale logs -i staging --limit 50
@@ -274,11 +361,11 @@ Examples:
274
361
  ],
275
362
  action: async (opts: LogsOpts) => {
276
363
  try {
277
- const prepared = await prepareLogsOpts(opts)
278
- if (prepared.follow) await follow(prepared)
279
- else await runOnce(prepared)
364
+ validateLogsOpts(opts)
280
365
  } catch (error) {
281
- failClosed(error, opts)
366
+ failInput(error, opts)
282
367
  }
368
+ if (opts.follow) await followLogs(opts)
369
+ else await runOnce(opts)
283
370
  },
284
371
  } satisfies CommandDefinition
@@ -7,8 +7,9 @@ import type { KernelCommandOpts } from '../connection'
7
7
  import type { CommandDefinition } from '../program/index'
8
8
 
9
9
  import { runKernelCommand } from '../connection'
10
+ import { AstraleError } from '../errors'
10
11
  import { prepareMutation } from '../graph/index'
11
- import { failClosed } from '../lib/log'
12
+ import { failInput } from '../lib/log'
12
13
  import { output } from '../lib/output'
13
14
  import { renderTable } from '../lib/table'
14
15
 
@@ -23,7 +24,7 @@ export async function mutateCommand(opts: MutateOpts): Promise<void> {
23
24
  try {
24
25
  mutation = prepareMutation(await readDocument(opts))
25
26
  } catch (error) {
26
- failClosed(error, opts)
27
+ failInput(error, opts)
27
28
  }
28
29
 
29
30
  if (opts.dry) {
@@ -51,9 +52,9 @@ async function readDocument(opts: MutateOpts): Promise<unknown> {
51
52
  try {
52
53
  raw = await readFile(opts.file, 'utf8')
53
54
  } catch (error) {
54
- throw new Error(
55
- `Cannot read --file ${opts.file}: ${error instanceof Error ? error.message : error}`,
56
- )
55
+ throw new AstraleError('FILE_READ_FAILED', `Cannot read --file ${opts.file}.`, undefined, {
56
+ cause: error,
57
+ })
57
58
  }
58
59
  return parseJson(raw, opts.file)
59
60
  }
@@ -6,8 +6,9 @@ import type { KernelCommandOpts, SelfExpansionMeta } from '../connection'
6
6
  import type { CommandDefinition } from '../program/index'
7
7
 
8
8
  import { expandSelfInPath, runKernelCommand, withSelfHint } from '../connection'
9
+ import { AstraleError } from '../errors'
9
10
  import { prepareQuery, type QueryCommandInput } from '../graph/index'
10
- import { failClosed } from '../lib/log'
11
+ import { failInput } from '../lib/log'
11
12
  import { isMachine, output } from '../lib/output'
12
13
 
13
14
  type QueryOpts = KernelCommandOpts & {
@@ -22,48 +23,49 @@ type QueryOpts = KernelCommandOpts & {
22
23
 
23
24
  export async function queryCommand(sources: string[], opts: QueryOpts): Promise<void> {
24
25
  let input: QueryCommandInput
25
- let meta: SelfExpansionMeta | undefined
26
26
  try {
27
27
  const ast = await readAst(opts)
28
- if (ast === undefined) {
29
- const expanded = await Promise.all(sources.map((source) => expandSelfInPath(source, opts)))
30
- input = {
31
- sources: expanded.map(({ path }) => path),
32
- definition: opts.definition,
33
- edge: opts.edge,
34
- direction: opts.direction,
35
- limit: opts.limit,
36
- cursor: opts.cursor,
37
- }
38
- meta = expanded.find((entry) => entry.meta !== undefined)?.meta
39
- } else {
40
- input = {
41
- sources,
42
- ast,
43
- definition: opts.definition,
44
- edge: opts.edge,
45
- direction: opts.direction,
46
- limit: opts.limit,
47
- cursor: opts.cursor,
48
- }
28
+ input = {
29
+ sources,
30
+ ...(ast === undefined ? {} : { ast }),
31
+ definition: opts.definition,
32
+ edge: opts.edge,
33
+ direction: opts.direction,
34
+ limit: opts.limit,
35
+ cursor: opts.cursor,
49
36
  }
50
37
  } catch (error) {
51
- failClosed(error, opts)
38
+ failInput(error, opts)
52
39
  }
53
40
 
54
41
  let prepared
55
42
  try {
56
43
  prepared = prepareQuery(input)
57
44
  } catch (error) {
58
- failClosed(error, opts)
45
+ failInput(error, opts)
59
46
  }
60
47
 
61
48
  await runKernelCommand({
62
49
  opts,
63
50
  label: 'Query',
64
- fn: ({ graph }) => withSelfHint(() => graph.query(prepared.ast, { page: prepared.page }), meta),
51
+ fn: async (context) => {
52
+ const expanded = await Promise.all(sources.map((source) => expandSelfInPath(source, context)))
53
+ const meta: SelfExpansionMeta | undefined = expanded.find(
54
+ (entry) => entry.meta !== undefined,
55
+ )?.meta
56
+ const resolved =
57
+ meta === undefined
58
+ ? prepared
59
+ : prepareQuery({ ...input, sources: expanded.map(({ path }) => path) })
60
+ return withSelfHint(() => context.graph.query(resolved.ast, { page: resolved.page }), meta)
61
+ },
65
62
  format: (response, format) => {
66
- output(response.result, format)
63
+ output(
64
+ response.page.next === undefined
65
+ ? response.result
66
+ : { ...response.result, page: { next: response.page.next } },
67
+ format,
68
+ )
67
69
  if (response.page.next && !isMachine(format)) {
68
70
  process.stderr.write(` cursor: ${response.page.next}\n`)
69
71
  }
@@ -81,9 +83,9 @@ async function readAst(opts: QueryOpts): Promise<unknown | undefined> {
81
83
  try {
82
84
  raw = await readFile(opts.file, 'utf8')
83
85
  } catch (error) {
84
- throw new Error(
85
- `Cannot read --file ${opts.file}: ${error instanceof Error ? error.message : error}`,
86
- )
86
+ throw new AstraleError('FILE_READ_FAILED', `Cannot read --file ${opts.file}.`, undefined, {
87
+ cause: error,
88
+ })
87
89
  }
88
90
  return parseJson(raw, opts.file)
89
91
  }
@@ -105,7 +107,8 @@ Behavior:
105
107
  one exact Edge-Class expansion; --direction defaults to outgoing. --ast and
106
108
  --file accept a complete canonical astrale.graph.query/v6 document, including
107
109
  Property ordering and Node or Edge reference/value projections. --cursor resumes
108
- one caller-bound query scope.
110
+ one caller-bound query scope. Machine output adds page.next only when another
111
+ page exists; pass that opaque value back through --cursor.
109
112
 
110
113
  Legacy depth/children selector JSON and raw Cypher are not portable Kernel
111
114
  V2 query contracts and are not accepted.
@@ -122,7 +125,7 @@ Examples:
122
125
  { flags: '-f, --file <path>', description: 'Read a canonical Query V6 document from a file' },
123
126
  {
124
127
  flags: '--definition <path>',
125
- description: 'Select Nodes implementing one exact Class or Interface',
128
+ description: 'Select Nodes satisfying one exact Class',
126
129
  },
127
130
  { flags: '--edge <class>', description: 'Expand one exact Edge Class' },
128
131
  {
@@ -4,13 +4,23 @@ import type { CommandDefinition } from '../../program/index'
4
4
 
5
5
  import { log } from '../../lib/log'
6
6
  import { analyzeSession } from '../../telemetry/analyze'
7
- import { listSessions } from '../../telemetry/store'
7
+ import { sweepStore } from '../../telemetry/retention'
8
+ import { scanSessions } from '../../telemetry/store'
8
9
  import { restampLock, releaseLock } from '../../telemetry/trigger'
9
10
 
10
11
  export default {
11
12
  name: 'analyze',
12
13
  description: 'Analyze a recorded session for DX frictions (dry-run: writes report.md)',
13
- arguments: [{ name: 'id', description: 'Session id (default: most recent closed, unanalyzed)' }],
14
+ arguments: [
15
+ // Optional: the action resolves the most recent closed, unanalyzed session
16
+ // when no id is given, which is how a human is meant to invoke this. The
17
+ // opportunistic trigger always passes an explicit id.
18
+ {
19
+ name: 'id',
20
+ description: 'Session id (default: most recent closed, unanalyzed)',
21
+ required: false,
22
+ },
23
+ ],
14
24
  options: [
15
25
  { flags: '--file', description: 'File cleared findings as issues on the admin tracker' },
16
26
  { flags: '--model <model>', description: 'Model for the analyzer pass' },
@@ -22,10 +32,10 @@ export default {
22
32
  opts: { file?: boolean; model?: string; force?: boolean; auto?: boolean },
23
33
  ) => {
24
34
  if (opts.auto) restampLock()
35
+ let target = id
25
36
  try {
26
- let target = id
27
37
  if (!target) {
28
- const candidate = listSessions().find((s) => s.closed && (opts.force || !s.analyzed))
38
+ const candidate = scanSessions().find((s) => s.closed && (opts.force || !s.analyzed))
29
39
  if (!candidate) {
30
40
  if (!opts.auto) log.dim('Nothing to analyze — no closed, unanalyzed session.')
31
41
  return
@@ -45,6 +55,18 @@ export default {
45
55
  }
46
56
  } finally {
47
57
  if (opts.auto) releaseLock()
58
+ // The size bound is enforced here, not on the CLI's critical path:
59
+ // measuring every file of every session is only affordable in this
60
+ // process, and this is precisely where the store just grew. The session
61
+ // just analyzed is protected — it is the freshest evidence on disk.
62
+ try {
63
+ const swept = sweepStore({ protect: new Set(target === undefined ? [] : [target]) })
64
+ if (!opts.auto && swept.removed.length > 0) {
65
+ log.dim(`retention: removed ${swept.removed.length} session(s)`)
66
+ }
67
+ } catch {
68
+ /* retention must never fail the command that triggered it */
69
+ }
48
70
  }
49
71
  },
50
72
  } satisfies CommandDefinition
@@ -1,4 +1,4 @@
1
- import { issuer } from '@astrale-os/sdk/auth'
1
+ import { issuer, type AuthApi, type IssuerId, type MintedCredential } from '@astrale-os/sdk/auth'
2
2
 
3
3
  import type { KernelCommandOpts } from '../connection'
4
4
  import type { CommandDefinition } from '../program/index'
@@ -6,12 +6,12 @@ import type { CommandDefinition } from '../program/index'
6
6
  import { runKernelCommand } from '../connection'
7
7
  import { AstraleError } from '../errors'
8
8
  import { decodeJwtExpiration } from '../lib/local-status'
9
- import { failClosed, log } from '../lib/log'
9
+ import { failInput, log } from '../lib/log'
10
10
  import { output } from '../lib/output'
11
11
 
12
12
  /**
13
- * `astrale token` — mint a fresh delegation token for the active instance
14
- * + active identity through the bound AuthApi.
13
+ * `astrale token` — mint a fresh audience-bound token for the active instance
14
+ * and selected identity through the bound AuthApi.
15
15
  */
16
16
  export type TokenOpts = KernelCommandOpts & {
17
17
  audience?: string
@@ -21,23 +21,25 @@ export type TokenOpts = KernelCommandOpts & {
21
21
  for?: string
22
22
  }
23
23
 
24
+ const DEFAULT_TOKEN_TTL_SECONDS = 4 * 60
25
+
24
26
  export async function tokenCommand(opts: TokenOpts): Promise<void> {
25
27
  const commandOpts: TokenOpts = opts.for && !opts.as ? { ...opts, as: opts.for } : opts
26
- const ttl = parseTtl(commandOpts.ttl)
28
+ let ttl: number
29
+ try {
30
+ ttl = parseTtl(commandOpts.ttl)
31
+ } catch (error) {
32
+ failInput(error, opts)
33
+ }
27
34
  await runKernelCommand<string>({
28
35
  opts: commandOpts,
29
- label: 'Minting delegation token',
36
+ label: 'Minting token',
30
37
  fn: async (ctx) => {
31
38
  const audience =
32
39
  commandOpts.audience === undefined
33
40
  ? ctx.target.kernelIssuer
34
41
  : issuer.accept(commandOpts.audience)
35
- const self = await ctx.auth.whoami()
36
- return ctx.auth.delegate(self.id, {
37
- audience,
38
- ttlSeconds: ttl,
39
- attenuation: { kind: 'identity', self: true },
40
- })
42
+ return issueToken(ctx.auth, ctx.target.kernelIssuer, audience, ttl)
41
43
  },
42
44
  format: (token, fmtOpts) => {
43
45
  if (fmtOpts.json || fmtOpts.format !== undefined) {
@@ -45,15 +47,31 @@ export async function tokenCommand(opts: TokenOpts): Promise<void> {
45
47
  return
46
48
  }
47
49
  if (!fmtOpts.raw && (process.stdout.isTTY ?? false)) {
48
- log.dim(' (delegation token — ES256, self-identity)')
50
+ log.dim(' (audience-bound token — ES256, self-identity)')
49
51
  }
50
52
  process.stdout.write(`${token}\n`)
51
53
  },
52
54
  })
53
55
  }
54
56
 
57
+ /** Issue either a top-level Kernel credential or an external-audience delegation. */
58
+ export async function issueToken(
59
+ auth: Pick<AuthApi, 'delegate' | 'mint' | 'whoami'>,
60
+ kernel: IssuerId,
61
+ audience: IssuerId,
62
+ ttlSeconds: number,
63
+ ): Promise<MintedCredential> {
64
+ if (audience === kernel) return auth.mint({ ttlSeconds })
65
+ const self = await auth.whoami()
66
+ return auth.delegate(self.id, {
67
+ audience,
68
+ ttlSeconds,
69
+ attenuation: { kind: 'identity', self: true },
70
+ })
71
+ }
72
+
55
73
  export function parseTtl(raw: string | undefined): number {
56
- if (raw === undefined) return 3600
74
+ if (raw === undefined) return DEFAULT_TOKEN_TTL_SECONDS
57
75
  if (!/^\d+$/.test(raw)) {
58
76
  throw new AstraleError(
59
77
  'INVALID_FLAG',
@@ -72,16 +90,17 @@ export function parseTtl(raw: string | undefined): number {
72
90
 
73
91
  export default {
74
92
  name: 'token',
75
- description: 'Mint a fresh delegation token for the active instance + identity',
93
+ description: 'Mint a fresh audience-bound credential for the active instance + identity',
76
94
  afterHelpText: `
77
95
  Behavior:
78
- Delegates the selected authenticated identity for 3600 seconds by default.
79
- The default audience is the target Kernel issuer; choose --audience when the
80
- credential is intended for another service. --for is an alias of --as.
96
+ Mints a token for the selected authenticated identity for 240 seconds by default.
97
+ The default Kernel audience produces a top-level Grant credential reusable with
98
+ --creds. A different --audience produces a delegated service envelope. --for is
99
+ an alias of --as.
81
100
 
82
- The receiver must admit the exact token audience. A Kernel-audience token can
83
- be reused with --creds; a service-audience token can be sent as a Bearer token
84
- to that service's authenticated endpoint.
101
+ Every receiver must admit the exact token audience. A service-audience token can
102
+ be sent as a Bearer token only to that service's authenticated endpoint. The
103
+ requested lifetime cannot exceed the selected source credential's remaining life.
85
104
 
86
105
  Examples:
87
106
  $ export TOKEN=$(astrale token --audience shell.astrale.ai --raw)
@@ -93,14 +112,10 @@ Examples:
93
112
  flags: '--audience <aud>',
94
113
  description: 'Token audience (default: target Kernel issuer)',
95
114
  },
96
- { flags: '--ttl <sec>', description: 'TTL in seconds (default: 3600)' },
115
+ { flags: '--ttl <sec>', description: 'TTL in seconds (default: 240)' },
97
116
  { flags: '--for <identity>', description: 'Mint the token for this identity (alias of --as)' },
98
117
  ],
99
118
  action: async (opts) => {
100
- try {
101
- await tokenCommand(opts as Parameters<typeof tokenCommand>[0])
102
- } catch (error) {
103
- failClosed(error, opts as TokenOpts)
104
- }
119
+ await tokenCommand(opts as Parameters<typeof tokenCommand>[0])
105
120
  },
106
121
  } satisfies CommandDefinition