@contrail/cli 3.5.0-alpha.11f30e6 → 3.5.0-alpha.9d881a5

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/README.md CHANGED
@@ -17,7 +17,7 @@ $ npm install -g @contrail/cli
17
17
  $ contrail COMMAND
18
18
  running command...
19
19
  $ contrail (--version)
20
- @contrail/cli/3.5.0-alpha.11f30e6 linux-x64 node-v24.14.1
20
+ @contrail/cli/3.5.0-alpha.9d881a5 linux-x64 node-v24.14.1
21
21
  $ contrail --help [COMMAND]
22
22
  USAGE
23
23
  $ contrail COMMAND
@@ -1,12 +1,7 @@
1
1
  import { Dash0Client } from './client';
2
2
  import { AttributeFilter, FlatSpan, OtlpAttribute, OtlpResourceSpans, OtlpSpan, TimeReferenceRange } from './types';
3
3
  export declare function paginateSpans(client: Dash0Client, filter: AttributeFilter[], timeRange: TimeReferenceRange, limit?: number): AsyncGenerator<OtlpResourceSpans, void, void>;
4
- export interface SampledTrace {
5
- traceId: string;
6
- anchorNs: bigint;
7
- }
8
- export declare function sampleTraceIdsForAction(client: Dash0Client, actionIdentifier: string, timeRange: TimeReferenceRange, maxTraces: number): Promise<SampledTrace[]>;
9
- export declare function bracketWindowAround(anchorNs: bigint, halfWindowMin?: number): TimeReferenceRange;
4
+ export declare function sampleTraceIdsForAction(client: Dash0Client, actionIdentifier: string, timeRange: TimeReferenceRange, maxTraces: number): Promise<string[]>;
10
5
  export declare function fetchTraceSpans(client: Dash0Client, traceIdHex: string, timeRange: TimeReferenceRange): Promise<FlatSpan[]>;
11
6
  export declare function toFlat(sp: OtlpSpan, resourceAttrs: Record<string, string | number | boolean>): FlatSpan;
12
7
  export declare function flattenAttrs(attrs: OtlpAttribute[]): Record<string, string | number | boolean>;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.b64ToHex = exports.flattenAttrs = exports.toFlat = exports.fetchTraceSpans = exports.bracketWindowAround = exports.sampleTraceIdsForAction = exports.paginateSpans = void 0;
3
+ exports.b64ToHex = exports.flattenAttrs = exports.toFlat = exports.fetchTraceSpans = exports.sampleTraceIdsForAction = exports.paginateSpans = void 0;
4
4
  const PAGE_LIMIT = 50;
5
5
  const MAX_PAGES_PER_QUERY = 200;
