@bli-cockpit/cli 0.2.15 → 0.2.17
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.
|
@@ -15,7 +15,7 @@ export async function runCockpitCli(argv, io) {
|
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
if (command === "--version" || command === "-V" || command === "version") {
|
|
18
|
-
writeLine(io?.stdout ?? process.stdout, "0.2.
|
|
18
|
+
writeLine(io?.stdout ?? process.stdout, "0.2.17");
|
|
19
19
|
return 0;
|
|
20
20
|
}
|
|
21
21
|
|
|
@@ -140,6 +140,7 @@ export async function uploadRawEvidenceFilesChunked(options) {
|
|
|
140
140
|
continue;
|
|
141
141
|
}
|
|
142
142
|
const outcome = await uploadOneObject(options, entry, disposition, chunkSizeBytes);
|
|
143
|
+
await reportAbandonedUpload(options, disposition, outcome);
|
|
143
144
|
outcomes.push(outcome);
|
|
144
145
|
for (const duplicate of entry.duplicates) {
|
|
145
146
|
outcomes.push(duplicateOutcome(outcome, duplicate));
|
|
@@ -171,6 +172,72 @@ function duplicateOutcome(primary, duplicate) {
|
|
|
171
172
|
uploaded_chunk_count: 0,
|
|
172
173
|
};
|
|
173
174
|
}
|
|
175
|
+
/**
|
|
176
|
+
* Tell the server why we gave up on a row `begin` already opened (BLI-2539).
|
|
177
|
+
*
|
|
178
|
+
* `begin` opens a ledger row per object and the chunks then go one object at a
|
|
179
|
+
* time, so one object failing leaves its siblings untouched — the production
|
|
180
|
+
* shape is a lone failure among committed rows. This function is the only thing
|
|
181
|
+
* standing between that failure and a row that sits `pending` with a null
|
|
182
|
+
* reason until a drain relabels it `staging_incomplete` weeks later, which
|
|
183
|
+
* names the shape and not the cause. 275 rows reached that state by 2026-08-14.
|
|
184
|
+
*
|
|
185
|
+
* The server records the reason on the still-open row (for a reason classified
|
|
186
|
+
* permanent it fails the row closed), so reporting a give-up never costs the
|
|
187
|
+
* staged chunks the next sync resumes from.
|
|
188
|
+
*
|
|
189
|
+
* Deliberately best-effort: the upload has already failed and the caller's
|
|
190
|
+
* outcome is the answer that matters. Losing the abort as well leaves exactly
|
|
191
|
+
* the row we had before this existed, so a delivery failure must not throw —
|
|
192
|
+
* but it is named on stderr rather than swallowed, because a reason the server
|
|
193
|
+
* never received is invisible precisely where BLI-2539 needed it visible.
|
|
194
|
+
*/
|
|
195
|
+
async function reportAbandonedUpload(options, disposition, outcome) {
|
|
196
|
+
if (outcome.upload_state !== "upload_failed")
|
|
197
|
+
return;
|
|
198
|
+
if (!outcome.reason)
|
|
199
|
+
return;
|
|
200
|
+
// Only a row this call actually opened or resumed. `conflict` never allocated
|
|
201
|
+
// one for us, and `already_committed` names durable content an abort must not
|
|
202
|
+
// touch.
|
|
203
|
+
if (disposition.disposition !== "new" && disposition.disposition !== "resume") {
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
if (!disposition.upload_id)
|
|
207
|
+
return;
|
|
208
|
+
const reason = conformReasonLabel(outcome.reason);
|
|
209
|
+
const response = await requestJson(options, "/api/ambient/evidence/upload/abort", {
|
|
210
|
+
schema_version: "ambient-raw-evidence-upload-abort.v1",
|
|
211
|
+
generated_at: options.generatedAt,
|
|
212
|
+
provenance: options.provenance,
|
|
213
|
+
upload_id: disposition.upload_id,
|
|
214
|
+
object_key: outcome.object_key,
|
|
215
|
+
reason,
|
|
216
|
+
uploaded_chunk_count: outcome.uploaded_chunk_count,
|
|
217
|
+
});
|
|
218
|
+
if (!response.ok) {
|
|
219
|
+
// status 404 is an old dashboard without the abort route; status 0 means
|
|
220
|
+
// the request itself never completed. Metadata only — ids, status, labels.
|
|
221
|
+
console.error("[evidence-abort] delivery failed; the server did not record the reason", JSON.stringify({
|
|
222
|
+
upload_id: disposition.upload_id,
|
|
223
|
+
reason,
|
|
224
|
+
http_status: response.status,
|
|
225
|
+
}));
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Force a reason into the label shape the abort route accepts.
|
|
230
|
+
*
|
|
231
|
+
* Every reason this client composes already fits, and this exists so that stays
|
|
232
|
+
* true without anyone having to remember it. A label the server rejects comes
|
|
233
|
+
* back 400 and the reason is lost — which is the precise failure BLI-2539 is
|
|
234
|
+
* about, arriving through the code meant to fix it. A mangled label that lands
|
|
235
|
+
* beats a perfect one that does not.
|
|
236
|
+
*/
|
|
237
|
+
function conformReasonLabel(reason) {
|
|
238
|
+
const conformed = reason.replace(/[^a-z0-9_:.-]/gi, "_").slice(0, 120);
|
|
239
|
+
return conformed.length > 0 ? conformed : "upload_failed_unlabelled";
|
|
240
|
+
}
|
|
174
241
|
async function uploadOneObject(options, entry, disposition, chunkSizeBytes) {
|
|
175
242
|
const objectKey = entry.file.pointer.object_key ?? "";
|
|
176
243
|
if (disposition.disposition === "already_committed") {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bli-cockpit/cli",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.17",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -26,6 +26,6 @@
|
|
|
26
26
|
"test": "node dist/cli.js --help && node ../../scripts/assert-public-cli-routing.mjs && node ../../scripts/assert-public-package-pack.mjs --workspace=@bli-cockpit/cli"
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@bli-cockpit/telemetry-core": "0.1.
|
|
29
|
+
"@bli-cockpit/telemetry-core": "0.1.19"
|
|
30
30
|
}
|
|
31
31
|
}
|