orizu 0.5.26 → 0.5.28

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 (164) hide show
  1. package/README.md +19 -0
  2. package/dist/artifacts-git-runtime.d.ts +34 -0
  3. package/dist/artifacts-git-runtime.d.ts.map +1 -0
  4. package/dist/artifacts-git-runtime.js +312 -0
  5. package/dist/cloudflare-artifacts-git-remote.d.ts +5 -0
  6. package/dist/cloudflare-artifacts-git-remote.d.ts.map +1 -0
  7. package/dist/cloudflare-artifacts-git-remote.js +21 -0
  8. package/dist/dataset-transfer-download.d.ts +2 -0
  9. package/dist/dataset-transfer-download.d.ts.map +1 -0
  10. package/dist/dataset-transfer-download.js +66 -0
  11. package/dist/dataset-transfer-policy.d.ts +2 -0
  12. package/dist/dataset-transfer-policy.d.ts.map +1 -0
  13. package/dist/dataset-transfer-policy.js +8 -0
  14. package/dist/dataset-transfer-upload.d.ts +42 -0
  15. package/dist/dataset-transfer-upload.d.ts.map +1 -0
  16. package/dist/dataset-transfer-upload.js +110 -0
  17. package/dist/git-credential.d.ts.map +1 -1
  18. package/dist/git-credential.js +1 -1
  19. package/dist/git-recovery-environment.d.ts +1 -0
  20. package/dist/git-recovery-environment.d.ts.map +1 -1
  21. package/dist/git-recovery-environment.js +1 -1
  22. package/dist/help.d.ts.map +1 -1
  23. package/dist/help.js +44 -11
  24. package/dist/hosted-boot.d.ts +75 -2
  25. package/dist/hosted-boot.d.ts.map +1 -1
  26. package/dist/hosted-boot.js +585 -102
  27. package/dist/hosted-bootstrap.d.ts +2 -0
  28. package/dist/hosted-bootstrap.d.ts.map +1 -1
  29. package/dist/hosted-bootstrap.js +12 -3
  30. package/dist/hosted-harness-opencode.d.ts +8 -9
  31. package/dist/hosted-harness-opencode.d.ts.map +1 -1
  32. package/dist/hosted-harness-opencode.js +126 -24
  33. package/dist/hosted-harness.d.ts +3 -0
  34. package/dist/hosted-harness.d.ts.map +1 -1
  35. package/dist/hosted-harness.js +4 -1
  36. package/dist/hosted-harvest.d.ts +30 -9
  37. package/dist/hosted-harvest.d.ts.map +1 -1
  38. package/dist/hosted-harvest.js +222 -28
  39. package/dist/hosted-loop-lifecycle.d.ts +15 -3
  40. package/dist/hosted-loop-lifecycle.d.ts.map +1 -1
  41. package/dist/hosted-loop-lifecycle.js +6 -4
  42. package/dist/hosted-loop.d.ts +47 -7
  43. package/dist/hosted-loop.d.ts.map +1 -1
  44. package/dist/hosted-loop.js +310 -64
  45. package/dist/hosted-question-limits.d.ts +7 -0
  46. package/dist/hosted-question-limits.d.ts.map +1 -0
  47. package/dist/hosted-question-limits.js +6 -0
  48. package/dist/hosted-question.d.ts +27 -0
  49. package/dist/hosted-question.d.ts.map +1 -0
  50. package/dist/hosted-question.js +118 -0
  51. package/dist/hosted-run-event-sink.d.ts +10 -5
  52. package/dist/hosted-run-event-sink.d.ts.map +1 -1
  53. package/dist/hosted-run-event-sink.js +12 -8
  54. package/dist/hosted-runtime-assets.d.ts +16 -7
  55. package/dist/hosted-runtime-assets.d.ts.map +1 -1
  56. package/dist/hosted-runtime-assets.js +40 -12
  57. package/dist/hosted-session-cli.d.ts.map +1 -1
  58. package/dist/hosted-session-cli.js +10 -19
  59. package/dist/http.d.ts +1 -1
  60. package/dist/http.d.ts.map +1 -1
  61. package/dist/http.js +10 -4
  62. package/dist/index.d.ts.map +1 -1
  63. package/dist/index.js +113 -92
  64. package/dist/instruction-set-gepa-launch.d.ts +1 -1
  65. package/dist/instruction-set-gepa-launch.d.ts.map +1 -1
  66. package/dist/instruction-set-gepa-launch.js +5 -3
  67. package/dist/instruction-set-loader.d.ts.map +1 -1
  68. package/dist/instruction-set-loader.js +63 -2
  69. package/dist/instruction-set-lock/index.d.ts +51 -0
  70. package/dist/instruction-set-lock/index.d.ts.map +1 -0
  71. package/dist/instruction-set-lock/index.js +459 -0
  72. package/dist/instruction-set-lock/schema.v1.d.ts +136 -0
  73. package/dist/instruction-set-lock/schema.v1.d.ts.map +1 -0
  74. package/dist/instruction-set-lock/schema.v1.js +155 -0
  75. package/dist/instruction-set-provenance.d.ts +26 -0
  76. package/dist/instruction-set-provenance.d.ts.map +1 -0
  77. package/dist/instruction-set-provenance.js +68 -0
  78. package/dist/instruction-set-sync/helpers.d.ts +22 -0
  79. package/dist/instruction-set-sync/helpers.d.ts.map +1 -0
  80. package/dist/instruction-set-sync/helpers.js +677 -0
  81. package/dist/instruction-set-sync/index.d.ts +60 -0
  82. package/dist/instruction-set-sync/index.d.ts.map +1 -0
  83. package/dist/instruction-set-sync/index.js +689 -0
  84. package/dist/instruction-set-update/index.d.ts +42 -0
  85. package/dist/instruction-set-update/index.d.ts.map +1 -0
  86. package/dist/instruction-set-update/index.js +445 -0
  87. package/dist/instruction-set-verify/index.d.ts +27 -0
  88. package/dist/instruction-set-verify/index.d.ts.map +1 -0
  89. package/dist/instruction-set-verify/index.js +865 -0
  90. package/dist/instruction-sets-cli.d.ts +1 -4
  91. package/dist/instruction-sets-cli.d.ts.map +1 -1
  92. package/dist/instruction-sets-cli.js +220 -109
  93. package/dist/model-config-fields.d.ts +35 -0
  94. package/dist/model-config-fields.d.ts.map +1 -0
  95. package/dist/model-config-fields.js +28 -0
  96. package/dist/optimizations-list-cli.d.ts.map +1 -1
  97. package/dist/optimizations-list-cli.js +7 -4
  98. package/dist/score-format.d.ts +2 -0
  99. package/dist/score-format.d.ts.map +1 -0
  100. package/dist/score-format.js +18 -0
  101. package/dist/workbench-repo-contract.d.ts +65 -0
  102. package/dist/workbench-repo-contract.d.ts.map +1 -0
  103. package/dist/workbench-repo-contract.js +250 -0
  104. package/dist/workspace-bootstrap-job.d.ts +45 -0
  105. package/dist/workspace-bootstrap-job.d.ts.map +1 -0
  106. package/dist/workspace-bootstrap-job.js +431 -0
  107. package/dist/workspace-bootstrap-seed.d.ts +74 -0
  108. package/dist/workspace-bootstrap-seed.d.ts.map +1 -0
  109. package/dist/workspace-bootstrap-seed.js +209 -0
  110. package/package.json +4 -3
  111. package/src/artifacts-git-runtime.ts +360 -0
  112. package/src/cloudflare-artifacts-git-remote.ts +24 -0
  113. package/src/dataset-transfer-download.ts +61 -0
  114. package/src/dataset-transfer-policy.ts +8 -0
  115. package/src/dataset-transfer-upload.ts +145 -0
  116. package/src/git-credential.ts +1 -2
  117. package/src/git-recovery-environment.ts +1 -1
  118. package/src/help.ts +44 -11
  119. package/src/hosted-boot.ts +644 -109
  120. package/src/hosted-bootstrap.ts +14 -3
  121. package/src/hosted-harness-opencode.ts +130 -28
  122. package/src/hosted-harness.ts +4 -1
  123. package/src/hosted-harvest.ts +258 -33
  124. package/src/hosted-loop-lifecycle.ts +19 -5
  125. package/src/hosted-loop.ts +340 -68
  126. package/src/hosted-question-limits.ts +6 -0
  127. package/src/hosted-question.ts +145 -0
  128. package/src/hosted-run-event-sink.ts +24 -12
  129. package/src/hosted-runtime-assets.ts +51 -11
  130. package/src/hosted-session-cli.ts +10 -24
  131. package/src/http.ts +10 -4
  132. package/src/index.ts +108 -134
  133. package/src/instruction-set-gepa-launch.ts +4 -4
  134. package/src/instruction-set-loader.ts +53 -2
  135. package/src/instruction-set-lock/index.ts +561 -0
  136. package/src/instruction-set-lock/schema.v1.json +141 -0
  137. package/src/instruction-set-lock/schema.v1.ts +155 -0
  138. package/src/instruction-set-provenance.ts +89 -0
  139. package/src/instruction-set-sync/helpers.ts +707 -0
  140. package/src/instruction-set-sync/index.ts +791 -0
  141. package/src/instruction-set-update/index.ts +488 -0
  142. package/src/instruction-set-verify/index.ts +1067 -0
  143. package/src/instruction-sets-cli.ts +226 -114
  144. package/src/model-config-fields.ts +31 -0
  145. package/src/optimizations-list-cli.ts +7 -4
  146. package/src/score-format.ts +16 -0
  147. package/src/workbench-repo-contract.ts +314 -0
  148. package/src/workspace-bootstrap-job.ts +611 -0
  149. package/src/workspace-bootstrap-seed.ts +350 -0
  150. package/vendor/orizu-gepa/src/orizu_gepa_connector/instruction_set_loader.py +46 -2
  151. package/vendor/skills/orizu/SKILL.md +4 -2
  152. package/vendor/skills/orizu/flows/promote.md +4 -0
  153. package/vendor/skills/orizu/references/assess-and-plan.md +1 -1
  154. package/vendor/skills/orizu/references/authority-map.md +1 -1
  155. package/vendor/skills/orizu/references/cli-reference.md +151 -35
  156. package/vendor/skills/orizu/references/instructions-after-prompts.md +3 -3
  157. package/vendor/skills/orizu/references/migrate-existing-gepa-setup.md +5 -6
  158. package/vendor/skills/orizu/references/migrate-hand-rolled-instruction-layout.md +252 -0
  159. package/vendor/skills/orizu/references/optimization-reports.md +5 -4
  160. package/vendor/skills/orizu/references/optimization-with-gepa.md +1 -1
  161. package/vendor/skills/orizu/references/orizu-in-your-codebase.md +195 -0
  162. package/vendor/skills/orizu/references/primer.md +4 -2
  163. package/vendor/skills/orizu/references/prompt-control-plane.md +33 -29
  164. package/vendor/skills/orizu/references/vocabulary.md +1 -1