6
6
  async function* paginateSpans(client, filter, timeRange, limit = PAGE_LIMIT) {
@@ -21,8 +21,8 @@ async function* paginateSpans(client, filter, timeRange, limit = PAGE_LIMIT) {
21
21
  }
22
22
  exports.paginateSpans = paginateSpans;
23
23
  async function sampleTraceIdsForAction(client, actionIdentifier, timeRange, maxTraces) {
24
- const seen = new Map();
25
- outer: for await (const rs of paginateSpans(client, [
24
+ const seen = new Set();
25
+ for await (const rs of paginateSpans(client, [
26
26
  {
27
27
  key: 'contrail.event_workflow.action.identifier',
28
28
  operator: 'is',
@@ -31,33 +31,15 @@ async function sampleTraceIdsForAction(client, actionIdentifier, timeRange, maxT
31
31
  ], timeRange)) {
32
32
  for (const ss of rs.scopeSpans ?? []) {
33
33
  for (const sp of ss.spans ?? []) {
34
- const traceId = b64ToHex(sp.traceId);
35
- const startNs = BigInt(sp.startTimeUnixNano);
36
- const existing = seen.get(traceId);
37
- if (existing === undefined || startNs < existing) {
38
- seen.set(traceId, startNs);
39
- }
34
+ seen.add(b64ToHex(sp.traceId));
40
35
  if (seen.size >= maxTraces)
41
- break outer;
36
+ return [...seen];
42
37
  }
43
38
  }
44
39
  }
45
- return [...seen.entries()].map(([traceId, anchorNs]) => ({ traceId, anchorNs }));
40
+ return [...seen];
46
41
  }
47
42
  exports.sampleTraceIdsForAction = sampleTraceIdsForAction;
48
- // Build a Dash0 absolute time range bracketing a trace anchor. ±halfWindowMin
49
- // around the anchor in ISO RFC3339; resilient to clock drift between sample
50
- // and fetch and wide enough to capture the full trace.
51
- function bracketWindowAround(anchorNs, halfWindowMin = 5) {
52
- const halfMs = BigInt(halfWindowMin) * 60000n;
53
- const fromMs = Number((anchorNs - halfMs * 1000000n) / 1000000n);
54
- const toMs = Number((anchorNs + halfMs * 1000000n) / 1000000n);
55
- return {
56
- from: new Date(fromMs).toISOString(),
57
- to: new Date(toMs).toISOString(),
58
- };
59
- }
60
- exports.bracketWindowAround = bracketWindowAround;
61
43
  async function fetchTraceSpans(client, traceIdHex, timeRange) {
62
44
  const out = [];
63
45
  for await (const rs of paginateSpans(client, [{ key: 'otel.trace.id', operator: 'is', value: traceIdHex }], timeRange)) {
@@ -60,24 +60,20 @@ class AssessImpact extends basecommand_1.BaseCommand {
60
60
  this.log(`Samples: target ${samples}`);
61
61
  this.log('');
62
62
  this.log('Sampling trace IDs...');
63
- const sampled = await (0, spans_1.sampleTraceIdsForAction)(client, identifier, timeRange, samples);
64
- if (sampled.length === 0) {
63
+ const traceIds = await (0, spans_1.sampleTraceIdsForAction)(client, identifier, timeRange, samples);
64
+ if (traceIds.length === 0) {
65
65
  this.warn(`No spans found for action.identifier="${identifier}" in the last ${lookback}. ` +
66
66
  'Verify the identifier and dataset.');
67
67
  return;
68
68
  }
69
- this.log(`Got ${sampled.length} trace(s). Fetching subtrees...`);
69
+ this.log(`Got ${traceIds.length} trace(s). Fetching subtrees...`);
70
70
  const perTraceDescendants = [];
71
71
  const actionDurationsNs = [];
72
72
  let dropped = 0;
73
73
  let totalOrphans = 0;
74
74
  let preciseRootCount = 0;
75
- for (const { traceId, anchorNs } of sampled) {
76
- // Bracket each trace fetch on the absolute timestamp captured at sample
77
- // time. Avoids sliding-window edge effects where a trace at the boundary
78
- // of the lookback window has its earliest spans excluded.
79
- const traceWindow = (0, spans_1.bracketWindowAround)(anchorNs);
80
- const spans = await (0, spans_1.fetchTraceSpans)(client, traceId, traceWindow);
75
+ for (const traceId of traceIds) {
76
+ const spans = await (0, spans_1.fetchTraceSpans)(client, traceId, timeRange);
81
77
  const result = (0, subtree_1.extractActionSubtree)(spans, identifier);
82
78
  if (!result) {
83
79
  dropped++;
@@ -83,17 +83,10 @@ function extractActionSubtree(spans, identifier) {
83
83
  queue.push({ spanId: c.spanId, depth: depth + 1, serverAncestors: childServerCount });
84
84
  }
85
85
  }
86
- // An orphan is a span whose parent was never observed AND whose time range
87
- // falls inside the action span's time range. Orphans outside that range are
88
- // unrelated trace spans (siblings of the action's lineage) and shouldn't
89
- // taint the propagation-gap warning.
90
86
  let orphanCount = 0;
91
87
  for (const s of spans) {
92
- if (!s.parentSpanId || byId.has(s.parentSpanId))
93
- continue;
94
- if (s.startNs < actionSpan.startNs || s.startNs > actionSpan.endNs)
95
- continue;
96
- orphanCount++;
88
+ if (s.parentSpanId && !byId.has(s.parentSpanId))
89
+ orphanCount++;
97
90
  }
98
91
  return { descendants, actionSpan, preciseRoot, orphanCount };
99
92
  }
@@ -88,48 +88,16 @@ describe('extractActionSubtree', () => {
88
88
  expect(ids).toEqual(['http1', 'nestedDdb']);
89
89
  });
90
90
  });
91
- describe('orphan counting', () => {
92
- it('counts orphans inside the action span time range', () => {
93
- const spans = [
94
- s({
95
- spanId: 'consumer',
96
- parentSpanId: '',
97
- startNs: 1000n,
98
- endNs: 9000n,
99
- attrs: { 'contrail.event_workflow.action.identifier': IDENT },
100
- }),
101
- // Inside [1000, 9000] with missing parent → counts.
102
- s({
103
- spanId: 'innerOrphan',
104
- parentSpanId: 'missing',
105
- startNs: 5000n,
106
- endNs: 6000n,
107
- }),
108
- ];
109
- const result = (0, subtree_1.extractActionSubtree)(spans, IDENT);
110
- expect(result.orphanCount).toBe(1);
111
- });
112
- it('ignores orphans whose time falls outside the action span', () => {
113
- const spans = [
114
- s({
115
- spanId: 'consumer',
116
- parentSpanId: '',
117
- startNs: 1000n,
118
- endNs: 9000n,
119
- attrs: { 'contrail.event_workflow.action.identifier': IDENT },
120
- }),
121
- // Sibling-of-action lineage span. Real parent is unobserved (window
122
- // edge effect) but its time is outside the action's range — noise,
123
- // not a real propagation gap.
124
- s({
125
- spanId: 'unrelated',
126
- parentSpanId: 'missing',
127
- startNs: 50000n,
128
- endNs: 60000n,
129
- }),
130
- ];
131
- const result = (0, subtree_1.extractActionSubtree)(spans, IDENT);
132
- expect(result.orphanCount).toBe(0);
133
- });
91
+ it('counts orphans (parent_id pointing outside the trace)', () => {
92
+ const spans = [
93
+ s({
94
+ spanId: 'consumer',
95
+ parentSpanId: '',
96
+ attrs: { 'contrail.event_workflow.action.identifier': IDENT },
97
+ }),
98
+ s({ spanId: 'unattached', parentSpanId: 'missing-parent' }),
99
+ ];
100
+ const result = (0, subtree_1.extractActionSubtree)(spans, IDENT);
101
+ expect(result.orphanCount).toBe(1);
134
102
  });
135
103
  });
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "3.5.0-alpha.11f30e6",
2
+ "version": "3.5.0-alpha.9d881a5",
3
3
  "commands": {
4
4
  "app:create": {
5
5
  "id": "app:create",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contrail/cli",
3
- "version": "3.5.0-alpha.11f30e6",
3
+ "version": "3.5.0-alpha.9d881a5",
4
4
  "description": "VibeIQ's Contrail Platform CLI.",
5
5
  "bin": {
6
6
  "contrail-cli": "./bin/run",