@bli-cockpit/telemetry-core 0.1.16 → 0.1.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.
- package/dist/evidence-upload.d.ts +2 -0
- package/dist/evidence-upload.js +17 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/upload-failure-class.d.ts +63 -0
- package/dist/upload-failure-class.js +109 -0
- package/package.json +1 -1
|
@@ -448,6 +448,7 @@ export declare const CodexSessionAttributionSchema: z.ZodObject<{
|
|
|
448
448
|
upload_failed: "upload_failed";
|
|
449
449
|
not_uploaded: "not_uploaded";
|
|
450
450
|
}>>;
|
|
451
|
+
upload_reason: z.ZodOptional<z.ZodString>;
|
|
451
452
|
}, z.core.$strict>;
|
|
452
453
|
export type CodexSessionAttribution = z.infer<typeof CodexSessionAttributionSchema>;
|
|
453
454
|
export declare const CodexSessionAttributionReportRequestSchema: z.ZodObject<{
|
|
@@ -516,6 +517,7 @@ export declare const CodexSessionAttributionReportRequestSchema: z.ZodObject<{
|
|
|
516
517
|
upload_failed: "upload_failed";
|
|
517
518
|
not_uploaded: "not_uploaded";
|
|
518
519
|
}>>;
|
|
520
|
+
upload_reason: z.ZodOptional<z.ZodString>;
|
|
519
521
|
}, z.core.$strict>>;
|
|
520
522
|
}, z.core.$strict>;
|
|
521
523
|
export type CodexSessionAttributionReportRequest = z.infer<typeof CodexSessionAttributionReportRequestSchema>;
|
package/dist/evidence-upload.js
CHANGED
|
@@ -217,6 +217,23 @@ export const CodexSessionAttributionSchema = z
|
|
|
217
217
|
.optional(),
|
|
218
218
|
raw_evidence_pointer_id: NonEmptyStringSchema.optional(),
|
|
219
219
|
upload_state: CodexSessionUploadStateSchema.optional(),
|
|
220
|
+
/**
|
|
221
|
+
* Why this session has no durable pointer.
|
|
222
|
+
*
|
|
223
|
+
* `attribution_reason` is required, so a session that fails attribution
|
|
224
|
+
* always says why. Until BLI-2107 the upload half had no such field, and
|
|
225
|
+
* `not_uploaded` was written as the branch of last resort — the state
|
|
226
|
+
* meaning "we did not upload it and we are not saying why". 1,118 of the
|
|
227
|
+
* 2,400 sessions missing a durable pointer on 2026-07-28 sat in exactly
|
|
228
|
+
* that hole, 87 of them fully `attributed` with no ambiguity at all.
|
|
229
|
+
*
|
|
230
|
+
* That is unfixable and un-retryable by construction: you cannot tell a
|
|
231
|
+
* dropped network from a file we will never accept, so a retry either
|
|
232
|
+
* repeats a doomed attempt forever or gives up on a recoverable one.
|
|
233
|
+
* Optional only because already-published CLI versions do not send it;
|
|
234
|
+
* every write path in this repo sets it.
|
|
235
|
+
*/
|
|
236
|
+
upload_reason: SafeLabelSchema.optional(),
|
|
220
237
|
})
|
|
221
238
|
.strict();
|
|
222
239
|
export const CodexSessionAttributionReportRequestSchema = z
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a caller should do about it.
|
|
3
|
+
*
|
|
4
|
+
* - `transient` — something outside the run interfered. The identical retry is
|
|
5
|
+
* the right retry.
|
|
6
|
+
* - `deterministic` — the run completed and the answer was no. Repeating it
|
|
7
|
+
* changes nothing; this needs a different method or a human.
|
|
8
|
+
* - `withheld_by_policy` — collection worked and we chose not to upload. Not a
|
|
9
|
+
* failure, and emphatically not something to "fix" by retrying: that would be
|
|
10
|
+
* defeating a redaction guard.
|
|
11
|
+
* - `unknown` — an unlisted reason. Not silently lumped in with the others,
|
|
12
|
+
* because the fleet contract's whole point is that a green status must never
|
|
13
|
+
* hide missing collection.
|
|
14
|
+
*/
|
|
15
|
+
export type UploadFailureClass = "transient" | "deterministic" | "withheld_by_policy" | "unknown";
|
|
16
|
+
/**
|
|
17
|
+
* The reason written when nothing in the pipeline explained itself.
|
|
18
|
+
*
|
|
19
|
+
* This is the honest name for the hole BLI-2107 found. It is expected to be
|
|
20
|
+
* non-zero on the first release and to fall toward zero as each path that can
|
|
21
|
+
* reach it learns to say why — a session carrying this label means the collector
|
|
22
|
+
* declined to upload and no code on the way there recorded a cause.
|
|
23
|
+
*/
|
|
24
|
+
export declare const NO_UPLOAD_ATTEMPT_RECORDED = "no_upload_attempt_recorded";
|
|
25
|
+
/**
|
|
26
|
+
* Storage refused the finished object because of its size.
|
|
27
|
+
*
|
|
28
|
+
* Named here rather than at the commit route so the label the server writes and
|
|
29
|
+
* the label the collector classifies are the same string by construction. They
|
|
30
|
+
* drifted apart once already: the collector could say `file_too_large` about a
|
|
31
|
+
* file it declined to send, and had no word at all for storage declining to
|
|
32
|
+
* keep one it had already received.
|
|
33
|
+
*/
|
|
34
|
+
export declare const STORAGE_REJECTED_OBJECT_TOO_LARGE = "storage_rejected_object_too_large";
|
|
35
|
+
/** Storage refused the finished object because of its declared media type. */
|
|
36
|
+
export declare const STORAGE_REJECTED_MEDIA_TYPE = "storage_rejected_media_type";
|
|
37
|
+
/** Classify a persisted upload reason. Unlisted reasons stay `unknown`. */
|
|
38
|
+
export declare function classifyUploadFailure(reason: string | null | undefined): UploadFailureClass;
|
|
39
|
+
/**
|
|
40
|
+
* Whether repeating the identical attempt could plausibly succeed.
|
|
41
|
+
*
|
|
42
|
+
* `unknown` answers false. An unclassified reason is one nobody has reasoned
|
|
43
|
+
* about yet, and retrying on that basis is the loop this ticket exists to end;
|
|
44
|
+
* it should surface for a decision instead.
|
|
45
|
+
*/
|
|
46
|
+
export declare function isRetryableUploadFailure(reason: string | null | undefined): boolean;
|
|
47
|
+
/**
|
|
48
|
+
* Whether repeating the attempt is known to be pointless.
|
|
49
|
+
*
|
|
50
|
+
* The deliberate mirror image of `isRetryableUploadFailure`, and not its
|
|
51
|
+
* negation: `unknown` answers false here and false there. The two questions
|
|
52
|
+
* are asked in different places and an unlisted reason has to answer them
|
|
53
|
+
* differently.
|
|
54
|
+
*
|
|
55
|
+
* `isRetryableUploadFailure` decides whether to *claim* a retry will help, so
|
|
56
|
+
* silence means no. This one decides whether to *stop* retrying, so silence
|
|
57
|
+
* means keep going — the labels the collector composes on the fly
|
|
58
|
+
* (`commit_failed_http_503`, `chunk_7_failed_http_500`) are unlisted by
|
|
59
|
+
* construction, and treating them as permanent would abandon a sync over a
|
|
60
|
+
* storage blip. Only a reason somebody has classified as deterministic or
|
|
61
|
+
* withheld ends the loop.
|
|
62
|
+
*/
|
|
63
|
+
export declare function isPermanentUploadFailure(reason: string | null | undefined): boolean;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
// BLI-2107: what to do about a session that has no durable pointer.
|
|
2
|
+
//
|
|
3
|
+
// A retry that repeats the same call is only correct when something outside the
|
|
4
|
+
// run interfered — the network dropped, a budget was exhausted, a lock was
|
|
5
|
+
// held. If the run completed and the answer was still no, repeating it produces
|
|
6
|
+
// the same no, forever, and the only signal that anything is wrong is a number
|
|
7
|
+
// that never improves. That is how 1,118 sessions reached 2026-07-28 with no
|
|
8
|
+
// pointer and no explanation.
|
|
9
|
+
//
|
|
10
|
+
// So the reason is not just recorded, it is classified, and the class decides
|
|
11
|
+
// the response. Deliberately a flat lookup rather than pattern matching on the
|
|
12
|
+
// label: a rule like "contains `failed` means retry" quietly reclassifies every
|
|
13
|
+
// future reason somebody adds, which is the same class of guessing the person
|
|
14
|
+
// resolver refuses to do. An unlisted reason is `unknown` and stays visible
|
|
15
|
+
// until a human decides which bucket it belongs in.
|
|
16
|
+
/**
|
|
17
|
+
* The reason written when nothing in the pipeline explained itself.
|
|
18
|
+
*
|
|
19
|
+
* This is the honest name for the hole BLI-2107 found. It is expected to be
|
|
20
|
+
* non-zero on the first release and to fall toward zero as each path that can
|
|
21
|
+
* reach it learns to say why — a session carrying this label means the collector
|
|
22
|
+
* declined to upload and no code on the way there recorded a cause.
|
|
23
|
+
*/
|
|
24
|
+
export const NO_UPLOAD_ATTEMPT_RECORDED = "no_upload_attempt_recorded";
|
|
25
|
+
/**
|
|
26
|
+
* Storage refused the finished object because of its size.
|
|
27
|
+
*
|
|
28
|
+
* Named here rather than at the commit route so the label the server writes and
|
|
29
|
+
* the label the collector classifies are the same string by construction. They
|
|
30
|
+
* drifted apart once already: the collector could say `file_too_large` about a
|
|
31
|
+
* file it declined to send, and had no word at all for storage declining to
|
|
32
|
+
* keep one it had already received.
|
|
33
|
+
*/
|
|
34
|
+
export const STORAGE_REJECTED_OBJECT_TOO_LARGE = "storage_rejected_object_too_large";
|
|
35
|
+
/** Storage refused the finished object because of its declared media type. */
|
|
36
|
+
export const STORAGE_REJECTED_MEDIA_TYPE = "storage_rejected_media_type";
|
|
37
|
+
const UPLOAD_FAILURE_CLASSES = {
|
|
38
|
+
// Budgets and locks: the file was fine and the run simply ran out of room or
|
|
39
|
+
// was beaten to it. The next pass has room.
|
|
40
|
+
deferred_object_budget: "transient",
|
|
41
|
+
deferred_byte_budget: "transient",
|
|
42
|
+
sync_already_running: "transient",
|
|
43
|
+
// Delivery. The bytes never reached the other end, or the run died on the way.
|
|
44
|
+
upload_failed: "transient",
|
|
45
|
+
collection_failed: "transient",
|
|
46
|
+
file_read_failed: "transient",
|
|
47
|
+
git_diff_failed: "transient",
|
|
48
|
+
// The file itself is the problem, and it will be the same size and the same
|
|
49
|
+
// shape on the next pass. Retrying is a promise nobody can keep.
|
|
50
|
+
file_too_large: "deterministic",
|
|
51
|
+
// Storage looked at the finished object and said no. The bytes are already
|
|
52
|
+
// final and content-addressed, so the next attempt presents the identical
|
|
53
|
+
// object to the identical rule (BLI-2528). Retrying these is what let 76
|
|
54
|
+
// objects retry for 57 days without one of them ever landing.
|
|
55
|
+
[STORAGE_REJECTED_OBJECT_TOO_LARGE]: "deterministic",
|
|
56
|
+
[STORAGE_REJECTED_MEDIA_TYPE]: "deterministic",
|
|
57
|
+
// The content-addressed key already holds different bytes. One of the two is
|
|
58
|
+
// wrong and repeating the write cannot decide which; a human has to look.
|
|
59
|
+
object_conflict: "deterministic",
|
|
60
|
+
// Attribution never produced a target, so there is nowhere to put the object.
|
|
61
|
+
// Widening discovery can change this; running the same scan again cannot.
|
|
62
|
+
cwd_outside_scanned_worktrees: "deterministic",
|
|
63
|
+
cwd_not_a_repo: "deterministic",
|
|
64
|
+
no_repo_signals: "deterministic",
|
|
65
|
+
multiple_worktrees_close_scores: "deterministic",
|
|
66
|
+
// Working as designed. Never retry these — a retry here is an attempt to get
|
|
67
|
+
// a redaction guard to change its mind.
|
|
68
|
+
secret_like_content_guard: "withheld_by_policy",
|
|
69
|
+
secret_like_file_name: "withheld_by_policy",
|
|
70
|
+
secret_redaction_failed: "withheld_by_policy",
|
|
71
|
+
// The server-side name for the same guard, returned by the commit route.
|
|
72
|
+
secret_guard_rejected: "withheld_by_policy",
|
|
73
|
+
};
|
|
74
|
+
/** Classify a persisted upload reason. Unlisted reasons stay `unknown`. */
|
|
75
|
+
export function classifyUploadFailure(reason) {
|
|
76
|
+
if (!reason)
|
|
77
|
+
return "unknown";
|
|
78
|
+
return UPLOAD_FAILURE_CLASSES[reason] ?? "unknown";
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Whether repeating the identical attempt could plausibly succeed.
|
|
82
|
+
*
|
|
83
|
+
* `unknown` answers false. An unclassified reason is one nobody has reasoned
|
|
84
|
+
* about yet, and retrying on that basis is the loop this ticket exists to end;
|
|
85
|
+
* it should surface for a decision instead.
|
|
86
|
+
*/
|
|
87
|
+
export function isRetryableUploadFailure(reason) {
|
|
88
|
+
return classifyUploadFailure(reason) === "transient";
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Whether repeating the attempt is known to be pointless.
|
|
92
|
+
*
|
|
93
|
+
* The deliberate mirror image of `isRetryableUploadFailure`, and not its
|
|
94
|
+
* negation: `unknown` answers false here and false there. The two questions
|
|
95
|
+
* are asked in different places and an unlisted reason has to answer them
|
|
96
|
+
* differently.
|
|
97
|
+
*
|
|
98
|
+
* `isRetryableUploadFailure` decides whether to *claim* a retry will help, so
|
|
99
|
+
* silence means no. This one decides whether to *stop* retrying, so silence
|
|
100
|
+
* means keep going — the labels the collector composes on the fly
|
|
101
|
+
* (`commit_failed_http_503`, `chunk_7_failed_http_500`) are unlisted by
|
|
102
|
+
* construction, and treating them as permanent would abandon a sync over a
|
|
103
|
+
* storage blip. Only a reason somebody has classified as deterministic or
|
|
104
|
+
* withheld ends the loop.
|
|
105
|
+
*/
|
|
106
|
+
export function isPermanentUploadFailure(reason) {
|
|
107
|
+
const failureClass = classifyUploadFailure(reason);
|
|
108
|
+
return failureClass === "deterministic" || failureClass === "withheld_by_policy";
|
|
109
|
+
}
|