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,193 @@
1
+ import 'fs';
2
+ import { mkdir, appendFile } from 'fs/promises';
3
+ import path from 'path';
4
+
5
+ // src/ledger.ts
6
+
7
+ // src/types.ts
8
+ var LEDGER_ENTRY_SPEC = "autotel-pact-ledger-entry/v0.2.0";
9
+
10
+ // src/ledger.ts
11
+ var DEFAULT_DIR = ".autotel-pact";
12
+ function resolveLedgerDir(opts = {}) {
13
+ return path.resolve(process.cwd(), opts.dir ?? process.env.AUTOTEL_PACT_LEDGER_DIR ?? DEFAULT_DIR);
14
+ }
15
+ function resolveRunId(opts = {}) {
16
+ const explicit = opts.runId ?? process.env.AUTOTEL_PACT_RUN_ID;
17
+ if (explicit) return explicit;
18
+ return `local-${(/* @__PURE__ */ new Date()).toISOString().replaceAll(/[:.]/g, "-")}`;
19
+ }
20
+ function ledgerPath(opts = {}) {
21
+ const dir = resolveLedgerDir(opts);
22
+ return path.join(dir, `ledger-${resolveRunId(opts)}.jsonl`);
23
+ }
24
+ var MAX_PENDING_WRITES = 4096;
25
+ var asyncWriteChain = Promise.resolve();
26
+ var pendingWrites = 0;
27
+ async function appendLedgerEntryAsync(entry, opts = {}) {
28
+ if (pendingWrites >= MAX_PENDING_WRITES) {
29
+ await asyncWriteChain;
30
+ }
31
+ const filePath = ledgerPath(opts);
32
+ const normalized = entry.type === "provider_verification_run" ? entry : { ...entry, spec: LEDGER_ENTRY_SPEC, type: "interaction" };
33
+ const line = JSON.stringify(normalized);
34
+ pendingWrites++;
35
+ const run = asyncWriteChain.then(async () => {
36
+ try {
37
+ await mkdir(path.dirname(filePath), { recursive: true });
38
+ await appendFile(filePath, line + "\n", "utf8");
39
+ } finally {
40
+ pendingWrites--;
41
+ }
42
+ });
43
+ asyncWriteChain = run.catch(() => {
44
+ });
45
+ return run;
46
+ }
47
+ async function flushLedgerWrites() {
48
+ await asyncWriteChain;
49
+ }
50
+
51
+ // src/attrs.ts
52
+ var PACT_ATTRS = {
53
+ CONSUMER: "pact.consumer",
54
+ PROVIDER: "pact.provider",
55
+ KIND: "pact.kind",
56
+ INTERACTION_DESCRIPTION: "pact.interaction.description",
57
+ INTERACTION_ID: "pact.interaction.id",
58
+ INTERACTION_STATES: "pact.interaction.states"};
59
+
60
+ // src/processor.ts
61
+ var DEFAULT_MAX_QUEUE = 1024;
62
+ var WARN_INTERVAL_MS = 6e4;
63
+ function attrString(attrs, key) {
64
+ const v = attrs[key];
65
+ if (typeof v === "string" && v.length > 0) return v;
66
+ return void 0;
67
+ }
68
+ function attrStates(attrs) {
69
+ const v = attrs[PACT_ATTRS.INTERACTION_STATES];
70
+ if (Array.isArray(v)) {
71
+ return v.filter((s) => typeof s === "string");
72
+ }
73
+ return [];
74
+ }
75
+ function ledgerEntryFromSpan(span) {
76
+ const attrs = span.attributes;
77
+ const consumer = attrString(attrs, PACT_ATTRS.CONSUMER);
78
+ const provider = attrString(attrs, PACT_ATTRS.PROVIDER);
79
+ const description = attrString(attrs, PACT_ATTRS.INTERACTION_DESCRIPTION);
80
+ const interactionId = attrString(attrs, PACT_ATTRS.INTERACTION_ID);
81
+ if (!consumer || !provider || !description && !interactionId) {
82
+ return null;
83
+ }
84
+ const kindRaw = attrString(attrs, PACT_ATTRS.KIND);
85
+ const kind = kindRaw === "http" ? "http" : "message";
86
+ const ctx = span.spanContext();
87
+ const errored = span.status?.code === 2;
88
+ const entry = {
89
+ type: "interaction",
90
+ spec: LEDGER_ENTRY_SPEC,
91
+ consumer,
92
+ provider,
93
+ interaction: description ?? interactionId,
94
+ interaction_id: interactionId,
95
+ states: attrStates(attrs),
96
+ kind,
97
+ outcome: errored ? "failed" : "passed",
98
+ source: "production",
99
+ role: "consumer",
100
+ duration_ms: 0,
101
+ observed_at: (/* @__PURE__ */ new Date()).toISOString(),
102
+ trace_id: ctx.traceId,
103
+ span_id: ctx.spanId,
104
+ run_id: process.env.AUTOTEL_PACT_RUN_ID,
105
+ git_sha: process.env.GIT_SHA ?? process.env.GITHUB_SHA
106
+ };
107
+ if (errored && span.status?.message) {
108
+ entry.error = span.status.message;
109
+ }
110
+ return entry;
111
+ }
112
+ var PactLedgerSpanProcessor = class {
113
+ opts;
114
+ maxQueue;
115
+ pending = [];
116
+ flushing = false;
117
+ drops = 0;
118
+ lastWarnAt = 0;
119
+ constructor(opts = {}) {
120
+ this.opts = opts;
121
+ this.maxQueue = opts.maxQueueSize ?? DEFAULT_MAX_QUEUE;
122
+ }
123
+ onStart(_span, _parentContext) {
124
+ }
125
+ onEnd(span) {
126
+ try {
127
+ const entry = ledgerEntryFromSpan(span);
128
+ if (!entry) return;
129
+ if (this.pending.length >= this.maxQueue) {
130
+ this.pending.shift();
131
+ this.drops++;
132
+ this.opts.onDrop?.("queue_full");
133
+ this.maybeWarn(
134
+ `autotel-pact: dropped oldest queued ledger entry (queue full, max ${this.maxQueue}). ${this.drops} total drops.`
135
+ );
136
+ }
137
+ this.pending.push({ entry, opts: this.opts });
138
+ queueMicrotask(() => {
139
+ void this.flush();
140
+ });
141
+ } catch (error) {
142
+ this.opts.onWriteError?.(error);
143
+ }
144
+ }
145
+ maybeWarn(message) {
146
+ const now = Date.now();
147
+ if (now - this.lastWarnAt < WARN_INTERVAL_MS) return;
148
+ this.lastWarnAt = now;
149
+ if (this.opts.onWarn) {
150
+ this.opts.onWarn(message);
151
+ } else {
152
+ console.warn(message);
153
+ }
154
+ }
155
+ async flush() {
156
+ if (this.flushing) return;
157
+ this.flushing = true;
158
+ try {
159
+ while (this.pending.length > 0) {
160
+ const item = this.pending.shift();
161
+ try {
162
+ await appendLedgerEntryAsync(item.entry, item.opts);
163
+ } catch (error) {
164
+ this.opts.onWriteError?.(error);
165
+ this.maybeWarn(
166
+ `autotel-pact: ledger write failed (fail-open): ${error instanceof Error ? error.message : String(error)}`
167
+ );
168
+ }
169
+ }
170
+ } finally {
171
+ this.flushing = false;
172
+ if (this.pending.length > 0) {
173
+ queueMicrotask(() => {
174
+ void this.flush();
175
+ });
176
+ }
177
+ }
178
+ }
179
+ async forceFlush() {
180
+ await this.flush();
181
+ await flushLedgerWrites();
182
+ }
183
+ async shutdown() {
184
+ await this.forceFlush();
185
+ }
186
+ };
187
+ function createPactLedgerProcessor(opts = {}) {
188
+ return new PactLedgerSpanProcessor(opts);
189
+ }
190
+
191
+ export { PactLedgerSpanProcessor, createPactLedgerProcessor };
192
+ //# sourceMappingURL=processor.js.map
193
+ //# sourceMappingURL=processor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/types.ts","../src/ledger.ts","../src/attrs.ts","../src/processor.ts"],"names":[],"mappings":"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","file":"processor.js","sourcesContent":["/**\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","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","import { appendLedgerEntryAsync, flushLedgerWrites, type LedgerOptions } from './ledger.js';\nimport { PACT_ATTRS } from './attrs.js';\nimport { LEDGER_ENTRY_SPEC, type InteractionLedgerEntry, type PactKind } from './types.js';\n\n/** Minimal ReadableSpan shape — avoids hard dependency on sdk-trace-base. */\nexport interface ReadableSpanLike {\n attributes: Record<string, unknown>;\n spanContext(): { traceId: string; spanId: string };\n /** OTel SpanStatus. code 2 = ERROR (see @opentelemetry/api SpanStatusCode). */\n status?: { code: number; message?: string };\n}\n\nexport interface SpanLike {\n spanContext(): { traceId: string; spanId: string };\n}\n\n/** Opaque parent context — matches OTel SpanProcessor without a hard dependency. */\nexport type OtelContext = unknown;\n\nexport interface SpanProcessorLike {\n onStart(span: SpanLike, parentContext: OtelContext): void;\n onEnd(span: ReadableSpanLike): void;\n shutdown(): Promise<void>;\n forceFlush(): Promise<void>;\n}\n\nexport interface PactLedgerProcessorOptions extends LedgerOptions {\n /** Max queued ledger writes before dropping (default 1024). */\n maxQueueSize?: number;\n onDrop?: (reason: 'queue_full') => void;\n onWriteError?: (error: unknown) => void;\n onWarn?: (message: string) => void;\n}\n\nconst DEFAULT_MAX_QUEUE = 1024;\nconst WARN_INTERVAL_MS = 60_000;\n\ntype QueueItem = { entry: InteractionLedgerEntry; opts: LedgerOptions };\n\nfunction attrString(attrs: Record<string, unknown>, key: string): string | undefined {\n const v = attrs[key];\n if (typeof v === 'string' && v.length > 0) return v;\n return undefined;\n}\n\nfunction attrStates(attrs: Record<string, unknown>): string[] {\n const v = attrs[PACT_ATTRS.INTERACTION_STATES];\n if (Array.isArray(v)) {\n return v.filter((s): s is string => typeof s === 'string');\n }\n return [];\n}\n\nfunction ledgerEntryFromSpan(span: ReadableSpanLike): InteractionLedgerEntry | null {\n const attrs = span.attributes;\n const consumer = attrString(attrs, PACT_ATTRS.CONSUMER);\n const provider = attrString(attrs, PACT_ATTRS.PROVIDER);\n const description = attrString(attrs, PACT_ATTRS.INTERACTION_DESCRIPTION);\n const interactionId = attrString(attrs, PACT_ATTRS.INTERACTION_ID);\n if (!consumer || !provider || (!description && !interactionId)) {\n return null;\n }\n\n const kindRaw = attrString(attrs, PACT_ATTRS.KIND);\n const kind: PactKind = kindRaw === 'http' ? 'http' : 'message';\n const ctx = span.spanContext();\n // SpanStatusCode.ERROR === 2. Treat anything else (UNSET, OK) as passed.\n const errored = span.status?.code === 2;\n\n const entry: InteractionLedgerEntry = {\n type: 'interaction',\n spec: LEDGER_ENTRY_SPEC,\n consumer,\n provider,\n interaction: description ?? interactionId!,\n interaction_id: interactionId,\n states: attrStates(attrs),\n kind,\n outcome: errored ? 'failed' : 'passed',\n source: 'production',\n role: 'consumer',\n duration_ms: 0,\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 };\n if (errored && span.status?.message) {\n entry.error = span.status.message;\n }\n return entry;\n}\n\n/**\n * Records pact-tagged spans to the JSONL ledger. Bounded queue, drop-on-full, fail-open.\n */\nexport class PactLedgerSpanProcessor implements SpanProcessorLike {\n private readonly opts: PactLedgerProcessorOptions;\n private readonly maxQueue: number;\n private pending: QueueItem[] = [];\n private flushing = false;\n private drops = 0;\n private lastWarnAt = 0;\n\n constructor(opts: PactLedgerProcessorOptions = {}) {\n this.opts = opts;\n this.maxQueue = opts.maxQueueSize ?? DEFAULT_MAX_QUEUE;\n }\n\n onStart(_span: SpanLike, _parentContext: OtelContext): void {\n // no-op\n }\n\n onEnd(span: ReadableSpanLike): void {\n try {\n const entry = ledgerEntryFromSpan(span);\n if (!entry) return;\n\n if (this.pending.length >= this.maxQueue) {\n // FIFO eviction: drop the oldest queued entry to keep the newest.\n // Recent evidence is more valuable than backlog under sustained pressure.\n this.pending.shift();\n this.drops++;\n this.opts.onDrop?.('queue_full');\n this.maybeWarn(\n `autotel-pact: dropped oldest queued ledger entry (queue full, max ${this.maxQueue}). ` +\n `${this.drops} total drops.`,\n );\n }\n\n this.pending.push({ entry, opts: this.opts });\n queueMicrotask(() => {\n void this.flush();\n });\n } catch (error) {\n this.opts.onWriteError?.(error);\n }\n }\n\n private maybeWarn(message: string): void {\n const now = Date.now();\n if (now - this.lastWarnAt < WARN_INTERVAL_MS) return;\n this.lastWarnAt = now;\n if (this.opts.onWarn) {\n this.opts.onWarn(message);\n } else {\n console.warn(message);\n }\n }\n\n private async flush(): Promise<void> {\n if (this.flushing) return;\n this.flushing = true;\n try {\n while (this.pending.length > 0) {\n const item = this.pending.shift()!;\n try {\n await appendLedgerEntryAsync(item.entry, item.opts);\n } catch (error) {\n this.opts.onWriteError?.(error);\n this.maybeWarn(\n `autotel-pact: ledger write failed (fail-open): ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n }\n } finally {\n this.flushing = false;\n if (this.pending.length > 0) {\n queueMicrotask(() => {\n void this.flush();\n });\n }\n }\n }\n\n async forceFlush(): Promise<void> {\n await this.flush();\n await flushLedgerWrites();\n }\n\n async shutdown(): Promise<void> {\n await this.forceFlush();\n }\n}\n\nexport function createPactLedgerProcessor(\n opts: PactLedgerProcessorOptions = {},\n): PactLedgerSpanProcessor {\n return new PactLedgerSpanProcessor(opts);\n}\n"]}
@@ -0,0 +1,219 @@
1
+ 'use strict';
2
+
3
+ var path = require('path');
4
+ var autotel = require('autotel');
5
+ var fs = require('fs');
6
+ require('fs/promises');
7
+
8
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
9
+
10
+ var path__default = /*#__PURE__*/_interopDefault(path);
11
+
12
+ // src/wrapper-provider.ts
13
+
14
+ // src/types.ts
15
+ var LEDGER_ENTRY_SPEC = "autotel-pact-ledger-entry/v0.2.0";
16
+
17
+ // src/ledger.ts
18
+ var DEFAULT_DIR = ".autotel-pact";
19
+ function resolveLedgerDir(opts = {}) {
20
+ return path__default.default.resolve(process.cwd(), opts.dir ?? process.env.AUTOTEL_PACT_LEDGER_DIR ?? DEFAULT_DIR);
21
+ }
22
+ function resolveRunId(opts = {}) {
23
+ const explicit = opts.runId ?? process.env.AUTOTEL_PACT_RUN_ID;
24
+ if (explicit) return explicit;
25
+ return `local-${(/* @__PURE__ */ new Date()).toISOString().replaceAll(/[:.]/g, "-")}`;
26
+ }
27
+ function ledgerPath(opts = {}) {
28
+ const dir = resolveLedgerDir(opts);
29
+ return path__default.default.join(dir, `ledger-${resolveRunId(opts)}.jsonl`);
30
+ }
31
+ function writeLine(filePath, entry) {
32
+ fs.mkdirSync(path__default.default.dirname(filePath), { recursive: true });
33
+ fs.appendFileSync(filePath, JSON.stringify(entry) + "\n", "utf8");
34
+ }
35
+ function appendLedgerEntry(entry, opts = {}) {
36
+ const filePath = ledgerPath(opts);
37
+ const normalized = entry.type === "provider_verification_run" ? entry : { ...entry, spec: LEDGER_ENTRY_SPEC, type: "interaction" };
38
+ writeLine(filePath, normalized);
39
+ }
40
+ function appendProviderVerificationFailure(entry, opts = {}) {
41
+ appendLedgerEntry(
42
+ {
43
+ type: "provider_verification_run",
44
+ spec: LEDGER_ENTRY_SPEC,
45
+ outcome: "failed",
46
+ role: "provider",
47
+ source: entry.source ?? "test",
48
+ consumer: entry.consumer,
49
+ provider: entry.provider,
50
+ observed_at: entry.observed_at,
51
+ error: entry.error,
52
+ run_id: entry.run_id,
53
+ git_sha: entry.git_sha,
54
+ trace_id: entry.trace_id,
55
+ span_id: entry.span_id
56
+ },
57
+ opts
58
+ );
59
+ }
60
+ Promise.resolve();
61
+ function extractInteractionId(metadata) {
62
+ if (!metadata) return void 0;
63
+ const id = metadata.interactionId ?? metadata.interaction_id;
64
+ return typeof id === "string" && id.length > 0 ? id : void 0;
65
+ }
66
+ function interactionsFromPactFile(pact) {
67
+ const consumer = pact.consumer?.name;
68
+ const provider = pact.provider?.name;
69
+ if (!consumer || !provider) return [];
70
+ const keys = [];
71
+ for (const m of pact.messages ?? []) {
72
+ keys.push({
73
+ consumer,
74
+ provider,
75
+ interaction: m.description,
76
+ kind: "message",
77
+ interactionId: extractInteractionId(m.metadata)
78
+ });
79
+ }
80
+ for (const i of pact.interactions ?? []) {
81
+ keys.push({
82
+ consumer,
83
+ provider,
84
+ interaction: i.description,
85
+ kind: "http",
86
+ interactionId: extractInteractionId(i.metadata)
87
+ });
88
+ }
89
+ return keys;
90
+ }
91
+ function parsePactFile(filePath) {
92
+ try {
93
+ return JSON.parse(fs.readFileSync(filePath, "utf8"));
94
+ } catch {
95
+ return null;
96
+ }
97
+ }
98
+
99
+ // src/attrs.ts
100
+ var PACT_ATTRS = {
101
+ CONSUMER: "pact.consumer",
102
+ PROVIDER: "pact.provider",
103
+ KIND: "pact.kind",
104
+ INTERACTION_DESCRIPTION: "pact.interaction.description",
105
+ INTERACTION_ID: "pact.interaction.id",
106
+ INTERACTION_STATES: "pact.interaction.states",
107
+ CONTRACT_FILE: "pact.contract.file",
108
+ OUTCOME: "pact.outcome"
109
+ };
110
+
111
+ // src/wrapper-provider.ts
112
+ function resolvePactPaths(opts) {
113
+ const urls = opts.pactUrls ?? [];
114
+ return urls.map((u) => path__default.default.resolve(process.cwd(), u));
115
+ }
116
+ function inferConsumerFromPacts(pactPaths, fallback) {
117
+ for (const filePath of pactPaths) {
118
+ const pact = parsePactFile(filePath);
119
+ if (pact?.consumer?.name) return pact.consumer.name;
120
+ }
121
+ if (fallback) return fallback;
122
+ throw new Error(
123
+ "autotel-pact: could not infer consumer from pact files. Pass `consumer` in options."
124
+ );
125
+ }
126
+ function kindForPactFile(filePath) {
127
+ const pact = parsePactFile(filePath);
128
+ if (!pact) return "message";
129
+ if ((pact.interactions?.length ?? 0) > 0) return "http";
130
+ return "message";
131
+ }
132
+ async function loadVerifier(VerifierClass) {
133
+ if (VerifierClass) return VerifierClass;
134
+ const mod = await import('@pact-foundation/pact');
135
+ const Verifier = mod.Verifier;
136
+ if (!Verifier) {
137
+ throw new Error(
138
+ "autotel-pact: @pact-foundation/pact Verifier not found. Install the peer dependency."
139
+ );
140
+ }
141
+ return Verifier;
142
+ }
143
+ async function withProviderVerification(verifierOpts, wrapOpts = {}) {
144
+ const pactPaths = resolvePactPaths(verifierOpts);
145
+ const provider = verifierOpts.provider;
146
+ const consumer = inferConsumerFromPacts(pactPaths, wrapOpts.consumer);
147
+ const spanName = wrapOpts.spanName ?? "pact.verification";
148
+ const start = process.hrtime.bigint();
149
+ const Verifier = wrapOpts.skipVerifier ? void 0 : await loadVerifier(wrapOpts.Verifier);
150
+ return autotel.span(spanName, async () => {
151
+ const span = autotel.getActiveSpan();
152
+ span?.setAttributes({
153
+ [PACT_ATTRS.CONSUMER]: consumer,
154
+ [PACT_ATTRS.PROVIDER]: provider,
155
+ [PACT_ATTRS.KIND]: pactPaths.length === 1 ? kindForPactFile(pactPaths[0]) : "message",
156
+ "pact.role": "provider"
157
+ });
158
+ try {
159
+ if (Verifier) {
160
+ await new Verifier(verifierOpts).verifyProvider();
161
+ }
162
+ span?.setAttributes({ [PACT_ATTRS.OUTCOME]: "passed" });
163
+ const ctx = span?.spanContext();
164
+ const base = {
165
+ source: "test",
166
+ role: "provider",
167
+ outcome: "passed",
168
+ observed_at: (/* @__PURE__ */ new Date()).toISOString(),
169
+ trace_id: ctx?.traceId,
170
+ span_id: ctx?.spanId,
171
+ run_id: process.env.AUTOTEL_PACT_RUN_ID,
172
+ git_sha: process.env.GIT_SHA ?? process.env.GITHUB_SHA
173
+ };
174
+ for (const filePath of pactPaths) {
175
+ const pact = parsePactFile(filePath);
176
+ if (!pact) continue;
177
+ const interactions = interactionsFromPactFile(pact);
178
+ for (const i of interactions) {
179
+ const entry = {
180
+ type: "interaction",
181
+ spec: LEDGER_ENTRY_SPEC,
182
+ consumer: i.consumer,
183
+ provider: i.provider,
184
+ interaction: i.interaction,
185
+ interaction_id: i.interactionId,
186
+ states: [],
187
+ kind: i.kind,
188
+ duration_ms: Number(process.hrtime.bigint() - start) / 1e6,
189
+ ...base
190
+ };
191
+ appendLedgerEntry(entry, wrapOpts);
192
+ }
193
+ }
194
+ } catch (error) {
195
+ span?.setAttributes({ [PACT_ATTRS.OUTCOME]: "failed" });
196
+ const message = error instanceof Error ? error.message : String(error);
197
+ const ctx = span?.spanContext();
198
+ appendProviderVerificationFailure(
199
+ {
200
+ consumer,
201
+ provider,
202
+ source: "test",
203
+ observed_at: (/* @__PURE__ */ new Date()).toISOString(),
204
+ error: message,
205
+ trace_id: ctx?.traceId,
206
+ span_id: ctx?.spanId,
207
+ run_id: process.env.AUTOTEL_PACT_RUN_ID,
208
+ git_sha: process.env.GIT_SHA ?? process.env.GITHUB_SHA
209
+ },
210
+ wrapOpts
211
+ );
212
+ throw error;
213
+ }
214
+ });
215
+ }
216
+
217
+ exports.withProviderVerification = withProviderVerification;
218
+ //# sourceMappingURL=provider.cjs.map
219
+ //# sourceMappingURL=provider.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/types.ts","../src/ledger.ts","../src/pact-file.ts","../src/attrs.ts","../src/wrapper-provider.ts"],"names":["path","mkdirSync","appendFileSync","readFileSync","autotelSpan","getActiveSpan"],"mappings":"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","file":"provider.cjs","sourcesContent":["/**\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","import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';\nimport path from 'node:path';\nimport type { PactFile, PactInteractionKey } from './types.js';\n\n/**\n * Walk a directory and return absolute paths of all *.json pact files.\n */\nexport function listPactFiles(dir: string): string[] {\n if (!existsSync(dir) || !statSync(dir).isDirectory()) return [];\n const out: string[] = [];\n for (const entry of readdirSync(dir, { withFileTypes: true })) {\n const full = path.join(dir, entry.name);\n if (entry.isDirectory()) {\n out.push(...listPactFiles(full));\n } else if (entry.isFile() && entry.name.endsWith('.json')) {\n out.push(full);\n }\n }\n return out;\n}\n\nexport function extractInteractionId(\n metadata: Record<string, unknown> | undefined,\n): string | undefined {\n if (!metadata) return undefined;\n const id = metadata.interactionId ?? metadata.interaction_id;\n return typeof id === 'string' && id.length > 0 ? id : undefined;\n}\n\n/**\n * Extract interaction tuples declared in a pact file.\n */\nexport function interactionsFromPactFile(pact: PactFile): PactInteractionKey[] {\n const consumer = pact.consumer?.name;\n const provider = pact.provider?.name;\n if (!consumer || !provider) return [];\n const keys: PactInteractionKey[] = [];\n for (const m of pact.messages ?? []) {\n keys.push({\n consumer,\n provider,\n interaction: m.description,\n kind: 'message',\n interactionId: extractInteractionId(m.metadata),\n });\n }\n for (const i of pact.interactions ?? []) {\n keys.push({\n consumer,\n provider,\n interaction: i.description,\n kind: 'http',\n interactionId: extractInteractionId(i.metadata),\n });\n }\n return keys;\n}\n\nexport function parsePactFile(filePath: string): PactFile | null {\n try {\n return JSON.parse(readFileSync(filePath, 'utf8')) as PactFile;\n } catch {\n return null;\n }\n}\n","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","import path from 'node:path';\nimport { span as autotelSpan, getActiveSpan } from 'autotel';\nimport { appendLedgerEntry, appendProviderVerificationFailure, type LedgerOptions } from './ledger.js';\nimport { interactionsFromPactFile, parsePactFile } from './pact-file.js';\nimport { LEDGER_ENTRY_SPEC, type InteractionLedgerEntry, type PactKind } from './types.js';\nimport { PACT_ATTRS } from './attrs.js';\n\n/**\n * Minimal Verifier options — structural match for @pact-foundation/pact Verifier.\n */\nexport interface VerifierOptionsLike {\n provider: string;\n providerBaseUrl: string;\n pactUrls?: string[];\n logLevel?: string;\n [key: string]: unknown;\n}\n\nexport interface VerifierLike {\n verifyProvider: () => Promise<unknown>;\n}\n\nexport interface VerifierConstructor {\n new (options: VerifierOptionsLike): VerifierLike;\n}\n\nexport interface WithProviderVerificationOptions extends LedgerOptions {\n /** Consumer name when not inferrable from pact files. */\n consumer?: string;\n spanName?: string;\n /** Custom Verifier class (defaults to dynamic import from @pact-foundation/pact). */\n Verifier?: VerifierConstructor;\n /**\n * Skip loading and calling the Verifier entirely. Emits the same\n * per-interaction ledger rows as a successful verification, parsed from the\n * supplied pact files. Use for demos, smoke tests, and audit-pipeline\n * exercises where running a real provider is impractical. Do not use in\n * production CI: it will mark every interaction in the pact file as\n * provider-verified without any actual verification.\n */\n skipVerifier?: boolean;\n}\n\nfunction resolvePactPaths(opts: VerifierOptionsLike): string[] {\n const urls = opts.pactUrls ?? [];\n return urls.map((u) => path.resolve(process.cwd(), u));\n}\n\nfunction inferConsumerFromPacts(pactPaths: string[], fallback?: string): string {\n for (const filePath of pactPaths) {\n const pact = parsePactFile(filePath);\n if (pact?.consumer?.name) return pact.consumer.name;\n }\n if (fallback) return fallback;\n throw new Error(\n 'autotel-pact: could not infer consumer from pact files. Pass `consumer` in options.',\n );\n}\n\nfunction kindForPactFile(filePath: string): PactKind {\n const pact = parsePactFile(filePath);\n if (!pact) return 'message';\n if ((pact.interactions?.length ?? 0) > 0) return 'http';\n return 'message';\n}\n\nasync function loadVerifier(\n VerifierClass?: VerifierConstructor,\n): Promise<VerifierConstructor> {\n if (VerifierClass) return VerifierClass;\n const mod = await import('@pact-foundation/pact');\n const Verifier = (mod as { Verifier?: VerifierConstructor }).Verifier;\n if (!Verifier) {\n throw new Error(\n 'autotel-pact: @pact-foundation/pact Verifier not found. Install the peer dependency.',\n );\n }\n return Verifier;\n}\n\n/**\n * Wrap provider verification — records per-interaction evidence only on success.\n */\nexport async function withProviderVerification(\n verifierOpts: VerifierOptionsLike,\n wrapOpts: WithProviderVerificationOptions = {},\n): Promise<void> {\n const pactPaths = resolvePactPaths(verifierOpts);\n const provider = verifierOpts.provider;\n const consumer = inferConsumerFromPacts(pactPaths, wrapOpts.consumer);\n const spanName = wrapOpts.spanName ?? 'pact.verification';\n const start = process.hrtime.bigint();\n const Verifier = wrapOpts.skipVerifier ? undefined : await loadVerifier(wrapOpts.Verifier);\n\n return autotelSpan(spanName, async () => {\n const span = getActiveSpan();\n span?.setAttributes({\n [PACT_ATTRS.CONSUMER]: consumer,\n [PACT_ATTRS.PROVIDER]: provider,\n [PACT_ATTRS.KIND]: pactPaths.length === 1 ? kindForPactFile(pactPaths[0]!) : 'message',\n 'pact.role': 'provider',\n });\n\n try {\n if (Verifier) {\n await new Verifier(verifierOpts).verifyProvider();\n }\n span?.setAttributes({ [PACT_ATTRS.OUTCOME]: 'passed' });\n\n const ctx = span?.spanContext();\n const base = {\n source: 'test' as const,\n role: 'provider' as const,\n outcome: 'passed' as const,\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 };\n\n for (const filePath of pactPaths) {\n const pact = parsePactFile(filePath);\n if (!pact) continue;\n const interactions = interactionsFromPactFile(pact);\n for (const i of interactions) {\n const entry: InteractionLedgerEntry = {\n type: 'interaction',\n spec: LEDGER_ENTRY_SPEC,\n consumer: i.consumer,\n provider: i.provider,\n interaction: i.interaction,\n interaction_id: i.interactionId,\n states: [],\n kind: i.kind,\n duration_ms: Number(process.hrtime.bigint() - start) / 1e6,\n ...base,\n };\n appendLedgerEntry(entry, wrapOpts);\n }\n }\n } catch (error) {\n span?.setAttributes({ [PACT_ATTRS.OUTCOME]: 'failed' });\n const message = error instanceof Error ? error.message : String(error);\n const ctx = span?.spanContext();\n appendProviderVerificationFailure(\n {\n consumer,\n provider,\n source: 'test',\n observed_at: new Date().toISOString(),\n error: message,\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 },\n wrapOpts,\n );\n throw error;\n }\n });\n}\n"]}
@@ -0,0 +1,41 @@
1
+ import { L as LedgerOptions } from './ledger-D88TzN1c.cjs';
2
+ import './types-BHGiwqcp.cjs';
3
+
4
+ /**
5
+ * Minimal Verifier options — structural match for @pact-foundation/pact Verifier.
6
+ */
7
+ interface VerifierOptionsLike {
8
+ provider: string;
9
+ providerBaseUrl: string;
10
+ pactUrls?: string[];
11
+ logLevel?: string;
12
+ [key: string]: unknown;
13
+ }
14
+ interface VerifierLike {
15
+ verifyProvider: () => Promise<unknown>;
16
+ }
17
+ interface VerifierConstructor {
18
+ new (options: VerifierOptionsLike): VerifierLike;
19
+ }
20
+ interface WithProviderVerificationOptions extends LedgerOptions {
21
+ /** Consumer name when not inferrable from pact files. */
22
+ consumer?: string;
23
+ spanName?: string;
24
+ /** Custom Verifier class (defaults to dynamic import from @pact-foundation/pact). */
25
+ Verifier?: VerifierConstructor;
26
+ /**
27
+ * Skip loading and calling the Verifier entirely. Emits the same
28
+ * per-interaction ledger rows as a successful verification, parsed from the
29
+ * supplied pact files. Use for demos, smoke tests, and audit-pipeline
30
+ * exercises where running a real provider is impractical. Do not use in
31
+ * production CI: it will mark every interaction in the pact file as
32
+ * provider-verified without any actual verification.
33
+ */
34
+ skipVerifier?: boolean;
35
+ }
36
+ /**
37
+ * Wrap provider verification — records per-interaction evidence only on success.
38
+ */
39
+ declare function withProviderVerification(verifierOpts: VerifierOptionsLike, wrapOpts?: WithProviderVerificationOptions): Promise<void>;
40
+
41
+ export { type VerifierConstructor, type VerifierLike, type VerifierOptionsLike, type WithProviderVerificationOptions, withProviderVerification };
@@ -0,0 +1,41 @@
1
+ import { L as LedgerOptions } from './ledger-BuBmfWNc.js';
2
+ import './types-BHGiwqcp.js';
3
+
4
+ /**
5
+ * Minimal Verifier options — structural match for @pact-foundation/pact Verifier.
6
+ */
7
+ interface VerifierOptionsLike {
8
+ provider: string;
9
+ providerBaseUrl: string;
10
+ pactUrls?: string[];
11
+ logLevel?: string;
12
+ [key: string]: unknown;
13
+ }
14
+ interface VerifierLike {
15
+ verifyProvider: () => Promise<unknown>;
16
+ }
17
+ interface VerifierConstructor {
18
+ new (options: VerifierOptionsLike): VerifierLike;
19
+ }
20
+ interface WithProviderVerificationOptions extends LedgerOptions {
21
+ /** Consumer name when not inferrable from pact files. */
22
+ consumer?: string;
23
+ spanName?: string;
24
+ /** Custom Verifier class (defaults to dynamic import from @pact-foundation/pact). */
25
+ Verifier?: VerifierConstructor;
26
+ /**
27
+ * Skip loading and calling the Verifier entirely. Emits the same
28
+ * per-interaction ledger rows as a successful verification, parsed from the
29
+ * supplied pact files. Use for demos, smoke tests, and audit-pipeline
30
+ * exercises where running a real provider is impractical. Do not use in
31
+ * production CI: it will mark every interaction in the pact file as
32
+ * provider-verified without any actual verification.
33
+ */
34
+ skipVerifier?: boolean;
35
+ }
36
+ /**
37
+ * Wrap provider verification — records per-interaction evidence only on success.
38
+ */
39
+ declare function withProviderVerification(verifierOpts: VerifierOptionsLike, wrapOpts?: WithProviderVerificationOptions): Promise<void>;
40
+
41
+ export { type VerifierConstructor, type VerifierLike, type VerifierOptionsLike, type WithProviderVerificationOptions, withProviderVerification };