autotel-schema 0.1.0
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 +131 -0
- package/dist/cli.cjs +111 -0
- package/dist/cli.cjs.map +1 -0
- package/dist/cli.d.cts +14 -0
- package/dist/cli.d.cts.map +1 -0
- package/dist/cli.d.ts +14 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +82 -0
- package/dist/cli.js.map +1 -0
- package/dist/contract-DGjxR9nb.d.cts +123 -0
- package/dist/contract-DGjxR9nb.d.cts.map +1 -0
- package/dist/contract-DGjxR9nb.d.ts +123 -0
- package/dist/contract-DGjxR9nb.d.ts.map +1 -0
- package/dist/diff-BQPh72vY.d.cts +89 -0
- package/dist/diff-BQPh72vY.d.cts.map +1 -0
- package/dist/diff-D7qkNn0-.d.ts +89 -0
- package/dist/diff-D7qkNn0-.d.ts.map +1 -0
- package/dist/diff.cjs +185 -0
- package/dist/diff.cjs.map +1 -0
- package/dist/diff.d.cts +2 -0
- package/dist/diff.d.ts +2 -0
- package/dist/diff.js +181 -0
- package/dist/diff.js.map +1 -0
- package/dist/index.cjs +63 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +33 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +43 -0
- package/dist/index.js.map +1 -0
- package/dist/processor-CK7LAdaa.d.ts +100 -0
- package/dist/processor-CK7LAdaa.d.ts.map +1 -0
- package/dist/processor-CkBkzK6y.d.cts +100 -0
- package/dist/processor-CkBkzK6y.d.cts.map +1 -0
- package/dist/processor-D93TAXvZ.cjs +366 -0
- package/dist/processor-D93TAXvZ.cjs.map +1 -0
- package/dist/processor-FmvKYllX.js +306 -0
- package/dist/processor-FmvKYllX.js.map +1 -0
- package/dist/processor.cjs +5 -0
- package/dist/processor.d.cts +2 -0
- package/dist/processor.d.ts +2 -0
- package/dist/processor.js +3 -0
- package/dist/snapshot-CyWGJaJT.cjs +119 -0
- package/dist/snapshot-CyWGJaJT.cjs.map +1 -0
- package/dist/snapshot-h8pb_Up_.js +89 -0
- package/dist/snapshot-h8pb_Up_.js.map +1 -0
- package/package.json +80 -0
- package/src/attrs.ts +23 -0
- package/src/cli.ts +117 -0
- package/src/contract.test.ts +67 -0
- package/src/contract.ts +231 -0
- package/src/diff.ts +282 -0
- package/src/index.ts +88 -0
- package/src/processor.test.ts +74 -0
- package/src/processor.ts +152 -0
- package/src/redaction.ts +64 -0
- package/src/snapshot.test.ts +88 -0
- package/src/snapshot.ts +119 -0
- package/src/validate.test.ts +100 -0
- package/src/validate.ts +237 -0
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { o as Stability, r as AttributeType, s as TelemetryContract } from "./contract-DGjxR9nb.cjs";
|
|
2
|
+
|
|
3
|
+
//#region src/attrs.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Wire constants for the schema contract — the keys autotel-schema reads from
|
|
6
|
+
* and stamps onto spans. Dependency-free so CLI, browser, and edge code can
|
|
7
|
+
* share the exact same strings the runtime uses.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Resource/span attributes that announce the contract to a reader. Stamping
|
|
11
|
+
* `telemetry.schema.version` means an agent following a trace knows *which*
|
|
12
|
+
* version of your public telemetry API it is looking at — the difference
|
|
13
|
+
* between "confidently correct" and "confidently wrong" after a rename.
|
|
14
|
+
*/
|
|
15
|
+
declare const SCHEMA_ATTRS: {
|
|
16
|
+
/** The service this contract describes (mirrors `service.name`). */readonly SERVICE: "telemetry.schema.service"; /** Semver of the telemetry contract that produced this span. */
|
|
17
|
+
readonly VERSION: "telemetry.schema.version";
|
|
18
|
+
};
|
|
19
|
+
type SchemaAttributeKey = (typeof SCHEMA_ATTRS)[keyof typeof SCHEMA_ATTRS];
|
|
20
|
+
/** Snapshot file spec marker — bump only on a breaking snapshot format change. */
|
|
21
|
+
declare const SNAPSHOT_SPEC: "autotel-schema-snapshot/v1";
|
|
22
|
+
//#endregion
|
|
23
|
+
//#region src/snapshot.d.ts
|
|
24
|
+
/** Flattened, fully-resolved attribute record in a snapshot. */
|
|
25
|
+
interface SnapshotAttribute {
|
|
26
|
+
type: AttributeType;
|
|
27
|
+
stability: Stability;
|
|
28
|
+
required: boolean;
|
|
29
|
+
highCardinality: boolean;
|
|
30
|
+
enum?: readonly (string | number)[];
|
|
31
|
+
replacedBy?: string;
|
|
32
|
+
description?: string;
|
|
33
|
+
}
|
|
34
|
+
interface SnapshotSpan {
|
|
35
|
+
stability: Stability;
|
|
36
|
+
additionalAttributes: boolean;
|
|
37
|
+
description?: string;
|
|
38
|
+
attributes: Record<string, SnapshotAttribute>;
|
|
39
|
+
}
|
|
40
|
+
/** Canonical, comparable representation of a {@link TelemetryContract}. */
|
|
41
|
+
interface ContractSnapshot {
|
|
42
|
+
spec: typeof SNAPSHOT_SPEC;
|
|
43
|
+
service: string;
|
|
44
|
+
version: string;
|
|
45
|
+
commonAttributes: Record<string, SnapshotAttribute>;
|
|
46
|
+
spans: Record<string, SnapshotSpan>;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Produce a deterministic, JSON-serializable snapshot from a contract. Keys are
|
|
50
|
+
* sorted so two snapshots of the same logical contract are byte-identical —
|
|
51
|
+
* important for clean `git diff`s and stable CI comparisons.
|
|
52
|
+
*/
|
|
53
|
+
declare function contractToSnapshot(contract: TelemetryContract): ContractSnapshot;
|
|
54
|
+
/** Pretty, deterministic JSON for writing a snapshot to disk. */
|
|
55
|
+
declare function serializeSnapshot(snapshot: ContractSnapshot): string;
|
|
56
|
+
/** Parse and structurally validate a snapshot read from disk. */
|
|
57
|
+
declare function parseSnapshot(json: string): ContractSnapshot;
|
|
58
|
+
//#endregion
|
|
59
|
+
//#region src/diff.d.ts
|
|
60
|
+
type ChangeKind = 'breaking' | 'additive' | 'neutral';
|
|
61
|
+
type ChangeType = 'span_removed' | 'span_added' | 'attribute_removed' | 'attribute_added' | 'type_changed' | 'required_added' | 'required_removed' | 'enum_value_removed' | 'enum_value_added' | 'stability_downgraded' | 'stability_advanced' | 'deprecated' | 'replacement_documented';
|
|
62
|
+
interface SnapshotChange {
|
|
63
|
+
kind: ChangeKind;
|
|
64
|
+
type: ChangeType;
|
|
65
|
+
/** Span the change applies to (`*` = common attributes / contract-wide). */
|
|
66
|
+
span: string;
|
|
67
|
+
attribute?: string;
|
|
68
|
+
message: string;
|
|
69
|
+
}
|
|
70
|
+
interface SnapshotDiff {
|
|
71
|
+
service: string;
|
|
72
|
+
previousVersion: string;
|
|
73
|
+
nextVersion: string;
|
|
74
|
+
breaking: SnapshotChange[];
|
|
75
|
+
additive: SnapshotChange[];
|
|
76
|
+
neutral: SnapshotChange[];
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Diff two snapshots, classifying every change. The `breaking` array is what a
|
|
80
|
+
* CI gate keys off; `hasBreakingChanges()` is the convenience predicate.
|
|
81
|
+
*/
|
|
82
|
+
declare function diffSnapshots(previous: ContractSnapshot, next: ContractSnapshot): SnapshotDiff;
|
|
83
|
+
/** `true` when the diff contains at least one breaking change. */
|
|
84
|
+
declare function hasBreakingChanges(diff: SnapshotDiff): boolean;
|
|
85
|
+
/** Markdown rendering of a diff — for CI logs and PR comments. */
|
|
86
|
+
declare function formatDiff(diff: SnapshotDiff): string;
|
|
87
|
+
//#endregion
|
|
88
|
+
export { diffSnapshots as a, ContractSnapshot as c, contractToSnapshot as d, parseSnapshot as f, SchemaAttributeKey as g, SNAPSHOT_SPEC as h, SnapshotDiff as i, SnapshotAttribute as l, SCHEMA_ATTRS as m, ChangeType as n, formatDiff as o, serializeSnapshot as p, SnapshotChange as r, hasBreakingChanges as s, ChangeKind as t, SnapshotSpan as u };
|
|
89
|
+
//# sourceMappingURL=diff-BQPh72vY.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diff-BQPh72vY.d.cts","names":[],"sources":["../src/attrs.ts","../src/snapshot.ts","../src/diff.ts"],"mappings":";;;;;;AAYA;;;;;AAOA;;;cAPa,YAAA;EAOmE,6EAFtE,OAAA,8BAKwD;EAAA,SALxD,OAAA;AAAA;AAAA,KAEE,kBAAA,WAA6B,YAAA,eAA2B,YAAY;;cAGnE,aAAA;;;AAHb;AAAA,UCHiB,iBAAA;EACf,IAAA,EAAM,aAAA;EACN,SAAA,EAAW,SAAS;EACpB,QAAA;EACA,eAAA;EACA,IAAA;EACA,UAAA;EACA,WAAA;AAAA;AAAA,UAGe,YAAA;EACf,SAAA,EAAW,SAAA;EACX,oBAAA;EACA,WAAA;EACA,UAAA,EAAY,MAAA,SAAe,iBAAA;AAAA;;UAIZ,gBAAA;EACf,IAAA,SAAa,aAAA;EACb,OAAA;EACA,OAAA;EACA,gBAAA,EAAkB,MAAA,SAAe,iBAAA;EACjC,KAAA,EAAO,MAAA,SAAe,YAAA;AAAA;;;;AAhBX;AAGb;iBA0CgB,kBAAA,CACd,QAAA,EAAU,iBAAA,GACT,gBAAgB;;iBAgCH,iBAAA,CAAkB,QAA0B,EAAhB,gBAAgB;;iBAK5C,aAAA,CAAc,IAAA,WAAe,gBAAgB;;;KC/FjD,UAAA;AAAA,KAEA,UAAA;AAAA,UAeK,cAAA;EACf,IAAA,EAAM,UAAA;EACN,IAAA,EAAM,UAAU;;EAEhB,IAAA;EACA,SAAA;EACA,OAAA;AAAA;AAAA,UAGe,YAAA;EACf,OAAA;EACA,eAAA;EACA,WAAA;EACA,QAAA,EAAU,cAAA;EACV,QAAA,EAAU,cAAA;EACV,OAAA,EAAS,cAAA;AAAA;;;;;iBAqKK,aAAA,CACd,QAAA,EAAU,gBAAA,EACV,IAAA,EAAM,gBAAA,GACL,YAAA;;iBAwCa,kBAAA,CAAmB,IAAkB,EAAZ,YAAY;ADrOxC;AAAA,iBC0OG,UAAA,CAAW,IAAkB,EAAZ,YAAY"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { o as Stability, r as AttributeType, s as TelemetryContract } from "./contract-DGjxR9nb.js";
|
|
2
|
+
|
|
3
|
+
//#region src/attrs.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Wire constants for the schema contract — the keys autotel-schema reads from
|
|
6
|
+
* and stamps onto spans. Dependency-free so CLI, browser, and edge code can
|
|
7
|
+
* share the exact same strings the runtime uses.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Resource/span attributes that announce the contract to a reader. Stamping
|
|
11
|
+
* `telemetry.schema.version` means an agent following a trace knows *which*
|
|
12
|
+
* version of your public telemetry API it is looking at — the difference
|
|
13
|
+
* between "confidently correct" and "confidently wrong" after a rename.
|
|
14
|
+
*/
|
|
15
|
+
declare const SCHEMA_ATTRS: {
|
|
16
|
+
/** The service this contract describes (mirrors `service.name`). */readonly SERVICE: "telemetry.schema.service"; /** Semver of the telemetry contract that produced this span. */
|
|
17
|
+
readonly VERSION: "telemetry.schema.version";
|
|
18
|
+
};
|
|
19
|
+
type SchemaAttributeKey = (typeof SCHEMA_ATTRS)[keyof typeof SCHEMA_ATTRS];
|
|
20
|
+
/** Snapshot file spec marker — bump only on a breaking snapshot format change. */
|
|
21
|
+
declare const SNAPSHOT_SPEC: "autotel-schema-snapshot/v1";
|
|
22
|
+
//#endregion
|
|
23
|
+
//#region src/snapshot.d.ts
|
|
24
|
+
/** Flattened, fully-resolved attribute record in a snapshot. */
|
|
25
|
+
interface SnapshotAttribute {
|
|
26
|
+
type: AttributeType;
|
|
27
|
+
stability: Stability;
|
|
28
|
+
required: boolean;
|
|
29
|
+
highCardinality: boolean;
|
|
30
|
+
enum?: readonly (string | number)[];
|
|
31
|
+
replacedBy?: string;
|
|
32
|
+
description?: string;
|
|
33
|
+
}
|
|
34
|
+
interface SnapshotSpan {
|
|
35
|
+
stability: Stability;
|
|
36
|
+
additionalAttributes: boolean;
|
|
37
|
+
description?: string;
|
|
38
|
+
attributes: Record<string, SnapshotAttribute>;
|
|
39
|
+
}
|
|
40
|
+
/** Canonical, comparable representation of a {@link TelemetryContract}. */
|
|
41
|
+
interface ContractSnapshot {
|
|
42
|
+
spec: typeof SNAPSHOT_SPEC;
|
|
43
|
+
service: string;
|
|
44
|
+
version: string;
|
|
45
|
+
commonAttributes: Record<string, SnapshotAttribute>;
|
|
46
|
+
spans: Record<string, SnapshotSpan>;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Produce a deterministic, JSON-serializable snapshot from a contract. Keys are
|
|
50
|
+
* sorted so two snapshots of the same logical contract are byte-identical —
|
|
51
|
+
* important for clean `git diff`s and stable CI comparisons.
|
|
52
|
+
*/
|
|
53
|
+
declare function contractToSnapshot(contract: TelemetryContract): ContractSnapshot;
|
|
54
|
+
/** Pretty, deterministic JSON for writing a snapshot to disk. */
|
|
55
|
+
declare function serializeSnapshot(snapshot: ContractSnapshot): string;
|
|
56
|
+
/** Parse and structurally validate a snapshot read from disk. */
|
|
57
|
+
declare function parseSnapshot(json: string): ContractSnapshot;
|
|
58
|
+
//#endregion
|
|
59
|
+
//#region src/diff.d.ts
|
|
60
|
+
type ChangeKind = 'breaking' | 'additive' | 'neutral';
|
|
61
|
+
type ChangeType = 'span_removed' | 'span_added' | 'attribute_removed' | 'attribute_added' | 'type_changed' | 'required_added' | 'required_removed' | 'enum_value_removed' | 'enum_value_added' | 'stability_downgraded' | 'stability_advanced' | 'deprecated' | 'replacement_documented';
|
|
62
|
+
interface SnapshotChange {
|
|
63
|
+
kind: ChangeKind;
|
|
64
|
+
type: ChangeType;
|
|
65
|
+
/** Span the change applies to (`*` = common attributes / contract-wide). */
|
|
66
|
+
span: string;
|
|
67
|
+
attribute?: string;
|
|
68
|
+
message: string;
|
|
69
|
+
}
|
|
70
|
+
interface SnapshotDiff {
|
|
71
|
+
service: string;
|
|
72
|
+
previousVersion: string;
|
|
73
|
+
nextVersion: string;
|
|
74
|
+
breaking: SnapshotChange[];
|
|
75
|
+
additive: SnapshotChange[];
|
|
76
|
+
neutral: SnapshotChange[];
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Diff two snapshots, classifying every change. The `breaking` array is what a
|
|
80
|
+
* CI gate keys off; `hasBreakingChanges()` is the convenience predicate.
|
|
81
|
+
*/
|
|
82
|
+
declare function diffSnapshots(previous: ContractSnapshot, next: ContractSnapshot): SnapshotDiff;
|
|
83
|
+
/** `true` when the diff contains at least one breaking change. */
|
|
84
|
+
declare function hasBreakingChanges(diff: SnapshotDiff): boolean;
|
|
85
|
+
/** Markdown rendering of a diff — for CI logs and PR comments. */
|
|
86
|
+
declare function formatDiff(diff: SnapshotDiff): string;
|
|
87
|
+
//#endregion
|
|
88
|
+
export { diffSnapshots as a, ContractSnapshot as c, contractToSnapshot as d, parseSnapshot as f, SchemaAttributeKey as g, SNAPSHOT_SPEC as h, SnapshotDiff as i, SnapshotAttribute as l, SCHEMA_ATTRS as m, ChangeType as n, formatDiff as o, serializeSnapshot as p, SnapshotChange as r, hasBreakingChanges as s, ChangeKind as t, SnapshotSpan as u };
|
|
89
|
+
//# sourceMappingURL=diff-D7qkNn0-.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diff-D7qkNn0-.d.ts","names":[],"sources":["../src/attrs.ts","../src/snapshot.ts","../src/diff.ts"],"mappings":";;;;;;AAYA;;;;;AAOA;;;cAPa,YAAA;EAOmE,6EAFtE,OAAA,8BAKwD;EAAA,SALxD,OAAA;AAAA;AAAA,KAEE,kBAAA,WAA6B,YAAA,eAA2B,YAAY;;cAGnE,aAAA;;;AAHb;AAAA,UCHiB,iBAAA;EACf,IAAA,EAAM,aAAA;EACN,SAAA,EAAW,SAAS;EACpB,QAAA;EACA,eAAA;EACA,IAAA;EACA,UAAA;EACA,WAAA;AAAA;AAAA,UAGe,YAAA;EACf,SAAA,EAAW,SAAA;EACX,oBAAA;EACA,WAAA;EACA,UAAA,EAAY,MAAA,SAAe,iBAAA;AAAA;;UAIZ,gBAAA;EACf,IAAA,SAAa,aAAA;EACb,OAAA;EACA,OAAA;EACA,gBAAA,EAAkB,MAAA,SAAe,iBAAA;EACjC,KAAA,EAAO,MAAA,SAAe,YAAA;AAAA;;;;AAhBX;AAGb;iBA0CgB,kBAAA,CACd,QAAA,EAAU,iBAAA,GACT,gBAAgB;;iBAgCH,iBAAA,CAAkB,QAA0B,EAAhB,gBAAgB;;iBAK5C,aAAA,CAAc,IAAA,WAAe,gBAAgB;;;KC/FjD,UAAA;AAAA,KAEA,UAAA;AAAA,UAeK,cAAA;EACf,IAAA,EAAM,UAAA;EACN,IAAA,EAAM,UAAU;;EAEhB,IAAA;EACA,SAAA;EACA,OAAA;AAAA;AAAA,UAGe,YAAA;EACf,OAAA;EACA,eAAA;EACA,WAAA;EACA,QAAA,EAAU,cAAA;EACV,QAAA,EAAU,cAAA;EACV,OAAA,EAAS,cAAA;AAAA;;;;;iBAqKK,aAAA,CACd,QAAA,EAAU,gBAAA,EACV,IAAA,EAAM,gBAAA,GACL,YAAA;;iBAwCa,kBAAA,CAAmB,IAAkB,EAAZ,YAAY;ADrOxC;AAAA,iBC0OG,UAAA,CAAW,IAAkB,EAAZ,YAAY"}
|
package/dist/diff.cjs
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
|
+
|
|
3
|
+
//#region src/diff.ts
|
|
4
|
+
/**
|
|
5
|
+
* How every cross-stability transition is classified. Keyed `prev->next` so all
|
|
6
|
+
* six transitions are explicit and auditable — no silent fall-through. Same-
|
|
7
|
+
* stability transitions are absent (no change to report).
|
|
8
|
+
*/
|
|
9
|
+
const STABILITY_TRANSITIONS = {
|
|
10
|
+
"stable->experimental": {
|
|
11
|
+
kind: "breaking",
|
|
12
|
+
type: "stability_downgraded"
|
|
13
|
+
},
|
|
14
|
+
"stable->deprecated": {
|
|
15
|
+
kind: "additive",
|
|
16
|
+
type: "deprecated"
|
|
17
|
+
},
|
|
18
|
+
"experimental->stable": {
|
|
19
|
+
kind: "neutral",
|
|
20
|
+
type: "stability_advanced"
|
|
21
|
+
},
|
|
22
|
+
"experimental->deprecated": {
|
|
23
|
+
kind: "additive",
|
|
24
|
+
type: "deprecated"
|
|
25
|
+
},
|
|
26
|
+
"deprecated->stable": {
|
|
27
|
+
kind: "neutral",
|
|
28
|
+
type: "stability_advanced"
|
|
29
|
+
},
|
|
30
|
+
"deprecated->experimental": {
|
|
31
|
+
kind: "breaking",
|
|
32
|
+
type: "stability_downgraded"
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
function stabilityMessage(type, attribute, prev, next) {
|
|
36
|
+
if (type === "deprecated") return `attribute "${attribute}" was deprecated${next.replacedBy ? ` (use "${next.replacedBy}")` : ""}`;
|
|
37
|
+
if (type === "stability_downgraded") return `attribute "${attribute}" stability downgraded ${prev.stability} → ${next.stability}`;
|
|
38
|
+
return `attribute "${attribute}" promoted ${prev.stability} → ${next.stability}`;
|
|
39
|
+
}
|
|
40
|
+
function push(diff, change) {
|
|
41
|
+
if (change.kind === "breaking") diff.breaking.push(change);
|
|
42
|
+
else if (change.kind === "additive") diff.additive.push(change);
|
|
43
|
+
else diff.neutral.push(change);
|
|
44
|
+
}
|
|
45
|
+
function diffAttribute(diff, span, attribute, prev, next) {
|
|
46
|
+
if (prev.type !== next.type) push(diff, {
|
|
47
|
+
kind: "breaking",
|
|
48
|
+
type: "type_changed",
|
|
49
|
+
span,
|
|
50
|
+
attribute,
|
|
51
|
+
message: `attribute "${attribute}" changed type ${prev.type} → ${next.type}`
|
|
52
|
+
});
|
|
53
|
+
if (!prev.required && next.required) push(diff, {
|
|
54
|
+
kind: "breaking",
|
|
55
|
+
type: "required_added",
|
|
56
|
+
span,
|
|
57
|
+
attribute,
|
|
58
|
+
message: `attribute "${attribute}" became required`
|
|
59
|
+
});
|
|
60
|
+
else if (prev.required && !next.required) push(diff, {
|
|
61
|
+
kind: "additive",
|
|
62
|
+
type: "required_removed",
|
|
63
|
+
span,
|
|
64
|
+
attribute,
|
|
65
|
+
message: `attribute "${attribute}" is no longer required`
|
|
66
|
+
});
|
|
67
|
+
if (prev.enum && next.enum) {
|
|
68
|
+
const nextSet = new Set(next.enum);
|
|
69
|
+
const removed = prev.enum.filter((v) => !nextSet.has(v));
|
|
70
|
+
const prevSet = new Set(prev.enum);
|
|
71
|
+
const added = next.enum.filter((v) => !prevSet.has(v));
|
|
72
|
+
if (removed.length > 0) push(diff, {
|
|
73
|
+
kind: "breaking",
|
|
74
|
+
type: "enum_value_removed",
|
|
75
|
+
span,
|
|
76
|
+
attribute,
|
|
77
|
+
message: `attribute "${attribute}" dropped enum value(s) ${JSON.stringify(removed)}`
|
|
78
|
+
});
|
|
79
|
+
if (added.length > 0) push(diff, {
|
|
80
|
+
kind: "additive",
|
|
81
|
+
type: "enum_value_added",
|
|
82
|
+
span,
|
|
83
|
+
attribute,
|
|
84
|
+
message: `attribute "${attribute}" added enum value(s) ${JSON.stringify(added)}`
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
if (prev.stability !== next.stability) {
|
|
88
|
+
const transition = STABILITY_TRANSITIONS[`${prev.stability}->${next.stability}`];
|
|
89
|
+
if (transition) push(diff, {
|
|
90
|
+
...transition,
|
|
91
|
+
span,
|
|
92
|
+
attribute,
|
|
93
|
+
message: stabilityMessage(transition.type, attribute, prev, next)
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
function diffAttributeMaps(diff, span, prev, next) {
|
|
98
|
+
for (const [key, prevAttr] of Object.entries(prev)) {
|
|
99
|
+
const nextAttr = next[key];
|
|
100
|
+
if (!nextAttr) {
|
|
101
|
+
push(diff, {
|
|
102
|
+
kind: "breaking",
|
|
103
|
+
type: prevAttr.replacedBy ? "replacement_documented" : "attribute_removed",
|
|
104
|
+
span,
|
|
105
|
+
attribute: key,
|
|
106
|
+
message: prevAttr.replacedBy ? `attribute "${key}" removed — replaced by "${prevAttr.replacedBy}"` : `attribute "${key}" was removed`
|
|
107
|
+
});
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
diffAttribute(diff, span, key, prevAttr, nextAttr);
|
|
111
|
+
}
|
|
112
|
+
for (const key of Object.keys(next)) if (!prev[key]) {
|
|
113
|
+
const added = next[key];
|
|
114
|
+
push(diff, {
|
|
115
|
+
kind: added.required ? "breaking" : "additive",
|
|
116
|
+
type: "attribute_added",
|
|
117
|
+
span,
|
|
118
|
+
attribute: key,
|
|
119
|
+
message: added.required ? `new required attribute "${key}" added` : `new attribute "${key}" added`
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Diff two snapshots, classifying every change. The `breaking` array is what a
|
|
125
|
+
* CI gate keys off; `hasBreakingChanges()` is the convenience predicate.
|
|
126
|
+
*/
|
|
127
|
+
function diffSnapshots(previous, next) {
|
|
128
|
+
const diff = {
|
|
129
|
+
service: next.service,
|
|
130
|
+
previousVersion: previous.version,
|
|
131
|
+
nextVersion: next.version,
|
|
132
|
+
breaking: [],
|
|
133
|
+
additive: [],
|
|
134
|
+
neutral: []
|
|
135
|
+
};
|
|
136
|
+
diffAttributeMaps(diff, "*", previous.commonAttributes, next.commonAttributes);
|
|
137
|
+
for (const [name, prevSpan] of Object.entries(previous.spans)) {
|
|
138
|
+
const nextSpan = next.spans[name];
|
|
139
|
+
if (!nextSpan) {
|
|
140
|
+
push(diff, {
|
|
141
|
+
kind: "breaking",
|
|
142
|
+
type: "span_removed",
|
|
143
|
+
span: name,
|
|
144
|
+
message: `span "${name}" was removed`
|
|
145
|
+
});
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
diffAttributeMaps(diff, name, prevSpan.attributes, nextSpan.attributes);
|
|
149
|
+
}
|
|
150
|
+
for (const name of Object.keys(next.spans)) if (!previous.spans[name]) push(diff, {
|
|
151
|
+
kind: "additive",
|
|
152
|
+
type: "span_added",
|
|
153
|
+
span: name,
|
|
154
|
+
message: `new span "${name}" added`
|
|
155
|
+
});
|
|
156
|
+
return diff;
|
|
157
|
+
}
|
|
158
|
+
/** `true` when the diff contains at least one breaking change. */
|
|
159
|
+
function hasBreakingChanges(diff) {
|
|
160
|
+
return diff.breaking.length > 0;
|
|
161
|
+
}
|
|
162
|
+
/** Markdown rendering of a diff — for CI logs and PR comments. */
|
|
163
|
+
function formatDiff(diff) {
|
|
164
|
+
const lines = [`# Telemetry contract diff: ${diff.service} ${diff.previousVersion} → ${diff.nextVersion}`, ""];
|
|
165
|
+
const section = (title, changes) => {
|
|
166
|
+
if (changes.length === 0) return;
|
|
167
|
+
lines.push(`## ${title} (${changes.length})`, "");
|
|
168
|
+
for (const c of changes) {
|
|
169
|
+
const where = c.attribute ? `\`${c.span}.${c.attribute}\`` : `\`${c.span}\``;
|
|
170
|
+
lines.push(`- ${where}: ${c.message}`);
|
|
171
|
+
}
|
|
172
|
+
lines.push("");
|
|
173
|
+
};
|
|
174
|
+
section("💥 Breaking", diff.breaking);
|
|
175
|
+
section("➕ Additive", diff.additive);
|
|
176
|
+
section("• Neutral", diff.neutral);
|
|
177
|
+
if (diff.breaking.length === 0 && diff.additive.length === 0 && diff.neutral.length === 0) lines.push("No changes to the telemetry contract.", "");
|
|
178
|
+
return lines.join("\n");
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
//#endregion
|
|
182
|
+
exports.diffSnapshots = diffSnapshots;
|
|
183
|
+
exports.formatDiff = formatDiff;
|
|
184
|
+
exports.hasBreakingChanges = hasBreakingChanges;
|
|
185
|
+
//# sourceMappingURL=diff.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diff.cjs","names":[],"sources":["../src/diff.ts"],"sourcesContent":["/**\n * Snapshot diffing — the CI gate that catches breaking changes to your trace\n * surface before they ship.\n *\n * \"If you wouldn't ship a rename to your public API without a changelog, don't\n * do it to your traces.\" This module is what makes that enforceable: classify\n * every change between two snapshots as breaking, additive, or neutral, and let\n * CI fail on the breaking ones.\n */\n\nimport type { ContractSnapshot, SnapshotAttribute } from './snapshot.js';\n\nexport type ChangeKind = 'breaking' | 'additive' | 'neutral';\n\nexport type ChangeType =\n | 'span_removed'\n | 'span_added'\n | 'attribute_removed'\n | 'attribute_added'\n | 'type_changed'\n | 'required_added'\n | 'required_removed'\n | 'enum_value_removed'\n | 'enum_value_added'\n | 'stability_downgraded'\n | 'stability_advanced'\n | 'deprecated'\n | 'replacement_documented';\n\nexport interface SnapshotChange {\n kind: ChangeKind;\n type: ChangeType;\n /** Span the change applies to (`*` = common attributes / contract-wide). */\n span: string;\n attribute?: string;\n message: string;\n}\n\nexport interface SnapshotDiff {\n service: string;\n previousVersion: string;\n nextVersion: string;\n breaking: SnapshotChange[];\n additive: SnapshotChange[];\n neutral: SnapshotChange[];\n}\n\n/**\n * How every cross-stability transition is classified. Keyed `prev->next` so all\n * six transitions are explicit and auditable — no silent fall-through. Same-\n * stability transitions are absent (no change to report).\n */\nconst STABILITY_TRANSITIONS: Record<\n string,\n { kind: ChangeKind; type: ChangeType }\n> = {\n 'stable->experimental': { kind: 'breaking', type: 'stability_downgraded' },\n 'stable->deprecated': { kind: 'additive', type: 'deprecated' },\n 'experimental->stable': { kind: 'neutral', type: 'stability_advanced' },\n 'experimental->deprecated': { kind: 'additive', type: 'deprecated' },\n 'deprecated->stable': { kind: 'neutral', type: 'stability_advanced' },\n 'deprecated->experimental': { kind: 'breaking', type: 'stability_downgraded' },\n};\n\nfunction stabilityMessage(\n type: ChangeType,\n attribute: string,\n prev: SnapshotAttribute,\n next: SnapshotAttribute,\n): string {\n if (type === 'deprecated') {\n return `attribute \"${attribute}\" was deprecated${next.replacedBy ? ` (use \"${next.replacedBy}\")` : ''}`;\n }\n if (type === 'stability_downgraded') {\n return `attribute \"${attribute}\" stability downgraded ${prev.stability} → ${next.stability}`;\n }\n return `attribute \"${attribute}\" promoted ${prev.stability} → ${next.stability}`;\n}\n\nfunction push(\n diff: SnapshotDiff,\n change: SnapshotChange,\n): void {\n if (change.kind === 'breaking') diff.breaking.push(change);\n else if (change.kind === 'additive') diff.additive.push(change);\n else diff.neutral.push(change);\n}\n\nfunction diffAttribute(\n diff: SnapshotDiff,\n span: string,\n attribute: string,\n prev: SnapshotAttribute,\n next: SnapshotAttribute,\n): void {\n if (prev.type !== next.type) {\n push(diff, {\n kind: 'breaking',\n type: 'type_changed',\n span,\n attribute,\n message: `attribute \"${attribute}\" changed type ${prev.type} → ${next.type}`,\n });\n }\n\n if (!prev.required && next.required) {\n push(diff, {\n kind: 'breaking',\n type: 'required_added',\n span,\n attribute,\n message: `attribute \"${attribute}\" became required`,\n });\n } else if (prev.required && !next.required) {\n push(diff, {\n kind: 'additive',\n type: 'required_removed',\n span,\n attribute,\n message: `attribute \"${attribute}\" is no longer required`,\n });\n }\n\n // Enum: removing a permitted value can break a producer that still emits it.\n if (prev.enum && next.enum) {\n const nextSet = new Set(next.enum);\n const removed = prev.enum.filter((v) => !nextSet.has(v));\n const prevSet = new Set(prev.enum);\n const added = next.enum.filter((v) => !prevSet.has(v));\n if (removed.length > 0) {\n push(diff, {\n kind: 'breaking',\n type: 'enum_value_removed',\n span,\n attribute,\n message: `attribute \"${attribute}\" dropped enum value(s) ${JSON.stringify(removed)}`,\n });\n }\n if (added.length > 0) {\n push(diff, {\n kind: 'additive',\n type: 'enum_value_added',\n span,\n attribute,\n message: `attribute \"${attribute}\" added enum value(s) ${JSON.stringify(added)}`,\n });\n }\n }\n\n if (prev.stability !== next.stability) {\n const transition =\n STABILITY_TRANSITIONS[`${prev.stability}->${next.stability}`];\n if (transition) {\n push(diff, {\n ...transition,\n span,\n attribute,\n message: stabilityMessage(transition.type, attribute, prev, next),\n });\n }\n }\n}\n\nfunction diffAttributeMaps(\n diff: SnapshotDiff,\n span: string,\n prev: Record<string, SnapshotAttribute>,\n next: Record<string, SnapshotAttribute>,\n): void {\n for (const [key, prevAttr] of Object.entries(prev)) {\n const nextAttr = next[key];\n if (!nextAttr) {\n // A removed attribute whose replacement is named is a documented\n // migration (still breaking — but reported as such with the pointer).\n push(diff, {\n kind: 'breaking',\n type: prevAttr.replacedBy ? 'replacement_documented' : 'attribute_removed',\n span,\n attribute: key,\n message: prevAttr.replacedBy\n ? `attribute \"${key}\" removed — replaced by \"${prevAttr.replacedBy}\"`\n : `attribute \"${key}\" was removed`,\n });\n continue;\n }\n diffAttribute(diff, span, key, prevAttr, nextAttr);\n }\n for (const key of Object.keys(next)) {\n if (!prev[key]) {\n const added = next[key];\n // Always `attribute_added` (the event is \"new attribute\"); severity rides\n // on `kind` — a new *required* attribute breaks existing producers.\n push(diff, {\n kind: added.required ? 'breaking' : 'additive',\n type: 'attribute_added',\n span,\n attribute: key,\n message: added.required\n ? `new required attribute \"${key}\" added`\n : `new attribute \"${key}\" added`,\n });\n }\n }\n}\n\n/**\n * Diff two snapshots, classifying every change. The `breaking` array is what a\n * CI gate keys off; `hasBreakingChanges()` is the convenience predicate.\n */\nexport function diffSnapshots(\n previous: ContractSnapshot,\n next: ContractSnapshot,\n): SnapshotDiff {\n const diff: SnapshotDiff = {\n service: next.service,\n previousVersion: previous.version,\n nextVersion: next.version,\n breaking: [],\n additive: [],\n neutral: [],\n };\n\n diffAttributeMaps(diff, '*', previous.commonAttributes, next.commonAttributes);\n\n for (const [name, prevSpan] of Object.entries(previous.spans)) {\n const nextSpan = next.spans[name];\n if (!nextSpan) {\n push(diff, {\n kind: 'breaking',\n type: 'span_removed',\n span: name,\n message: `span \"${name}\" was removed`,\n });\n continue;\n }\n diffAttributeMaps(diff, name, prevSpan.attributes, nextSpan.attributes);\n }\n for (const name of Object.keys(next.spans)) {\n if (!previous.spans[name]) {\n push(diff, {\n kind: 'additive',\n type: 'span_added',\n span: name,\n message: `new span \"${name}\" added`,\n });\n }\n }\n\n return diff;\n}\n\n/** `true` when the diff contains at least one breaking change. */\nexport function hasBreakingChanges(diff: SnapshotDiff): boolean {\n return diff.breaking.length > 0;\n}\n\n/** Markdown rendering of a diff — for CI logs and PR comments. */\nexport function formatDiff(diff: SnapshotDiff): string {\n const lines: string[] = [ \n `# Telemetry contract diff: ${diff.service} ${diff.previousVersion} → ${diff.nextVersion}`,\n ''];\n const section = (title: string, changes: SnapshotChange[]) => {\n if (changes.length === 0) return;\n lines.push(`## ${title} (${changes.length})`, '');\n for (const c of changes) {\n const where = c.attribute ? `\\`${c.span}.${c.attribute}\\`` : `\\`${c.span}\\``;\n lines.push(`- ${where}: ${c.message}`);\n }\n lines.push('');\n };\n section('💥 Breaking', diff.breaking);\n section('➕ Additive', diff.additive);\n section('• Neutral', diff.neutral);\n if (\n diff.breaking.length === 0 &&\n diff.additive.length === 0 &&\n diff.neutral.length === 0\n ) {\n lines.push('No changes to the telemetry contract.', '');\n }\n return lines.join('\\n');\n}\n"],"mappings":"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"}
|
package/dist/diff.d.cts
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { a as diffSnapshots, i as SnapshotDiff, n as ChangeType, o as formatDiff, r as SnapshotChange, s as hasBreakingChanges, t as ChangeKind } from "./diff-BQPh72vY.cjs";
|
|
2
|
+
export { ChangeKind, ChangeType, SnapshotChange, SnapshotDiff, diffSnapshots, formatDiff, hasBreakingChanges };
|
package/dist/diff.d.ts
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { a as diffSnapshots, i as SnapshotDiff, n as ChangeType, o as formatDiff, r as SnapshotChange, s as hasBreakingChanges, t as ChangeKind } from "./diff-D7qkNn0-.js";
|
|
2
|
+
export { ChangeKind, ChangeType, SnapshotChange, SnapshotDiff, diffSnapshots, formatDiff, hasBreakingChanges };
|
package/dist/diff.js
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
//#region src/diff.ts
|
|
2
|
+
/**
|
|
3
|
+
* How every cross-stability transition is classified. Keyed `prev->next` so all
|
|
4
|
+
* six transitions are explicit and auditable — no silent fall-through. Same-
|
|
5
|
+
* stability transitions are absent (no change to report).
|
|
6
|
+
*/
|
|
7
|
+
const STABILITY_TRANSITIONS = {
|
|
8
|
+
"stable->experimental": {
|
|
9
|
+
kind: "breaking",
|
|
10
|
+
type: "stability_downgraded"
|
|
11
|
+
},
|
|
12
|
+
"stable->deprecated": {
|
|
13
|
+
kind: "additive",
|
|
14
|
+
type: "deprecated"
|
|
15
|
+
},
|
|
16
|
+
"experimental->stable": {
|
|
17
|
+
kind: "neutral",
|
|
18
|
+
type: "stability_advanced"
|
|
19
|
+
},
|
|
20
|
+
"experimental->deprecated": {
|
|
21
|
+
kind: "additive",
|
|
22
|
+
type: "deprecated"
|
|
23
|
+
},
|
|
24
|
+
"deprecated->stable": {
|
|
25
|
+
kind: "neutral",
|
|
26
|
+
type: "stability_advanced"
|
|
27
|
+
},
|
|
28
|
+
"deprecated->experimental": {
|
|
29
|
+
kind: "breaking",
|
|
30
|
+
type: "stability_downgraded"
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
function stabilityMessage(type, attribute, prev, next) {
|
|
34
|
+
if (type === "deprecated") return `attribute "${attribute}" was deprecated${next.replacedBy ? ` (use "${next.replacedBy}")` : ""}`;
|
|
35
|
+
if (type === "stability_downgraded") return `attribute "${attribute}" stability downgraded ${prev.stability} → ${next.stability}`;
|
|
36
|
+
return `attribute "${attribute}" promoted ${prev.stability} → ${next.stability}`;
|
|
37
|
+
}
|
|
38
|
+
function push(diff, change) {
|
|
39
|
+
if (change.kind === "breaking") diff.breaking.push(change);
|
|
40
|
+
else if (change.kind === "additive") diff.additive.push(change);
|
|
41
|
+
else diff.neutral.push(change);
|
|
42
|
+
}
|
|
43
|
+
function diffAttribute(diff, span, attribute, prev, next) {
|
|
44
|
+
if (prev.type !== next.type) push(diff, {
|
|
45
|
+
kind: "breaking",
|
|
46
|
+
type: "type_changed",
|
|
47
|
+
span,
|
|
48
|
+
attribute,
|
|
49
|
+
message: `attribute "${attribute}" changed type ${prev.type} → ${next.type}`
|
|
50
|
+
});
|
|
51
|
+
if (!prev.required && next.required) push(diff, {
|
|
52
|
+
kind: "breaking",
|
|
53
|
+
type: "required_added",
|
|
54
|
+
span,
|
|
55
|
+
attribute,
|
|
56
|
+
message: `attribute "${attribute}" became required`
|
|
57
|
+
});
|
|
58
|
+
else if (prev.required && !next.required) push(diff, {
|
|
59
|
+
kind: "additive",
|
|
60
|
+
type: "required_removed",
|
|
61
|
+
span,
|
|
62
|
+
attribute,
|
|
63
|
+
message: `attribute "${attribute}" is no longer required`
|
|
64
|
+
});
|
|
65
|
+
if (prev.enum && next.enum) {
|
|
66
|
+
const nextSet = new Set(next.enum);
|
|
67
|
+
const removed = prev.enum.filter((v) => !nextSet.has(v));
|
|
68
|
+
const prevSet = new Set(prev.enum);
|
|
69
|
+
const added = next.enum.filter((v) => !prevSet.has(v));
|
|
70
|
+
if (removed.length > 0) push(diff, {
|
|
71
|
+
kind: "breaking",
|
|
72
|
+
type: "enum_value_removed",
|
|
73
|
+
span,
|
|
74
|
+
attribute,
|
|
75
|
+
message: `attribute "${attribute}" dropped enum value(s) ${JSON.stringify(removed)}`
|
|
76
|
+
});
|
|
77
|
+
if (added.length > 0) push(diff, {
|
|
78
|
+
kind: "additive",
|
|
79
|
+
type: "enum_value_added",
|
|
80
|
+
span,
|
|
81
|
+
attribute,
|
|
82
|
+
message: `attribute "${attribute}" added enum value(s) ${JSON.stringify(added)}`
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
if (prev.stability !== next.stability) {
|
|
86
|
+
const transition = STABILITY_TRANSITIONS[`${prev.stability}->${next.stability}`];
|
|
87
|
+
if (transition) push(diff, {
|
|
88
|
+
...transition,
|
|
89
|
+
span,
|
|
90
|
+
attribute,
|
|
91
|
+
message: stabilityMessage(transition.type, attribute, prev, next)
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
function diffAttributeMaps(diff, span, prev, next) {
|
|
96
|
+
for (const [key, prevAttr] of Object.entries(prev)) {
|
|
97
|
+
const nextAttr = next[key];
|
|
98
|
+
if (!nextAttr) {
|
|
99
|
+
push(diff, {
|
|
100
|
+
kind: "breaking",
|
|
101
|
+
type: prevAttr.replacedBy ? "replacement_documented" : "attribute_removed",
|
|
102
|
+
span,
|
|
103
|
+
attribute: key,
|
|
104
|
+
message: prevAttr.replacedBy ? `attribute "${key}" removed — replaced by "${prevAttr.replacedBy}"` : `attribute "${key}" was removed`
|
|
105
|
+
});
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
diffAttribute(diff, span, key, prevAttr, nextAttr);
|
|
109
|
+
}
|
|
110
|
+
for (const key of Object.keys(next)) if (!prev[key]) {
|
|
111
|
+
const added = next[key];
|
|
112
|
+
push(diff, {
|
|
113
|
+
kind: added.required ? "breaking" : "additive",
|
|
114
|
+
type: "attribute_added",
|
|
115
|
+
span,
|
|
116
|
+
attribute: key,
|
|
117
|
+
message: added.required ? `new required attribute "${key}" added` : `new attribute "${key}" added`
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Diff two snapshots, classifying every change. The `breaking` array is what a
|
|
123
|
+
* CI gate keys off; `hasBreakingChanges()` is the convenience predicate.
|
|
124
|
+
*/
|
|
125
|
+
function diffSnapshots(previous, next) {
|
|
126
|
+
const diff = {
|
|
127
|
+
service: next.service,
|
|
128
|
+
previousVersion: previous.version,
|
|
129
|
+
nextVersion: next.version,
|
|
130
|
+
breaking: [],
|
|
131
|
+
additive: [],
|
|
132
|
+
neutral: []
|
|
133
|
+
};
|
|
134
|
+
diffAttributeMaps(diff, "*", previous.commonAttributes, next.commonAttributes);
|
|
135
|
+
for (const [name, prevSpan] of Object.entries(previous.spans)) {
|
|
136
|
+
const nextSpan = next.spans[name];
|
|
137
|
+
if (!nextSpan) {
|
|
138
|
+
push(diff, {
|
|
139
|
+
kind: "breaking",
|
|
140
|
+
type: "span_removed",
|
|
141
|
+
span: name,
|
|
142
|
+
message: `span "${name}" was removed`
|
|
143
|
+
});
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
diffAttributeMaps(diff, name, prevSpan.attributes, nextSpan.attributes);
|
|
147
|
+
}
|
|
148
|
+
for (const name of Object.keys(next.spans)) if (!previous.spans[name]) push(diff, {
|
|
149
|
+
kind: "additive",
|
|
150
|
+
type: "span_added",
|
|
151
|
+
span: name,
|
|
152
|
+
message: `new span "${name}" added`
|
|
153
|
+
});
|
|
154
|
+
return diff;
|
|
155
|
+
}
|
|
156
|
+
/** `true` when the diff contains at least one breaking change. */
|
|
157
|
+
function hasBreakingChanges(diff) {
|
|
158
|
+
return diff.breaking.length > 0;
|
|
159
|
+
}
|
|
160
|
+
/** Markdown rendering of a diff — for CI logs and PR comments. */
|
|
161
|
+
function formatDiff(diff) {
|
|
162
|
+
const lines = [`# Telemetry contract diff: ${diff.service} ${diff.previousVersion} → ${diff.nextVersion}`, ""];
|
|
163
|
+
const section = (title, changes) => {
|
|
164
|
+
if (changes.length === 0) return;
|
|
165
|
+
lines.push(`## ${title} (${changes.length})`, "");
|
|
166
|
+
for (const c of changes) {
|
|
167
|
+
const where = c.attribute ? `\`${c.span}.${c.attribute}\`` : `\`${c.span}\``;
|
|
168
|
+
lines.push(`- ${where}: ${c.message}`);
|
|
169
|
+
}
|
|
170
|
+
lines.push("");
|
|
171
|
+
};
|
|
172
|
+
section("💥 Breaking", diff.breaking);
|
|
173
|
+
section("➕ Additive", diff.additive);
|
|
174
|
+
section("• Neutral", diff.neutral);
|
|
175
|
+
if (diff.breaking.length === 0 && diff.additive.length === 0 && diff.neutral.length === 0) lines.push("No changes to the telemetry contract.", "");
|
|
176
|
+
return lines.join("\n");
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
//#endregion
|
|
180
|
+
export { diffSnapshots, formatDiff, hasBreakingChanges };
|
|
181
|
+
//# sourceMappingURL=diff.js.map
|
package/dist/diff.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diff.js","names":[],"sources":["../src/diff.ts"],"sourcesContent":["/**\n * Snapshot diffing — the CI gate that catches breaking changes to your trace\n * surface before they ship.\n *\n * \"If you wouldn't ship a rename to your public API without a changelog, don't\n * do it to your traces.\" This module is what makes that enforceable: classify\n * every change between two snapshots as breaking, additive, or neutral, and let\n * CI fail on the breaking ones.\n */\n\nimport type { ContractSnapshot, SnapshotAttribute } from './snapshot.js';\n\nexport type ChangeKind = 'breaking' | 'additive' | 'neutral';\n\nexport type ChangeType =\n | 'span_removed'\n | 'span_added'\n | 'attribute_removed'\n | 'attribute_added'\n | 'type_changed'\n | 'required_added'\n | 'required_removed'\n | 'enum_value_removed'\n | 'enum_value_added'\n | 'stability_downgraded'\n | 'stability_advanced'\n | 'deprecated'\n | 'replacement_documented';\n\nexport interface SnapshotChange {\n kind: ChangeKind;\n type: ChangeType;\n /** Span the change applies to (`*` = common attributes / contract-wide). */\n span: string;\n attribute?: string;\n message: string;\n}\n\nexport interface SnapshotDiff {\n service: string;\n previousVersion: string;\n nextVersion: string;\n breaking: SnapshotChange[];\n additive: SnapshotChange[];\n neutral: SnapshotChange[];\n}\n\n/**\n * How every cross-stability transition is classified. Keyed `prev->next` so all\n * six transitions are explicit and auditable — no silent fall-through. Same-\n * stability transitions are absent (no change to report).\n */\nconst STABILITY_TRANSITIONS: Record<\n string,\n { kind: ChangeKind; type: ChangeType }\n> = {\n 'stable->experimental': { kind: 'breaking', type: 'stability_downgraded' },\n 'stable->deprecated': { kind: 'additive', type: 'deprecated' },\n 'experimental->stable': { kind: 'neutral', type: 'stability_advanced' },\n 'experimental->deprecated': { kind: 'additive', type: 'deprecated' },\n 'deprecated->stable': { kind: 'neutral', type: 'stability_advanced' },\n 'deprecated->experimental': { kind: 'breaking', type: 'stability_downgraded' },\n};\n\nfunction stabilityMessage(\n type: ChangeType,\n attribute: string,\n prev: SnapshotAttribute,\n next: SnapshotAttribute,\n): string {\n if (type === 'deprecated') {\n return `attribute \"${attribute}\" was deprecated${next.replacedBy ? ` (use \"${next.replacedBy}\")` : ''}`;\n }\n if (type === 'stability_downgraded') {\n return `attribute \"${attribute}\" stability downgraded ${prev.stability} → ${next.stability}`;\n }\n return `attribute \"${attribute}\" promoted ${prev.stability} → ${next.stability}`;\n}\n\nfunction push(\n diff: SnapshotDiff,\n change: SnapshotChange,\n): void {\n if (change.kind === 'breaking') diff.breaking.push(change);\n else if (change.kind === 'additive') diff.additive.push(change);\n else diff.neutral.push(change);\n}\n\nfunction diffAttribute(\n diff: SnapshotDiff,\n span: string,\n attribute: string,\n prev: SnapshotAttribute,\n next: SnapshotAttribute,\n): void {\n if (prev.type !== next.type) {\n push(diff, {\n kind: 'breaking',\n type: 'type_changed',\n span,\n attribute,\n message: `attribute \"${attribute}\" changed type ${prev.type} → ${next.type}`,\n });\n }\n\n if (!prev.required && next.required) {\n push(diff, {\n kind: 'breaking',\n type: 'required_added',\n span,\n attribute,\n message: `attribute \"${attribute}\" became required`,\n });\n } else if (prev.required && !next.required) {\n push(diff, {\n kind: 'additive',\n type: 'required_removed',\n span,\n attribute,\n message: `attribute \"${attribute}\" is no longer required`,\n });\n }\n\n // Enum: removing a permitted value can break a producer that still emits it.\n if (prev.enum && next.enum) {\n const nextSet = new Set(next.enum);\n const removed = prev.enum.filter((v) => !nextSet.has(v));\n const prevSet = new Set(prev.enum);\n const added = next.enum.filter((v) => !prevSet.has(v));\n if (removed.length > 0) {\n push(diff, {\n kind: 'breaking',\n type: 'enum_value_removed',\n span,\n attribute,\n message: `attribute \"${attribute}\" dropped enum value(s) ${JSON.stringify(removed)}`,\n });\n }\n if (added.length > 0) {\n push(diff, {\n kind: 'additive',\n type: 'enum_value_added',\n span,\n attribute,\n message: `attribute \"${attribute}\" added enum value(s) ${JSON.stringify(added)}`,\n });\n }\n }\n\n if (prev.stability !== next.stability) {\n const transition =\n STABILITY_TRANSITIONS[`${prev.stability}->${next.stability}`];\n if (transition) {\n push(diff, {\n ...transition,\n span,\n attribute,\n message: stabilityMessage(transition.type, attribute, prev, next),\n });\n }\n }\n}\n\nfunction diffAttributeMaps(\n diff: SnapshotDiff,\n span: string,\n prev: Record<string, SnapshotAttribute>,\n next: Record<string, SnapshotAttribute>,\n): void {\n for (const [key, prevAttr] of Object.entries(prev)) {\n const nextAttr = next[key];\n if (!nextAttr) {\n // A removed attribute whose replacement is named is a documented\n // migration (still breaking — but reported as such with the pointer).\n push(diff, {\n kind: 'breaking',\n type: prevAttr.replacedBy ? 'replacement_documented' : 'attribute_removed',\n span,\n attribute: key,\n message: prevAttr.replacedBy\n ? `attribute \"${key}\" removed — replaced by \"${prevAttr.replacedBy}\"`\n : `attribute \"${key}\" was removed`,\n });\n continue;\n }\n diffAttribute(diff, span, key, prevAttr, nextAttr);\n }\n for (const key of Object.keys(next)) {\n if (!prev[key]) {\n const added = next[key];\n // Always `attribute_added` (the event is \"new attribute\"); severity rides\n // on `kind` — a new *required* attribute breaks existing producers.\n push(diff, {\n kind: added.required ? 'breaking' : 'additive',\n type: 'attribute_added',\n span,\n attribute: key,\n message: added.required\n ? `new required attribute \"${key}\" added`\n : `new attribute \"${key}\" added`,\n });\n }\n }\n}\n\n/**\n * Diff two snapshots, classifying every change. The `breaking` array is what a\n * CI gate keys off; `hasBreakingChanges()` is the convenience predicate.\n */\nexport function diffSnapshots(\n previous: ContractSnapshot,\n next: ContractSnapshot,\n): SnapshotDiff {\n const diff: SnapshotDiff = {\n service: next.service,\n previousVersion: previous.version,\n nextVersion: next.version,\n breaking: [],\n additive: [],\n neutral: [],\n };\n\n diffAttributeMaps(diff, '*', previous.commonAttributes, next.commonAttributes);\n\n for (const [name, prevSpan] of Object.entries(previous.spans)) {\n const nextSpan = next.spans[name];\n if (!nextSpan) {\n push(diff, {\n kind: 'breaking',\n type: 'span_removed',\n span: name,\n message: `span \"${name}\" was removed`,\n });\n continue;\n }\n diffAttributeMaps(diff, name, prevSpan.attributes, nextSpan.attributes);\n }\n for (const name of Object.keys(next.spans)) {\n if (!previous.spans[name]) {\n push(diff, {\n kind: 'additive',\n type: 'span_added',\n span: name,\n message: `new span \"${name}\" added`,\n });\n }\n }\n\n return diff;\n}\n\n/** `true` when the diff contains at least one breaking change. */\nexport function hasBreakingChanges(diff: SnapshotDiff): boolean {\n return diff.breaking.length > 0;\n}\n\n/** Markdown rendering of a diff — for CI logs and PR comments. */\nexport function formatDiff(diff: SnapshotDiff): string {\n const lines: string[] = [ \n `# Telemetry contract diff: ${diff.service} ${diff.previousVersion} → ${diff.nextVersion}`,\n ''];\n const section = (title: string, changes: SnapshotChange[]) => {\n if (changes.length === 0) return;\n lines.push(`## ${title} (${changes.length})`, '');\n for (const c of changes) {\n const where = c.attribute ? `\\`${c.span}.${c.attribute}\\`` : `\\`${c.span}\\``;\n lines.push(`- ${where}: ${c.message}`);\n }\n lines.push('');\n };\n section('💥 Breaking', diff.breaking);\n section('➕ Additive', diff.additive);\n section('• Neutral', diff.neutral);\n if (\n diff.breaking.length === 0 &&\n diff.additive.length === 0 &&\n diff.neutral.length === 0\n ) {\n lines.push('No changes to the telemetry contract.', '');\n }\n return lines.join('\\n');\n}\n"],"mappings":"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"}
|