orizu 0.5.41 → 0.5.43

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 (118) hide show
  1. package/dist/account-directory.d.ts.map +1 -1
  2. package/dist/account-directory.js +6 -32
  3. package/dist/app-publish-workspace.js +1 -1
  4. package/dist/archive-artifact-workflow.d.ts +23 -0
  5. package/dist/archive-artifact-workflow.d.ts.map +1 -0
  6. package/dist/archive-artifact-workflow.js +100 -0
  7. package/dist/archive-artifacts-cli.d.ts.map +1 -1
  8. package/dist/archive-artifacts-cli.js +7 -11
  9. package/dist/artifact-archive.d.ts.map +1 -1
  10. package/dist/artifact-archive.js +2 -0
  11. package/dist/credentials.d.ts.map +1 -1
  12. package/dist/credentials.js +2 -0
  13. package/dist/dataset-commands.d.ts.map +1 -1
  14. package/dist/dataset-commands.js +19 -29
  15. package/dist/dataset-split-create-workflow.d.ts +16 -0
  16. package/dist/dataset-split-create-workflow.d.ts.map +1 -0
  17. package/dist/dataset-split-create-workflow.js +115 -0
  18. package/dist/git-credential.js +1 -1
  19. package/dist/help.d.ts.map +1 -1
  20. package/dist/help.js +10 -2
  21. package/dist/hosted-boot.d.ts +7 -0
  22. package/dist/hosted-boot.d.ts.map +1 -1
  23. package/dist/hosted-boot.js +290 -60
  24. package/dist/hosted-harness.d.ts +1 -0
  25. package/dist/hosted-harness.d.ts.map +1 -1
  26. package/dist/hosted-harness.js +2 -0
  27. package/dist/hosted-harvest.d.ts +2 -1
  28. package/dist/hosted-harvest.d.ts.map +1 -1
  29. package/dist/hosted-harvest.js +29 -17
  30. package/dist/hosted-live-channel.d.ts +31 -0
  31. package/dist/hosted-live-channel.d.ts.map +1 -0
  32. package/dist/hosted-live-channel.js +147 -0
  33. package/dist/hosted-loop.d.ts +8 -1
  34. package/dist/hosted-loop.d.ts.map +1 -1
  35. package/dist/hosted-loop.js +45 -0
  36. package/dist/hosted-run-event-sink.d.ts +19 -0
  37. package/dist/hosted-run-event-sink.d.ts.map +1 -1
  38. package/dist/hosted-run-event-sink.js +106 -3
  39. package/dist/hosted-runtime-assets.js +1 -1
  40. package/dist/hosted-session-cli.d.ts.map +1 -1
  41. package/dist/hosted-session-cli.js +4 -0
  42. package/dist/hosted-session-seed.d.ts +46 -6
  43. package/dist/hosted-session-seed.d.ts.map +1 -1
  44. package/dist/hosted-session-seed.js +95 -9
  45. package/dist/hosted-skill-staging.d.ts +36 -12
  46. package/dist/hosted-skill-staging.d.ts.map +1 -1
  47. package/dist/hosted-skill-staging.js +96 -34
  48. package/dist/http.d.ts +2 -1
  49. package/dist/http.d.ts.map +1 -1
  50. package/dist/http.js +10 -6
  51. package/dist/index.d.ts.map +1 -1
  52. package/dist/index.js +16 -16
  53. package/dist/orizu-skill-links.d.ts +48 -0
  54. package/dist/orizu-skill-links.d.ts.map +1 -0
  55. package/dist/orizu-skill-links.js +99 -0
  56. package/dist/preview-runtime.d.ts +1 -0
  57. package/dist/preview-runtime.d.ts.map +1 -1
  58. package/dist/preview-runtime.js +15 -0
  59. package/dist/repository-backup-core.d.ts.map +1 -1
  60. package/dist/repository-backup-core.js +2 -0
  61. package/dist/request-outcome.d.ts +5 -0
  62. package/dist/request-outcome.d.ts.map +1 -0
  63. package/dist/request-outcome.js +27 -0
  64. package/dist/score-save-workflow.d.ts +26 -0
  65. package/dist/score-save-workflow.d.ts.map +1 -0
  66. package/dist/score-save-workflow.js +120 -0
  67. package/dist/setup-and-skills-commands.d.ts.map +1 -1
  68. package/dist/setup-and-skills-commands.js +52 -7
  69. package/dist/setup-onboarding.d.ts.map +1 -1
  70. package/dist/setup-onboarding.js +6 -2
  71. package/dist/skill-installer.d.ts +16 -0
  72. package/dist/skill-installer.d.ts.map +1 -1
  73. package/dist/skill-installer.js +35 -5
  74. package/dist/team-create-workflow.d.ts +16 -0
  75. package/dist/team-create-workflow.d.ts.map +1 -0
  76. package/dist/team-create-workflow.js +107 -0
  77. package/dist/team-member-change-workflow.d.ts +25 -0
  78. package/dist/team-member-change-workflow.d.ts.map +1 -0
  79. package/dist/team-member-change-workflow.js +132 -0
  80. package/dist/workbench-cli.d.ts.map +1 -1
  81. package/dist/workbench-cli.js +24 -4
  82. package/dist/workspace.d.ts.map +1 -1
  83. package/dist/workspace.js +2 -0
  84. package/package.json +1 -1
  85. package/src/account-directory.ts +6 -41
  86. package/src/app-publish-workspace.ts +1 -1
  87. package/src/archive-artifact-workflow.ts +101 -0
  88. package/src/archive-artifacts-cli.ts +7 -28
  89. package/src/artifact-archive.ts +2 -0
  90. package/src/credentials.ts +2 -0
  91. package/src/dataset-commands.ts +19 -31
  92. package/src/dataset-split-create-workflow.ts +128 -0
  93. package/src/git-credential.ts +1 -1
  94. package/src/help.ts +10 -2
  95. package/src/hosted-boot.ts +292 -59
  96. package/src/hosted-harness.ts +2 -0
  97. package/src/hosted-harvest.ts +30 -17
  98. package/src/hosted-live-channel.ts +167 -0
  99. package/src/hosted-loop.ts +50 -0
  100. package/src/hosted-run-event-sink.ts +118 -3
  101. package/src/hosted-runtime-assets.ts +1 -1
  102. package/src/hosted-session-cli.ts +4 -0
  103. package/src/hosted-session-seed.ts +111 -10
  104. package/src/hosted-skill-staging.ts +110 -36
  105. package/src/http.ts +12 -7
  106. package/src/index.ts +17 -21
  107. package/src/orizu-skill-links.ts +114 -0
  108. package/src/preview-runtime.ts +16 -0
  109. package/src/repository-backup-core.ts +2 -0
  110. package/src/request-outcome.ts +28 -0
  111. package/src/score-save-workflow.ts +132 -0
  112. package/src/setup-and-skills-commands.ts +60 -6
  113. package/src/setup-onboarding.ts +6 -2
  114. package/src/skill-installer.ts +46 -5
  115. package/src/team-create-workflow.ts +109 -0
  116. package/src/team-member-change-workflow.ts +146 -0
  117. package/src/workbench-cli.ts +24 -4
  118. package/src/workspace.ts +2 -0