package/README.md CHANGED
@@ -427,6 +427,25 @@ Task and dataset export support:
427
427
  - `json`
428
428
  - `jsonl`
429
429
 
430
+ Dataset `upload` and `push` create an immutable version after the rows are
431
+ acknowledged. With `--json`, upload prints one result document. A failed snapshot
432
+ reports the uploaded dataset identity so recovery can target the snapshot step.
433
+ If a write's response is lost or times out, inspect the dataset before retrying:
434
+ the last chunk may already have committed.
435
+
436
+ Dataset downloads retry transient read failures up to three attempts. Each
437
+ transfer request, including its response body, has a two-minute deadline;
438
+ `ORIZU_DATASET_TRANSFER_TIMEOUT_MS` accepts 1–300000 milliseconds. Downloads
439
+ receive the complete response before writing to the destination. Existing files
440
+ keep their inode, ownership, permissions and ACLs; writes follow existing
441
+ symlinks and update hardlinked readers. New files request mode `0600`, subject
442
+ to the filesystem's access rules. A failed network transfer leaves an existing output
443
+ untouched. The final write retains ordinary filesystem behavior: it is not
444
+ atomic, and a disk failure during that write can leave partial output.
445
+ SIGINT/SIGTERM cancels a download. Upload writes
446
+ have no additional transient retries; the existing session refresh behavior is
447
+ unchanged.
448
+
430
449
  ## Interactive And Automated Usage
