@bli-cockpit/telemetry-core 0.1.15 → 0.1.16

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.
@@ -26,10 +26,24 @@ export declare function normalizeActorLabel(rawLabel: string): string;
26
26
  export declare function unresolvedActorId(rawLabel: string): string;
27
27
  export interface DecisionEventIdInput {
28
28
  captureSource: string;
29
- /** Hash of the file this was read out of; null for a live hook firing. */
30
- sourceArtifactHashSha256: string | null;
31
- /** Line within that file; null when the artifact is not line-oriented. */
32
- sourceLineNumber: number | null;
29
+ /**
30
+ * Hash of exactly the bytes that make up this one decision; null for a live
31
+ * hook firing, which has no artifact at all.
32
+ *
33
+ * "This one decision" and not "the file it arrived in", which is the whole
34
+ * point. A verdict JSON holds one decision, so its record is the file and the
35
+ * two hashes coincide. `merge-log.jsonl` holds thousands and gains another
36
+ * every time somebody merges, so its record is the single line — hashing the
37
+ * file there would change every id in it on the next append, and a re-import
38
+ * would write a second copy of the ledger instead of updating the rows it
39
+ * already has.
40
+ *
41
+ * Which snapshot of which file the record came out of is still worth knowing;
42
+ * it is kept as `source_artifact_hash_sha256`, beside `source_line_number`, as
43
+ * provenance. Neither is an identity input. Conflating the two jobs in one
44
+ * field is what made a growing ledger un-re-importable.
45
+ */
46
+ sourceRecordHashSha256: string | null;
33
47
  kind: string;
34
48
  /**
35
49
  * Only for a live firing, which has no artifact to hash and no line to point
@@ -43,10 +57,19 @@ export interface DecisionEventIdInput {
43
57
  /**
44
58
  * The id of one decision event.
45
59
  *
46
- * Two consequences worth stating. Re-running the backfill is idempotent, because
47
- * the same line of the same file always yields the same id. And an *edited*
48
- * artifact produces a new id rather than mutating an old one, which keeps the
49
- * store append-only in the same way jarvis_corrections is history is added to,
50
- * never rewritten.
60
+ * Three consequences worth stating. Re-running the backfill is idempotent,
61
+ * because the same record always yields the same id no matter how much the file
62
+ * around it has grown or how far down it has been pushed. An *edited* record
63
+ * produces a new id rather than mutating an old one, which keeps the store
64
+ * append-only in the same way jarvis_corrections is — history is added to, never
65
+ * rewritten. And two byte-identical records collapse onto one row, which is the
66
+ * intended reading: in a ledger, identical bytes mean a hook wrote the same
67
+ * decision twice. If that is ever false the ledger format is underspecified, and
68
+ * the fix belongs there rather than in a tiebreaker here.
69
+ *
70
+ * The line number is deliberately absent. It is the one part of a record's
71
+ * position that a rotation or a compaction can change without the decision
72
+ * itself changing at all, and an id that moves when a file is renumbered is
73
+ * worse than a duplicate: it silently lands on some other decision's row.
51
74
  */
52
75
  export declare function decisionEventId(input: DecisionEventIdInput): string;
@@ -52,19 +52,25 @@ export function unresolvedActorId(rawLabel) {
52
52
  /**
53
53
  * The id of one decision event.
54
54
  *
55
- * Two consequences worth stating. Re-running the backfill is idempotent, because
56
- * the same line of the same file always yields the same id. And an *edited*
57
- * artifact produces a new id rather than mutating an old one, which keeps the
58
- * store append-only in the same way jarvis_corrections is history is added to,
59
- * never rewritten.
55
+ * Three consequences worth stating. Re-running the backfill is idempotent,
56
+ * because the same record always yields the same id no matter how much the file
57
+ * around it has grown or how far down it has been pushed. An *edited* record
58
+ * produces a new id rather than mutating an old one, which keeps the store
59
+ * append-only in the same way jarvis_corrections is — history is added to, never
60
+ * rewritten. And two byte-identical records collapse onto one row, which is the
61
+ * intended reading: in a ledger, identical bytes mean a hook wrote the same
62
+ * decision twice. If that is ever false the ledger format is underspecified, and
63
+ * the fix belongs there rather than in a tiebreaker here.
64
+ *
65
+ * The line number is deliberately absent. It is the one part of a record's
66
+ * position that a rotation or a compaction can change without the decision
67
+ * itself changing at all, and an id that moves when a file is renumbered is
68
+ * worse than a duplicate: it silently lands on some other decision's row.
60
69
  */
61
70
  export function decisionEventId(input) {
62
71
  const parts = [
63
72
  input.captureSource,
64
- input.sourceArtifactHashSha256 ?? "",
65
- input.sourceLineNumber === null || input.sourceLineNumber === undefined
66
- ? ""
67
- : String(input.sourceLineNumber),
73
+ input.sourceRecordHashSha256 ?? "",
68
74
  input.kind,
69
75
  input.occurredAt ?? "",
70
76
  input.subjectDiscriminator ?? "",
@@ -411,6 +411,7 @@ export declare const DecisionEventSchema: z.ZodObject<{
411
411
  source_line_number: z.ZodNullable<z.ZodNumber>;
412
412
  rule_id: z.ZodNullable<z.ZodString>;
413
413
  rulebook_version: z.ZodNullable<z.ZodString>;
414
+ host: z.ZodNullable<z.ZodString>;
414
415
  evidence_pointers: z.ZodArray<z.ZodObject<{
415
416
  kind: z.ZodEnum<{
416
417
  claude_state_path: "claude_state_path";
@@ -593,6 +594,7 @@ export declare const DecisionEventEnvelopeSchema: z.ZodObject<{
593
594
  source_line_number: z.ZodNullable<z.ZodNumber>;
594
595
  rule_id: z.ZodNullable<z.ZodString>;
595
596
  rulebook_version: z.ZodNullable<z.ZodString>;
597
+ host: z.ZodNullable<z.ZodString>;
596
598
  evidence_pointers: z.ZodArray<z.ZodObject<{
597
599
  kind: z.ZodEnum<{
598
600
  claude_state_path: "claude_state_path";
@@ -330,7 +330,8 @@ export const DecisionEventSchema = z
330
330
  .object({
331
331
  /**
332
332
  * Deterministic, so re-running the backfill is idempotent and an edited
333
- * artifact produces a new id rather than mutating an old one. See
333
+ * record produces a new id rather than mutating an old one. Derived from the
334
+ * record's own bytes, never from the file around them — see
334
335
  * `decisionEventId`.
335
336
  */
336
337
  decision_event_id: NonEmptyStringSchema.max(64),
@@ -340,7 +341,13 @@ export const DecisionEventSchema = z
340
341
  subject: DecisionSubjectSchema,
341
342
  outcome: DecisionOutcomeSchema,
342
343
  capture_source: DecisionCaptureSourceSchema,
343
- /** The artifact this was read out of, so an edit is detectable. */
344
+ /**
345
+ * Which snapshot of which file this was read out of, and where in it. Both
346
+ * are provenance, not identity: a re-import of a ledger that has grown since
347
+ * refreshes them on the existing row. Identity comes from the record's own
348
+ * bytes instead, which is what lets that re-import update rather than
349
+ * duplicate — see `decisionEventId`.
350
+ */
344
351
  source_artifact_hash_sha256: Sha256Schema.nullable(),
345
352
  source_line_number: z.number().int().nonnegative().nullable(),
346
353
  /**
@@ -350,6 +357,20 @@ export const DecisionEventSchema = z
350
357
  */
351
358
  rule_id: NonEmptyStringSchema.max(40).nullable(),
352
359
  rulebook_version: VersionStringSchema.nullable(),
360
+ /**
361
+ * Which machine the decision was made on, straight from the ledger line.
362
+ *
363
+ * A column rather than an `attributes` key, and deliberately beside
364
+ * `rulebook_version`: both are provenance about the *decision* rather than
365
+ * about the actor, and "did the laptop behave differently from the desktop"
366
+ * is a group-by question. A jsonb key would answer it by digging.
367
+ *
368
+ * Nullable because both ledgers only started stamping `host` on 2026-08-04
369
+ * (BLI-2435). Every line before that legitimately has none, so absence is
370
+ * history rather than an error, and a record without one must ingest
371
+ * cleanly.
372
+ */
373
+ host: NonEmptyStringSchema.max(253).nullable(),
353
374
  evidence_pointers: z.array(DecisionEvidencePointerSchema).max(20),
354
375
  /**
355
376
  * Small, flat, non-narrative extras. Enforced as scalar-or-scalar-array by
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bli-cockpit/telemetry-core",
3
- "version": "0.1.15",
3
+ "version": "0.1.16",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",