@@ -0,0 +1,101 @@
1
+ import { Cause, Effect, Exit, Result, Schema } from 'effect'
2
+
3
+ import { datasetTransferTimeoutMs } from './dataset-transfer-policy.js'
4
+ import { isJsonErrorResponsePayload } from './error-response.js'
5
+ import { authedFetch } from './http.js'
6
+ import { sanitizeTerminalText } from './json-response.js'
7
+
8
+ // Loaded only through `await import(...)` from archive-artifacts-cli.
9
+
10
+ const MAY_BE_SAVED = 'The change may already be saved: running the same command again is safe and shows its current state.'
11
+
12
+ const ArchiveReply = Schema.Struct({
13
+ artifact: Schema.Struct({
14
+ id: Schema.String,
15
+ type: Schema.String,
16
+ status: Schema.Literals(['active', 'archived']),
17
+ archivedAt: Schema.NullOr(Schema.String),
18
+ assigneeId: Schema.optionalKey(Schema.String),
19
+ }),
20
+ })
21
+ export type ArchiveReply = typeof ArchiveReply.Type
22
+
23
+ export interface ArchiveRequest {
24
+ path: string
25
+ body: { archived: boolean; assigneeId?: string }
26
+ // For example "archive dataset": the start of every failure message.
27
+ label: string
28
+ fetcher?: (path: string, init?: RequestInit) => Promise<Response>
29
+ }
30
+
31
+ // A refusal (4xx) means nothing changed. After any other failure once the
32
+ // request was sent, the change may have committed; repeating it is harmless.
33
+ class ArchiveFailure extends Error {
34
+ constructor(message: string, readonly mayBeSaved: boolean) { super(message) }
35
+ }
36
+
37
+ async function reason(response: Response): Promise<string> {
38
+ const text = await response.text()
39
+ let payload: unknown = null
40
+ try { payload = JSON.parse(text) } catch { /* not JSON */ }
41
+ if (isJsonErrorResponsePayload(response, payload) && payload.error.length > 0) {
42
+ return sanitizeTerminalText(payload.error).slice(0, 180)
43
+ }
44
+ // Never print a page body: a gateway's HTML says nothing useful.
45
+ return `the server returned a page that isn't JSON (status ${response.status})`
46
+ }
47
+
48
+ function archiveRequest(request: ArchiveRequest, timeoutMs: number) {
49
+ return Effect.suspend(() => {
50
+ let sent = false
51
+ let timedOut = false
52
+ return Effect.tryPromise({
53
+ try: async signal => {
54
+ const controller = new AbortController()
55
+ const abort = () => controller.abort()
56
+ const timer = setTimeout(() => { timedOut = true; controller.abort() }, timeoutMs)
57
+ if (signal.aborted) abort()
58
+ else signal.addEventListener('abort', abort, { once: true })
59
+ try {
60
+ const init: RequestInit = {
61
+ method: 'PATCH',
62
+ headers: { 'content-type': 'application/json' },
63
+ body: JSON.stringify(request.body),
64
+ signal: controller.signal,
65
+ }
66
+ const response = request.fetcher
67
+ ? (sent = true, await request.fetcher(request.path, init))
68
+ : await authedFetch(request.path, init, () => { sent = true })
69
+ if (!response.ok) {
70
+ throw new ArchiveFailure(`Failed to ${request.label}: ${await reason(response)}`, response.status < 400 || response.status >= 500)
71
+ }
72
+ let data: unknown = null
73
+ try { data = JSON.parse(await response.text()) } catch { /* decoded below */ }
74
+ const decoded = Schema.decodeUnknownResult(ArchiveReply)(data)
75
+ if (Result.isFailure(decoded)) {
76
+ throw new ArchiveFailure(`Failed to ${request.label}: the server's answer could not be read (status ${response.status})`, true)
77
+ }
78
+ return decoded.success
79
+ } finally {
80
+ clearTimeout(timer)
81
+ signal.removeEventListener('abort', abort)
82
+ controller.abort()
83
+ }
84
+ },
85
+ catch: cause => {
86
+ if (timedOut) return new ArchiveFailure(`Failed to ${request.label}: the request exceeded its deadline`, true)
87
+ if (cause instanceof ArchiveFailure) return cause
88
+ const message = cause instanceof Error ? cause.message : String(cause)
89
+ // Before sending (for example, not signed in) the message stands alone.
90
+ return new ArchiveFailure(sent ? `Failed to ${request.label}: ${message}` : message, sent)
91
+ },
92
+ })
93
+ }).pipe(Effect.mapError(failure => new Error(failure.mayBeSaved ? `${failure.message}\n${MAY_BE_SAVED}` : failure.message)))
94
+ }
95
+
96
+ // Nothing to clean up, so signals keep Node's default handling.
97
+ export async function archiveArtifact(request: ArchiveRequest): Promise<ArchiveReply> {
98
+ const exit = await Effect.runPromiseExit(archiveRequest(request, datasetTransferTimeoutMs()))
99
+ if (Exit.isSuccess(exit)) return exit.value
100
+ throw Cause.squash(exit.cause)
101
+ }
@@ -27,14 +27,6 @@ export interface ArchiveArtifactsIo {
27
27
  fetcher?: (path: string, init?: RequestInit) => Promise<Response>
28
28
  }
