@bli-cockpit/cli 0.2.54 → 0.2.56
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/adapters/attribution-core-fallbacks.js +247 -0
- package/dist/adapters/attribution-core-paths.js +182 -0
- package/dist/adapters/attribution-core-score.js +159 -0
- package/dist/adapters/attribution-core-types.js +13 -0
- package/dist/adapters/attribution-core.js +13 -565
- package/dist/adapters/claude-attribution-discovery.js +186 -0
- package/dist/adapters/claude-attribution-score.js +204 -0
- package/dist/adapters/claude-attribution-signals.js +180 -0
- package/dist/adapters/claude-attribution-types.js +25 -0
- package/dist/adapters/claude-attribution.js +14 -569
- package/dist/commands/doctor-access.js +129 -0
- package/dist/commands/doctor-pipeline.js +326 -0
- package/dist/commands/doctor-registration.js +105 -0
- package/dist/commands/doctor-report.js +111 -0
- package/dist/commands/doctor-update.js +120 -0
- package/dist/commands/doctor.js +8 -753
- package/dist/commands/heartbeat.js +8 -0
- package/dist/commands/jarvis-contracts.js +8 -0
- package/dist/commands/jarvis-render.js +413 -0
- package/dist/commands/jarvis-turn.js +305 -0
- package/dist/commands/jarvis.js +23 -698
- package/dist/commands/local-args-collector-setup.js +250 -0
- package/dist/commands/local-args-collector-status.js +227 -0
- package/dist/commands/local-args-collector-work.js +175 -0
- package/dist/commands/local-args-collector.js +19 -624
- package/dist/commands/local-args-tower-admin.js +456 -0
- package/dist/commands/local-args-tower-chat.js +194 -0
- package/dist/commands/local-args-tower-pages.js +314 -0
- package/dist/commands/local-args-tower.js +13 -880
- package/dist/commands/local-help.js +10 -2
- package/dist/commands/onboard-completion.js +136 -0
- package/dist/commands/onboard-flows.js +165 -0
- package/dist/commands/onboard-setup.js +102 -0
- package/dist/commands/onboard.js +5 -392
- package/dist/commands/public-root.js +1 -1
- package/dist/commands/session-sync-counters.js +55 -0
- package/dist/commands/session-sync-health.js +8 -1
- package/dist/commands/session-sync-plan.js +47 -7
- package/dist/commands/session-sync-scan.js +4 -4
- package/dist/commands/session-sync.js +6 -0
- package/dist/commands/settings-render.js +27 -0
- package/dist/commands/sync-followups.js +5 -1
- package/dist/commands/sync.js +5 -1
- package/dist/commands/team-device-reasons.js +16 -0
- package/dist/commands/team.js +87 -7
- package/dist/evidence-upload-client.js +14 -763
- package/dist/evidence-upload-object.js +181 -0
- package/dist/evidence-upload-plan.js +233 -0
- package/dist/evidence-upload-terminal.js +309 -0
- package/dist/evidence-upload-transport.js +104 -0
- package/dist/spool/local-spool-io.js +122 -0
- package/dist/spool/local-spool-mutations.js +174 -0
- package/dist/spool/local-spool-parse.js +143 -0
- package/dist/spool/local-spool-types.js +22 -0
- package/dist/spool/local-spool.js +20 -426
- package/dist/upload-evidence-delivery-offer.js +144 -0
- package/dist/upload-evidence-delivery-reconcile.js +134 -0
- package/dist/upload-evidence-delivery-summary.js +205 -0
- package/dist/upload-evidence-delivery.js +12 -482
- package/package.json +3 -3
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { EvidenceCompletenessPayloadSchema, } from "@bli-cockpit/telemetry-core";
|
|
2
|
+
/**
|
|
3
|
+
* 4. Reconciling the envelope with what actually became durable.
|
|
4
|
+
*
|
|
5
|
+
* Ingest refuses pointers whose objects never became durable, so failed
|
|
6
|
+
* uploads are pruned from the envelope instead of failing the whole sync.
|
|
7
|
+
* Successful upload responses can also carry server-side sanitized hash and
|
|
8
|
+
* redaction metadata; apply those before ingest so refs describe the bytes
|
|
9
|
+
* actually stored in the durable bucket.
|
|
10
|
+
*/
|
|
11
|
+
export function applyRawEvidenceUploadOutcomes(envelope, outcomes) {
|
|
12
|
+
// Content-addressed keys mean one pointer id can carry several outcomes
|
|
13
|
+
// (byte-identical files); the pointer is durable if ANY outcome succeeded.
|
|
14
|
+
const durablePointers = new Map();
|
|
15
|
+
for (const outcome of outcomes) {
|
|
16
|
+
if (outcome.upload_state === "upload_failed")
|
|
17
|
+
continue;
|
|
18
|
+
durablePointers.set(outcome.pointer.raw_evidence_pointer_id, outcome.pointer);
|
|
19
|
+
}
|
|
20
|
+
const durablePointerIds = new Set([...durablePointers.keys()]);
|
|
21
|
+
const failedPointerIds = new Set(outcomes
|
|
22
|
+
.filter((outcome) => outcome.upload_state === "upload_failed" &&
|
|
23
|
+
!durablePointerIds.has(outcome.pointer.raw_evidence_pointer_id))
|
|
24
|
+
.map((outcome) => outcome.pointer.raw_evidence_pointer_id));
|
|
25
|
+
const failedOutcomes = outcomes.filter((outcome) => failedPointerIds.has(outcome.pointer.raw_evidence_pointer_id));
|
|
26
|
+
if (failedPointerIds.size === 0 && durablePointers.size === 0)
|
|
27
|
+
return envelope;
|
|
28
|
+
return {
|
|
29
|
+
...envelope,
|
|
30
|
+
events: envelope.events.map((event) => ({
|
|
31
|
+
...event,
|
|
32
|
+
metrics: failedPointerIds.size > 0
|
|
33
|
+
? {
|
|
34
|
+
...event.metrics,
|
|
35
|
+
evidence_failed_count: (event.metrics["evidence_failed_count"] ?? 0) +
|
|
36
|
+
failedOutcomes.length,
|
|
37
|
+
}
|
|
38
|
+
: event.metrics,
|
|
39
|
+
attributes: failedPointerIds.size > 0 && event.evidence_completeness
|
|
40
|
+
? {
|
|
41
|
+
...event.attributes,
|
|
42
|
+
evidence_completeness_schema_version: event.evidence_completeness.schema_version,
|
|
43
|
+
evidence_completeness_status: "partial",
|
|
44
|
+
evidence_incomplete: true,
|
|
45
|
+
}
|
|
46
|
+
: event.attributes,
|
|
47
|
+
evidence_completeness: failedPointerIds.size > 0 && event.evidence_completeness
|
|
48
|
+
? markCompletenessUploadFailures(event.evidence_completeness, failedOutcomes)
|
|
49
|
+
: event.evidence_completeness,
|
|
50
|
+
raw_evidence_pointers: event.raw_evidence_pointers.flatMap((pointer) => {
|
|
51
|
+
const pointerId = pointer.raw_evidence_pointer_id;
|
|
52
|
+
if (failedPointerIds.has(pointerId))
|
|
53
|
+
return [];
|
|
54
|
+
return [durablePointers.get(pointerId) ?? pointer];
|
|
55
|
+
}),
|
|
56
|
+
redaction: failedPointerIds.size > 0
|
|
57
|
+
? {
|
|
58
|
+
...event.redaction,
|
|
59
|
+
raw_evidence_pointer_ids: event.redaction.raw_evidence_pointer_ids.filter((pointerId) => !failedPointerIds.has(pointerId)),
|
|
60
|
+
}
|
|
61
|
+
: event.redaction,
|
|
62
|
+
})),
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Fold upload failures back into the completeness payload the event carries, so
|
|
67
|
+
* a downstream reader sees `partial` and the per-source failure counts rather
|
|
68
|
+
* than a complete-looking payload with fewer pointers than it claims.
|
|
69
|
+
*/
|
|
70
|
+
function markCompletenessUploadFailures(completeness, failedOutcomes) {
|
|
71
|
+
const failureCounts = new Map();
|
|
72
|
+
for (const outcome of failedOutcomes) {
|
|
73
|
+
const source = outcome.kind ?? "raw_evidence";
|
|
74
|
+
failureCounts.set(source, (failureCounts.get(source) ?? 0) + 1);
|
|
75
|
+
}
|
|
76
|
+
const totalFailures = [...failureCounts.values()].reduce((sum, count) => sum + count, 0);
|
|
77
|
+
const sourceCounts = [...completeness.source_counts];
|
|
78
|
+
for (const [source, count] of failureCounts) {
|
|
79
|
+
const existingIndex = sourceCounts.findIndex((entry) => entry.source === source);
|
|
80
|
+
if (existingIndex === -1) {
|
|
81
|
+
sourceCounts.push({
|
|
82
|
+
source,
|
|
83
|
+
scanned_count: 0,
|
|
84
|
+
included_count: 0,
|
|
85
|
+
skipped_count: 0,
|
|
86
|
+
truncated_count: 0,
|
|
87
|
+
deferred_count: 0,
|
|
88
|
+
reused_count: 0,
|
|
89
|
+
failed_count: count,
|
|
90
|
+
});
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
const existing = sourceCounts[existingIndex];
|
|
94
|
+
if (!existing)
|
|
95
|
+
continue;
|
|
96
|
+
sourceCounts[existingIndex] = {
|
|
97
|
+
...existing,
|
|
98
|
+
failed_count: existing.failed_count + count,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
const failureReasons = [...completeness.failure_reasons];
|
|
102
|
+
for (const [source, count] of failureCounts) {
|
|
103
|
+
const reason = "upload_failed";
|
|
104
|
+
const existingIndex = failureReasons.findIndex((entry) => entry.source === source && entry.reason === reason);
|
|
105
|
+
if (existingIndex === -1) {
|
|
106
|
+
failureReasons.push({ source, reason, count });
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
const existing = failureReasons[existingIndex];
|
|
110
|
+
if (existing) {
|
|
111
|
+
failureReasons[existingIndex] = {
|
|
112
|
+
...existing,
|
|
113
|
+
count: existing.count + count,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return EvidenceCompletenessPayloadSchema.parse({
|
|
119
|
+
...completeness,
|
|
120
|
+
status: "partial",
|
|
121
|
+
source_counts: sourceCounts,
|
|
122
|
+
totals: {
|
|
123
|
+
...completeness.totals,
|
|
124
|
+
failed_count: completeness.totals.failed_count + totalFailures,
|
|
125
|
+
},
|
|
126
|
+
failure_reasons: failureReasons.sort((a, b) => `${a.source}:${a.reason}`.localeCompare(`${b.source}:${b.reason}`)),
|
|
127
|
+
notes: [
|
|
128
|
+
...new Set([
|
|
129
|
+
...completeness.notes,
|
|
130
|
+
"Some collected evidence did not become durable; downstream analysis should lower confidence.",
|
|
131
|
+
]),
|
|
132
|
+
],
|
|
133
|
+
});
|
|
134
|
+
}
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import { isPermanentUploadFailure } from "@bli-cockpit/telemetry-core";
|
|
2
|
+
import { DELIVERY_BACKOFF_HOLDING_REASON, summarizeStuckEvidence, } from "./raw-evidence-staging.js";
|
|
3
|
+
/**
|
|
4
|
+
* Which adapters a queued retry has to re-run.
|
|
5
|
+
*
|
|
6
|
+
* Anything this sync touched or tried to touch counts, including a directory it
|
|
7
|
+
* could not read — a source that failed to scan is exactly the one a retry must
|
|
8
|
+
* come back to.
|
|
9
|
+
*/
|
|
10
|
+
export function retrySourcesForFailedSync(options, facts) {
|
|
11
|
+
const sources = new Set();
|
|
12
|
+
if ((options.codexSessionFiles?.length ?? 0) > 0 ||
|
|
13
|
+
(options.codexAttributionScan?.directory_read_failed_count ?? 0) > 0 ||
|
|
14
|
+
(options.codexAttributionScan?.stat_failed_count ?? 0) > 0 ||
|
|
15
|
+
facts?.evidence_completeness.source_counts.some((count) => count.source.startsWith("codex_") &&
|
|
16
|
+
count.scanned_count + count.included_count + count.reused_count > 0)) {
|
|
17
|
+
sources.add("codex");
|
|
18
|
+
}
|
|
19
|
+
if ((options.claudeSessionFiles?.length ?? 0) > 0 ||
|
|
20
|
+
(options.claudeAttributionScan?.project_dir_read_failed_count ?? 0) > 0 ||
|
|
21
|
+
(options.claudeAttributionScan?.session_stat_failed_count ?? 0) > 0 ||
|
|
22
|
+
(options.claudeAttributionScan?.sidecar_dir_read_failed_count ?? 0) > 0 ||
|
|
23
|
+
(options.claudeAttributionScan?.sidecar_stat_failed_count ?? 0) > 0 ||
|
|
24
|
+
facts?.evidence_completeness.source_counts.some((count) => count.source.startsWith("claude_") &&
|
|
25
|
+
count.scanned_count + count.included_count + count.reused_count > 0)) {
|
|
26
|
+
sources.add("claude_code");
|
|
27
|
+
}
|
|
28
|
+
return [...sources];
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* The failed uploads a later attempt could still rescue.
|
|
32
|
+
*
|
|
33
|
+
* An object storage has already refused on its own terms is not one of them,
|
|
34
|
+
* and counting it as one is what kept Edward's Mac in `retry_pending` through
|
|
35
|
+
* 13 consecutive syncs that were never going to end differently (BLI-2528).
|
|
36
|
+
*/
|
|
37
|
+
function retryableFailedOutcomes(outcomes) {
|
|
38
|
+
return outcomes.filter((outcome) => outcome.upload_state === "upload_failed" &&
|
|
39
|
+
!isPermanentUploadFailure(outcome.reason));
|
|
40
|
+
}
|
|
41
|
+
/** Failed uploads that no retry can rescue, kept so they can still be named. */
|
|
42
|
+
function permanentFailedOutcomes(outcomes) {
|
|
43
|
+
return outcomes.filter((outcome) => outcome.upload_state === "upload_failed" &&
|
|
44
|
+
isPermanentUploadFailure(outcome.reason));
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* The reason to show for objects that failed for good.
|
|
48
|
+
*
|
|
49
|
+
* Returns null when there are none. These never queue a retry, but they must
|
|
50
|
+
* never disappear either: a machine with a permanently rejected object has
|
|
51
|
+
* missing collection, and a status that reads clean would hide it.
|
|
52
|
+
*/
|
|
53
|
+
export function permanentEvidenceFailureReason(outcomes) {
|
|
54
|
+
const reasons = new Set(permanentFailedOutcomes(outcomes).map((outcome) => outcome.reason ?? "unknown"));
|
|
55
|
+
if (reasons.size === 0)
|
|
56
|
+
return null;
|
|
57
|
+
return `raw_evidence_permanently_rejected:${[...reasons].sort().join(",")}`;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Leave a dated record of the objects this machine gave up on.
|
|
61
|
+
*
|
|
62
|
+
* stderr, which launchd captures to `sync.err.log`, so an unattended machine
|
|
63
|
+
* still says what it lost. Reason labels and counts only — never a path or a
|
|
64
|
+
* byte of content.
|
|
65
|
+
*/
|
|
66
|
+
export function logPermanentlyRejectedEvidence(options) {
|
|
67
|
+
console.error("[cockpit-sync] raw evidence permanently rejected", JSON.stringify({
|
|
68
|
+
attempted_at: options.attemptedAt,
|
|
69
|
+
reason: options.reason,
|
|
70
|
+
object_count: permanentFailedOutcomes(options.outcomes).length,
|
|
71
|
+
}));
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Is there anything a later sync could still turn into evidence?
|
|
75
|
+
*
|
|
76
|
+
* Any one of these is enough: an upload that failed for a rescuable reason,
|
|
77
|
+
* evidence deferred by this sync's byte or object budget, a completeness
|
|
78
|
+
* payload that reports failure, or a skip a retry could undo.
|
|
79
|
+
*/
|
|
80
|
+
export function hasRetryableEvidenceGap(facts, outcomes) {
|
|
81
|
+
if (retryableFailedOutcomes(outcomes).length > 0) {
|
|
82
|
+
return true;
|
|
83
|
+
}
|
|
84
|
+
if (!facts)
|
|
85
|
+
return false;
|
|
86
|
+
if (facts.deferred_byte_budget_count > 0 ||
|
|
87
|
+
facts.deferred_object_budget_count > 0) {
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
if (facts.evidence_completeness.status === "failed" ||
|
|
91
|
+
facts.evidence_completeness.totals.failed_count > 0 ||
|
|
92
|
+
facts.evidence_completeness.failure_reasons.length > 0) {
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
95
|
+
return facts.evidence_completeness.skip_reasons.some(({ reason }) => isRetryableEvidenceSkipReason(reason));
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Skip reasons a later sync can still turn into evidence.
|
|
99
|
+
*
|
|
100
|
+
* `delivery_backoff_holding` is one of them, and it has to be: a source held by
|
|
101
|
+
* backoff is missing collection right now. If it did not land here the sync
|
|
102
|
+
* would read clean while a transcript sat undelivered for nine days, which is
|
|
103
|
+
* the exact shape of BLI-3066.
|
|
104
|
+
*/
|
|
105
|
+
function isRetryableEvidenceSkipReason(reason) {
|
|
106
|
+
if (reason === DELIVERY_BACKOFF_HOLDING_REASON)
|
|
107
|
+
return true;
|
|
108
|
+
return /(?:read|stat|directory)_failed|session_limit_overflow/iu.test(reason);
|
|
109
|
+
}
|
|
110
|
+
/** Every distinct reason behind the gap, sorted, as one spool-ready label. */
|
|
111
|
+
export function retryableEvidenceGapReason(facts, outcomes) {
|
|
112
|
+
const reasons = new Set();
|
|
113
|
+
for (const outcome of retryableFailedOutcomes(outcomes)) {
|
|
114
|
+
reasons.add(outcome.reason ?? "upload_failed");
|
|
115
|
+
}
|
|
116
|
+
if (facts) {
|
|
117
|
+
if (facts.deferred_byte_budget_count > 0)
|
|
118
|
+
reasons.add("deferred_byte_budget");
|
|
119
|
+
if (facts.deferred_object_budget_count > 0) {
|
|
120
|
+
reasons.add("deferred_object_budget");
|
|
121
|
+
}
|
|
122
|
+
for (const { reason } of facts.evidence_completeness.failure_reasons) {
|
|
123
|
+
reasons.add(reason);
|
|
124
|
+
}
|
|
125
|
+
if (facts.evidence_completeness.status === "failed" &&
|
|
126
|
+
facts.evidence_completeness.failure_reasons.length === 0) {
|
|
127
|
+
reasons.add("evidence_completeness_failed");
|
|
128
|
+
}
|
|
129
|
+
for (const { reason } of facts.evidence_completeness.skip_reasons) {
|
|
130
|
+
if (isRetryableEvidenceSkipReason(reason)) {
|
|
131
|
+
reasons.add(reason);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return `partial_raw_evidence_retry_required:${[
|
|
136
|
+
...reasons,
|
|
137
|
+
].sort().join(",") || "unknown"}`;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* The counts `cockpit sync --json` and `cockpit status` read.
|
|
141
|
+
*
|
|
142
|
+
* Two kinds of reuse are added together on purpose: files this machine skipped
|
|
143
|
+
* because its own cursor already had the bytes, and files the server recognised
|
|
144
|
+
* from their content hash.
|
|
145
|
+
*/
|
|
146
|
+
export function summarizeRawEvidenceDelivery(built, outcomes, uploadedChunkCount, cursor, staging, now) {
|
|
147
|
+
const stuck = summarizeStuckEvidence(staging, now);
|
|
148
|
+
const cursorReused = built.raw_evidence_facts?.reused ?? [];
|
|
149
|
+
const serverReusedCount = outcomes.filter((outcome) => outcome.upload_state === "reused_existing").length;
|
|
150
|
+
const failed = outcomes.filter((outcome) => outcome.upload_state === "upload_failed");
|
|
151
|
+
const retryRequired = hasRetryableEvidenceGap(built.raw_evidence_facts, outcomes);
|
|
152
|
+
const retryReason = retryRequired
|
|
153
|
+
? retryableEvidenceGapReason(built.raw_evidence_facts, outcomes)
|
|
154
|
+
: null;
|
|
155
|
+
const cursorReusedOutcomes = cursorReused.map((entry) => ({
|
|
156
|
+
object_key: cursor.objects[entry.content_hash_sha256]?.object_key ?? "",
|
|
157
|
+
raw_evidence_pointer_id: cursor.objects[entry.content_hash_sha256]?.object_key ?? "",
|
|
158
|
+
kind: entry.kind,
|
|
159
|
+
codex_session_id: entry.codex_session_id,
|
|
160
|
+
...(entry.artifact_metadata
|
|
161
|
+
? { artifact_metadata: entry.artifact_metadata }
|
|
162
|
+
: {}),
|
|
163
|
+
upload_state: "reused_existing",
|
|
164
|
+
reason: "cursor_content_match",
|
|
165
|
+
}));
|
|
166
|
+
return {
|
|
167
|
+
raw_evidence_file_count: built.raw_evidence_upload_files.length,
|
|
168
|
+
raw_evidence_uploaded_object_count: outcomes.filter((outcome) => outcome.upload_state === "uploaded").length,
|
|
169
|
+
raw_evidence_uploaded_chunk_count: uploadedChunkCount,
|
|
170
|
+
raw_evidence_reused_count: cursorReused.length + serverReusedCount,
|
|
171
|
+
raw_evidence_failed_count: failed.length,
|
|
172
|
+
raw_evidence_sanitized_count: built.raw_evidence_facts?.sanitized_count ?? 0,
|
|
173
|
+
raw_evidence_failure_reasons: [
|
|
174
|
+
...new Set(failed.map((outcome) => outcome.reason ?? "unknown")),
|
|
175
|
+
],
|
|
176
|
+
raw_evidence_retry_required: retryRequired,
|
|
177
|
+
raw_evidence_retry_reasons: retryReason
|
|
178
|
+
? retryReason
|
|
179
|
+
.replace(/^partial_raw_evidence_retry_required:/u, "")
|
|
180
|
+
.split(",")
|
|
181
|
+
.filter(Boolean)
|
|
182
|
+
: [],
|
|
183
|
+
raw_evidence_outcomes: [
|
|
184
|
+
...outcomes.map((outcome) => ({
|
|
185
|
+
object_key: outcome.object_key,
|
|
186
|
+
raw_evidence_pointer_id: outcome.pointer.raw_evidence_pointer_id,
|
|
187
|
+
kind: outcome.kind,
|
|
188
|
+
codex_session_id: outcome.codex_session_id,
|
|
189
|
+
...(outcome.artifact_metadata
|
|
190
|
+
? { artifact_metadata: outcome.artifact_metadata }
|
|
191
|
+
: {}),
|
|
192
|
+
upload_state: outcome.upload_state,
|
|
193
|
+
reason: outcome.reason,
|
|
194
|
+
})),
|
|
195
|
+
...cursorReusedOutcomes,
|
|
196
|
+
],
|
|
197
|
+
raw_evidence_deferred_byte_budget: built.raw_evidence_facts?.deferred_byte_budget_count ?? 0,
|
|
198
|
+
raw_evidence_deferred_object_budget: built.raw_evidence_facts?.deferred_object_budget_count ?? 0,
|
|
199
|
+
cursor_tracked_object_count: Object.keys(cursor.objects).length,
|
|
200
|
+
raw_evidence_delivery_held_count: outcomes.filter((outcome) => outcome.reason === DELIVERY_BACKOFF_HOLDING_REASON).length + (built.raw_evidence_facts?.delivery_held_count ?? 0),
|
|
201
|
+
raw_evidence_stuck_object_count: stuck.stuck_object_count,
|
|
202
|
+
raw_evidence_max_delivery_attempts: stuck.max_attempts,
|
|
203
|
+
raw_evidence_oldest_delivery_failure_at: stuck.oldest_first_failed_at,
|
|
204
|
+
};
|
|
205
|
+
}
|