orizu 0.5.39 → 0.5.41
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +77 -0
- package/dist/dataset-clone-workflow.d.ts +11 -0
- package/dist/dataset-clone-workflow.d.ts.map +1 -0
- package/dist/dataset-clone-workflow.js +100 -0
- package/dist/dataset-commands.d.ts +3 -1
- package/dist/dataset-commands.d.ts.map +1 -1
- package/dist/dataset-commands.js +107 -91
- package/dist/dataset-delete-workflow.d.ts +4 -0
- package/dist/dataset-delete-workflow.d.ts.map +1 -0
- package/dist/dataset-delete-workflow.js +75 -0
- package/dist/dataset-lock-workflow.d.ts +12 -0
- package/dist/dataset-lock-workflow.d.ts.map +1 -0
- package/dist/dataset-lock-workflow.js +76 -0
- package/dist/dataset-readme-workflow.d.ts +13 -0
- package/dist/dataset-readme-workflow.d.ts.map +1 -0
- package/dist/dataset-readme-workflow.js +82 -0
- package/dist/dataset-row-workflow.d.ts +14 -0
- package/dist/dataset-row-workflow.d.ts.map +1 -0
- package/dist/dataset-row-workflow.js +202 -0
- package/dist/dataset-split-reads.d.ts +28 -0
- package/dist/dataset-split-reads.d.ts.map +1 -0
- package/dist/dataset-split-reads.js +47 -0
- package/dist/dataset-transfer-upload.d.ts +2 -6
- package/dist/dataset-transfer-upload.d.ts.map +1 -1
- package/dist/dataset-transfer-upload.js +9 -2
- package/dist/dataset-upload-snapshot.d.ts.map +1 -1
- package/dist/dataset-upload-snapshot.js +7 -1
- package/dist/dataset-version-workflow.d.ts +24 -0
- package/dist/dataset-version-workflow.d.ts.map +1 -0
- package/dist/dataset-version-workflow.js +99 -0
- package/dist/feedback-cli.js +3 -3
- package/dist/help.d.ts.map +1 -1
- package/dist/help.js +29 -3
- package/dist/hosted-boot.d.ts +1 -0
- package/dist/hosted-boot.d.ts.map +1 -1
- package/dist/hosted-boot.js +17 -0
- package/dist/hosted-harness-opencode.d.ts +5 -6
- package/dist/hosted-harness-opencode.d.ts.map +1 -1
- package/dist/hosted-harness-opencode.js +73 -59
- package/dist/hosted-loop-lifecycle.d.ts +6 -11
- package/dist/hosted-loop-lifecycle.d.ts.map +1 -1
- package/dist/hosted-loop-lifecycle.js +6 -11
- package/dist/hosted-loop.d.ts +9 -0
- package/dist/hosted-loop.d.ts.map +1 -1
- package/dist/hosted-loop.js +19 -10
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -3
- package/dist/instruction-delivery.d.ts +14 -0
- package/dist/instruction-delivery.d.ts.map +1 -0
- package/dist/instruction-delivery.js +103 -0
- package/dist/instruction-set-sync/index.d.ts +17 -0
- package/dist/instruction-set-sync/index.d.ts.map +1 -1
- package/dist/instruction-set-sync/index.js +44 -17
- package/dist/instruction-set-update/index.d.ts +2 -2
- package/dist/instruction-set-update/index.d.ts.map +1 -1
- package/dist/instruction-set-update/index.js +2 -2
- package/dist/instruction-sets-cli.d.ts.map +1 -1
- package/dist/instruction-sets-cli.js +19 -27
- package/dist/json-response.d.ts +1 -0
- package/dist/json-response.d.ts.map +1 -1
- package/dist/json-response.js +7 -0
- package/dist/last-error-record.d.ts +4 -2
- package/dist/last-error-record.d.ts.map +1 -1
- package/dist/last-error-record.js +10 -68
- package/dist/last-error-workflow.d.ts +4 -0
- package/dist/last-error-workflow.d.ts.map +1 -0
- package/dist/last-error-workflow.js +78 -0
- package/dist/preview-runtime.d.ts +2 -2
- package/dist/preview-runtime.d.ts.map +1 -1
- package/dist/preview-runtime.js +37 -7
- package/dist/task-commands.d.ts.map +1 -1
- package/dist/task-commands.js +9 -16
- package/dist/task-export-workflow.d.ts +4 -0
- package/dist/task-export-workflow.d.ts.map +1 -0
- package/dist/task-export-workflow.js +46 -0
- package/dist/task-report-workflow.d.ts +47 -0
- package/dist/task-report-workflow.d.ts.map +1 -0
- package/dist/task-report-workflow.js +160 -0
- package/dist/workbench-cli.js +1 -1
- package/package.json +1 -1
- package/src/dataset-clone-workflow.ts +100 -0
- package/src/dataset-commands.ts +95 -142
- package/src/dataset-delete-workflow.ts +72 -0
- package/src/dataset-lock-workflow.ts +74 -0
- package/src/dataset-readme-workflow.ts +80 -0
- package/src/dataset-row-workflow.ts +248 -0
- package/src/dataset-split-reads.ts +43 -0
- package/src/dataset-transfer-upload.ts +12 -5
- package/src/dataset-upload-snapshot.ts +8 -1
- package/src/dataset-version-workflow.ts +105 -0
- package/src/feedback-cli.ts +4 -4
- package/src/help.ts +29 -3
- package/src/hosted-boot.ts +16 -1
- package/src/hosted-harness-opencode.ts +74 -72
- package/src/hosted-loop-lifecycle.ts +6 -11
- package/src/hosted-loop.ts +20 -10
- package/src/hosted-model-cache.d.mts +2 -0
- package/src/hosted-model-cache.mjs +53 -0
- package/src/index.ts +12 -2
- package/src/instruction-delivery.ts +123 -0
- package/src/instruction-set-sync/index.ts +61 -14
- package/src/instruction-set-update/index.ts +4 -3
- package/src/instruction-sets-cli.ts +19 -27
- package/src/json-response.ts +8 -0
- package/src/last-error-record.ts +11 -85
- package/src/last-error-workflow.ts +121 -0
- package/src/preview-runtime.ts +40 -10
- package/src/task-commands.ts +9 -43
- package/src/task-export-workflow.ts +56 -0
- package/src/task-report-workflow.ts +179 -0
- package/src/workbench-cli.ts +1 -1
- package/vendor/skills/orizu/references/cli-reference.md +34 -0
- package/vendor/skills/orizu/references/dataset-design.md +1 -1
- package/dist/dataset-edit-rows.d.ts +0 -10
- package/dist/dataset-edit-rows.d.ts.map +0 -1
- package/dist/dataset-edit-rows.js +0 -90
- package/src/dataset-edit-rows.ts +0 -124
|
@@ -0,0 +1,80 @@
|
|
|
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 delete.
|
|
8
|
+
|
|
9
|
+
const MAY_BE_SAVED = "The README may already be saved as a new version: check the dataset's README tab before saving again."
|
|
10
|
+
const SAVED_AT_SAME_TIME = 'Another README save for this dataset happened at the same time, so yours was not saved. Run the command again to save it as the next version.'
|
|
11
|
+
|
|
12
|
+
const SaveReply = Schema.Struct({
|
|
13
|
+
version: Schema.Struct({
|
|
14
|
+
id: Schema.String,
|
|
15
|
+
dataset_id: Schema.String,
|
|
16
|
+
version_num: Schema.Number,
|
|
17
|
+
created_at: Schema.String,
|
|
18
|
+
}),
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
export type SavedReadmeVersion = typeof SaveReply.Type['version']
|
|
22
|
+
|
|
23
|
+
// A refusal (4xx) means nothing was saved. After any other failure once the
|
|
24
|
+
// request was sent, a new version may exist; saving again would add another.
|
|
25
|
+
class SaveFailure extends Error {
|
|
26
|
+
constructor(message: string, readonly mayBeSaved: boolean) { super(message) }
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function saveRequest(datasetId: string, markdown: string, timeoutMs: number) {
|
|
30
|
+
return Effect.suspend(() => {
|
|
31
|
+
let sent = false
|
|
32
|
+
let timedOut = false
|
|
33
|
+
return Effect.tryPromise({
|
|
34
|
+
try: async signal => {
|
|
35
|
+
const request = new AbortController()
|
|
36
|
+
const abort = () => request.abort()
|
|
37
|
+
const timer = setTimeout(() => { timedOut = true; request.abort() }, timeoutMs)
|
|
38
|
+
if (signal.aborted) abort()
|
|
39
|
+
else signal.addEventListener('abort', abort, { once: true })
|
|
40
|
+
try {
|
|
41
|
+
const response = await authedFetch(`/api/cli/datasets/${encodeURIComponent(datasetId)}/readme`, {
|
|
42
|
+
method: 'POST',
|
|
43
|
+
headers: { 'Content-Type': 'application/json' },
|
|
44
|
+
body: JSON.stringify({ markdown }),
|
|
45
|
+
signal: request.signal,
|
|
46
|
+
}, () => { sent = true })
|
|
47
|
+
if (!response.ok) {
|
|
48
|
+
const message = `Failed to save dataset README: ${await response.text()}`
|
|
49
|
+
// The unique version number refused a save that lost a race.
|
|
50
|
+
if (response.status === 409) throw new SaveFailure(`${message}\n${SAVED_AT_SAME_TIME}`, false)
|
|
51
|
+
throw new SaveFailure(message, response.status < 400 || response.status >= 500)
|
|
52
|
+
}
|
|
53
|
+
const data = await parseJsonResponse<unknown>(response, 'Dataset README save')
|
|
54
|
+
const decoded = Schema.decodeUnknownResult(SaveReply)(data)
|
|
55
|
+
if (Result.isFailure(decoded)) {
|
|
56
|
+
throw new SaveFailure(`Dataset README save returned an unexpected response (status ${response.status}).`, true)
|
|
57
|
+
}
|
|
58
|
+
return decoded.success.version
|
|
59
|
+
} finally {
|
|
60
|
+
clearTimeout(timer)
|
|
61
|
+
signal.removeEventListener('abort', abort)
|
|
62
|
+
request.abort()
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
catch: cause => {
|
|
66
|
+
if (timedOut) return new SaveFailure('DATASET_TRANSFER_TIMEOUT: README save exceeded its deadline', sent)
|
|
67
|
+
if (cause instanceof SaveFailure) return cause
|
|
68
|
+
return new SaveFailure(cause instanceof Error ? cause.message : String(cause), sent)
|
|
69
|
+
},
|
|
70
|
+
})
|
|
71
|
+
}).pipe(Effect.mapError(failure => new Error(failure.mayBeSaved ? `${failure.message}\n${MAY_BE_SAVED}` : failure.message)))
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// One request, never retried: a retry after an unknown outcome could add a
|
|
75
|
+
// duplicate version.
|
|
76
|
+
export async function saveDatasetReadme(datasetId: string, markdown: string): Promise<SavedReadmeVersion> {
|
|
77
|
+
const exit = await Effect.runPromiseExit(saveRequest(datasetId, markdown, datasetTransferTimeoutMs()))
|
|
78
|
+
if (Exit.isSuccess(exit)) return exit.value
|
|
79
|
+
throw Cause.squash(exit.cause)
|
|
80
|
+
}
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
import { Cause, Effect, Exit, Result, Schema } from 'effect'
|
|
2
|
+
|
|
3
|
+
import { datasetTransferTimeoutMs } from './dataset-transfer-policy.js'
|
|
4
|
+
import { extractErrorMessage } from './error-response.js'
|
|
5
|
+
import { parseDatasetFile } from './file-parser.js'
|
|
6
|
+
import { authedFetch } from './http.js'
|
|
7
|
+
import { parseJsonResponse } from './json-response.js'
|
|
8
|
+
|
|
9
|
+
// Loaded only through `await import(...)` from dataset-commands, like append.
|
|
10
|
+
|
|
11
|
+
const MAX_REQUEST_BYTES = 3 * 1024 * 1024
|
|
12
|
+
const MAX_CHUNK_ROWS = 10
|
|
13
|
+
const EMPTY_REQUEST_BYTES = Buffer.byteLength('{"rows":[]}', 'utf8')
|
|
14
|
+
|
|
15
|
+
type DatasetRowEdit = Record<string, unknown> & { id: string }
|
|
16
|
+
|
|
17
|
+
// 'rejected': the server answered with a refusal, so nothing was written.
|
|
18
|
+
// 'unknown': the request was sent but no trustworthy answer came back.
|
|
19
|
+
type Outcome = 'rejected' | 'unknown' | 'not_sent'
|
|
20
|
+
class RowsRequestFailure extends Error {
|
|
21
|
+
readonly _tag = 'RowsRequestFailure'
|
|
22
|
+
constructor(message: string, readonly outcome: Outcome) { super(message) }
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const Dataset = Schema.Struct({ id: Schema.String, name: Schema.String, rowCount: Schema.Number })
|
|
26
|
+
const EditAcknowledgement = Schema.Struct({ dataset: Dataset, updatedCount: Schema.Number })
|
|
27
|
+
type EditAcknowledgement = typeof EditAcknowledgement.Type
|
|
28
|
+
|
|
29
|
+
export interface DatasetRowsOptions {
|
|
30
|
+
datasetId: string
|
|
31
|
+
json: boolean
|
|
32
|
+
printJson: (value: Record<string, unknown>) => void
|
|
33
|
+
printLine: (value: string) => void
|
|
34
|
+
sanitize: (value: string) => string
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function normalizeRows(rows: Array<Record<string, unknown>>): DatasetRowEdit[] {
|
|
38
|
+
if (rows.length === 0) {
|
|
39
|
+
throw new Error('Dataset edit file must contain at least one row')
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const seenRowIds = new Set<string>()
|
|
43
|
+
return rows.map((row, index) => {
|
|
44
|
+
const rowId = typeof row.id === 'string' ? row.id.trim() : ''
|
|
45
|
+
if (!rowId) {
|
|
46
|
+
throw new Error(`Dataset edit file rows[${index}] must include a non-empty string id`)
|
|
47
|
+
}
|
|
48
|
+
if (seenRowIds.has(rowId)) {
|
|
49
|
+
throw new Error(`Dataset edit file rows[${index}].id duplicates a previous row id`)
|
|
50
|
+
}
|
|
51
|
+
seenRowIds.add(rowId)
|
|
52
|
+
return { ...row, id: rowId }
|
|
53
|
+
})
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function chunkRows(rows: DatasetRowEdit[], sanitize: (value: string) => string): DatasetRowEdit[][] {
|
|
57
|
+
const chunks: DatasetRowEdit[][] = []
|
|
58
|
+
let currentChunk: DatasetRowEdit[] = []
|
|
59
|
+
let currentBytes = EMPTY_REQUEST_BYTES
|
|
60
|
+
|
|
61
|
+
for (const row of rows) {
|
|
62
|
+
const rowBytes = Buffer.byteLength(JSON.stringify(row), 'utf8')
|
|
63
|
+
const framedRowBytes = rowBytes + (currentChunk.length > 0 ? 1 : 0)
|
|
64
|
+
if (
|
|
65
|
+
currentChunk.length > 0 &&
|
|
66
|
+
(currentChunk.length >= MAX_CHUNK_ROWS || currentBytes + framedRowBytes > MAX_REQUEST_BYTES)
|
|
67
|
+
) {
|
|
68
|
+
chunks.push(currentChunk)
|
|
69
|
+
currentChunk = []
|
|
70
|
+
currentBytes = EMPTY_REQUEST_BYTES
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const singleRowRequestBytes = EMPTY_REQUEST_BYTES + rowBytes
|
|
74
|
+
if (singleRowRequestBytes > MAX_REQUEST_BYTES) {
|
|
75
|
+
throw new Error(
|
|
76
|
+
`Dataset edit row ${sanitize(row.id)} is ${singleRowRequestBytes} bytes, ` +
|
|
77
|
+
`which exceeds the ${MAX_REQUEST_BYTES}-byte edit request limit`
|
|
78
|
+
)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
currentChunk.push(row)
|
|
82
|
+
currentBytes += rowBytes + (currentChunk.length > 1 ? 1 : 0)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (currentChunk.length > 0) chunks.push(currentChunk)
|
|
86
|
+
return chunks
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// One rows request with a deadline that covers the whole reply. A deadline,
|
|
90
|
+
// a lost connection or a broken reply after sending is an unknown outcome.
|
|
91
|
+
function rowsRequest<A>(
|
|
92
|
+
datasetId: string,
|
|
93
|
+
method: 'PATCH' | 'DELETE',
|
|
94
|
+
body: unknown,
|
|
95
|
+
label: string,
|
|
96
|
+
timeoutMs: number,
|
|
97
|
+
read: (response: Response) => Promise<A>,
|
|
98
|
+
) {
|
|
99
|
+
return Effect.suspend(() => {
|
|
100
|
+
let sent = false
|
|
101
|
+
let timedOut = false
|
|
102
|
+
return Effect.tryPromise({
|
|
103
|
+
try: async signal => {
|
|
104
|
+
const request = new AbortController()
|
|
105
|
+
const abort = () => request.abort()
|
|
106
|
+
const timer = setTimeout(() => { timedOut = true; request.abort() }, timeoutMs)
|
|
107
|
+
if (signal.aborted) abort()
|
|
108
|
+
else signal.addEventListener('abort', abort, { once: true })
|
|
109
|
+
try {
|
|
110
|
+
const response = await authedFetch(`/api/cli/datasets/${encodeURIComponent(datasetId)}/rows`, {
|
|
111
|
+
method,
|
|
112
|
+
headers: { 'Content-Type': 'application/json' },
|
|
113
|
+
body: JSON.stringify(body),
|
|
114
|
+
signal: request.signal,
|
|
115
|
+
}, () => { sent = true })
|
|
116
|
+
return await read(response)
|
|
117
|
+
} finally {
|
|
118
|
+
clearTimeout(timer)
|
|
119
|
+
signal.removeEventListener('abort', abort)
|
|
120
|
+
request.abort()
|
|
121
|
+
}
|
|
122
|
+
},
|
|
123
|
+
catch: cause => {
|
|
124
|
+
if (timedOut) {
|
|
125
|
+
return sent
|
|
126
|
+
? new RowsRequestFailure(`DATASET_TRANSFER_TIMEOUT: ${label} exceeded its deadline`, 'unknown')
|
|
127
|
+
: new RowsRequestFailure(`DATASET_TRANSFER_TIMEOUT: ${label} exceeded its deadline before the request was sent`, 'not_sent')
|
|
128
|
+
}
|
|
129
|
+
if (cause instanceof RowsRequestFailure) return cause
|
|
130
|
+
return new RowsRequestFailure(cause instanceof Error ? cause.message : String(cause), sent ? 'unknown' : 'not_sent')
|
|
131
|
+
},
|
|
132
|
+
})
|
|
133
|
+
})
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function refusalOutcome(status: number): Outcome {
|
|
137
|
+
return status >= 400 && status < 500 ? 'rejected' : 'unknown'
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function editProgram(options: DatasetRowsOptions & { file: string }, timeoutMs: number) {
|
|
141
|
+
return Effect.gen(function*() {
|
|
142
|
+
const chunks = yield* Effect.try({
|
|
143
|
+
try: () => chunkRows(normalizeRows(parseDatasetFile(options.file).rows), options.sanitize),
|
|
144
|
+
catch: cause => cause,
|
|
145
|
+
})
|
|
146
|
+
let totalUpdated = 0
|
|
147
|
+
// normalizeRows refuses an empty file, so the loop always sets this.
|
|
148
|
+
let lastResult!: EditAcknowledgement
|
|
149
|
+
|
|
150
|
+
for (let index = 0; index < chunks.length; index += 1) {
|
|
151
|
+
const chunk = chunks[index]
|
|
152
|
+
if (!options.json && chunks.length > 1) {
|
|
153
|
+
options.printLine(`Updating chunk ${index + 1}/${chunks.length} (${chunk.length} rows)...`)
|
|
154
|
+
}
|
|
155
|
+
const acknowledged = yield* Effect.result(rowsRequest(options.datasetId, 'PATCH', { rows: chunk }, 'Dataset row edit', timeoutMs, async response => {
|
|
156
|
+
if (!response.ok) {
|
|
157
|
+
throw new RowsRequestFailure(`HTTP ${response.status}: ${await extractErrorMessage(response)}`, refusalOutcome(response.status))
|
|
158
|
+
}
|
|
159
|
+
const data: unknown = await response.json()
|
|
160
|
+
const decoded = Schema.decodeUnknownResult(EditAcknowledgement)(data)
|
|
161
|
+
if (Result.isFailure(decoded)) {
|
|
162
|
+
throw new RowsRequestFailure(`Server returned an unreadable acknowledgement for a ${chunk.length}-row chunk`, 'unknown')
|
|
163
|
+
}
|
|
164
|
+
if (!Number.isInteger(decoded.success.updatedCount) || decoded.success.updatedCount !== chunk.length) {
|
|
165
|
+
throw new RowsRequestFailure(`Server reported ${String(decoded.success.updatedCount)} updated rows for a ${chunk.length}-row chunk`, 'unknown')
|
|
166
|
+
}
|
|
167
|
+
return decoded.success
|
|
168
|
+
}))
|
|
169
|
+
if (Result.isFailure(acknowledged)) {
|
|
170
|
+
const { message, outcome } = acknowledged.failure
|
|
171
|
+
const advice = outcome === 'unknown'
|
|
172
|
+
? 'This chunk may already be applied: check these rows in the dataset before retrying.'
|
|
173
|
+
: 'Retry with a file containing only the remaining row edits.'
|
|
174
|
+
return yield* Effect.fail(new Error(
|
|
175
|
+
`Chunk ${index + 1}/${chunks.length} failed: ${message}\n` +
|
|
176
|
+
`${totalUpdated} rows from ${index} chunk(s) were already updated. ${advice}`
|
|
177
|
+
))
|
|
178
|
+
}
|
|
179
|
+
totalUpdated += acknowledged.success.updatedCount
|
|
180
|
+
lastResult = acknowledged.success
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (options.json) {
|
|
184
|
+
options.printJson({ dataset: lastResult.dataset, updatedCount: totalUpdated })
|
|
185
|
+
return
|
|
186
|
+
}
|
|
187
|
+
options.printLine(
|
|
188
|
+
`Updated ${totalUpdated} rows in dataset ${options.sanitize(lastResult.dataset.name)} ` +
|
|
189
|
+
`(${options.sanitize(lastResult.dataset.id)}). Current row count: ${lastResult.dataset.rowCount}`
|
|
190
|
+
)
|
|
191
|
+
})
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function deleteProgram(options: DatasetRowsOptions & { rowIds: string[] }, timeoutMs: number) {
|
|
195
|
+
return Effect.gen(function*() {
|
|
196
|
+
const deleted = yield* rowsRequest(options.datasetId, 'DELETE', { rowIds: options.rowIds }, 'Dataset row removal', timeoutMs, async response => {
|
|
197
|
+
if (!response.ok) {
|
|
198
|
+
throw new RowsRequestFailure(await response.text(), refusalOutcome(response.status))
|
|
199
|
+
}
|
|
200
|
+
return parseJsonResponse<{
|
|
201
|
+
dataset: { id: string; name: string; rowCount: number }
|
|
202
|
+
deletedCount: number
|
|
203
|
+
}>(response, 'Dataset delete rows')
|
|
204
|
+
}).pipe(Effect.mapError(({ message, outcome }) => new Error(
|
|
205
|
+
`Delete rows failed: ${message}` +
|
|
206
|
+
(outcome === 'unknown' ? '\nThe rows may already be removed: check the dataset before retrying.' : '')
|
|
207
|
+
)))
|
|
208
|
+
if (options.json) {
|
|
209
|
+
options.printJson({ dataset: deleted.dataset, deletedCount: deleted.deletedCount })
|
|
210
|
+
return
|
|
211
|
+
}
|
|
212
|
+
options.printLine(
|
|
213
|
+
`Deleted ${deleted.deletedCount} rows from dataset ${options.sanitize(deleted.dataset.name)} (${options.sanitize(deleted.dataset.id)}). New row count: ${deleted.dataset.rowCount}`
|
|
214
|
+
)
|
|
215
|
+
})
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Ctrl-C or SIGTERM aborts the request in flight. Returns the exit code for
|
|
219
|
+
// an interruption; other failures throw their original error.
|
|
220
|
+
async function runRowsCommand(name: string, program: (timeoutMs: number) => Effect.Effect<void, unknown>): Promise<0 | 130 | 143> {
|
|
221
|
+
const timeoutMs = datasetTransferTimeoutMs()
|
|
222
|
+
const controller = new AbortController()
|
|
223
|
+
let interrupted: 130 | 143 | undefined
|
|
224
|
+
const interrupt = () => { interrupted ??= 130; controller.abort() }
|
|
225
|
+
const terminate = () => { interrupted ??= 143; controller.abort() }
|
|
226
|
+
process.on('SIGINT', interrupt)
|
|
227
|
+
process.on('SIGTERM', terminate)
|
|
228
|
+
try {
|
|
229
|
+
const exit = await Effect.runPromiseExit(program(timeoutMs), { signal: controller.signal })
|
|
230
|
+
if (interrupted !== undefined) {
|
|
231
|
+
process.stderr.write(`${name} interrupted. The request in flight may already be applied: check the dataset before retrying.\n`)
|
|
232
|
+
return interrupted
|
|
233
|
+
}
|
|
234
|
+
if (Exit.isFailure(exit)) throw Cause.squash(exit.cause)
|
|
235
|
+
return 0
|
|
236
|
+
} finally {
|
|
237
|
+
process.removeListener('SIGINT', interrupt)
|
|
238
|
+
process.removeListener('SIGTERM', terminate)
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
export function editDatasetRows(options: DatasetRowsOptions & { file: string }): Promise<0 | 130 | 143> {
|
|
243
|
+
return runRowsCommand('Dataset row edit', timeoutMs => editProgram(options, timeoutMs))
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
export function deleteDatasetRows(options: DatasetRowsOptions & { rowIds: string[] }): Promise<0 | 130 | 143> {
|
|
247
|
+
return runRowsCommand('Dataset row removal', timeoutMs => deleteProgram(options, timeoutMs))
|
|
248
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { Effect, Result, Schema } from 'effect'
|
|
2
|
+
import { writeFileSync } from 'node:fs'
|
|
3
|
+
|
|
4
|
+
import { authedFetch } from './http.js'
|
|
5
|
+
import { extractErrorMessage } from './error-response.js'
|
|
6
|
+
import { datasetTransferTimeoutMs } from './dataset-transfer-policy.js'
|
|
7
|
+
|
|
8
|
+
const SplitExport = Schema.Struct({
|
|
9
|
+
split_set_id: Schema.String, dataset_version_id: Schema.String,
|
|
10
|
+
name: Schema.String, strategy: Schema.String, seed: Schema.NullOr(Schema.Number), metadata: Schema.Unknown,
|
|
11
|
+
partitions: Schema.Array(Schema.Struct({ name: Schema.String, row_ids: Schema.Array(Schema.String), metadata: Schema.Unknown })),
|
|
12
|
+
})
|
|
13
|
+
const SplitList = Schema.Struct({
|
|
14
|
+
dataset_version_id: Schema.String,
|
|
15
|
+
split_sets: Schema.Array(Schema.Struct({ id: Schema.String, name: Schema.String, strategy: Schema.String, seed: Schema.NullOr(Schema.Number), partitions: Schema.Array(Schema.Struct({ name: Schema.String, row_count: Schema.Number })) })),
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
async function read<A>(path: string, decode: (value: unknown) => A): Promise<A> {
|
|
19
|
+
const result = await Effect.runPromise(Effect.result(Effect.tryPromise({
|
|
20
|
+
try: async signal => {
|
|
21
|
+
const response = await authedFetch(path, { signal })
|
|
22
|
+
if (!response.ok) throw new Error(await extractErrorMessage(response))
|
|
23
|
+
try { return decode(await response.json()) } catch (cause) { throw new Error('Dataset split response is incomplete or unreadable', { cause }) }
|
|
24
|
+
}, catch: cause => cause instanceof Error ? cause : new Error('Dataset split read failed', { cause }),
|
|
25
|
+
}).pipe(Effect.timeoutOrElse({ duration: datasetTransferTimeoutMs(), orElse: () => Effect.fail(new Error('Dataset split read exceeded its deadline')) }))))
|
|
26
|
+
if (Result.isFailure(result)) throw result.failure
|
|
27
|
+
return result.success
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function listDatasetSplits(versionId: string) { return read(`/api/cli/dataset-versions/${encodeURIComponent(versionId)}/split-sets`, Schema.decodeUnknownSync(SplitList)) }
|
|
31
|
+
export function showDatasetSplit(setId: string) { return read(`/api/cli/split-sets/${encodeURIComponent(setId)}`, Schema.decodeUnknownSync(SplitExport)) }
|
|
32
|
+
|
|
33
|
+
export async function downloadPinnedDatasetSplit(versionId: string, setId: string, split: string, format: string, filename: string) {
|
|
34
|
+
const query = new URLSearchParams({ split_set: setId, split, format })
|
|
35
|
+
const result = await Effect.runPromise(Effect.result(Effect.tryPromise({
|
|
36
|
+
try: async signal => {
|
|
37
|
+
const response = await authedFetch(`/api/cli/dataset-versions/${encodeURIComponent(versionId)}/download?${query}`, { signal })
|
|
38
|
+
if (!response.ok) throw new Error(`Download failed: ${await extractErrorMessage(response)}`)
|
|
39
|
+
return new Uint8Array(await response.arrayBuffer())
|
|
40
|
+
}, catch: cause => cause instanceof Error ? cause : new Error('Dataset split download failed', { cause }),
|
|
41
|
+
}).pipe(Effect.timeoutOrElse({ duration: datasetTransferTimeoutMs(), orElse: () => Effect.fail(new Error('Dataset split download exceeded its deadline')) }), Effect.flatMap(bytes => Effect.try({ try: () => writeFileSync(filename, bytes, { mode: 0o600 }), catch: cause => new Error('Could not save dataset split download', { cause }) })))))
|
|
42
|
+
if (Result.isFailure(result)) throw result.failure
|
|
43
|
+
}
|
|
@@ -2,6 +2,7 @@ import { Context, Effect, Layer, Result } from 'effect'
|
|
|
2
2
|
|
|
3
3
|
import { ensureDatasetUploadSnapshot } from './dataset-upload-snapshot.js'
|
|
4
4
|
import { datasetTransferTimeoutMs } from './dataset-transfer-policy.js'
|
|
5
|
+
import { datasetVersionSnapshot, type DatasetVersionFailure, type DatasetVersionResponse } from './dataset-version-workflow.js'
|
|
5
6
|
import { extractErrorMessage } from './error-response.js'
|
|
6
7
|
import { authedFetch } from './http.js'
|
|
7
8
|
import { parseJsonResponse, sanitizeTerminalText } from './json-response.js'
|
|
@@ -12,7 +13,6 @@ export interface DatasetUploadResponse {
|
|
|
12
13
|
readmeVersion?: { id: string; version_num: number; created_at: string } | null
|
|
13
14
|
}
|
|
14
15
|
|
|
15
|
-
interface DatasetSnapshot { datasetVersion: { id: string } }
|
|
16
16
|
interface DatasetAppendResponse { dataset: { id: string; name: string; rowCount: number }; appendedCount: number }
|
|
17
17
|
type UploadRows = Array<Record<string, unknown>>
|
|
18
18
|
type UploadSource = { kind: 'jsonl'; file: string } | { kind: 'rows'; rows: UploadRows; sourceType: 'csv' | 'json' | 'jsonl' }
|
|
@@ -41,10 +41,17 @@ class DatasetInputFailure extends Error {
|
|
|
41
41
|
interface DatasetUploadApi {
|
|
42
42
|
create: (rows: UploadRows, sourceType: string) => Effect.Effect<DatasetUploadResponse, DatasetWriteFailure>
|
|
43
43
|
append: (id: string, rows: UploadRows) => Effect.Effect<DatasetAppendResponse, DatasetWriteFailure>
|
|
44
|
-
snapshot: (id: string) => Effect.Effect<
|
|
44
|
+
snapshot: (id: string) => Effect.Effect<DatasetVersionResponse, DatasetVersionFailure>
|
|
45
45
|
}
|
|
46
46
|
const DatasetUploadApi = Context.Service<DatasetUploadApi>('orizu/DatasetUploadApi')
|
|
47
47
|
|
|
48
|
+
// authedFetch raises this code only when it sent nothing, or after the server
|
|
49
|
+
// answered 401 (it writes nothing) and the stored credentials changed, so it
|
|
50
|
+
// refused to replay. Either way nothing was written (ORI-2431).
|
|
51
|
+
function isAuthContextChange(cause: unknown) {
|
|
52
|
+
return cause instanceof Error && cause.message.startsWith('ORIZU_AUTH_CONTEXT_CHANGED:')
|
|
53
|
+
}
|
|
54
|
+
|
|
48
55
|
// Only the existing authentication client's rejected-token refresh may replay a
|
|
49
56
|
// request. No transient/transport retries belong around these non-idempotent writes.
|
|
50
57
|
function post<T>(path: string, body: unknown, context: string, prefix: string, timeoutMs: number): Effect.Effect<T, DatasetWriteFailure> {
|
|
@@ -62,7 +69,7 @@ function post<T>(path: string, body: unknown, context: string, prefix: string, t
|
|
|
62
69
|
return await parseJsonResponse<T>(response, context)
|
|
63
70
|
},
|
|
64
71
|
catch: cause => cause instanceof DatasetWriteFailure ? cause : new DatasetWriteFailure(
|
|
65
|
-
cause instanceof Error ? cause.message : String(cause), requestStarted ? 'unknown' : 'rejected', { cause },
|
|
72
|
+
cause instanceof Error ? cause.message : String(cause), requestStarted && !isAuthContextChange(cause) ? 'unknown' : 'rejected', { cause },
|
|
66
73
|
),
|
|
67
74
|
}).pipe(Effect.timeoutOrElse({
|
|
68
75
|
duration: timeoutMs,
|
|
@@ -79,7 +86,7 @@ function liveUploadApi(options: DatasetUploadOptions) {
|
|
|
79
86
|
...(options.readmeMarkdown !== null ? { readmeMarkdown: options.readmeMarkdown } : {}),
|
|
80
87
|
}, 'Dataset upload', 'Upload failed', timeoutMs),
|
|
81
88
|
append: (id, rows) => post<DatasetAppendResponse>(`/api/cli/datasets/${encodeURIComponent(id)}/rows`, { rows }, 'Dataset append', 'Append failed', timeoutMs),
|
|
82
|
-
snapshot: id =>
|
|
89
|
+
snapshot: id => datasetVersionSnapshot(id, {}, timeoutMs),
|
|
83
90
|
})
|
|
84
91
|
}
|
|
85
92
|
|
|
@@ -140,6 +147,6 @@ function uploadProgram(options: DatasetUploadOptions) {
|
|
|
140
147
|
})
|
|
141
148
|
}
|
|
142
149
|
|
|
143
|
-
export function transferDatasetUpload(options: DatasetUploadOptions): Promise<{ data: DatasetUploadResponse; version:
|
|
150
|
+
export function transferDatasetUpload(options: DatasetUploadOptions): Promise<{ data: DatasetUploadResponse; version: DatasetVersionResponse }> {
|
|
144
151
|
return expectedPromise(uploadProgram(options).pipe(Effect.scoped, Effect.provide(liveUploadApi(options))))
|
|
145
152
|
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { DatasetVersionFailure, VERSION_MAY_BE_CREATED } from './dataset-version-workflow.js'
|
|
2
|
+
|
|
1
3
|
interface UploadedDatasetIdentity {
|
|
2
4
|
id: string
|
|
3
5
|
name: string
|
|
@@ -20,6 +22,8 @@ export async function ensureDatasetUploadSnapshot<T extends DatasetVersionSnapsh
|
|
|
20
22
|
} catch (error) {
|
|
21
23
|
const detail = error instanceof Error ? error.message : String(error)
|
|
22
24
|
const retryCommand = `orizu datasets versions create --dataset ${dataset.id}`
|
|
25
|
+
// After an unclear outcome the version may exist; check before retrying.
|
|
26
|
+
const mayBeCreated = error instanceof DatasetVersionFailure && error.mayBeCreated
|
|
23
27
|
|
|
24
28
|
if (jsonOutput) {
|
|
25
29
|
throw new Error(JSON.stringify({
|
|
@@ -29,13 +33,16 @@ export async function ensureDatasetUploadSnapshot<T extends DatasetVersionSnapsh
|
|
|
29
33
|
dataset_id: dataset.id,
|
|
30
34
|
dataset_version_id: null,
|
|
31
35
|
retry_command: retryCommand,
|
|
36
|
+
...(mayBeCreated ? { may_be_created: true } : {}),
|
|
32
37
|
}))
|
|
33
38
|
}
|
|
34
39
|
|
|
35
40
|
throw new Error(
|
|
36
41
|
`Dataset upload completed but dataset version creation failed: ${detail}\n` +
|
|
37
42
|
`Dataset ${dataset.name} (${dataset.id}) was created with ${dataset.rowCount} rows. ` +
|
|
38
|
-
|
|
43
|
+
(mayBeCreated
|
|
44
|
+
? `${VERSION_MAY_BE_CREATED} If it is not there, create it with: ${retryCommand}`
|
|
45
|
+
: `Retry only the snapshot step with: ${retryCommand}`)
|
|
39
46
|
)
|
|
40
47
|
}
|
|
41
48
|
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { Cause, Effect, Exit, Result, Schema } from 'effect'
|
|
2
|
+
|
|
3
|
+
import { datasetTransferTimeoutMs } from './dataset-transfer-policy.js'
|
|
4
|
+
import { extractErrorMessage, isJsonErrorResponsePayload } from './error-response.js'
|
|
5
|
+
import { authedFetch } from './http.js'
|
|
6
|
+
import { parseJsonResponse } from './json-response.js'
|
|
7
|
+
|
|
8
|
+
// Loaded only through `await import(...)`, like the README and delete workflows.
|
|
9
|
+
|
|
10
|
+
export const VERSION_MAY_BE_CREATED = "The version may already be created: check the dataset's versions in the web app before creating it again."
|
|
11
|
+
// What the server says when it cannot tell whether the version was saved.
|
|
12
|
+
const SERVER_MAY_BE_CREATED = 'Dataset version may already be created'
|
|
13
|
+
|
|
14
|
+
const CreateReply = Schema.Struct({
|
|
15
|
+
datasetVersion: Schema.Struct({
|
|
16
|
+
id: Schema.NonEmptyString,
|
|
17
|
+
rowCount: Schema.Number,
|
|
18
|
+
artifactFormat: Schema.optional(Schema.String),
|
|
19
|
+
artifactStoragePath: Schema.optional(Schema.String),
|
|
20
|
+
}),
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
export type DatasetVersionResponse = typeof CreateReply.Type
|
|
24
|
+
|
|
25
|
+
export interface DatasetVersionRequest {
|
|
26
|
+
versionLabel?: string | null
|
|
27
|
+
readmeMarkdown?: string | null
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// The version may exist when the server says so, when the reply was lost or
|
|
31
|
+
// unreadable after the request was sent, or when a 5xx did not come from the
|
|
32
|
+
// app (a gateway giving up while the app kept working). Any other error reply
|
|
33
|
+
// is a clear refusal.
|
|
34
|
+
export class DatasetVersionFailure extends Error {
|
|
35
|
+
readonly _tag = 'DatasetVersionFailure'
|
|
36
|
+
constructor(message: string, readonly mayBeCreated: boolean) { super(message) }
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async function appErrorOf(response: Response): Promise<string | null> {
|
|
40
|
+
try {
|
|
41
|
+
const payload: unknown = await response.json()
|
|
42
|
+
return isJsonErrorResponsePayload(response, payload) ? payload.error : null
|
|
43
|
+
} catch {
|
|
44
|
+
return null
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// One request, never retried: a retry after an unknown outcome could add a
|
|
49
|
+
// second version.
|
|
50
|
+
export function datasetVersionSnapshot(datasetId: string, options: DatasetVersionRequest, timeoutMs: number): Effect.Effect<DatasetVersionResponse, DatasetVersionFailure> {
|
|
51
|
+
return Effect.suspend(() => {
|
|
52
|
+
let sent = false
|
|
53
|
+
let timedOut = false
|
|
54
|
+
return Effect.tryPromise({
|
|
55
|
+
try: async signal => {
|
|
56
|
+
const request = new AbortController()
|
|
57
|
+
const abort = () => request.abort()
|
|
58
|
+
const timer = setTimeout(() => { timedOut = true; request.abort() }, timeoutMs)
|
|
59
|
+
if (signal.aborted) abort()
|
|
60
|
+
else signal.addEventListener('abort', abort, { once: true })
|
|
61
|
+
try {
|
|
62
|
+
const response = await authedFetch(`/api/cli/datasets/${encodeURIComponent(datasetId)}/versions`, {
|
|
63
|
+
method: 'POST',
|
|
64
|
+
headers: { 'Content-Type': 'application/json' },
|
|
65
|
+
body: JSON.stringify({
|
|
66
|
+
...(options.versionLabel ? { versionLabel: options.versionLabel } : {}),
|
|
67
|
+
...(options.readmeMarkdown !== undefined && options.readmeMarkdown !== null
|
|
68
|
+
? { readmeMarkdown: options.readmeMarkdown }
|
|
69
|
+
: {}),
|
|
70
|
+
}),
|
|
71
|
+
signal: request.signal,
|
|
72
|
+
}, () => { sent = true })
|
|
73
|
+
if (!response.ok) {
|
|
74
|
+
const serverError = await appErrorOf(response.clone())
|
|
75
|
+
const message = `Failed to create dataset version: ${await extractErrorMessage(response)}`
|
|
76
|
+
throw new DatasetVersionFailure(message, response.status < 400 || serverError === SERVER_MAY_BE_CREATED || (response.status >= 500 && serverError === null))
|
|
77
|
+
}
|
|
78
|
+
const data = await parseJsonResponse<unknown>(response, 'Dataset version create')
|
|
79
|
+
const decoded = Schema.decodeUnknownResult(CreateReply)(data)
|
|
80
|
+
if (Result.isFailure(decoded)) {
|
|
81
|
+
throw new DatasetVersionFailure(`Dataset version create returned an unexpected response (status ${response.status}).`, true)
|
|
82
|
+
}
|
|
83
|
+
return decoded.success
|
|
84
|
+
} finally {
|
|
85
|
+
clearTimeout(timer)
|
|
86
|
+
signal.removeEventListener('abort', abort)
|
|
87
|
+
request.abort()
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
catch: cause => {
|
|
91
|
+
if (timedOut) return new DatasetVersionFailure('DATASET_TRANSFER_TIMEOUT: Dataset version create exceeded its deadline', sent)
|
|
92
|
+
if (cause instanceof DatasetVersionFailure) return cause
|
|
93
|
+
return new DatasetVersionFailure(cause instanceof Error ? cause.message : String(cause), sent)
|
|
94
|
+
},
|
|
95
|
+
})
|
|
96
|
+
})
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export async function createDatasetVersionSnapshot(datasetId: string, options: DatasetVersionRequest): Promise<DatasetVersionResponse> {
|
|
100
|
+
const exit = await Effect.runPromiseExit(datasetVersionSnapshot(datasetId, options, datasetTransferTimeoutMs()).pipe(
|
|
101
|
+
Effect.mapError(failure => new Error(failure.mayBeCreated ? `${failure.message}\n${VERSION_MAY_BE_CREATED}` : failure.message)),
|
|
102
|
+
))
|
|
103
|
+
if (Exit.isSuccess(exit)) return exit.value
|
|
104
|
+
throw Cause.squash(exit.cause)
|
|
105
|
+
}
|
package/src/feedback-cli.ts
CHANGED
|
@@ -419,15 +419,15 @@ function resolveProjectContext(
|
|
|
419
419
|
return { teamSlug: workspaceTeam, projectSlug: null }
|
|
420
420
|
}
|
|
421
421
|
|
|
422
|
-
function lastErrorForContext(
|
|
422
|
+
async function lastErrorForContext(
|
|
423
423
|
configDirectory: string,
|
|
424
424
|
serverBaseUrl: string,
|
|
425
425
|
teamSlug: string | null,
|
|
426
426
|
now: number,
|
|
427
427
|
printErr: (message: string) => void,
|
|
428
428
|
scrubNotice: (message: string) => string
|
|
429
|
-
): LastErrorRecord | null {
|
|
430
|
-
const record = readLastErrorRecord(configDirectory)
|
|
429
|
+
): Promise<LastErrorRecord | null> {
|
|
430
|
+
const record = await readLastErrorRecord(configDirectory)
|
|
431
431
|
if (!record || !isLastErrorRecordShape(record)) return null
|
|
432
432
|
if (teamSlug === null) {
|
|
433
433
|
printErr('last error not attached: no team context')
|
|
@@ -633,7 +633,7 @@ export async function feedbackCommand(args: string[], io: FeedbackCliIo): Promis
|
|
|
633
633
|
const values = { ...fromFile, ...parsed.values }
|
|
634
634
|
const lastError = parsed.suppressLastError
|
|
635
635
|
? null
|
|
636
|
-
: lastErrorForContext(
|
|
636
|
+
: await lastErrorForContext(
|
|
637
637
|
configDirectory,
|
|
638
638
|
baseUrl,
|
|
639
639
|
projectContext.teamSlug,
|
package/src/help.ts
CHANGED
|
@@ -948,17 +948,43 @@ export const COMMAND_DOCS: CliCommandDoc[] = [
|
|
|
948
948
|
summary: 'Create a dataset version.',
|
|
949
949
|
group: 'Datasets',
|
|
950
950
|
},
|
|
951
|
+
{
|
|
952
|
+
path: ['datasets', 'splits', 'list'],
|
|
953
|
+
usage: 'orizu datasets splits list <datasetVersionId> [--json]',
|
|
954
|
+
summary: 'Discover saved split sets, partition names and row counts for an immutable dataset version.',
|
|
955
|
+
group: 'Datasets',
|
|
956
|
+
options: [{ name: '--json', help: 'Print saved split sets and partition row counts as JSON.' }],
|
|
957
|
+
examples: ['orizu datasets splits list DATASET_VERSION_ID --json'],
|
|
958
|
+
},
|
|
959
|
+
{
|
|
960
|
+
path: ['datasets', 'splits', 'show'],
|
|
961
|
+
usage: 'orizu datasets splits show <splitSetId> [--json]',
|
|
962
|
+
summary: 'Inspect the pinned version, set identity and exact ordered row membership of every partition, including empty splits.',
|
|
963
|
+
group: 'Datasets',
|
|
964
|
+
options: [{ name: '--json', help: 'Export reusable assignments: name, strategy, seed, metadata and partitions [{name, row_ids}], with source identity for inspection. Import uses the explicit target version.' }],
|
|
965
|
+
examples: ['orizu datasets splits show SPLIT_SET_ID --json > split.json', 'orizu datasets splits create TARGET_DATASET_VERSION_ID --from-file split.json --json'],
|
|
966
|
+
},
|
|
951
967
|
{
|
|
952
968
|
path: ['datasets', 'splits', 'create'],
|
|
953
969
|
usage: 'orizu datasets splits create <datasetVersionId> [--from-file <split.json>] [--json]',
|
|
954
|
-
summary: 'Create a split set for
|
|
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.',
|
|
955
971
|
group: 'Datasets',
|
|
956
972
|
},
|
|
957
973
|
{
|
|
958
974
|
path: ['datasets', 'download'],
|
|
959
|
-
usage: 'orizu datasets download [--dataset <datasetId|datasetUrl>] [--project <team/project>] [--format <csv|json|jsonl>] [--out <path>]',
|
|
960
|
-
summary: 'Download
|
|
975
|
+
usage: 'orizu datasets download [--dataset <datasetId|datasetUrl>] [--project <team/project>] [--dataset-version <id> --split-set <id> --split <name>] [--format <csv|json|jsonl>] [--out <path>]',
|
|
976
|
+
summary: 'Download live dataset rows, or one named split from an immutable version with canonical row IDs and existing fields in partition order. Pinned reads require all three selectors and JSON or JSONL; incomplete artifacts fail without falling back to live rows.',
|
|
961
977
|
group: 'Datasets',
|
|
978
|
+
options: [
|
|
979
|
+
{ name: '--dataset <datasetId|datasetUrl>', help: 'Dataset for an existing live download.' },
|
|
980
|
+
{ name: '--project <team/project>', help: 'Project scope for live dataset selection.' },
|
|
981
|
+
{ name: '--dataset-version <id>', help: 'Immutable version for a pinned split download; requires --split-set and --split.' },
|
|
982
|
+
{ name: '--split-set <id>', help: 'Saved assignment set belonging to the selected version.' },
|
|
983
|
+
{ name: '--split <name>', help: 'Exact partition name, for example validation. For names beginning with --, use --split=--validation (also --split=--json or --split=--help).' },
|
|
984
|
+
{ name: '--format <csv|json|jsonl>', help: 'Output format. Pinned split downloads support json and jsonl only.' },
|
|
985
|
+
{ name: '--out <path>', help: 'Write rows to a file.' },
|
|
986
|
+
],
|
|
987
|
+
examples: ['orizu datasets download --dataset-version DATASET_VERSION_ID --split-set SPLIT_SET_ID --split validation --format jsonl --out validation.jsonl', 'orizu datasets download --dataset DATASET_ID --format jsonl --out dataset.jsonl'],
|
|
962
988
|
},
|
|
963
989
|
{
|
|
964
990
|
path: ['datasets', 'append'],
|