29
29
 
30
- interface ArchiveArtifactResult {
31
- id: string
32
- type: ArchiveArtifactType
33
- status: 'active' | 'archived'
34
- archivedAt: string | null
35
- assigneeId?: string
36
- }
37
-
38
30
  interface AssignmentSummary {
39
31
  taskId: string
40
32
  taskTitle: string
@@ -262,27 +254,14 @@ export async function archiveArtifactCommand(
262
254
  archived: action === 'archive',
263
255
  ...(assigneeId ? { assigneeId } : {}),
264
256
  }
265
- const fetcher = io.fetcher || authedFetch
266
- const response = await fetcher(
267
- `/api/cli/archive-artifacts/${artifactType}/${encodeURIComponent(artifactId)}` +
257
+ const { archiveArtifact } = await import('./archive-artifact-workflow.js')
258
+ const data = await archiveArtifact({
259
+ path: `/api/cli/archive-artifacts/${artifactType}/${encodeURIComponent(artifactId)}` +
268
260
  `?${params.toString()}`,
269
- {
270
- method: 'PATCH',
271
- headers: { 'content-type': 'application/json' },
272
- body: JSON.stringify(body),
273
- }
274
- )
275
- if (!response.ok) {
276
- throw new Error(
277
- `Failed to ${action} ${artifactType}: ` +
278
- await extractErrorMessage(response)
279
- )
280
- }
281
-
282
- const data = await parseJsonPayload<{ artifact: ArchiveArtifactResult }>(
283
- response,
284
- `${action === 'archive' ? 'Archive' : 'Restore'} ${artifactType}`
285
- )
261
+ body,
262
+ label: `${action} ${artifactType}`,
263
+ fetcher: io.fetcher,
264
+ })
286
265
  if (io.json) {
287
266
  io.print(JSON.stringify(data))
288
267
  return
@@ -1,3 +1,5 @@
1
+ // Reads only the artifact folder it is given.
2
+ // @tia-reads: none
1
3
  import { createHash } from 'crypto'
2
4
  import { readdirSync, readFileSync, statSync } from 'fs'
3
5
  import { join } from 'path'
@@ -1,3 +1,5 @@
1
+ // Reads only the user's credential, lock and token files.
2
+ // @tia-reads: none
1
3
  import {
2
4
  chmodSync,
3
5
  closeSync,
@@ -20,8 +20,7 @@ import {
20
20
  selectDatasetInteractively,
21
21
  selectTaskIdInteractively,
22
22
  } from './account-directory.js'
23
- import { sanitizeTerminalText, parseJsonResponse } from './json-response.js'
24
- import { authedFetch } from './http.js'
23
+ import { sanitizeTerminalText } from './json-response.js'
25
24
  import { printJson, printLine } from './cli-console-output.js'
26
25
  import { readJsonFile } from './app-commands.js'
27
26
  import { printDatasetSummaries } from './archive-artifacts-cli.js'
@@ -78,15 +77,16 @@ function parseRatioFlag(name: string, fallback: number): number {
78
77
  return parsed
79
78
  }
80
79
 
81
- function parsePositiveIntegerFlag(name: string, fallback: number): number {
80
+ // The app accepts 0 and stores seeds as 32-bit integers.
81
+ function parseSeedFlag(name: string, fallback: number): number {
82
82
  const value = getArg(name)
83
83
  if (!value) {
84
84
  return fallback
85
85
  }
86
86
 
87
87
  const parsed = Number(value)
88
- if (!Number.isInteger(parsed) || parsed <= 0) {
89
- throw new Error(`${name} must be a positive integer`)
88
+ if (!Number.isInteger(parsed) || parsed < 0 || parsed > 2147483647) {
89
+ throw new Error(`${name} must be a whole number from 0 to 2147483647`)
90
90
  }
91
91
 
92
92
  return parsed
@@ -189,12 +189,15 @@ async function createDatasetSplitSet() {
189
189
  const splitSpec = splitFile ? readJsonFile(splitFile) : null
190
190
  const name = getArg('--name') || (typeof splitSpec?.name === 'string' ? splitSpec.name : 'default')
191
191
  const strategy = getArg('--strategy') || (typeof splitSpec?.strategy === 'string' ? splitSpec.strategy : 'random')
192
- const seed = splitSpec && (splitSpec.seed === null || typeof splitSpec.seed === 'number')
192
+ // A file's seed goes to the app as written, so one that isn't a whole number
193
+ // is refused there instead of quietly replaced.
194
+ const seed = splitSpec && Object.prototype.hasOwnProperty.call(splitSpec, 'seed')
193
195
  ? splitSpec.seed
194
- : parsePositiveIntegerFlag('--seed', 1)
196
+ : parseSeedFlag('--seed', 1)
195
197
  const train = parseRatioFlag('--train', 0.7)
196
198
  const validation = parseRatioFlag('--validation', 0.2)
197
- const test = parseRatioFlag('--test', 0.1)
199
+ // Without --test, test takes whatever train and validation leave.
200
+ const test = getArg('--test') ? parseRatioFlag('--test', 0) : undefined
198
201
 
199
202
  if (!datasetVersionId) {
200
203
  throw new Error(
@@ -202,37 +205,22 @@ async function createDatasetSplitSet() {
202
205
  )
203
206
  }
204
207
 
205
- const partitions = splitSpec && Array.isArray(splitSpec.partitions) ? splitSpec.partitions : undefined
208
+ const { createSplitSet, splitRatioProblem } = await import('./dataset-split-create-workflow.js')
209
+ const ratioProblem = splitRatioProblem(train, validation, test)
210
+ if (ratioProblem) throw new Error(ratioProblem)
211
+
212
+ const partitions = splitSpec && Object.prototype.hasOwnProperty.call(splitSpec, 'partitions') ? splitSpec.partitions : undefined
206
213
  const metadata = splitSpec && Object.prototype.hasOwnProperty.call(splitSpec, 'metadata')
207
214
  ? splitSpec.metadata
208
215
  : undefined
209
216
 
210
- const response = await authedFetch(`/api/cli/dataset-versions/${encodeURIComponent(datasetVersionId)}/split-sets`, {
211
- method: 'POST',
212
- headers: { 'Content-Type': 'application/json' },
213
- body: JSON.stringify({
214
- name,
215
- strategy,
216
- seed,
217
- train,
218
- validation,
219
- test,
220
- partitions,
221
- metadata,
222
- }),
223
- })
224
-
225
- if (!response.ok) {
226
- throw new Error(`Failed to create dataset split set: ${await response.text()}`)
227
- }
228
-
229
- const data = await parseJsonResponse<{ splitSet: { id: string } }>(response, 'Dataset split set create')
217
+ const splitSetId = await createSplitSet({ versionId: datasetVersionId, name, strategy, seed, train, validation, test, partitions, metadata })
230
218
  if (hasJsonFlag()) {
231
- printJson({ split_set_id: data.splitSet.id })
219
+ printJson({ split_set_id: splitSetId })
232
220
  return
233
221
  }
234
222
 
235
- printLine(`Created split set ${sanitizeTerminalText(data.splitSet.id)}`)
223
+ printLine(`Created split set ${sanitizeTerminalText(splitSetId)}`)
236
224
  }
237
225
 
238
226
  async function listDatasets() {
@@ -0,0 +1,128 @@
1
+ import { Cause, Effect, Exit, Result, Schema } from 'effect'
2
+
3
+ import { datasetTransferTimeoutMs } from './dataset-transfer-policy.js'
4
+ import { authedFetch } from './http.js'
5
+ import { parseJsonResponse } from './json-response.js'
6
+
7
+ // Loaded only through `await import(...)` from dataset-commands, like clone.
8
+
9
+ const SERVER_MAY_BE_CREATED = 'Dataset split set may already be created'
10
+ // Ratios like 0.7 + 0.2 + 0.1 don't add up to exactly 1 in floating point.
11
+ const RATIO_GAP = 1e-9
12
+
13
+ const CreateReply = Schema.Struct({ splitSet: Schema.Struct({ id: Schema.String }) })
14
+ const ErrorReply = Schema.Struct({ error: Schema.String })
15
+
16
+ export interface SplitSetRequest {
17
+ versionId: string
18
+ name: string
19
+ strategy: string
20
+ seed: unknown
21
+ train: number
22
+ validation: number
23
+ /** Absent means "whatever train and validation leave". */
24
+ test: number | undefined
25
+ partitions: unknown
26
+ metadata: unknown
27
+ }
28
+
29
+ const shown = (value: number) => Math.round(value * 1e12) / 1e12
30
+
31
+ /** The same rule the app enforces, checked before anything is sent. */
32
+ export function splitRatioProblem(train: number, validation: number, test: number | undefined): string | null {
33
+ if (train + validation > 1 + RATIO_GAP) return `--train plus --validation must not be more than 1 (they add up to ${shown(train + validation)})`
34
+ if (test !== undefined && Math.abs(train + validation + test - 1) > RATIO_GAP) {
35
+ return `--train, --validation and --test must add up to 1 (they add up to ${shown(train + validation + test)})`
36
+ }
37
+ return null
38
+ }
39
+
40
+ // A refusal means nothing was saved. After any other failure once the request
41
+ // was sent, the set may exist. The version allows one set per name, so running
42
+ // the same command again can't make a second copy; it is still never retried
43
+ // automatically, and the user is told how to check.
44
+ class SplitSetFailure extends Error {
45
+ constructor(message: string, readonly mayBeCreated: boolean) { super(message) }
46
+ }
47
+
48
+ function mayBeCreatedGuidance(request: SplitSetRequest): string {
49
+ return `The split set ${JSON.stringify(request.name)} may already have been created. Run \`orizu datasets splits list ${request.versionId}\` to check. Running the same command again is safe: a set with that name is refused, not duplicated.`
50
+ }
51
+
52
+ // authedFetch raises this code only when it sent nothing, or when the server
53
+ // answered 401 and the changed credentials stopped a replay. Nothing was saved.
54
+ function isAuthContextChange(cause: unknown): boolean {
55
+ return cause instanceof Error && cause.message.startsWith('ORIZU_AUTH_CONTEXT_CHANGED:')
56
+ }
57
+
58
+ function serverError(text: string): string | null {
59
+ try {
60
+ const decoded = Schema.decodeUnknownResult(ErrorReply)(JSON.parse(text))
61
+ return Result.isSuccess(decoded) ? decoded.success.error : null
62
+ } catch {
63
+ return null
64
+ }
65
+ }
66
+
67
+ function createRequest(request: SplitSetRequest, timeoutMs: number) {
68
+ return Effect.suspend(() => {
69
+ let sent = false
70
+ let timedOut = false
71
+ return Effect.tryPromise({
72
+ try: async signal => {
73
+ const abortController = new AbortController()
74
+ const abort = () => abortController.abort()
75
+ const timer = setTimeout(() => { timedOut = true; abortController.abort() }, timeoutMs)
76
+ if (signal.aborted) abort()
77
+ else signal.addEventListener('abort', abort, { once: true })
78
+ try {
79
+ const response = await authedFetch(`/api/cli/dataset-versions/${encodeURIComponent(request.versionId)}/split-sets`, {
80
+ method: 'POST',
81
+ headers: { 'Content-Type': 'application/json' },
82
+ body: JSON.stringify({
83
+ name: request.name,
84
+ strategy: request.strategy,
85
+ seed: request.seed,
86
+ train: request.train,
87
+ validation: request.validation,
88
+ test: request.test,
89
+ partitions: request.partitions,
90
+ metadata: request.metadata,
91
+ }),
92
+ signal: abortController.signal,
93
+ }, () => { sent = true })
94
+ if (!response.ok) {
95
+ const text = await response.text()
96
+ const error = serverError(text)
97
+ // The app's own 5xx bodies name what happened; anything else at 5xx
98
+ // (a gateway page, an empty body) says nothing about the set.
99
+ const mayBeCreated = response.status < 400 || error === SERVER_MAY_BE_CREATED || (response.status >= 500 && error === null)
100
+ throw new SplitSetFailure(`Failed to create dataset split set: ${text}`, mayBeCreated)
101
+ }
102
+ const data = await parseJsonResponse<unknown>(response, 'Dataset split set create')
103
+ const decoded = Schema.decodeUnknownResult(CreateReply)(data)
104
+ if (Result.isFailure(decoded)) {
105
+ throw new SplitSetFailure(`Dataset split set create returned an unexpected response (status ${response.status}).`, true)
106
+ }
107
+ return decoded.success.splitSet.id
108
+ } finally {
109
+ clearTimeout(timer)
110
+ signal.removeEventListener('abort', abort)
111
+ abortController.abort()
112
+ }
113
+ },
114
+ catch: cause => {
115
+ if (timedOut) return new SplitSetFailure('DATASET_TRANSFER_TIMEOUT: Dataset split set create exceeded its deadline', sent)
116
+ if (cause instanceof SplitSetFailure) return cause
117
+ return new SplitSetFailure(cause instanceof Error ? cause.message : String(cause), sent && !isAuthContextChange(cause))
118
+ },
119
+ })
120
+ }).pipe(Effect.mapError(failure => new Error(failure.mayBeCreated ? `${failure.message}\n${mayBeCreatedGuidance(request)}` : failure.message)))
121
+ }
122
+
123
+ // Nothing to clean up, so signals keep Node's default handling.
124
+ export async function createSplitSet(request: SplitSetRequest): Promise<string> {
125
+ const exit = await Effect.runPromiseExit(createRequest(request, datasetTransferTimeoutMs()))
126
+ if (Exit.isSuccess(exit)) return exit.value
127
+ throw Cause.squash(exit.cause)
128
+ }
@@ -377,7 +377,7 @@ async function mintArtifactsCredential(
377
377
  `/api/cli/workspaces/${encodeURIComponent(workspaceId)}/repo-token`,
378
378
  {
379
379
  method: 'POST',
380
- headers: { 'Content-Type': 'application/json' },
380
+ headers: { 'Content-Type': 'application/json', 'X-Orizu-Session-Seed-Protocol': '1' },
381
381
  body: JSON.stringify({ purpose, ...(purpose === 'team_read' ? { sessionId: process.env.ORIZU_SESSION_ID?.trim() } : {}) }),
382
382
  }
383
383
  )
package/src/help.ts CHANGED
@@ -966,9 +966,17 @@ export const COMMAND_DOCS: CliCommandDoc[] = [
966
966
  },
967
967
  {
968
968
  path: ['datasets', 'splits', 'create'],
969
- usage: 'orizu datasets splits create <datasetVersionId> [--from-file <split.json>] [--json]',
970
- summary: 'Create a split set for the explicit target dataset version. --from-file accepts splits show JSON; canonical row IDs must exist in the target.',
969
+ usage: 'orizu datasets splits create <datasetVersionId> [--from-file <split.json>] [--name <name>] [--seed <n>] [--train <ratio>] [--validation <ratio>] [--test <ratio>] [--json]',
970
+ summary: 'Create a split set for the explicit target dataset version. --from-file accepts splits show JSON; canonical row IDs must exist in the target. Each version allows one set per name.',
971
971
  group: 'Datasets',
972
+ options: [
973
+ { name: '--from-file <split.json>', help: 'Saved partitions to import, for example from splits show --json.' },
974
+ { name: '--name <name>', help: 'Set name; defaults to the file name field, then "default".' },
975
+ { name: '--seed <n>', help: 'Whole number from 0 to 2147483647 for a random split (default 1). The same seed gives the same rows.' },
976
+ { name: '--train <ratio>', help: 'Share of rows for train, from 0 to 1 (default 0.7).' },
977
+ { name: '--validation <ratio>', help: 'Share of rows for validation (default 0.2). Train plus validation must not be more than 1.' },
978
+ { name: '--test <ratio>', help: 'Share of rows for test. Defaults to what train and validation leave; if given, the three must add up to 1.' },
979
+ ],
972
980
  },
973
981
  {
974
982
  path: ['datasets', 'download'],