431
450
 
432
451
  Many commands can prompt for missing team, project, app, dataset, or task selections in an interactive terminal. In scripts and CI, pass explicit flags instead:
@@ -0,0 +1,34 @@
1
+ export interface GitCommandResult {
2
+ exitCode: number;
3
+ stdout: string;
4
+ stderr: string;
5
+ timedOut?: boolean;
6
+ cancelled?: boolean;
7
+ }
8
+ export interface GitAuth {
9
+ askPassPath: string;
10
+ token: string;
11
+ }
12
+ export interface GitRunOptions {
13
+ cwd: string;
14
+ auth?: GitAuth;
15
+ env?: NodeJS.ProcessEnv;
16
+ timeoutMs?: number;
17
+ signal?: AbortSignal;
18
+ commitIdentityEnv?: Readonly<Record<string, string>>;
19
+ }
20
+ export type GitRunner = (args: string[], options: GitRunOptions) => Promise<GitCommandResult>;
21
+ export declare function assertCommitIdentityEnv(overlay: Readonly<Record<string, string>>): void;
22
+ export declare function parseArtifactTokenExpiry(token: string): number | null;
23
+ export declare function credentialSecretVariants(secrets: readonly (string | undefined)[]): string[];
24
+ export declare function redactSecrets(value: string, secrets: readonly (string | undefined)[]): string;
25
+ export declare function createEphemeralAskPass(root: string): Promise<{
26
+ path: string;
27
+ dispose: () => Promise<void>;
28
+ }>;
29
+ export declare function buildGitAuthEnvironment(auth: GitAuth, base?: NodeJS.ProcessEnv): NodeJS.ProcessEnv;
30
+ export declare function assertSafeGitInvocation(args: readonly string[], secrets?: readonly string[]): void;
31
+ export declare const defaultGitRunner: GitRunner;
32
+ export declare function gitOk(runGit: GitRunner, args: string[], options: GitRunOptions, operation: string): Promise<string>;
33
+ export declare function revParse(runGit: GitRunner, cwd: string, ref: string): Promise<string>;
34
+ //# sourceMappingURL=artifacts-git-runtime.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"artifacts-git-runtime.d.ts","sourceRoot":"","sources":["../src/artifacts-git-runtime.ts"],"names":[],"mappings":"AAoCA,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;IACd,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,SAAS,CAAC,EAAE,OAAO,CAAA;CACpB;AAED,MAAM,WAAW,OAAO;IACtB,WAAW,EAAE,MAAM,CAAA;IACnB,KAAK,EAAE,MAAM,CAAA;CACd;AAED,MAAM,WAAW,aAAa;IAC5B,GAAG,EAAE,MAAM,CAAA;IACX,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAA;IACvB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,MAAM,CAAC,EAAE,WAAW,CAAA;IACpB,iBAAiB,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;CACrD;AAED,MAAM,MAAM,SAAS,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE,aAAa,KAAK,OAAO,CAAC,gBAAgB,CAAC,CAAA;AAE7F,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,GAAG,IAAI,CASvF;AAED,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAkBrE;AAED,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,SAAS,CAAC,MAAM,GAAG,SAAS,CAAC,EAAE,GAAG,MAAM,EAAE,CAS3F;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,CAAC,MAAM,GAAG,SAAS,CAAC,EAAE,GAAG,MAAM,CAK7F;AAED,wBAAsB,sBAAsB,CAC1C,IAAI,EAAE,MAAM,GACX,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAAE,CAAC,CAsBzD;AAUD,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,OAAO,EACb,IAAI,GAAE,MAAM,CAAC,UAAwB,GACpC,MAAM,CAAC,UAAU,CAkBnB;AAgBD,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,OAAO,GAAE,SAAS,MAAM,EAAO,GAC9B,IAAI,CAYN;AAWD,eAAO,MAAM,gBAAgB,EAAE,SAoI3B,CAAA;AAEJ,wBAAsB,KAAK,CACzB,MAAM,EAAE,SAAS,EACjB,IAAI,EAAE,MAAM,EAAE,EACd,OAAO,EAAE,aAAa,EACtB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,MAAM,CAAC,CAUjB;AAED,wBAAsB,QAAQ,CAAC,MAAM,EAAE,SAAS,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAE3F"}
@@ -0,0 +1,312 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { chmod, mkdir, rm, writeFile } from 'node:fs/promises';
3
+ import { join } from 'node:path';
4
+ const ARTIFACT_TOKEN_BODY = /^[^\s\u0000-\u001f\u007f]{16,1024}$/;
5
+ const ARTIFACT_TOKEN_ADVISORY_SUFFIX = '?expires=';
6
+ const ARTIFACT_TOKEN_BARE_BODY = /art_v1_[0-9a-fA-F]{40}/;
7
+ const DEFAULT_GIT_TIMEOUT_MS = 30_000;
8
+ const GIT_TERMINATION_GRACE_MS = 1_000;
9
+ const GIT_CONTEXT_ENV_KEYS = [
10
+ 'GIT_DIR',
11
+ 'GIT_WORK_TREE',
12
+ 'GIT_INDEX_FILE',
13
+ 'GIT_OBJECT_DIRECTORY',
14
+ 'GIT_COMMON_DIR',
15
+ 'GIT_NAMESPACE',
16
+ ];
17
+ const GIT_CHILD_ENV_ALLOWLIST = [
18
+ 'PATH',
19
+ 'TMPDIR',
20
+ 'TMP',
21
+ 'TEMP',
22
+ 'SYSTEMROOT',
23
+ 'WINDIR',
24
+ 'COMSPEC',
25
+ 'PATHEXT',
26
+ 'LANG',
27
+ 'TZ',
28
+ 'SSL_CERT_FILE',
29
+ 'SSL_CERT_DIR',
30
+ 'CURL_CA_BUNDLE',
31
+ 'GIT_SSL_CAINFO',
32
+ ];
33
+ const COMMIT_IDENTITY_ENV_PATTERN = /^GIT_(AUTHOR|COMMITTER)_(NAME|EMAIL|DATE)$/;
34
+ export function assertCommitIdentityEnv(overlay) {
35
+ for (const key of Object.keys(overlay)) {
36
+ if (!COMMIT_IDENTITY_ENV_PATTERN.test(key)) {
37
+ throw new Error(`Refusing Git environment overlay ${JSON.stringify(key)}: only ` +
38
+ 'GIT_AUTHOR_/GIT_COMMITTER_ NAME, EMAIL and DATE may be set');
39
+ }
40
+ }
41
+ }
42
+ export function parseArtifactTokenExpiry(token) {
43
+ const separator = token.lastIndexOf(ARTIFACT_TOKEN_ADVISORY_SUFFIX);
44
+ if (separator !== -1) {
45
+ const body = token.slice(0, separator);
46
+ const suffix = token.slice(separator + ARTIFACT_TOKEN_ADVISORY_SUFFIX.length);
47
+ if (!ARTIFACT_TOKEN_BODY.test(body) || !/^\d{1,15}$/.test(suffix)) {
48
+ throw new Error('Artifacts returned an unexpected token format');
49
+ }
50
+ const expiry = Number(suffix);
51
+ if (!Number.isSafeInteger(expiry)) {
52
+ throw new Error('Artifacts returned an invalid token expiry');
53
+ }
54
+ return expiry;
55
+ }
56
+ if (!ARTIFACT_TOKEN_BODY.test(token)) {
57
+ throw new Error('Artifacts returned an unexpected token format');
58
+ }
59
+ return null;
60
+ }
61
+ export function credentialSecretVariants(secrets) {
62
+ const variants = new Set();
63
+ for (const secret of secrets) {
64
+ if (!secret)
65
+ continue;
66
+ variants.add(secret);
67
+ const bareBody = ARTIFACT_TOKEN_BARE_BODY.exec(secret)?.[0];
68
+ if (bareBody)
69
+ variants.add(bareBody);
70
+ }
71
+ return [...variants];
72
+ }
73
+ export function redactSecrets(value, secrets) {
74
+ return credentialSecretVariants(secrets)
75
+ .filter((secret) => secret.length >= 4)
76
+ .sort((left, right) => right.length - left.length)
77
+ .reduce((redacted, secret) => redacted.split(secret).join('[REDACTED]'), value);
78
+ }
79
+ export async function createEphemeralAskPass(root) {
80
+ const authDir = join(root, 'git-auth');
81
+ await mkdir(authDir, { recursive: true, mode: 0o700 });
82
+ await chmod(authDir, 0o700);
83
+ const path = join(authDir, 'askpass.sh');
84
+ const script = `#!/bin/sh
85
+ case "$1" in
86
+ *sername*) printf '%s\\n' x ;;
87
+ *assword*)
88
+ IFS= read -r password <&3 || exit 1
89
+ [ -n "$password" ] || exit 1
90
+ printf '%s\\n' "$password"
91
+ ;;
92
+ *) exit 1 ;;
93
+ esac
94
+ `;
95
+ await writeFile(path, script, { mode: 0o700 });
96
+ await chmod(path, 0o700);
97
+ return {
98
+ path,
99
+ dispose: () => rm(authDir, { recursive: true, force: true }),
100
+ };
101
+ }
102
+ function minimalGitChildEnvironment(base) {
103
+ const selected = { NODE_ENV: base.NODE_ENV };
104
+ for (const key of GIT_CHILD_ENV_ALLOWLIST) {
105
+ if (base[key] !== undefined)
106
+ selected[key] = base[key];
107
+ }
108
+ return selected;
109
+ }
110
+ export function buildGitAuthEnvironment(auth, base = process.env) {
111
+ parseArtifactTokenExpiry(auth.token);
112
+ const env = {
113
+ ...minimalGitChildEnvironment(base),
114
+ LC_ALL: 'C',
115
+ GIT_TERMINAL_PROMPT: '0',
116
+ GIT_ASKPASS: auth.askPassPath,
117
+ GIT_ASKPASS_REQUIRE: 'force',
118
+ GIT_CONFIG_NOSYSTEM: '1',
119
+ GIT_CONFIG_GLOBAL: '/dev/null',
120
+ GIT_CONFIG_COUNT: '2',
121
+ GIT_CONFIG_KEY_0: 'credential.helper',
122
+ GIT_CONFIG_VALUE_0: '',
123
+ GIT_CONFIG_KEY_1: 'credential.useHttpPath',
124
+ GIT_CONFIG_VALUE_1: 'true',
125
+ };
126
+ for (const key of GIT_CONTEXT_ENV_KEYS)
127
+ delete env[key];
128
+ return env;
129
+ }
130
+ function buildUnauthenticatedGitEnvironment(base = process.env) {
131
+ const env = {
132
+ ...minimalGitChildEnvironment(base),
133
+ LC_ALL: 'C',
134
+ GIT_TERMINAL_PROMPT: '0',
135
+ GIT_CONFIG_NOSYSTEM: '1',
136
+ GIT_CONFIG_GLOBAL: '/dev/null',
137
+ };
138
+ for (const key of GIT_CONTEXT_ENV_KEYS)
139
+ delete env[key];
140
+ return env;
141
+ }
142
+ export function assertSafeGitInvocation(args, secrets = []) {
143
+ const joined = args.join('\u0000');
144
+ for (const secret of credentialSecretVariants(secrets)) {
145
+ if (joined.includes(secret))
146
+ throw new Error('Refusing to place a credential in Git argv');
147
+ }
148
+ for (const arg of args) {
149
+ if (!/^https?:\/\//i.test(arg))
150
+ continue;
151
+ const url = new URL(arg);
152
+ if (url.username || url.password) {
153
+ throw new Error('Refusing a Git remote URL containing userinfo');
154
+ }
155
+ }
156
+ }
157
+ function signalGitProcess(child, signal) {
158
+ try {
159
+ if (process.platform !== 'win32' && child.pid)
160
+ process.kill(-child.pid, signal);
161
+ else
162
+ child.kill(signal);
163
+ }
164
+ catch {
165
+ // The process may have exited between the state check and the signal.
166
+ }
167
+ }
168
+ export const defaultGitRunner = (args, { cwd, auth, env: suppliedEnv, commitIdentityEnv, timeoutMs = DEFAULT_GIT_TIMEOUT_MS, signal }) => new Promise((resolve, reject) => {
169
+ if (!Number.isInteger(timeoutMs) || timeoutMs <= 0) {
170
+ reject(new Error('Git timeout must be a positive integer'));
171
+ return;
172
+ }
173
+ const secrets = auth ? [auth.token] : [];
174
+ assertSafeGitInvocation(args, secrets);
175
+ if (signal?.aborted) {
176
+ resolve({
177
+ exitCode: 130,
178
+ stdout: '',
179
+ stderr: 'Git operation cancelled',
180
+ cancelled: true,
181
+ });
182
+ return;
183
+ }
184
+ const baseEnv = auth
185
+ ? buildGitAuthEnvironment(auth, suppliedEnv)
186
+ : buildUnauthenticatedGitEnvironment(suppliedEnv);
187
+ let env = baseEnv;
188
+ if (commitIdentityEnv) {
189
+ assertCommitIdentityEnv(commitIdentityEnv);
190
+ env = { ...baseEnv, ...commitIdentityEnv };
191
+ }
192
+ const spawnArgs = auth ? ['-c', 'core.hooksPath=/dev/null', ...args] : args;
193
+ const child = spawn('git', spawnArgs, {
194
+ cwd,
195
+ env,
196
+ detached: process.platform !== 'win32',
197
+ stdio: auth ? ['ignore', 'pipe', 'pipe', 'pipe'] : ['ignore', 'pipe', 'pipe'],
198
+ });
199
+ let stdout = '';
200
+ let stderr = '';
201
+ let termination = null;
202
+ let settled = false;
203
+ let killTimer = null;
204
+ let hardStopTimer = null;
205
+ let timeoutTimer = null;
206
+ const cleanup = () => {
207
+ if (timeoutTimer)
208
+ clearTimeout(timeoutTimer);
209
+ if (killTimer)
210
+ clearTimeout(killTimer);
211
+ if (hardStopTimer)
212
+ clearTimeout(hardStopTimer);
213
+ signal?.removeEventListener('abort', cancel);
214
+ };
215
+ const finish = (exitCode) => {
216
+ if (settled)
217
+ return;
218
+ settled = true;
219
+ cleanup();
220
+ resolve({
221
+ exitCode,
222
+ stdout: redactSecrets(stdout, secrets),
223
+ stderr: redactSecrets(stderr, secrets),
224
+ timedOut: termination === 'timeout' || undefined,
225
+ cancelled: termination === 'cancelled' || undefined,
226
+ });
227
+ };
228
+ const terminate = (reason) => {
229
+ if (termination || settled)
230
+ return;
231
+ termination = reason;
232
+ signalGitProcess(child, 'SIGTERM');
233
+ killTimer = setTimeout(() => signalGitProcess(child, 'SIGKILL'), GIT_TERMINATION_GRACE_MS);
234
+ killTimer.unref?.();
235
+ hardStopTimer = setTimeout(() => finish(reason === 'timeout' ? 124 : 130), GIT_TERMINATION_GRACE_MS * 2);
236
+ hardStopTimer.unref?.();
237
+ };
238
+ const fail = (error) => {
239
+ if (settled)
240
+ return;
241
+ settled = true;
242
+ cleanup();
243
+ signalGitProcess(child, 'SIGKILL');
244
+ reject(error);
245
+ };
246
+ const cancel = () => terminate('cancelled');
247
+ timeoutTimer = setTimeout(() => terminate('timeout'), timeoutMs);
248
+ timeoutTimer.unref?.();
249
+ signal?.addEventListener('abort', cancel, { once: true });
250
+ if (signal?.aborted)
251
+ cancel();
252
+ const childStdout = child.stdout;
253
+ const childStderr = child.stderr;
254
+ if (!childStdout || !childStderr) {
255
+ fail(new Error('Could not capture Git process output'));
256
+ return;
257
+ }
258
+ childStdout.setEncoding('utf8');
259
+ childStderr.setEncoding('utf8');
260
+ childStdout.on('data', (chunk) => {
261
+ stdout += chunk;
262
+ });
263
+ childStderr.on('data', (chunk) => {
264
+ stderr += chunk;
265
+ });
266
+ child.on('error', (error) => {
267
+ if (termination) {
268
+ finish(termination === 'timeout' ? 124 : 130);
269
+ return;
270
+ }
271
+ fail(error);
272
+ });
273
+ child.on('close', (code) => {
274
+ finish(termination === 'timeout' ? 124 : termination === 'cancelled' ? 130 : (code ?? 1));
275
+ });
276
+ if (auth) {
277
+ const credentialPipe = child.stdio[3];
278
+ if (!credentialPipe) {
279
+ fail(new Error('Could not establish one-use Git credential pipe'));
280
+ return;
281
+ }
282
+ credentialPipe.on('error', () => {
283
+ /* EPIPE if Git fails before auth. */
284
+ });
285
+ if (termination) {
286
+ credentialPipe.destroy();
287
+ return;
288
+ }
289
+ try {
290
+ credentialPipe.end(`${auth.token}\n`);
291
+ }
292
+ catch (error) {
293
+ fail(error instanceof Error ? error : new Error('Could not write the one-use Git credential'));
294
+ }
295
+ }
296
+ });
297
+ export async function gitOk(runGit, args, options, operation) {
298
+ const result = await runGit(args, options);
299
+ if (result.timedOut)
300
+ throw new Error(`${operation} timed out`);
301
+ if (result.cancelled)
302
+ throw new Error(`${operation} cancelled`);
303
+ if (result.exitCode !== 0) {
304
+ const secrets = options.auth ? [options.auth.token] : [];
305
+ const detail = redactSecrets((result.stderr || result.stdout).trim(), secrets);
306
+ throw new Error(detail ? `${operation} failed: ${detail}` : `${operation} failed`);
307
+ }
308
+ return result.stdout.trim();
309
+ }
310
+ export async function revParse(runGit, cwd, ref) {
311
+ return gitOk(runGit, ['rev-parse', '--verify', ref], { cwd }, `resolve ${ref}`);
312
+ }
@@ -0,0 +1,5 @@
1
+ export declare const CLOUDFLARE_ARTIFACTS_HOST_PATTERN: RegExp;
2
+ export declare const CLOUDFLARE_ARTIFACTS_GIT_PATH_PATTERN: RegExp;
3
+ export declare function parseCanonicalCredentialFreeHttpsUrl(remote: unknown): URL | null;
4
+ export declare function isValidCloudflareArtifactsGitRemote(remote: unknown): remote is string;
5
+ //# sourceMappingURL=cloudflare-artifacts-git-remote.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cloudflare-artifacts-git-remote.d.ts","sourceRoot":"","sources":["../src/cloudflare-artifacts-git-remote.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,iCAAiC,QACC,CAAA;AAE/C,eAAO,MAAM,qCAAqC,QACmC,CAAA;AAErF,wBAAgB,oCAAoC,CAAC,MAAM,EAAE,OAAO,GAAG,GAAG,GAAG,IAAI,CAWhF;AAED,wBAAgB,mCAAmC,CAAC,MAAM,EAAE,OAAO,GAAG,MAAM,IAAI,MAAM,CAIrF"}
@@ -0,0 +1,21 @@
1
+ export const CLOUDFLARE_ARTIFACTS_HOST_PATTERN = /^[0-9a-f]{32}\.artifacts\.cloudflare\.net$/u;
2
+ export const CLOUDFLARE_ARTIFACTS_GIT_PATH_PATTERN = /^\/git\/[A-Za-z0-9][A-Za-z0-9._-]{0,255}\/[A-Za-z0-9][A-Za-z0-9._-]{0,255}\.git$/u;
3
+ export function parseCanonicalCredentialFreeHttpsUrl(remote) {
4
+ if (typeof remote !== 'string')
5
+ return null;
6
+ try {
7
+ const parsed = new URL(remote);
8
+ return parsed.protocol === 'https:' && !parsed.username && !parsed.password &&
9
+ !parsed.port && !parsed.search && !parsed.hash && parsed.toString() === remote
10
+ ? parsed
11
+ : null;
12
+ }
13
+ catch {
14
+ return null;
15
+ }
16
+ }
17
+ export function isValidCloudflareArtifactsGitRemote(remote) {
18
+ const parsed = parseCanonicalCredentialFreeHttpsUrl(remote);
19
+ return parsed !== null && CLOUDFLARE_ARTIFACTS_HOST_PATTERN.test(parsed.hostname) &&
20
+ CLOUDFLARE_ARTIFACTS_GIT_PATH_PATTERN.test(parsed.pathname);
21
+ }
@@ -0,0 +1,2 @@
1
+ export declare function transferDatasetDownload(datasetId: string, format: string, filename: string): Promise<void>;
2
+ //# sourceMappingURL=dataset-transfer-download.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dataset-transfer-download.d.ts","sourceRoot":"","sources":["../src/dataset-transfer-download.ts"],"names":[],"mappings":"AAmBA,wBAAsB,uBAAuB,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAyChH"}
@@ -0,0 +1,66 @@
1
+ import { Effect, Either, Schedule } from 'effect';
2
+ import { writeFileSync } from 'node:fs';
3
+ import { datasetTransferTimeoutMs } from './dataset-transfer-policy.js';
4
+ import { authedFetch } from './http.js';
5
+ class DownloadHttpError extends Error {
6
+ status;
7
+ _tag = 'DownloadHttpError';
8
+ constructor(status, detail) {
9
+ super(`Download failed (HTTP ${status}): ${detail}`);
10
+ this.status = status;
11
+ }
12
+ }
13
+ class DownloadTransportError extends Error {
14
+ _tag = 'DownloadTransportError';
15
+ constructor(cause) {
16
+ super('Dataset download transport failed', { cause });
17
+ }
18
+ }
19
+ export async function transferDatasetDownload(datasetId, format, filename) {
20
+ const timeoutMs = datasetTransferTimeoutMs();
21
+ const download = Effect.tryPromise({
22
+ try: async (signal) => {
23
+ const response = await authedFetch(`/api/cli/datasets/${encodeURIComponent(datasetId)}/download?format=${encodeURIComponent(format)}`, { signal });
24
+ if (!response.ok)
25
+ throw new DownloadHttpError(response.status, await response.text());
26
+ return new Uint8Array(await response.arrayBuffer());
27
+ },
28
+ catch: cause => {
29
+ if (cause instanceof TypeError)
30
+ return new DownloadTransportError(cause);
31
+ return cause instanceof Error ? cause : new Error('Dataset download request failed', { cause });
32
+ },
33
+ }).pipe(Effect.timeoutFail({
34
+ duration: timeoutMs,
35
+ onTimeout: () => new Error('DATASET_TRANSFER_TIMEOUT: Dataset download exceeded its deadline'),
36
+ }), Effect.retry({
37
+ times: 2,
38
+ schedule: Schedule.spaced(100),
39
+ while: error => error instanceof DownloadTransportError || (error instanceof DownloadHttpError && [429, 502, 503, 504].includes(error.status)),
40
+ }));
41
+ const fileError = (cause) => new Error(`DATASET_DOWNLOAD_FILE: Could not save dataset to ${filename}: ${cause instanceof Error ? cause.message : String(cause)}`, { cause });
42
+ const write = (bytes) => Effect.try({
43
+ // Wait for the complete response, then retain the CLI's existing in-place
44
+ // write semantics, including inode metadata, hardlinks and symlink targets.
45
+ try: () => writeFileSync(filename, bytes, { mode: 0o600 }),
46
+ catch: fileError,
47
+ });
48
+ const controller = new AbortController();
49
+ const handleInterrupt = () => controller.abort();
50
+ process.once('SIGINT', handleInterrupt);
51
+ process.once('SIGTERM', handleInterrupt);
52
+ try {
53
+ const result = await Effect.runPromise(Effect.either(Effect.flatMap(download, write)), { signal: controller.signal });
54
+ if (Either.isLeft(result))
55
+ throw result.left;
56
+ }
57
+ catch (error) {
58
+ if (controller.signal.aborted)
59
+ throw new Error('DATASET_TRANSFER_INTERRUPTED: Dataset download cancelled');
60
+ throw error;
61
+ }
62
+ finally {
63
+ process.removeListener('SIGINT', handleInterrupt);
64
+ process.removeListener('SIGTERM', handleInterrupt);
65
+ }
66
+ }
@@ -0,0 +1,2 @@
1
+ export declare function datasetTransferTimeoutMs(): number;
2
+ //# sourceMappingURL=dataset-transfer-policy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dataset-transfer-policy.d.ts","sourceRoot":"","sources":["../src/dataset-transfer-policy.ts"],"names":[],"mappings":"AAAA,wBAAgB,wBAAwB,IAAI,MAAM,CAOjD"}
@@ -0,0 +1,8 @@
1
+ export function datasetTransferTimeoutMs() {
2
+ const configured = process.env.ORIZU_DATASET_TRANSFER_TIMEOUT_MS;
3
+ const timeoutMs = configured === undefined ? 120_000 : Number(configured);
4
+ if (!Number.isSafeInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > 300_000) {
5
+ throw new Error('DATASET_TRANSFER_CONFIG: ORIZU_DATASET_TRANSFER_TIMEOUT_MS must be an integer from 1 to 300000');
6
+ }
7
+ return timeoutMs;
8
+ }
@@ -0,0 +1,42 @@
1
+ export interface DatasetUploadResponse {
2
+ dataset: {
3
+ id: string;
4
+ name: string;
5
+ rowCount: number;
6
+ sourceType: string;
7
+ url?: string;
8
+ };
9
+ readmeVersion?: {
10
+ id: string;
11
+ version_num: number;
12
+ created_at: string;
13
+ } | null;
14
+ }
15
+ interface DatasetSnapshot {
16
+ datasetVersion: {
17
+ id: string;
18
+ };
19
+ }
20
+ type UploadRows = Array<Record<string, unknown>>;
21
+ type UploadSource = {
22
+ kind: 'jsonl';
23
+ file: string;
24
+ } | {
25
+ kind: 'rows';
26
+ rows: UploadRows;
27
+ sourceType: 'csv' | 'json' | 'jsonl';
28
+ };
29
+ export interface DatasetUploadOptions {
30
+ project: string;
31
+ name: string;
32
+ source: UploadSource;
33
+ readmeMarkdown: string | null;
34
+ json: boolean;
35
+ onChunk: (index: number, rowCount: number) => void;
36
+ }
37
+ export declare function transferDatasetUpload(options: DatasetUploadOptions): Promise<{
38
+ data: DatasetUploadResponse;
39
+ version: DatasetSnapshot;
40
+ }>;
41
+ export {};
42
+ //# sourceMappingURL=dataset-transfer-upload.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dataset-transfer-upload.d.ts","sourceRoot":"","sources":["../src/dataset-transfer-upload.ts"],"names":[],"mappings":"AASA,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IACzF,aAAa,CAAC,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAA;CAC/E;AAED,UAAU,eAAe;IAAG,cAAc,EAAE;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE;AAE5D,KAAK,UAAU,GAAG,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAA;AAChD,KAAK,YAAY,GAAG;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,UAAU,CAAC;IAAC,UAAU,EAAE,KAAK,GAAG,MAAM,GAAG,OAAO,CAAA;CAAE,CAAA;AAE9H,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,YAAY,CAAA;IACpB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAA;IAC7B,IAAI,EAAE,OAAO,CAAA;IACb,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAA;CACnD;AAoHD,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC;IAAE,IAAI,EAAE,qBAAqB,CAAC;IAAC,OAAO,EAAE,eAAe,CAAA;CAAE,CAAC,CAEvI"}
@@ -0,0 +1,110 @@
1
+ import { Context, Effect, Either, Layer } from 'effect';
2
+ import { ensureDatasetUploadSnapshot } from './dataset-upload-snapshot.js';
3
+ import { datasetTransferTimeoutMs } from './dataset-transfer-policy.js';
4
+ import { extractErrorMessage } from './error-response.js';
5
+ import { authedFetch } from './http.js';
6
+ import { parseJsonResponse, sanitizeTerminalText } from './json-response.js';
7
+ import { streamJsonlRowChunks } from './jsonl-stream.js';
8
+ class DatasetWriteFailure extends Error {
9
+ outcome;
10
+ _tag = 'DatasetWriteFailure';
11
+ constructor(message, outcome, options) {
12
+ super(message, options);
13
+ this.outcome = outcome;
14
+ }
15
+ }
16
+ class DatasetInputFailure extends Error {
17
+ _tag = 'DatasetInputFailure';
18
+ constructor(cause) { super(cause instanceof Error ? cause.message : String(cause), { cause }); }
19
+ }
20
+ const DatasetUploadApi = Context.GenericTag('orizu/DatasetUploadApi');
21
+ // Only the existing authentication client's rejected-token refresh may replay a
22
+ // request. No transient/transport retries belong around these non-idempotent writes.
23
+ function post(path, body, context, prefix, timeoutMs) {
24
+ return Effect.suspend(() => {
25
+ let requestStarted = false;
26
+ return Effect.tryPromise({
27
+ try: async (signal) => {
28
+ const response = await authedFetch(path, {
29
+ method: 'POST', headers: { 'Content-Type': 'application/json' },
30
+ body: JSON.stringify(body), signal,
31
+ }, () => { requestStarted = true; });
32
+ if (!response.ok) {
33
+ throw new DatasetWriteFailure(`${prefix}: ${await extractErrorMessage(response)}`, response.status >= 500 ? 'unknown' : 'rejected');
34
+ }
35
+ return await parseJsonResponse(response, context);
36
+ },
37
+ catch: cause => cause instanceof DatasetWriteFailure ? cause : new DatasetWriteFailure(cause instanceof Error ? cause.message : String(cause), requestStarted ? 'unknown' : 'rejected', { cause }),
38
+ }).pipe(Effect.timeoutFail({
39
+ duration: timeoutMs,
40
+ onTimeout: () => new DatasetWriteFailure(`DATASET_TRANSFER_TIMEOUT: ${context} exceeded its deadline`, requestStarted ? 'unknown' : 'rejected'),
41
+ }));
42
+ });
43
+ }
44
+ function liveUploadApi(options) {
45
+ const timeoutMs = datasetTransferTimeoutMs();
46
+ return Layer.succeed(DatasetUploadApi, {
47
+ create: (rows, sourceType) => post('/api/cli/datasets/upload', {
48
+ projectSlug: options.project, name: options.name, rows, sourceType,
49
+ ...(options.readmeMarkdown !== null ? { readmeMarkdown: options.readmeMarkdown } : {}),
50
+ }, 'Dataset upload', 'Upload failed', timeoutMs),
51
+ append: (id, rows) => post(`/api/cli/datasets/${encodeURIComponent(id)}/rows`, { rows }, 'Dataset append', 'Append failed', timeoutMs),
52
+ snapshot: id => post(`/api/cli/datasets/${encodeURIComponent(id)}/versions`, {}, 'Dataset version create', 'Failed to create dataset version', timeoutMs),
53
+ });
54
+ }
55
+ async function expectedPromise(program) {
56
+ const result = await Effect.runPromise(Effect.either(program));
57
+ if (Either.isLeft(result))
58
+ throw result.left;
59
+ return result.right;
60
+ }
61
+ function uploadProgram(options) {
62
+ return Effect.gen(function* () {
63
+ const api = yield* DatasetUploadApi;
64
+ const chunks = yield* Effect.acquireRelease(Effect.sync(() => options.source.kind === 'jsonl'
65
+ ? streamJsonlRowChunks(options.source.file)
66
+ : (async function* () { if (options.source.kind === 'rows')
67
+ yield options.source.rows; })()), iterator => Effect.promise(async () => { await iterator.return(undefined); }));
68
+ let data;
69
+ let confirmedRows = 0;
70
+ let chunkIndex = 0;
71
+ while (true) {
72
+ const next = yield* Effect.tryPromise({ try: () => chunks.next(), catch: cause => new DatasetInputFailure(cause) }).pipe(Effect.mapError(error => new Error(data
73
+ ? `Upload stopped while reading the next JSONL chunk: ${error.message}\nDataset ${sanitizeTerminalText(data.dataset.name)} (${sanitizeTerminalText(data.dataset.id)}) was created and ${confirmedRows} rows were uploaded. Fix the file, remove the first ${confirmedRows} rows, and run orizu datasets append --dataset ${data.dataset.id} --file <remaining-file>.`
74
+ : error.message)));
75
+ if (next.done)
76
+ break;
77
+ chunkIndex++;
78
+ if (options.source.kind === 'jsonl')
79
+ yield* Effect.sync(() => options.onChunk(chunkIndex, next.value.length));
80
+ const write = data
81
+ ? api.append(data.dataset.id, next.value).pipe(Effect.map(result => ({
82
+ data: { ...data, dataset: { ...data.dataset, rowCount: result.dataset.rowCount } },
83
+ added: result.appendedCount,
84
+ })))
85
+ : api.create(next.value, options.source.kind === 'jsonl' ? 'jsonl' : options.source.sourceType).pipe(Effect.map(result => ({ data: result, added: result.dataset.rowCount })));
86
+ const result = yield* write.pipe(Effect.mapError(error => {
87
+ if (error.outcome === 'unknown') {
88
+ return new Error(`DATASET_UPLOAD_UNCERTAIN: Chunk ${chunkIndex} may have committed, but its outcome could not be confirmed: ${error.message}\n` +
89
+ (data ? `Dataset ${sanitizeTerminalText(data.dataset.id)} has ${confirmedRows} confirmed uploaded rows. ` : 'Dataset creation could not be confirmed. ') +
90
+ 'Inspect the dataset and reconcile row IDs before retrying; do not replay this chunk blindly.');
91
+ }
92
+ return new Error(data
93
+ ? `Chunk ${chunkIndex} failed: ${error.message}\nDataset ${sanitizeTerminalText(data.dataset.name)} (${sanitizeTerminalText(data.dataset.id)}) was created and ${confirmedRows} rows were uploaded. To retry, remove the first ${confirmedRows} rows from your file and run orizu datasets append --dataset ${data.dataset.id} --file <remaining-file>.`
94
+ : error.message);
95
+ }));
96
+ data = result.data;
97
+ confirmedRows += result.added;
98
+ }
99
+ if (!data)
100
+ return yield* Effect.fail(new Error('Dataset file contains no rows'));
101
+ const version = yield* Effect.tryPromise({
102
+ try: () => ensureDatasetUploadSnapshot(data.dataset, options.json, id => expectedPromise(api.snapshot(id))),
103
+ catch: cause => cause instanceof Error ? cause : new Error(String(cause)),
104
+ });
105
+ return { data, version };
106
+ });
107
+ }
108
+ export function transferDatasetUpload(options) {
109
+ return expectedPromise(uploadProgram(options).pipe(Effect.scoped, Effect.provide(liveUploadApi(options))));
110
+ }