autotel-pact 0.2.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.
Files changed (86) hide show
  1. package/README.md +385 -0
  2. package/dist/audit.cjs +412 -0
  3. package/dist/audit.cjs.map +1 -0
  4. package/dist/audit.d.cts +25 -0
  5. package/dist/audit.d.ts +25 -0
  6. package/dist/audit.js +403 -0
  7. package/dist/audit.js.map +1 -0
  8. package/dist/auto-wrap.cjs +255 -0
  9. package/dist/auto-wrap.cjs.map +1 -0
  10. package/dist/auto-wrap.d.cts +57 -0
  11. package/dist/auto-wrap.d.ts +57 -0
  12. package/dist/auto-wrap.js +248 -0
  13. package/dist/auto-wrap.js.map +1 -0
  14. package/dist/broker.cjs +84 -0
  15. package/dist/broker.cjs.map +1 -0
  16. package/dist/broker.d.cts +23 -0
  17. package/dist/broker.d.ts +23 -0
  18. package/dist/broker.js +80 -0
  19. package/dist/broker.js.map +1 -0
  20. package/dist/cli.cjs +662 -0
  21. package/dist/cli.cjs.map +1 -0
  22. package/dist/cli.d.cts +4 -0
  23. package/dist/cli.d.ts +4 -0
  24. package/dist/cli.js +656 -0
  25. package/dist/cli.js.map +1 -0
  26. package/dist/index.cjs +967 -0
  27. package/dist/index.cjs.map +1 -0
  28. package/dist/index.d.cts +200 -0
  29. package/dist/index.d.ts +200 -0
  30. package/dist/index.js +932 -0
  31. package/dist/index.js.map +1 -0
  32. package/dist/ledger-BuBmfWNc.d.ts +22 -0
  33. package/dist/ledger-D88TzN1c.d.cts +22 -0
  34. package/dist/processor.cjs +200 -0
  35. package/dist/processor.cjs.map +1 -0
  36. package/dist/processor.d.cts +58 -0
  37. package/dist/processor.d.ts +58 -0
  38. package/dist/processor.js +193 -0
  39. package/dist/processor.js.map +1 -0
  40. package/dist/provider.cjs +219 -0
  41. package/dist/provider.cjs.map +1 -0
  42. package/dist/provider.d.cts +41 -0
  43. package/dist/provider.d.ts +41 -0
  44. package/dist/provider.js +213 -0
  45. package/dist/provider.js.map +1 -0
  46. package/dist/tag.cjs +50 -0
  47. package/dist/tag.cjs.map +1 -0
  48. package/dist/tag.d.cts +9 -0
  49. package/dist/tag.d.ts +9 -0
  50. package/dist/tag.js +48 -0
  51. package/dist/tag.js.map +1 -0
  52. package/dist/types-BHGiwqcp.d.cts +157 -0
  53. package/dist/types-BHGiwqcp.d.ts +157 -0
  54. package/package.json +108 -0
  55. package/schemas/README.md +24 -0
  56. package/schemas/audit-matrix-v0.2.0.json +78 -0
  57. package/schemas/ledger-entry-v0.2.0.json +77 -0
  58. package/src/attrs.test.ts +35 -0
  59. package/src/attrs.ts +53 -0
  60. package/src/audit.test.ts +189 -0
  61. package/src/audit.ts +251 -0
  62. package/src/auto-wrap.test.ts +149 -0
  63. package/src/auto-wrap.ts +283 -0
  64. package/src/broker.test.ts +175 -0
  65. package/src/broker.ts +118 -0
  66. package/src/cli.test.ts +148 -0
  67. package/src/cli.ts +287 -0
  68. package/src/index.ts +94 -0
  69. package/src/labels.ts +25 -0
  70. package/src/ledger-normalize.test.ts +141 -0
  71. package/src/ledger-normalize.ts +82 -0
  72. package/src/ledger.test.ts +92 -0
  73. package/src/ledger.ts +156 -0
  74. package/src/pact-file.test.ts +124 -0
  75. package/src/pact-file.ts +65 -0
  76. package/src/processor.test.ts +90 -0
  77. package/src/processor.ts +191 -0
  78. package/src/tag.test.ts +72 -0
  79. package/src/tag.ts +21 -0
  80. package/src/types.ts +169 -0
  81. package/src/wrapper-http.test.ts +133 -0
  82. package/src/wrapper-http.ts +194 -0
  83. package/src/wrapper-provider.test.ts +132 -0
  84. package/src/wrapper-provider.ts +163 -0
  85. package/src/wrapper.test.ts +176 -0
  86. package/src/wrapper.ts +221 -0
