@bli-cockpit/telemetry-core 0.1.42 → 0.1.44

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.
@@ -112,6 +112,10 @@ export declare const CollectorHeartbeatSchema: z.ZodObject<{
112
112
  hook_chars_p50: z.ZodOptional<z.ZodNumber>;
113
113
  hook_chars_p95: z.ZodOptional<z.ZodNumber>;
114
114
  hook_chars_samples: z.ZodOptional<z.ZodNumber>;
115
+ hook_stop_runs_24h: z.ZodOptional<z.ZodNumber>;
116
+ hook_stop_failed_24h: z.ZodOptional<z.ZodNumber>;
117
+ hook_session_start_runs_24h: z.ZodOptional<z.ZodNumber>;
118
+ hook_session_start_failed_24h: z.ZodOptional<z.ZodNumber>;
115
119
  hook_stats_reason: z.ZodOptional<z.ZodString>;
116
120
  hook_performance: z.ZodOptional<z.ZodObject<{
117
121
  schema_version: z.ZodLiteral<"memory-hook-performance.v1">;
@@ -0,0 +1,8 @@
1
+ /** In-object manifest. Legacy objects without this exact prefix remain raw. */
2
+ export declare const EVIDENCE_ENCODING_MANIFEST: Buffer<ArrayBuffer>;
3
+ export declare const MAX_DECODED_EVIDENCE_BYTES: number;
4
+ export declare function encodeEvidenceBytes(bytes: Uint8Array): Buffer;
5
+ export declare function decodeEvidenceBytes(bytes: Uint8Array): Buffer;
6
+ export declare function encodeEvidenceBody(body: Buffer | ReadableStream<Uint8Array>): Buffer | ReadableStream<Uint8Array>;
7
+ /** Lazy opening keeps read failures inside the caller's normal stream cleanup. */
8
+ export declare function decodeEvidenceStream(body: ReadableStream<Uint8Array>): ReadableStream<Uint8Array>;
@@ -0,0 +1,97 @@
1
+ import { gzipSync, gunzipSync } from "node:zlib";
2
+ /** In-object manifest. Legacy objects without this exact prefix remain raw. */
3
+ export const EVIDENCE_ENCODING_MANIFEST = Buffer.from('TOWER-EVIDENCE\n{"version":1,"encoding":"gzip"}\n');
4
+ export const MAX_DECODED_EVIDENCE_BYTES = 512 * 1024 * 1024;
5
+ export function encodeEvidenceBytes(bytes) {
6
+ return Buffer.concat([EVIDENCE_ENCODING_MANIFEST, gzipSync(bytes)]);
7
+ }
8
+ export function decodeEvidenceBytes(bytes) {
9
+ const buffer = Buffer.from(bytes);
10
+ return buffer.subarray(0, EVIDENCE_ENCODING_MANIFEST.length).equals(EVIDENCE_ENCODING_MANIFEST)
11
+ ? gunzipSync(buffer.subarray(EVIDENCE_ENCODING_MANIFEST.length), { maxOutputLength: MAX_DECODED_EVIDENCE_BYTES })
12
+ : buffer;
13
+ }
14
+ export function encodeEvidenceBody(body) {
15
+ if (Buffer.isBuffer(body))
16
+ return encodeEvidenceBytes(body);
17
+ const reader = body.pipeThrough(new CompressionStream("gzip")).getReader();
18
+ let prefixSent = false;
19
+ return new ReadableStream({
20
+ async pull(controller) {
21
+ if (!prefixSent) {
22
+ prefixSent = true;
23
+ controller.enqueue(EVIDENCE_ENCODING_MANIFEST);
24
+ return;
25
+ }
26
+ const next = await reader.read();
27
+ if (next.done)
28
+ controller.close();
29
+ else
30
+ controller.enqueue(next.value);
31
+ },
32
+ cancel(reason) { return reader.cancel(reason); },
33
+ });
34
+ }
35
+ /** Look ahead only as far as the manifest, then decode with bounded residency. */
36
+ async function openDecodedStream(reader) {
37
+ let head = Buffer.alloc(0);
38
+ while (head.length < EVIDENCE_ENCODING_MANIFEST.length) {
39
+ const next = await reader.read();
40
+ if (next.done)
41
+ break;
42
+ head = Buffer.concat([head, next.value]);
43
+ const prefixLength = Math.min(head.length, EVIDENCE_ENCODING_MANIFEST.length);
44
+ if (!head.subarray(0, prefixLength).equals(EVIDENCE_ENCODING_MANIFEST.subarray(0, prefixLength)))
45
+ break;
46
+ }
47
+ const encoded = head.subarray(0, EVIDENCE_ENCODING_MANIFEST.length).equals(EVIDENCE_ENCODING_MANIFEST);
48
+ let first = encoded ? head.subarray(EVIDENCE_ENCODING_MANIFEST.length) : head;
49
+ const remainder = new ReadableStream({
50
+ async pull(controller) {
51
+ if (first !== null) {
52
+ const bytes = first;
53
+ first = null;
54
+ controller.enqueue(bytes);
55
+ return;
56
+ }
57
+ const next = await reader.read();
58
+ if (next.done)
59
+ controller.close();
60
+ else
61
+ controller.enqueue(next.value);
62
+ },
63
+ cancel(reason) { return reader.cancel(reason); },
64
+ });
65
+ if (!encoded)
66
+ return remainder;
67
+ let decodedBytes = 0;
68
+ return remainder.pipeThrough(new DecompressionStream("gzip")).pipeThrough(new TransformStream({
69
+ transform(chunk, controller) {
70
+ decodedBytes += chunk.byteLength;
71
+ if (decodedBytes > MAX_DECODED_EVIDENCE_BYTES)
72
+ throw new Error("evidence_decoded_size_exceeded");
73
+ controller.enqueue(chunk);
74
+ },
75
+ }));
76
+ }
77
+ /** Lazy opening keeps read failures inside the caller's normal stream cleanup. */
78
+ export function decodeEvidenceStream(body) {
79
+ const sourceReader = body.getReader();
80
+ let reader;
81
+ return new ReadableStream({
82
+ async pull(controller) {
83
+ reader ??= (await openDecodedStream(sourceReader)).getReader();
84
+ const next = await reader.read();
85
+ if (next.done)
86
+ controller.close();
87
+ else
88
+ controller.enqueue(next.value);
89
+ },
90
+ async cancel(reason) {
91
+ if (reader)
92
+ await reader.cancel(reason);
93
+ else
94
+ await sourceReader.cancel(reason);
95
+ },
96
+ });
97
+ }
@@ -269,7 +269,11 @@ export declare const RawEvidenceUploadBeginResponseSchema: z.ZodObject<{
269
269
  }, z.core.$strict>;
270
270
  export type RawEvidenceUploadBeginResponse = z.infer<typeof RawEvidenceUploadBeginResponseSchema>;
271
271
  export declare const RawEvidenceUploadChunkRequestSchema: z.ZodObject<{
272
- schema_version: z.ZodLiteral<"ambient-raw-evidence-upload-chunk.v1">;
272
+ schema_version: z.ZodEnum<{
273
+ "ambient-raw-evidence-upload-chunk.v1": "ambient-raw-evidence-upload-chunk.v1";
274
+ "ambient-raw-evidence-upload-chunk.v2": "ambient-raw-evidence-upload-chunk.v2";
275
+ }>;
276
+ encoding: z.ZodOptional<z.ZodLiteral<"gzip">>;
273
277
  generated_at: z.ZodString;
274
278
  provenance: z.ZodObject<{
275
279
  capture_source: z.ZodEnum<{
@@ -122,7 +122,8 @@ export const RawEvidenceUploadBeginResponseSchema = z
122
122
  .strict();
123
123
  export const RawEvidenceUploadChunkRequestSchema = z
124
124
  .object({
125
- schema_version: z.literal("ambient-raw-evidence-upload-chunk.v1"),
125
+ schema_version: z.enum(["ambient-raw-evidence-upload-chunk.v1", "ambient-raw-evidence-upload-chunk.v2"]),
126
+ encoding: z.literal("gzip").optional(),
126
127
  generated_at: IsoDateTimeSchema,
127
128
  provenance: CaptureProvenanceSchema,
128
129
  upload_id: NonEmptyStringSchema,
package/dist/index.d.ts CHANGED
@@ -14,6 +14,7 @@ export * from "./memory-hook-stats.js";
14
14
  export * from "./memory-extract-context.js";
15
15
  export * from "./memory-hook-performance.js";
16
16
  export * from "./memory-install-receipt.js";
17
+ export * from "./outbound-guard.js";
17
18
  export * from "./paths.js";
18
19
  export * from "./privacy.js";
19
20
  export * from "./risk-flags.js";
package/dist/index.js CHANGED
@@ -14,6 +14,7 @@ export * from "./memory-hook-stats.js";
14
14
  export * from "./memory-extract-context.js";
15
15
  export * from "./memory-hook-performance.js";
16
16
  export * from "./memory-install-receipt.js";
17
+ export * from "./outbound-guard.js";
17
18
  export * from "./paths.js";
18
19
  export * from "./privacy.js";
19
20
  export * from "./risk-flags.js";
@@ -120,6 +120,10 @@ export declare const MemoryInstallReceiptSchema: z.ZodObject<{
120
120
  hook_chars_p50: z.ZodOptional<z.ZodNumber>;
121
121
  hook_chars_p95: z.ZodOptional<z.ZodNumber>;
122
122
  hook_chars_samples: z.ZodOptional<z.ZodNumber>;
123
+ hook_stop_runs_24h: z.ZodOptional<z.ZodNumber>;
124
+ hook_stop_failed_24h: z.ZodOptional<z.ZodNumber>;
125
+ hook_session_start_runs_24h: z.ZodOptional<z.ZodNumber>;
126
+ hook_session_start_failed_24h: z.ZodOptional<z.ZodNumber>;
123
127
  hook_stats_reason: z.ZodOptional<z.ZodString>;
124
128
  hook_performance: z.ZodOptional<z.ZodObject<{
125
129
  schema_version: z.ZodLiteral<"memory-hook-performance.v1">;
@@ -180,6 +180,30 @@ export const MemoryInstallReceiptSchema = z
180
180
  hook_chars_p50: z.number().int().min(0).optional(),
181
181
  hook_chars_p95: z.number().int().min(0).optional(),
182
182
  hook_chars_samples: z.number().int().min(0).optional(),
183
+ /**
184
+ * THE OTHER TWO HOOKS (BLI-4057).
185
+ *
186
+ * Everything above counts the PROMPT hook only — the recall. Three hooks
187
+ * are installed on every machine and until this ticket the fleet heard
188
+ * from exactly one of them, so a SAVE hook that stopped firing (the one
189
+ * that writes memory at all) was invisible everywhere: the install
190
+ * receipt said `hooks ok`, the counts said 40 prompt recalls, and nothing
191
+ * anywhere said that not one memory had been written in ten days.
192
+ *
193
+ * `hook_stop_runs_24h` is the save hook; `hook_session_start_runs_24h` is
194
+ * the session-start recall. Each has its own failure count beside it,
195
+ * because a hook that runs and fails every time is a different fact from
196
+ * one that never ran — and the first is invisible in a run count alone.
197
+ *
198
+ * Optional like every count above, and ABSENT MEANS NOT REPORTED: a
199
+ * collector older than this ticket sends none, and a zero here would read
200
+ * as "the save hook ran zero times", which is exactly the alarm this
201
+ * ticket exists to raise. A reader may never substitute one for the other.
202
+ */
203
+ hook_stop_runs_24h: z.number().int().min(0).optional(),
204
+ hook_stop_failed_24h: z.number().int().min(0).optional(),
205
+ hook_session_start_runs_24h: z.number().int().min(0).optional(),
206
+ hook_session_start_failed_24h: z.number().int().min(0).optional(),
183
207
  /** Named when the counts are absent because the file could not be read. */
184
208
  hook_stats_reason: ReasonLabelSchema.optional(),
185
209
  /** Observed ordinary invocations, not legacy floors or a full host trace. */
@@ -0,0 +1,77 @@
1
+ /**
2
+ * The one seam every outbound write passes through (BLI-4086).
3
+ *
4
+ * The secret detector in this package was called at roughly fifty sites and
5
+ * every one of them was INGRESS — evidence upload, memory save, receipt
6
+ * parsing, notes intake, harvest extraction. Nothing ran on the way out. So
7
+ * the system could tell that a laptop had uploaded a credential and was blind
8
+ * when the assistant mailed one to somebody, and the assistant reaches every
9
+ * send path we own through MCP (`mail_send`, `msg_send`, `work_comment_issue`,
10
+ * `docs_update`, `notes_share`).
11
+ *
12
+ * **Why one function and not a guard per surface.** Five surfaces already
13
+ * disagreed about almost everything else — mail throws `MailDoorError`, msg
14
+ * throws `MsgDoorError`, Slack throws a bare `Error` — and a rule that has to
15
+ * be re-implemented five times is a rule that holds in four places within a
16
+ * quarter. The precedent is the mandatory-AI-SDK seam: make the single path
17
+ * the only path and let a census test fail when a sixth surface appears
18
+ * without it. `outbound-guard-census.test.ts` in the dashboard is that test.
19
+ *
20
+ * **It masks; it never refuses.** Consistent with the standing order that
21
+ * nothing is dropped — a refused send is a person's message deleted by a
22
+ * regex, and this detector's own header explains at length why coarse
23
+ * false positives are tolerable only because masking never costs the content.
24
+ * A masked send still sends, with `[REDACTED:<rule_id>]` where the value was.
25
+ *
26
+ * **It lives here, not in the dashboard**, because the worker's Linear mirror
27
+ * is an egress path too and cannot import from `apps/dashboard`. One choke
28
+ * point that stops at an app boundary is two choke points.
29
+ */
30
+ /** The surfaces that can carry text out of Tower. Named so a log line can be grouped. */
31
+ export declare const OUTBOUND_SURFACES: readonly ["mail", "msg", "slack", "work", "docs", "linear_mirror", "cal"];
32
+ export type OutboundSurface = (typeof OUTBOUND_SURFACES)[number];
33
+ /** What was masked, for the caller that wants to say so in its own log line. */
34
+ export interface OutboundGuardReport {
35
+ /** True when at least one field changed. */
36
+ masked: boolean;
37
+ /** Field names that changed — names only, never values. */
38
+ maskedFields: string[];
39
+ /** Which rule caught what, and how much. The rule id IS the reason label. */
40
+ ruleCounts: {
41
+ rule_id: string;
42
+ match_count: number;
43
+ redacted_char_count: number;
44
+ }[];
45
+ /** Total matches across every field. */
46
+ matchCount: number;
47
+ }
48
+ export interface OutboundGuardResult<T> {
49
+ /** The same shape that came in, with secret-shaped spans replaced. */
50
+ fields: T;
51
+ report: OutboundGuardReport;
52
+ }
53
+ type GuardableFields = Record<string, string | null | undefined>;
54
+ /**
55
+ * Masks every secret-shaped span in `fields` and logs once if anything changed.
56
+ *
57
+ * `null` and `undefined` pass through untouched, so a caller can hand over an
58
+ * optional field (`html`, a patch's absent `title`) without a branch at every
59
+ * call site — the branch is what gets forgotten.
60
+ *
61
+ * The log line carries the surface, the field NAMES, the rule ids and the
62
+ * counts. It deliberately carries no fragment of the matched text: a guard
63
+ * that prints what it caught has moved the secret from a message into a log,
64
+ * which is the failure this whole family exists to prevent
65
+ * (`~/.claude/rules/security.md`, "never log PII").
66
+ */
67
+ export declare function guardOutboundFields<T extends GuardableFields>(surface: OutboundSurface, fields: T): OutboundGuardResult<T>;
68
+ /**
69
+ * The single-field shorthand, for a writer that has exactly one body to guard.
70
+ *
71
+ * Prefer `guardOutboundFields` when a writer has two or more — it produces ONE
72
+ * log line for the send rather than one per field, and a reader counting
73
+ * masked sends should not have to know how many columns a surface happens to
74
+ * have.
75
+ */
76
+ export declare function guardOutboundText(surface: OutboundSurface, field: string, text: string): string;
77
+ export {};
@@ -0,0 +1,119 @@
1
+ import { redactSecretLikeContent } from "./secret-guards-masking.js";
2
+ /**
3
+ * The one seam every outbound write passes through (BLI-4086).
4
+ *
5
+ * The secret detector in this package was called at roughly fifty sites and
6
+ * every one of them was INGRESS — evidence upload, memory save, receipt
7
+ * parsing, notes intake, harvest extraction. Nothing ran on the way out. So
8
+ * the system could tell that a laptop had uploaded a credential and was blind
9
+ * when the assistant mailed one to somebody, and the assistant reaches every
10
+ * send path we own through MCP (`mail_send`, `msg_send`, `work_comment_issue`,
11
+ * `docs_update`, `notes_share`).
12
+ *
13
+ * **Why one function and not a guard per surface.** Five surfaces already
14
+ * disagreed about almost everything else — mail throws `MailDoorError`, msg
15
+ * throws `MsgDoorError`, Slack throws a bare `Error` — and a rule that has to
16
+ * be re-implemented five times is a rule that holds in four places within a
17
+ * quarter. The precedent is the mandatory-AI-SDK seam: make the single path
18
+ * the only path and let a census test fail when a sixth surface appears
19
+ * without it. `outbound-guard-census.test.ts` in the dashboard is that test.
20
+ *
21
+ * **It masks; it never refuses.** Consistent with the standing order that
22
+ * nothing is dropped — a refused send is a person's message deleted by a
23
+ * regex, and this detector's own header explains at length why coarse
24
+ * false positives are tolerable only because masking never costs the content.
25
+ * A masked send still sends, with `[REDACTED:<rule_id>]` where the value was.
26
+ *
27
+ * **It lives here, not in the dashboard**, because the worker's Linear mirror
28
+ * is an egress path too and cannot import from `apps/dashboard`. One choke
29
+ * point that stops at an app boundary is two choke points.
30
+ */
31
+ /** The surfaces that can carry text out of Tower. Named so a log line can be grouped. */
32
+ export const OUTBOUND_SURFACES = [
33
+ "mail",
34
+ "msg",
35
+ "slack",
36
+ "work",
37
+ "docs",
38
+ "linear_mirror",
39
+ // BLI-4167. A calendar write is the widest egress on this list: Google
40
+ // EMAILS every attendee the moment the event lands, so the summary,
41
+ // description and location leave for addresses Tower does not control and
42
+ // no later edit unsends them. `lib/cal/write.ts` is the writer.
43
+ "cal",
44
+ ];
45
+ /**
46
+ * Masks every secret-shaped span in `fields` and logs once if anything changed.
47
+ *
48
+ * `null` and `undefined` pass through untouched, so a caller can hand over an
49
+ * optional field (`html`, a patch's absent `title`) without a branch at every
50
+ * call site — the branch is what gets forgotten.
51
+ *
52
+ * The log line carries the surface, the field NAMES, the rule ids and the
53
+ * counts. It deliberately carries no fragment of the matched text: a guard
54
+ * that prints what it caught has moved the secret from a message into a log,
55
+ * which is the failure this whole family exists to prevent
56
+ * (`~/.claude/rules/security.md`, "never log PII").
57
+ */
58
+ export function guardOutboundFields(surface, fields) {
59
+ const guarded = {};
60
+ const maskedFields = [];
61
+ const ruleTotals = new Map();
62
+ let matchCount = 0;
63
+ for (const [name, value] of Object.entries(fields)) {
64
+ if (typeof value !== "string" || value.length === 0) {
65
+ guarded[name] = value;
66
+ continue;
67
+ }
68
+ // `server_commit` is the honest source: the mask is applied by the server
69
+ // at the moment it commits the send. The enum has no `outbound` member and
70
+ // adding one would relabel live redaction receipts that
71
+ // `secret-guards-lock.test.ts` pins — a schema change for a log line is a
72
+ // bad trade, and no outbound mask is persisted as a receipt anyway.
73
+ const result = redactSecretLikeContent(value, {
74
+ appliedBy: "server_commit",
75
+ redactedFields: [name],
76
+ });
77
+ guarded[name] = result.text;
78
+ if (!result.redacted)
79
+ continue;
80
+ maskedFields.push(name);
81
+ for (const count of result.metadata?.rule_counts ?? []) {
82
+ const existing = ruleTotals.get(count.rule_id) ?? {
83
+ rule_id: count.rule_id,
84
+ match_count: 0,
85
+ redacted_char_count: 0,
86
+ };
87
+ existing.match_count += count.match_count;
88
+ existing.redacted_char_count += count.redacted_char_count;
89
+ ruleTotals.set(count.rule_id, existing);
90
+ matchCount += count.match_count;
91
+ }
92
+ }
93
+ const report = {
94
+ masked: maskedFields.length > 0,
95
+ maskedFields,
96
+ ruleCounts: [...ruleTotals.values()],
97
+ matchCount,
98
+ };
99
+ if (report.masked) {
100
+ console.warn("[outbound guard] masked", JSON.stringify({
101
+ surface,
102
+ masked_fields: report.maskedFields,
103
+ rule_counts: report.ruleCounts,
104
+ secret_like_match_count: report.matchCount,
105
+ }));
106
+ }
107
+ return { fields: guarded, report };
108
+ }
109
+ /**
110
+ * The single-field shorthand, for a writer that has exactly one body to guard.
111
+ *
112
+ * Prefer `guardOutboundFields` when a writer has two or more — it produces ONE
113
+ * log line for the send rather than one per field, and a reader counting
114
+ * masked sends should not have to know how many columns a surface happens to
115
+ * have.
116
+ */
117
+ export function guardOutboundText(surface, field, text) {
118
+ return guardOutboundFields(surface, { [field]: text }).fields[field];
119
+ }
@@ -11,8 +11,42 @@ const SECRET_REDACTION_RULES = [
11
11
  secretGroup: 2,
12
12
  },
13
13
  {
14
+ // The body stops at `{`, and excludes nothing else (BLI-4168).
15
+ //
16
+ // `[\s\S]*?` used to sit here, which gave the rule no notion of where one
17
+ // record ends. Transcripts are JSONL, so a key whose `-----END-----` never
18
+ // arrived — a truncated write, a killed session — paired with the next
19
+ // `-----END-----` many records later and collapsed everything between into
20
+ // one `[REDACTED:private_key_block]`. That over-masks: nothing leaked, but
21
+ // a session's worth of legitimate records was destroyed silently, because
22
+ // a redaction looks exactly like the guard working.
23
+ //
24
+ // `{` is the JSONL record boundary: it is what opens the next record, and a
25
+ // PEM body is base64, so it cannot appear inside a key. This is JSONL-record
26
+ // scoping specifically, and JSONL is the ONLY shape it fixes. The same
27
+ // function also receives plain text — a raw `.pem`, a git diff, a log tail,
28
+ // YAML, CSV, a markdown note — where `{` is not a boundary at all. There it
29
+ // splits two ways. Where a `{` does happen to fall between the markers it
30
+ // merely ends the match early, leaving a narrow residue; that residue is the
31
+ // price of the scope. Where NO `{` falls between an unterminated BEGIN and a
32
+ // later stray END — the ordinary case for every one of those formats — the
33
+ // match is still unbounded and the original over-masking is FULLY ALIVE.
34
+ // Measured on six shapes, each an unterminated key, then a bystander record,
35
+ // then a stray END: JSONL preserved the bystander and the other five
36
+ // destroyed it. This rule does not fix that case. BLI-4216 carries it.
37
+ //
38
+ // It MUST stay a DENYLIST of that one character. An allowlist of the PEM
39
+ // alphabet was tried here and reverted the same day: one character outside
40
+ // the class anywhere between BEGIN and END — a `.`, a `[info] ` log prefix,
41
+ // an `N | ` gutter, an ANSI reset, a diff hunk header — makes the whole
42
+ // rule fail to match, and the body is then left in PLAINTEXT while
43
+ // `private_key_header` masks only the BEGIN line and
44
+ // `containsSecretLikeContent` reports the residue clean, so
45
+ // `secret_redaction_failed` never fires. That is a leak, and a leak is
46
+ // strictly worse than the over-masking this rule exists to fix. The
47
+ // leak-direction tests in `secret-guards.test.ts` pin it.
14
48
  ruleId: "private_key_block",
15
- pattern: /-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z ]*PRIVATE KEY-----/g,
49
+ pattern: /-----BEGIN [A-Z ]*PRIVATE KEY-----[^{]*?-----END [A-Z ]*PRIVATE KEY-----/g,
16
50
  },
17
51
  {
18
52
  ruleId: "private_key_header",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bli-cockpit/telemetry-core",
3
- "version": "0.1.42",
3
+ "version": "0.1.44",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -18,6 +18,10 @@
18
18
  "./memory-experience": {
19
19
  "types": "./dist/memory-experience.d.ts",
20
20
  "import": "./dist/memory-experience.js"
21
+ },
22
+ "./evidence-storage-encoding": {
23
+ "types": "./dist/evidence-storage-encoding.d.ts",
24
+ "import": "./dist/evidence-storage-encoding.js"
21
25
  }
22
26
  },
23
27
  "publishConfig": {