@myelinbridge/cli 0.6.1 → 0.7.0

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 CHANGED
@@ -31,6 +31,10 @@ npx @myelinbridge/cli push ./run_042 --dataset onco1-wes --submit
31
31
  too (S3 multipart). Uploads go direct to storage over short-lived presigned
32
32
  URLs — no credential is stored on your machine, and revoking the API key cuts
33
33
  off signing immediately.
34
+ - **Every file is fingerprinted.** Since 0.7.0, `push` computes an MD5 of each
35
+ file while it uploads and records it with the delivery. If the client's
36
+ quality contract includes a checksum-manifest check, your delivery verifies
37
+ against your own `md5_manifest.csv` instead of reading as "unverifiable".
34
38
  - **Read the contract before you build the delivery.** `contract` prints what the
35
39
  client expects — every check as one plain sentence, grouped by what it answers
36
40
  (completeness, structure, validity, consistency, integrity, privacy), and marked
package/bin/myelin.js CHANGED
@@ -10,6 +10,7 @@
10
10
 
11
11
  import { readdirSync, statSync, createReadStream, readFileSync } from 'node:fs'
12
12
  import { resolve, join, relative, sep, basename } from 'node:path'
13
+ import { createHash } from 'node:crypto'
13
14
  import process from 'node:process'
14
15
 
15
16
  const API_URL = (process.env.MYELIN_API_URL ?? 'https://myelinbridge.com/api/v1').replace(/\/$/, '')
@@ -158,6 +159,27 @@ async function readPart(absPath, start, end) {
158
159
  return Buffer.concat(chunks)
159
160
  }
160
161
 
162
+ // Whole-file MD5, lowercase hex — the exact format md5sum-style manifests use
163
+ // and the only one the platform's manifest_checksums_match check can verify.
164
+ // A separate sequential read (parts upload 4-wide, out of order — a streaming
165
+ // hash cannot ride along), overlapped with the upload by the caller. Returns
166
+ // null on read failure: a hash miss must degrade the delivery to
167
+ // "unverifiable", never kill the push.
168
+ async function md5File(absPath) {
169
+ try {
170
+ const hash = createHash('md5')
171
+ await new Promise((res, rej) => {
172
+ const s = createReadStream(absPath)
173
+ s.on('data', (c) => hash.update(c))
174
+ s.on('end', res)
175
+ s.on('error', rej)
176
+ })
177
+ return hash.digest('hex')
178
+ } catch {
179
+ return null
180
+ }
181
+ }
182
+
161
183
  async function uploadFileMultipart(absPath, size, fileId, partSize, onPartDone) {
162
184
  const numParts = Math.max(1, Math.ceil(size / partSize))
163
185
  const allParts = Array.from({ length: numParts }, (_, i) => i + 1)
@@ -389,8 +411,19 @@ async function cmdPush() {
389
411
  const d = queue.shift()
390
412
  if (!d) return
391
413
  const local = byPath.get(d.path)
392
- const { upload_id, parts } = await uploadFileMultipart(local.abs, local.size, d.file_id, partSize)
393
- const c = await api('POST', `/files/${d.file_id}/confirm`, { upload_id, parts })
414
+ // Hash and upload overlap two independent reads of the same file. The
415
+ // checksum rides on confirm so files.checksum is populated for CLI
416
+ // pushes (0.7.0): without it every CLI delivery reads as "unverifiable"
417
+ // to the manifest_checksums_match quality check.
418
+ const [{ upload_id, parts }, checksum] = await Promise.all([
419
+ uploadFileMultipart(local.abs, local.size, d.file_id, partSize),
420
+ md5File(local.abs),
421
+ ])
422
+ const c = await api('POST', `/files/${d.file_id}/confirm`, {
423
+ upload_id,
424
+ parts,
425
+ ...(checksum ? { checksum, checksum_algorithm: 'md5' } : {}),
426
+ })
394
427
  if (c.status !== 200) die(`confirm ${d.path}: [${c.json?.code}] ${c.json?.detail ?? c.status}`)
395
428
  done++
396
429
  out(` ✓ ${d.path} (${done}/${pending.length})`)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@myelinbridge/cli",
3
- "version": "0.6.1",
3
+ "version": "0.7.0",
4
4
  "description": "Myelin Partner Ingestion CLI — push R&D data deliveries from a pipeline: preflight against the client's quality rules, resumable upload, submit, track review outcomes.",
5
5
  "type": "module",
6
6
  "bin": {