@@ -0,0 +1,255 @@
1
+ 'use strict';
2
+
3
+ var autotel = require('autotel');
4
+ var module$1 = require('module');
5
+ var fs = require('fs');
6
+ require('fs/promises');
7
+ var path = require('path');
8
+
9
+ var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
10
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
11
+
12
+ var path__default = /*#__PURE__*/_interopDefault(path);
13
+
14
+ // src/auto-wrap.ts
15
+
16
+ // src/attrs.ts
17
+ var PACT_ATTRS = {
18
+ CONSUMER: "pact.consumer",
19
+ PROVIDER: "pact.provider",
20
+ KIND: "pact.kind",
21
+ INTERACTION_DESCRIPTION: "pact.interaction.description",
22
+ INTERACTION_ID: "pact.interaction.id",
23
+ INTERACTION_STATES: "pact.interaction.states",
24
+ CONTRACT_FILE: "pact.contract.file",
25
+ OUTCOME: "pact.outcome"
26
+ };
27
+ function buildPactAttributes(meta, opts = {}) {
28
+ const attrs = {
29
+ [PACT_ATTRS.CONSUMER]: meta.consumer,
30
+ [PACT_ATTRS.PROVIDER]: meta.provider,
31
+ [PACT_ATTRS.KIND]: meta.kind,
32
+ [PACT_ATTRS.INTERACTION_DESCRIPTION]: meta.description,
33
+ [PACT_ATTRS.INTERACTION_STATES]: meta.states
34
+ };
35
+ if (opts.contractFile) {
36
+ attrs[PACT_ATTRS.CONTRACT_FILE] = opts.contractFile;
37
+ }
38
+ if (meta.interactionId) {
39
+ attrs[PACT_ATTRS.INTERACTION_ID] = meta.interactionId;
40
+ }
41
+ return attrs;
42
+ }
43
+ function outcomeAttribute(outcome) {
44
+ return { [PACT_ATTRS.OUTCOME]: outcome };
45
+ }
46
+
47
+ // src/types.ts
48
+ var LEDGER_ENTRY_SPEC = "autotel-pact-ledger-entry/v0.2.0";
49
+
50
+ // src/ledger.ts
51
+ var DEFAULT_DIR = ".autotel-pact";
52
+ function resolveLedgerDir(opts = {}) {
53
+ return path__default.default.resolve(process.cwd(), opts.dir ?? process.env.AUTOTEL_PACT_LEDGER_DIR ?? DEFAULT_DIR);
54
+ }
55
+ function resolveRunId(opts = {}) {
56
+ const explicit = opts.runId ?? process.env.AUTOTEL_PACT_RUN_ID;
57
+ if (explicit) return explicit;
58
+ return `local-${(/* @__PURE__ */ new Date()).toISOString().replaceAll(/[:.]/g, "-")}`;
59
+ }
60
+ function ledgerPath(opts = {}) {
61
+ const dir = resolveLedgerDir(opts);
62
+ return path__default.default.join(dir, `ledger-${resolveRunId(opts)}.jsonl`);
63
+ }
64
+ function writeLine(filePath, entry) {
65
+ fs.mkdirSync(path__default.default.dirname(filePath), { recursive: true });
66
+ fs.appendFileSync(filePath, JSON.stringify(entry) + "\n", "utf8");
67
+ }
68
+ function appendLedgerEntry(entry, opts = {}) {
69
+ const filePath = ledgerPath(opts);
70
+ const normalized = entry.type === "provider_verification_run" ? entry : { ...entry, spec: LEDGER_ENTRY_SPEC, type: "interaction" };
71
+ writeLine(filePath, normalized);
72
+ }
73
+ Promise.resolve();
74
+
75
+ // src/auto-wrap.ts
76
+ var INSTALLED = /* @__PURE__ */ Symbol.for("autotel-pact:auto-wrap-installed");
77
+ function installAutoWrap(pactModule) {
78
+ const pactJs = pactModule ?? loadPactJs();
79
+ if (!pactJs) return false;
80
+ patchMessagePact(pactJs);
81
+ patchHttpPact(pactJs);
82
+ return true;
83
+ }
84
+ function loadPactJs() {
85
+ try {
86
+ const require2 = module$1.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('auto-wrap.cjs', document.baseURI).href)));
87
+ return require2("@pact-foundation/pact");
88
+ } catch {
89
+ process.stderr.write(
90
+ "autotel-pact/auto-wrap: @pact-foundation/pact not installed \u2014 skipping.\n"
91
+ );
92
+ return void 0;
93
+ }
94
+ }
95
+ function patchMessagePact(mod) {
96
+ const ctor = mod.MessageConsumerPact;
97
+ if (!ctor) return;
98
+ const proto = ctor.prototype;
99
+ if (proto[INSTALLED]) return;
100
+ const originalVerify = proto.verify;
101
+ proto.verify = async function patchedVerify(handler) {
102
+ const start = process.hrtime.bigint();
103
+ let captured;
104
+ const consumer = this.config?.consumer ?? "<unknown>";
105
+ const provider = this.config?.provider ?? "<unknown>";
106
+ return autotel.span("pact.interaction", async (span) => {
107
+ span.setAttributes({
108
+ "pact.consumer": consumer,
109
+ "pact.provider": provider,
110
+ "pact.kind": "message"
111
+ });
112
+ try {
113
+ const result = await originalVerify.call(this, async (reified) => {
114
+ captured = reified;
115
+ const meta = {
116
+ consumer,
117
+ provider,
118
+ description: reified.description ?? "<unknown>",
119
+ states: (reified.providerStates ?? []).map((s) => s.name),
120
+ kind: "message"
121
+ };
122
+ span.setAttributes(buildPactAttributes(meta));
123
+ return handler(reified);
124
+ });
125
+ span.setAttributes(outcomeAttribute("passed"));
126
+ writeAutoLedgerEntry({
127
+ consumer,
128
+ provider,
129
+ interaction: captured?.description ?? "<unknown>",
130
+ states: (captured?.providerStates ?? []).map((s) => s.name),
131
+ kind: "message",
132
+ outcome: "passed",
133
+ start
134
+ });
135
+ return result;
136
+ } catch (error) {
137
+ span.setAttributes(outcomeAttribute("failed"));
138
+ writeAutoLedgerEntry({
139
+ consumer,
140
+ provider,
141
+ interaction: captured?.description ?? "<unknown>",
142
+ states: (captured?.providerStates ?? []).map((s) => s.name),
143
+ kind: "message",
144
+ outcome: "failed",
145
+ start,
146
+ error: error instanceof Error ? error.message : String(error)
147
+ });
148
+ throw error;
149
+ }
150
+ });
151
+ };
152
+ proto[INSTALLED] = true;
153
+ }
154
+ function patchHttpPact(mod) {
155
+ const ctor = mod.PactV3;
156
+ if (!ctor) return;
157
+ const proto = ctor.prototype;
158
+ if (proto[INSTALLED]) return;
159
+ const tracked = /* @__PURE__ */ new WeakMap();
160
+ const originalAdd = proto.addInteraction;
161
+ proto.addInteraction = function patchedAddInteraction(interaction) {
162
+ const list = tracked.get(this) ?? [];
163
+ list.push(interaction);
164
+ tracked.set(this, list);
165
+ return originalAdd.call(this, interaction);
166
+ };
167
+ const originalExecute = proto.executeTest;
168
+ proto.executeTest = async function patchedExecuteTest(fn) {
169
+ const start = process.hrtime.bigint();
170
+ const consumer = this.opts?.consumer ?? "<unknown>";
171
+ const provider = this.opts?.provider ?? "<unknown>";
172
+ const interactions = tracked.get(this) ?? [];
173
+ tracked.set(this, []);
174
+ return autotel.span("pact.interaction", async (span) => {
175
+ span.setAttributes({
176
+ "pact.consumer": consumer,
177
+ "pact.provider": provider,
178
+ "pact.kind": "http"
179
+ });
180
+ const first = interactions[0];
181
+ if (first) {
182
+ span.setAttributes(
183
+ buildPactAttributes({
184
+ consumer,
185
+ provider,
186
+ description: first.uponReceiving,
187
+ states: (first.states ?? []).map((s) => s.description),
188
+ kind: "http"
189
+ })
190
+ );
191
+ }
192
+ try {
193
+ const result = await originalExecute.call(this, fn);
194
+ span.setAttributes(outcomeAttribute("passed"));
195
+ for (const i of interactions) {
196
+ writeAutoLedgerEntry({
197
+ consumer,
198
+ provider,
199
+ interaction: i.uponReceiving,
200
+ states: (i.states ?? []).map((s) => s.description),
201
+ kind: "http",
202
+ outcome: "passed",
203
+ start
204
+ });
205
+ }
206
+ return result;
207
+ } catch (error_) {
208
+ span.setAttributes(outcomeAttribute("failed"));
209
+ const error = error_ instanceof Error ? error_.message : String(error_);
210
+ for (const i of interactions) {
211
+ writeAutoLedgerEntry({
212
+ consumer,
213
+ provider,
214
+ interaction: i.uponReceiving,
215
+ states: (i.states ?? []).map((s) => s.description),
216
+ kind: "http",
217
+ outcome: "failed",
218
+ start,
219
+ error
220
+ });
221
+ }
222
+ throw error_;
223
+ }
224
+ });
225
+ };
226
+ proto[INSTALLED] = true;
227
+ }
228
+ function writeAutoLedgerEntry(args) {
229
+ const ctx = autotel.getActiveSpan()?.spanContext();
230
+ const entry = {
231
+ type: "interaction",
232
+ spec: LEDGER_ENTRY_SPEC,
233
+ consumer: args.consumer,
234
+ provider: args.provider,
235
+ interaction: args.interaction,
236
+ states: args.states,
237
+ kind: args.kind,
238
+ source: "test",
239
+ role: "consumer",
240
+ outcome: args.outcome,
241
+ duration_ms: Number(process.hrtime.bigint() - args.start) / 1e6,
242
+ observed_at: (/* @__PURE__ */ new Date()).toISOString(),
243
+ trace_id: ctx?.traceId,
244
+ span_id: ctx?.spanId,
245
+ run_id: process.env.AUTOTEL_PACT_RUN_ID,
246
+ git_sha: process.env.GIT_SHA ?? process.env.GITHUB_SHA,
247
+ error: args.error
248
+ };
249
+ appendLedgerEntry(entry);
250
+ }
251
+ installAutoWrap();
252
+
253
+ exports.installAutoWrap = installAutoWrap;
254
+ //# sourceMappingURL=auto-wrap.cjs.map
255
+ //# sourceMappingURL=auto-wrap.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/attrs.ts","../src/types.ts","../src/ledger.ts","../src/auto-wrap.ts"],"names":["path","mkdirSync","appendFileSync","require","createRequire","autotelSpan","getActiveSpan"],"mappings":"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","file":"auto-wrap.cjs","sourcesContent":["import type { PactInteractionMeta, PactOutcome } from './types.js';\n\n/**\n * Attribute keys for Pact interactions. Centralised so the namespace is\n * a single source of truth and is forward-compatible with eventual OTel\n * semantic conventions.\n */\nexport const PACT_ATTRS = {\n CONSUMER: 'pact.consumer',\n PROVIDER: 'pact.provider',\n KIND: 'pact.kind',\n INTERACTION_DESCRIPTION: 'pact.interaction.description',\n INTERACTION_ID: 'pact.interaction.id',\n INTERACTION_STATES: 'pact.interaction.states',\n CONTRACT_FILE: 'pact.contract.file',\n OUTCOME: 'pact.outcome',\n} as const;\n\nexport type PactAttributeKey = (typeof PACT_ATTRS)[keyof typeof PACT_ATTRS];\n\n/**\n * Build the set of attributes to stamp on a span when an interaction is\n * about to be exercised. `outcome` is added later by the wrapper.\n */\nexport function buildPactAttributes(\n meta: PactInteractionMeta,\n opts: { contractFile?: string } = {},\n): Record<string, string | string[]> {\n const attrs: Record<string, string | string[]> = {\n [PACT_ATTRS.CONSUMER]: meta.consumer,\n [PACT_ATTRS.PROVIDER]: meta.provider,\n [PACT_ATTRS.KIND]: meta.kind,\n [PACT_ATTRS.INTERACTION_DESCRIPTION]: meta.description,\n [PACT_ATTRS.INTERACTION_STATES]: meta.states,\n };\n if (opts.contractFile) {\n attrs[PACT_ATTRS.CONTRACT_FILE] = opts.contractFile;\n }\n if (meta.interactionId) {\n attrs[PACT_ATTRS.INTERACTION_ID] = meta.interactionId;\n }\n return attrs;\n}\n\n/**\n * Helper that returns just the outcome attribute — stamped after the\n * handler resolves or rejects.\n */\nexport function outcomeAttribute(\n outcome: PactOutcome,\n): Record<string, string> {\n return { [PACT_ATTRS.OUTCOME]: outcome };\n}\n","/**\n * Kind of contract interaction observed.\n */\nexport type PactKind = 'message' | 'http';\n\nexport type PactOutcome = 'passed' | 'failed';\n\nexport type LedgerSource = 'test' | 'production';\nexport type LedgerRole = 'consumer' | 'provider';\n\nexport const LEDGER_ENTRY_SPEC = 'autotel-pact-ledger-entry/v0.2.0';\nexport const AUDIT_MATRIX_SPEC = 'autotel-pact-audit-matrix/v0.2.0';\n\n/**\n * Metadata about a single Pact interaction, derived from the reified message\n * plus the consumer/provider config. Stamped onto the span and the ledger entry.\n */\nexport interface PactInteractionMeta {\n consumer: string;\n provider: string;\n description: string;\n states: string[];\n kind: PactKind;\n interactionId?: string;\n}\n\n/**\n * Per-interaction ledger evidence (consumer exercise, provider verify, or production tag).\n */\nexport interface InteractionLedgerEntry {\n type?: 'interaction';\n spec: typeof LEDGER_ENTRY_SPEC;\n consumer: string;\n provider: string;\n interaction: string;\n interaction_id?: string;\n states: string[];\n kind: PactKind;\n outcome: PactOutcome;\n source: LedgerSource;\n role: LedgerRole;\n duration_ms: number;\n observed_at: string;\n trace_id?: string;\n span_id?: string;\n run_id?: string;\n git_sha?: string;\n error?: string;\n}\n\n/**\n * Run-level provider verification failure — does not imply per-interaction outcomes.\n */\nexport interface ProviderVerificationRunEntry {\n type: 'provider_verification_run';\n spec: typeof LEDGER_ENTRY_SPEC;\n consumer: string;\n provider: string;\n outcome: 'failed';\n source: LedgerSource;\n role: 'provider';\n observed_at: string;\n error: string;\n run_id?: string;\n git_sha?: string;\n trace_id?: string;\n span_id?: string;\n}\n\nexport type LedgerRecord = InteractionLedgerEntry | ProviderVerificationRunEntry;\n\nexport function isInteractionLedgerEntry(\n entry: LedgerRecord,\n): entry is InteractionLedgerEntry {\n return entry.type !== 'provider_verification_run';\n}\n\nexport function isProviderVerificationRun(\n entry: LedgerRecord,\n): entry is ProviderVerificationRunEntry {\n return entry.type === 'provider_verification_run';\n}\n\n/**\n * Shape of a Pact contract file on disk (subset we read).\n */\nexport interface PactFile {\n consumer: { name: string };\n provider: { name: string };\n messages?: Array<{\n description: string;\n providerStates?: Array<{ name: string }>;\n metadata?: Record<string, unknown>;\n }>;\n interactions?: Array<{\n description: string;\n providerStates?: Array<{ name: string }>;\n metadata?: Record<string, unknown>;\n }>;\n}\n\nexport interface BrokerVerification {\n consumer: string;\n provider: string;\n success: boolean;\n verifiedAt?: string;\n /**\n * Populated when the broker could not be reached or returned a non-2xx\n * response. Distinguishes \"broker said the pact is not verified\" (no error)\n * from \"we could not determine verification status\" (error set).\n */\n error?: string;\n}\n\n/**\n * One row in the audit matrix.\n */\nexport interface AuditRow {\n consumer: string;\n provider: string;\n interaction: string;\n interaction_id?: string;\n kind: PactKind;\n contracted: boolean;\n /** Any interaction-level ledger hit in the window (test or production). */\n observed: boolean;\n test_seen: boolean;\n prod_seen: boolean;\n provider_verified: boolean;\n broker_verified: boolean;\n broker_verified_at?: string;\n /** Set when the broker check failed (network error, non-2xx, parse error). */\n broker_error?: string;\n last_observed_at?: string;\n last_outcome?: PactOutcome;\n}\n\nexport interface AuditMatrix {\n spec: typeof AUDIT_MATRIX_SPEC;\n rows: AuditRow[];\n counts: {\n total: number;\n /** Any contracted row. */\n contracted: number;\n /** Any row with test_seen OR prod_seen. */\n observed: number;\n /** Contracted AND seen in a consumer test. */\n contracted_and_test_seen: number;\n /** Contracted but not seen in a consumer test (stale confidence). */\n contracted_not_test_seen: number;\n /** Seen (test or production) without a matching contract (ungoverned flow). */\n test_or_prod_seen_not_contracted: number;\n test_seen: number;\n prod_seen: number;\n provider_verified: number;\n broker_verified: number;\n };\n window_days: number;\n generated_at: string;\n verification_failures?: ProviderVerificationRunEntry[];\n}\n\nexport interface PactInteractionKey {\n consumer: string;\n provider: string;\n interaction: string;\n kind: PactKind;\n interactionId?: string;\n}\n","import {\n appendFileSync,\n mkdirSync,\n readdirSync,\n readFileSync,\n existsSync,\n} from 'node:fs';\nimport { appendFile, mkdir } from 'node:fs/promises';\nimport path from 'node:path';\nimport { normalizeLedgerRecord } from './ledger-normalize.js';\nimport { LEDGER_ENTRY_SPEC, type LedgerRecord } from './types.js';\n\nexport interface LedgerOptions {\n dir?: string;\n runId?: string;\n}\n\nconst DEFAULT_DIR = '.autotel-pact';\n\nfunction resolveLedgerDir(opts: LedgerOptions = {}): string {\n return path.resolve(process.cwd(), opts.dir ?? process.env.AUTOTEL_PACT_LEDGER_DIR ?? DEFAULT_DIR);\n}\n\nfunction resolveRunId(opts: LedgerOptions = {}): string {\n const explicit = opts.runId ?? process.env.AUTOTEL_PACT_RUN_ID;\n if (explicit) return explicit;\n return `local-${new Date().toISOString().replaceAll(/[:.]/g, '-')}`;\n}\n\nexport function ledgerPath(opts: LedgerOptions = {}): string {\n const dir = resolveLedgerDir(opts);\n return path.join(dir, `ledger-${resolveRunId(opts)}.jsonl`);\n}\n\nfunction writeLine(filePath: string, entry: LedgerRecord): void {\n mkdirSync(path.dirname(filePath), { recursive: true });\n appendFileSync(filePath, JSON.stringify(entry) + '\\n', 'utf8');\n}\n\n/**\n * Append a ledger record synchronously (tests and consumer wrappers).\n */\nexport function appendLedgerEntry(\n entry: LedgerRecord,\n opts: LedgerOptions = {},\n): void {\n const filePath = ledgerPath(opts);\n const normalized: LedgerRecord =\n entry.type === 'provider_verification_run'\n ? entry\n : { ...entry, spec: LEDGER_ENTRY_SPEC, type: 'interaction' as const };\n writeLine(filePath, normalized);\n}\n\nexport function appendProviderVerificationFailure(\n entry: Omit<\n import('./types.js').ProviderVerificationRunEntry,\n 'type' | 'spec' | 'outcome' | 'role'\n > & { error: string },\n opts: LedgerOptions = {},\n): void {\n appendLedgerEntry(\n {\n type: 'provider_verification_run',\n spec: LEDGER_ENTRY_SPEC,\n outcome: 'failed',\n role: 'provider',\n source: entry.source ?? 'test',\n consumer: entry.consumer,\n provider: entry.provider,\n observed_at: entry.observed_at,\n error: entry.error,\n run_id: entry.run_id,\n git_sha: entry.git_sha,\n trace_id: entry.trace_id,\n span_id: entry.span_id,\n },\n opts,\n );\n}\n\n/**\n * Read all ledger files and return normalized records.\n */\nexport function readLedger(opts: LedgerOptions = {}): LedgerRecord[] {\n const dir = resolveLedgerDir(opts);\n if (!existsSync(dir)) return [];\n const files = readdirSync(dir).filter((f) => f.endsWith('.jsonl'));\n const entries: LedgerRecord[] = [];\n for (const file of files) {\n const text = readFileSync(path.join(dir, file), 'utf8');\n for (const line of text.split('\\n')) {\n if (!line.trim()) continue;\n try {\n const normalized = normalizeLedgerRecord(JSON.parse(line));\n if (normalized) entries.push(normalized);\n } catch {\n // skip malformed lines\n }\n }\n }\n return entries;\n}\n\n/**\n * Serialized async writes for production span processor.\n * Bounded by a producer-side backpressure threshold: once `pendingWrites`\n * reaches `MAX_PENDING_WRITES`, new callers await drainage before queueing,\n * so memory cannot grow unbounded under sustained pressure.\n */\nconst MAX_PENDING_WRITES = 4096;\nlet asyncWriteChain: Promise<void> = Promise.resolve();\nlet pendingWrites = 0;\n\nexport async function appendLedgerEntryAsync(\n entry: LedgerRecord,\n opts: LedgerOptions = {},\n): Promise<void> {\n if (pendingWrites >= MAX_PENDING_WRITES) {\n await asyncWriteChain;\n }\n\n const filePath = ledgerPath(opts);\n const normalized: LedgerRecord =\n entry.type === 'provider_verification_run'\n ? entry\n : { ...entry, spec: LEDGER_ENTRY_SPEC, type: 'interaction' as const };\n const line = JSON.stringify(normalized);\n\n pendingWrites++;\n const run = asyncWriteChain.then(async () => {\n try {\n await mkdir(path.dirname(filePath), { recursive: true });\n await appendFile(filePath, line + '\\n', 'utf8');\n } finally {\n pendingWrites--;\n }\n });\n asyncWriteChain = run.catch(() => {});\n return run;\n}\n\nexport async function flushLedgerWrites(): Promise<void> {\n await asyncWriteChain;\n}\n\n/** @internal Reset async chain between tests. */\nexport function resetLedgerWriteChainForTests(): void {\n asyncWriteChain = Promise.resolve();\n pendingWrites = 0;\n}\n\n/** @internal Expose pending write count for tests. */\nexport function pendingLedgerWriteCount(): number {\n return pendingWrites;\n}\n","/**\n * Auto-wrap entry for vitest / jest setup files.\n *\n * Importing this module monkey-patches `@pact-foundation/pact`'s\n * `MessageConsumerPact.prototype.verify` and `PactV3.prototype.executeTest`\n * so every contract test in the project records a ledger entry without\n * users having to wrap each call by hand.\n *\n * Usage (vitest):\n *\n * ```ts\n * // vitest.config.ts\n * import { defineConfig } from 'vitest/config';\n * export default defineConfig({\n * test: { setupFiles: ['autotel-pact/auto-wrap'] },\n * });\n * ```\n *\n * Usage (jest):\n *\n * ```js\n * // jest.config.js\n * module.exports = { setupFilesAfterEach: ['autotel-pact/auto-wrap'] };\n * ```\n *\n * Configuration is via environment variables — there's no API surface,\n * because setup files don't get parameters:\n *\n * - `AUTOTEL_PACT_RUN_ID` — tag every ledger entry with this run id\n * - `AUTOTEL_PACT_LEDGER_DIR` — override the default `.autotel-pact/` dir\n *\n * Idempotent: importing twice is a no-op.\n *\n * Safe when Pact-JS isn't installed: logs a single warning and exits\n * cleanly. The same setup file can stay in place for a repo whose\n * non-contract test packages don't have Pact as a dependency.\n */\n\nimport { span as autotelSpan, getActiveSpan } from 'autotel';\nimport { createRequire } from 'node:module';\nimport { buildPactAttributes, outcomeAttribute } from './attrs.js';\nimport { appendLedgerEntry } from './ledger.js';\nimport { LEDGER_ENTRY_SPEC, type InteractionLedgerEntry, type PactInteractionMeta } from './types.js';\nimport type { HttpInteraction } from './wrapper-http.js';\nimport type { ReifiedMessage } from './wrapper.js';\n\nconst INSTALLED = Symbol.for('autotel-pact:auto-wrap-installed');\n\ninterface PactJsModule {\n MessageConsumerPact?: { prototype: Record<string | symbol, unknown> };\n PactV3?: { prototype: Record<string | symbol, unknown> };\n}\n\n/**\n * Install the auto-wrap. Called at module load when this file is imported.\n * Exposed for tests + explicit-invocation users.\n *\n * @param pactModule Override Pact-JS resolution. When omitted, requires\n * `@pact-foundation/pact` from disk. Tests can pass synthetic classes.\n *\n * Returns `true` if Pact-JS was found and patched, `false` otherwise.\n */\nexport function installAutoWrap(pactModule?: PactJsModule): boolean {\n const pactJs = pactModule ?? loadPactJs();\n if (!pactJs) return false;\n\n patchMessagePact(pactJs);\n patchHttpPact(pactJs);\n return true;\n}\n\nfunction loadPactJs(): PactJsModule | undefined {\n try {\n // Use createRequire so we work from both ESM and CJS without\n // pulling Pact-JS into the dynamic import graph.\n const require = createRequire(import.meta.url);\n return require('@pact-foundation/pact') as PactJsModule;\n } catch {\n process.stderr.write(\n 'autotel-pact/auto-wrap: @pact-foundation/pact not installed — skipping.\\n',\n );\n return undefined;\n }\n}\n\nfunction patchMessagePact(mod: PactJsModule): void {\n const ctor = mod.MessageConsumerPact;\n if (!ctor) return;\n const proto = ctor.prototype;\n if (proto[INSTALLED]) return;\n\n const originalVerify = proto.verify as (\n handler: (m: ReifiedMessage) => Promise<unknown>,\n ) => Promise<unknown>;\n\n proto.verify = async function patchedVerify(\n this: { config: { consumer: string; provider: string } },\n handler: (m: ReifiedMessage) => Promise<unknown>,\n ): Promise<unknown> {\n const start = process.hrtime.bigint();\n let captured: ReifiedMessage | undefined;\n const consumer = this.config?.consumer ?? '<unknown>';\n const provider = this.config?.provider ?? '<unknown>';\n\n return autotelSpan('pact.interaction', async (span) => {\n span.setAttributes({\n 'pact.consumer': consumer,\n 'pact.provider': provider,\n 'pact.kind': 'message',\n });\n\n try {\n const result = await originalVerify.call(this, async (reified) => {\n captured = reified;\n const meta: PactInteractionMeta = {\n consumer,\n provider,\n description: reified.description ?? '<unknown>',\n states: (reified.providerStates ?? []).map((s) => s.name),\n kind: 'message',\n };\n span.setAttributes(buildPactAttributes(meta));\n return handler(reified);\n });\n span.setAttributes(outcomeAttribute('passed'));\n writeAutoLedgerEntry({\n consumer,\n provider,\n interaction: captured?.description ?? '<unknown>',\n states: (captured?.providerStates ?? []).map((s) => s.name),\n kind: 'message',\n outcome: 'passed',\n start,\n });\n return result;\n } catch (error) {\n span.setAttributes(outcomeAttribute('failed'));\n writeAutoLedgerEntry({\n consumer,\n provider,\n interaction: captured?.description ?? '<unknown>',\n states: (captured?.providerStates ?? []).map((s) => s.name),\n kind: 'message',\n outcome: 'failed',\n start,\n error: error instanceof Error ? error.message : String(error),\n });\n throw error;\n }\n });\n };\n\n proto[INSTALLED] = true;\n}\n\nfunction patchHttpPact(mod: PactJsModule): void {\n const ctor = mod.PactV3;\n if (!ctor) return;\n const proto = ctor.prototype;\n if (proto[INSTALLED]) return;\n\n // Track interactions added to each PactV3 instance. Cleared after\n // executeTest emits ledger entries for them, so the same instance can\n // be reused across multiple test cases.\n const tracked = new WeakMap<object, HttpInteraction[]>();\n\n const originalAdd = proto.addInteraction as (i: HttpInteraction) => unknown;\n proto.addInteraction = function patchedAddInteraction(this: object, interaction: HttpInteraction) {\n const list = tracked.get(this) ?? [];\n list.push(interaction);\n tracked.set(this, list);\n return originalAdd.call(this, interaction);\n };\n\n const originalExecute = proto.executeTest as <T>(\n fn: (server: { url: string; port: number }) => Promise<T>,\n ) => Promise<T | undefined>;\n\n proto.executeTest = async function patchedExecuteTest<T>(\n this: { opts?: { consumer?: string; provider?: string } },\n fn: (server: { url: string; port: number }) => Promise<T>,\n ): Promise<T | undefined> {\n const start = process.hrtime.bigint();\n const consumer = this.opts?.consumer ?? '<unknown>';\n const provider = this.opts?.provider ?? '<unknown>';\n const interactions = tracked.get(this) ?? [];\n tracked.set(this, []);\n\n return autotelSpan('pact.interaction', async (span) => {\n span.setAttributes({\n 'pact.consumer': consumer,\n 'pact.provider': provider,\n 'pact.kind': 'http',\n });\n // If multiple interactions were added before executeTest, the span\n // attributes describe the first; ledger entries are still written\n // for each. Most real tests use one interaction per executeTest.\n const first = interactions[0];\n if (first) {\n span.setAttributes(\n buildPactAttributes({\n consumer,\n provider,\n description: first.uponReceiving,\n states: (first.states ?? []).map((s) => s.description),\n kind: 'http',\n }),\n );\n }\n\n try {\n const result = (await originalExecute.call(this, fn)) as T | undefined;\n span.setAttributes(outcomeAttribute('passed'));\n for (const i of interactions) {\n writeAutoLedgerEntry({\n consumer,\n provider,\n interaction: i.uponReceiving,\n states: (i.states ?? []).map((s) => s.description),\n kind: 'http',\n outcome: 'passed',\n start,\n });\n }\n return result;\n } catch (error_) {\n span.setAttributes(outcomeAttribute('failed'));\n const error = error_ instanceof Error ? error_.message : String(error_);\n for (const i of interactions) {\n writeAutoLedgerEntry({\n consumer,\n provider,\n interaction: i.uponReceiving,\n states: (i.states ?? []).map((s) => s.description),\n kind: 'http',\n outcome: 'failed',\n start,\n error,\n });\n }\n throw error_;\n }\n });\n };\n\n proto[INSTALLED] = true;\n}\n\nfunction writeAutoLedgerEntry(args: {\n consumer: string;\n provider: string;\n interaction: string;\n states: string[];\n kind: 'message' | 'http';\n outcome: 'passed' | 'failed';\n start: bigint;\n error?: string;\n}): void {\n const ctx = getActiveSpan()?.spanContext();\n const entry: InteractionLedgerEntry = {\n type: 'interaction',\n spec: LEDGER_ENTRY_SPEC,\n consumer: args.consumer,\n provider: args.provider,\n interaction: args.interaction,\n states: args.states,\n kind: args.kind,\n source: 'test',\n role: 'consumer',\n outcome: args.outcome,\n duration_ms: Number(process.hrtime.bigint() - args.start) / 1e6,\n observed_at: new Date().toISOString(),\n trace_id: ctx?.traceId,\n span_id: ctx?.spanId,\n run_id: process.env.AUTOTEL_PACT_RUN_ID,\n git_sha: process.env.GIT_SHA ?? process.env.GITHUB_SHA,\n error: args.error,\n };\n appendLedgerEntry(entry);\n}\n\n// Install on import — this file is meant to be loaded once as a setup file.\ninstallAutoWrap();\n"]}
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Auto-wrap entry for vitest / jest setup files.
3
+ *
4
+ * Importing this module monkey-patches `@pact-foundation/pact`'s
5
+ * `MessageConsumerPact.prototype.verify` and `PactV3.prototype.executeTest`
6
+ * so every contract test in the project records a ledger entry without
7
+ * users having to wrap each call by hand.
8
+ *
9
+ * Usage (vitest):
10
+ *
11
+ * ```ts
12
+ * // vitest.config.ts
13
+ * import { defineConfig } from 'vitest/config';
14
+ * export default defineConfig({
15
+ * test: { setupFiles: ['autotel-pact/auto-wrap'] },
16
+ * });
17
+ * ```
18
+ *
19
+ * Usage (jest):
20
+ *
21
+ * ```js
22
+ * // jest.config.js
23
+ * module.exports = { setupFilesAfterEach: ['autotel-pact/auto-wrap'] };
24
+ * ```
25
+ *
26
+ * Configuration is via environment variables — there's no API surface,
27
+ * because setup files don't get parameters:
28
+ *
29
+ * - `AUTOTEL_PACT_RUN_ID` — tag every ledger entry with this run id
30
+ * - `AUTOTEL_PACT_LEDGER_DIR` — override the default `.autotel-pact/` dir
31
+ *
32
+ * Idempotent: importing twice is a no-op.
33
+ *
34
+ * Safe when Pact-JS isn't installed: logs a single warning and exits
35
+ * cleanly. The same setup file can stay in place for a repo whose
36
+ * non-contract test packages don't have Pact as a dependency.
37
+ */
38
+ interface PactJsModule {
39
+ MessageConsumerPact?: {
40
+ prototype: Record<string | symbol, unknown>;
41
+ };
42
+ PactV3?: {
43
+ prototype: Record<string | symbol, unknown>;
44
+ };
45
+ }
46
+ /**
47
+ * Install the auto-wrap. Called at module load when this file is imported.
48
+ * Exposed for tests + explicit-invocation users.
49
+ *
50
+ * @param pactModule Override Pact-JS resolution. When omitted, requires
51
+ * `@pact-foundation/pact` from disk. Tests can pass synthetic classes.
52
+ *
53
+ * Returns `true` if Pact-JS was found and patched, `false` otherwise.
54
+ */
55
+ declare function installAutoWrap(pactModule?: PactJsModule): boolean;
56
+
57
+ export { installAutoWrap };
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Auto-wrap entry for vitest / jest setup files.
3
+ *
4
+ * Importing this module monkey-patches `@pact-foundation/pact`'s
5
+ * `MessageConsumerPact.prototype.verify` and `PactV3.prototype.executeTest`
6
+ * so every contract test in the project records a ledger entry without
7
+ * users having to wrap each call by hand.
8
+ *
9
+ * Usage (vitest):
10
+ *
11
+ * ```ts
12
+ * // vitest.config.ts
13
+ * import { defineConfig } from 'vitest/config';
14
+ * export default defineConfig({
15
+ * test: { setupFiles: ['autotel-pact/auto-wrap'] },
16
+ * });
17
+ * ```
18
+ *
19
+ * Usage (jest):
20
+ *
21
+ * ```js
22
+ * // jest.config.js
23
+ * module.exports = { setupFilesAfterEach: ['autotel-pact/auto-wrap'] };
24
+ * ```
25
+ *
26
+ * Configuration is via environment variables — there's no API surface,
27
+ * because setup files don't get parameters:
28
+ *
29
+ * - `AUTOTEL_PACT_RUN_ID` — tag every ledger entry with this run id
30
+ * - `AUTOTEL_PACT_LEDGER_DIR` — override the default `.autotel-pact/` dir
31
+ *
32
+ * Idempotent: importing twice is a no-op.
33
+ *
34
+ * Safe when Pact-JS isn't installed: logs a single warning and exits
35
+ * cleanly. The same setup file can stay in place for a repo whose
36
+ * non-contract test packages don't have Pact as a dependency.
37
+ */
38
+ interface PactJsModule {
39
+ MessageConsumerPact?: {
40
+ prototype: Record<string | symbol, unknown>;
41
+ };
42
+ PactV3?: {
43
+ prototype: Record<string | symbol, unknown>;
44
+ };
45
+ }
46
+ /**
47
+ * Install the auto-wrap. Called at module load when this file is imported.
48
+ * Exposed for tests + explicit-invocation users.
49
+ *
50
+ * @param pactModule Override Pact-JS resolution. When omitted, requires
51
+ * `@pact-foundation/pact` from disk. Tests can pass synthetic classes.
52
+ *
53
+ * Returns `true` if Pact-JS was found and patched, `false` otherwise.
54
+ */
55
+ declare function installAutoWrap(pactModule?: PactJsModule): boolean;
56
+
57
+ export { installAutoWrap };
@@ -0,0 +1,248 @@
1
+ import { span, getActiveSpan } from 'autotel';
2
+ import { createRequire } from 'module';
3
+ import { mkdirSync, appendFileSync } from 'fs';
4
+ import 'fs/promises';
5
+ import path from 'path';
6
+
7
+ // src/auto-wrap.ts
8
+
9
+ // src/attrs.ts
10
+ var PACT_ATTRS = {
11
+ CONSUMER: "pact.consumer",
12
+ PROVIDER: "pact.provider",
13
+ KIND: "pact.kind",
14
+ INTERACTION_DESCRIPTION: "pact.interaction.description",
15
+ INTERACTION_ID: "pact.interaction.id",
16
+ INTERACTION_STATES: "pact.interaction.states",
17
+ CONTRACT_FILE: "pact.contract.file",
18
+ OUTCOME: "pact.outcome"
19
+ };
20
+ function buildPactAttributes(meta, opts = {}) {
21
+ const attrs = {
22
+ [PACT_ATTRS.CONSUMER]: meta.consumer,
23
+ [PACT_ATTRS.PROVIDER]: meta.provider,
24
+ [PACT_ATTRS.KIND]: meta.kind,
25
+ [PACT_ATTRS.INTERACTION_DESCRIPTION]: meta.description,
26
+ [PACT_ATTRS.INTERACTION_STATES]: meta.states
27
+ };
28
+ if (opts.contractFile) {
29
+ attrs[PACT_ATTRS.CONTRACT_FILE] = opts.contractFile;
30
+ }
31
+ if (meta.interactionId) {
32
+ attrs[PACT_ATTRS.INTERACTION_ID] = meta.interactionId;
33
+ }
34
+ return attrs;
35
+ }
36
+ function outcomeAttribute(outcome) {
37
+ return { [PACT_ATTRS.OUTCOME]: outcome };
38
+ }
39
+
40
+ // src/types.ts
41
+ var LEDGER_ENTRY_SPEC = "autotel-pact-ledger-entry/v0.2.0";
42
+
43
+ // src/ledger.ts
44
+ var DEFAULT_DIR = ".autotel-pact";
45
+ function resolveLedgerDir(opts = {}) {
46
+ return path.resolve(process.cwd(), opts.dir ?? process.env.AUTOTEL_PACT_LEDGER_DIR ?? DEFAULT_DIR);
47
+ }
48
+ function resolveRunId(opts = {}) {
49
+ const explicit = opts.runId ?? process.env.AUTOTEL_PACT_RUN_ID;
50
+ if (explicit) return explicit;
51
+ return `local-${(/* @__PURE__ */ new Date()).toISOString().replaceAll(/[:.]/g, "-")}`;
52
+ }
53
+ function ledgerPath(opts = {}) {
54
+ const dir = resolveLedgerDir(opts);
55
+ return path.join(dir, `ledger-${resolveRunId(opts)}.jsonl`);
56
+ }
57
+ function writeLine(filePath, entry) {
58
+ mkdirSync(path.dirname(filePath), { recursive: true });
59
+ appendFileSync(filePath, JSON.stringify(entry) + "\n", "utf8");
60
+ }
61
+ function appendLedgerEntry(entry, opts = {}) {
62
+ const filePath = ledgerPath(opts);
63
+ const normalized = entry.type === "provider_verification_run" ? entry : { ...entry, spec: LEDGER_ENTRY_SPEC, type: "interaction" };
64
+ writeLine(filePath, normalized);
65
+ }
66
+ Promise.resolve();
67
+
68
+ // src/auto-wrap.ts
69
+ var INSTALLED = /* @__PURE__ */ Symbol.for("autotel-pact:auto-wrap-installed");
70
+ function installAutoWrap(pactModule) {
71
+ const pactJs = pactModule ?? loadPactJs();
72
+ if (!pactJs) return false;
73
+ patchMessagePact(pactJs);
74
+ patchHttpPact(pactJs);
75
+ return true;
76
+ }
77
+ function loadPactJs() {
78
+ try {
79
+ const require2 = createRequire(import.meta.url);
80
+ return require2("@pact-foundation/pact");
81
+ } catch {
82
+ process.stderr.write(
83
+ "autotel-pact/auto-wrap: @pact-foundation/pact not installed \u2014 skipping.\n"
84
+ );
85
+ return void 0;
86
+ }
87
+ }
88
+ function patchMessagePact(mod) {
89
+ const ctor = mod.MessageConsumerPact;
90
+ if (!ctor) return;
91
+ const proto = ctor.prototype;
92
+ if (proto[INSTALLED]) return;
93
+ const originalVerify = proto.verify;
94
+ proto.verify = async function patchedVerify(handler) {
95
+ const start = process.hrtime.bigint();
96
+ let captured;
97
+ const consumer = this.config?.consumer ?? "<unknown>";
98
+ const provider = this.config?.provider ?? "<unknown>";
99
+ return span("pact.interaction", async (span) => {
100
+ span.setAttributes({
101
+ "pact.consumer": consumer,
102
+ "pact.provider": provider,
103
+ "pact.kind": "message"
104
+ });
105
+ try {
106
+ const result = await originalVerify.call(this, async (reified) => {
107
+ captured = reified;
108
+ const meta = {
109
+ consumer,
110
+ provider,
111
+ description: reified.description ?? "<unknown>",
112
+ states: (reified.providerStates ?? []).map((s) => s.name),
113
+ kind: "message"
114
+ };
115
+ span.setAttributes(buildPactAttributes(meta));
116
+ return handler(reified);
117
+ });
118
+ span.setAttributes(outcomeAttribute("passed"));
119
+ writeAutoLedgerEntry({
120
+ consumer,
121
+ provider,
122
+ interaction: captured?.description ?? "<unknown>",
123
+ states: (captured?.providerStates ?? []).map((s) => s.name),
124
+ kind: "message",
125
+ outcome: "passed",
126
+ start
127
+ });
128
+ return result;
129
+ } catch (error) {
130
+ span.setAttributes(outcomeAttribute("failed"));
131
+ writeAutoLedgerEntry({
132
+ consumer,
133
+ provider,
134
+ interaction: captured?.description ?? "<unknown>",
135
+ states: (captured?.providerStates ?? []).map((s) => s.name),
136
+ kind: "message",
137
+ outcome: "failed",
138
+ start,
139
+ error: error instanceof Error ? error.message : String(error)
140
+ });
141
+ throw error;
142
+ }
143
+ });
144
+ };
145
+ proto[INSTALLED] = true;
146
+ }
147
+ function patchHttpPact(mod) {
148
+ const ctor = mod.PactV3;
149
+ if (!ctor) return;
150
+ const proto = ctor.prototype;
151
+ if (proto[INSTALLED]) return;
152
+ const tracked = /* @__PURE__ */ new WeakMap();
153
+ const originalAdd = proto.addInteraction;
154
+ proto.addInteraction = function patchedAddInteraction(interaction) {
155
+ const list = tracked.get(this) ?? [];
156
+ list.push(interaction);
157
+ tracked.set(this, list);
158
+ return originalAdd.call(this, interaction);
159
+ };
160
+ const originalExecute = proto.executeTest;
161
+ proto.executeTest = async function patchedExecuteTest(fn) {
162
+ const start = process.hrtime.bigint();
163
+ const consumer = this.opts?.consumer ?? "<unknown>";
164
+ const provider = this.opts?.provider ?? "<unknown>";
165
+ const interactions = tracked.get(this) ?? [];
166
+ tracked.set(this, []);
167
+ return span("pact.interaction", async (span) => {
168
+ span.setAttributes({
169
+ "pact.consumer": consumer,
170
+ "pact.provider": provider,
171
+ "pact.kind": "http"
172
+ });
173
+ const first = interactions[0];
174
+ if (first) {
175
+ span.setAttributes(
176
+ buildPactAttributes({
177
+ consumer,
178
+ provider,
179
+ description: first.uponReceiving,
180
+ states: (first.states ?? []).map((s) => s.description),
181
+ kind: "http"
182
+ })
183
+ );
184
+ }
185
+ try {
186
+ const result = await originalExecute.call(this, fn);
187
+ span.setAttributes(outcomeAttribute("passed"));
188
+ for (const i of interactions) {
189
+ writeAutoLedgerEntry({
190
+ consumer,
191
+ provider,
192
+ interaction: i.uponReceiving,
193
+ states: (i.states ?? []).map((s) => s.description),
194
+ kind: "http",
195
+ outcome: "passed",
196
+ start
197
+ });
198
+ }
199
+ return result;
200
+ } catch (error_) {
201
+ span.setAttributes(outcomeAttribute("failed"));
202
+ const error = error_ instanceof Error ? error_.message : String(error_);
203
+ for (const i of interactions) {
204
+ writeAutoLedgerEntry({
205
+ consumer,
206
+ provider,
207
+ interaction: i.uponReceiving,
208
+ states: (i.states ?? []).map((s) => s.description),
209
+ kind: "http",
210
+ outcome: "failed",
211
+ start,
212
+ error
213
+ });
214
+ }
215
+ throw error_;
216
+ }
217
+ });
218
+ };
219
+ proto[INSTALLED] = true;
220
+ }
221
+ function writeAutoLedgerEntry(args) {
222
+ const ctx = getActiveSpan()?.spanContext();
223
+ const entry = {
224
+ type: "interaction",
225
+ spec: LEDGER_ENTRY_SPEC,
226
+ consumer: args.consumer,
227
+ provider: args.provider,
228
+ interaction: args.interaction,
229
+ states: args.states,
230
+ kind: args.kind,
231
+ source: "test",
232
+ role: "consumer",
233
+ outcome: args.outcome,
234
+ duration_ms: Number(process.hrtime.bigint() - args.start) / 1e6,
235
+ observed_at: (/* @__PURE__ */ new Date()).toISOString(),
236
+ trace_id: ctx?.traceId,
237
+ span_id: ctx?.spanId,
238
+ run_id: process.env.AUTOTEL_PACT_RUN_ID,
239
+ git_sha: process.env.GIT_SHA ?? process.env.GITHUB_SHA,
240
+ error: args.error
241
+ };
242
+ appendLedgerEntry(entry);
243
+ }
244
+ installAutoWrap();
245
+
246
+ export { installAutoWrap };
247
+ //# sourceMappingURL=auto-wrap.js.map
248
+ //# sourceMappingURL=auto-wrap.js.map