@contractspec/lib.evolution 0.0.0-canary-20260113162409

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Chaman Ventures, SASU
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,53 @@
1
+ # @contractspec/lib.evolution
2
+
3
+ Website: https://contractspec.io/
4
+
5
+
6
+ **Safe regeneration for ContractSpec** — Evolve specs automatically while preserving invariants.
7
+
8
+ Auto-evolution utilities that analyze telemetry, suggest spec improvements, and route changes through approval workflows. Regenerate code safely, one module at a time.
9
+
10
+ ## Capabilities:
11
+
12
+ - Analyze telemetry to find anomalous specs
13
+ - Convert detected intent into spec suggestions
14
+ - Route suggestions through the AI approval workflow
15
+ - Persist approved specs back into the codebase
16
+
17
+ > Phase 3 anchor library – pairs with `@contractspec/lib.observability` and `@contractspec/lib.growth`.
18
+
19
+ ## Usage
20
+
21
+ ```ts
22
+ import {
23
+ SpecAnalyzer,
24
+ SpecGenerator,
25
+ SpecSuggestionOrchestrator,
26
+ } from '@contractspec/lib.evolution';
27
+
28
+ const analyzer = new SpecAnalyzer();
29
+ const stats = analyzer.analyzeSpecUsage(samples);
30
+ const anomalies = analyzer.detectAnomalies(stats);
31
+
32
+ const generator = new SpecGenerator({ getSpec });
33
+ const suggestion = generator.generateFromIntent(anomalies[0]);
34
+
35
+ await orchestrator.submit(suggestion, { agent: 'auto-evolve' });
36
+ ```
37
+
38
+ See `app/docs/libraries/evolution` in `@contractspec/app.web-contractspec-landing` for full docs.
39
+
40
+
41
+
42
+
43
+
44
+
45
+
46
+
47
+
48
+
49
+
50
+
51
+
52
+
53
+
@@ -0,0 +1 @@
1
+ import { ApprovalRequest, ApprovalStatus, ApprovalStore, ApprovalWorkflow } from "./workflow.mjs";
@@ -0,0 +1,136 @@
1
+ import { ToolCallInfo } from "../types.mjs";
2
+
3
+ //#region ../ai-agent/src/approval/workflow.d.ts
4
+ type ApprovalStatus = 'pending' | 'approved' | 'rejected';
5
+ /**
6
+ * Approval request for a tool execution.
7
+ *
8
+ * When a tool has `needsApproval: true` in AI SDK v6, the agent
9
+ * will pause and wait for approval before executing the tool.
10
+ */
11
+ interface ApprovalRequest {
12
+ /** Unique request ID */
13
+ id: string;
14
+ /** Agent session ID */
15
+ sessionId: string;
16
+ /** Agent ID */
17
+ agentId: string;
18
+ /** Tenant ID for scoping */
19
+ tenantId?: string;
20
+ /** Tool name requiring approval */
21
+ toolName: string;
22
+ /** Tool call ID from AI SDK */
23
+ toolCallId: string;
24
+ /** Tool arguments */
25
+ toolArgs: unknown;
26
+ /** Human-readable reason for approval */
27
+ reason: string;
28
+ /** When the approval was requested */
29
+ requestedAt: Date;
30
+ /** Current status */
31
+ status: ApprovalStatus;
32
+ /** Additional context payload */
33
+ payload?: Record<string, unknown>;
34
+ /** Who resolved the approval */
35
+ reviewer?: string;
36
+ /** When the approval was resolved */
37
+ resolvedAt?: Date;
38
+ /** Reviewer notes */
39
+ notes?: string;
40
+ }
41
+ /**
42
+ * Storage interface for approval requests.
43
+ */
44
+ interface ApprovalStore {
45
+ create(request: ApprovalRequest): Promise<void>;
46
+ get(id: string): Promise<ApprovalRequest | null>;
47
+ getByToolCallId(toolCallId: string): Promise<ApprovalRequest | null>;
48
+ update(id: string, updates: Partial<Omit<ApprovalRequest, 'id' | 'sessionId'>>): Promise<void>;
49
+ list(options?: {
50
+ status?: ApprovalStatus;
51
+ agentId?: string;
52
+ tenantId?: string;
53
+ }): Promise<ApprovalRequest[]>;
54
+ }
55
+ /**
56
+ * Approval workflow for managing tool execution approvals.
57
+ *
58
+ * Integrates with AI SDK v6's `needsApproval` feature on tools.
59
+ *
60
+ * @example
61
+ * ```typescript
62
+ * const workflow = new ApprovalWorkflow();
63
+ *
64
+ * // When a tool needs approval
65
+ * const request = await workflow.requestApproval({
66
+ * sessionId: 'sess_123',
67
+ * agentId: 'support.bot.v1',
68
+ * toolName: 'delete_account',
69
+ * toolCallId: 'call_abc',
70
+ * toolArgs: { userId: 'user_123' },
71
+ * reason: 'Account deletion requires human approval',
72
+ * });
73
+ *
74
+ * // When approval is granted
75
+ * await workflow.approve(request.id, 'admin@example.com', 'Verified identity');
76
+ *
77
+ * // Or rejected
78
+ * await workflow.reject(request.id, 'admin@example.com', 'Suspicious activity');
79
+ * ```
80
+ */
81
+ declare class ApprovalWorkflow {
82
+ private readonly store;
83
+ constructor(store?: ApprovalStore);
84
+ /**
85
+ * Request approval for a tool execution.
86
+ */
87
+ requestApproval(params: {
88
+ sessionId: string;
89
+ agentId: string;
90
+ tenantId?: string;
91
+ toolName: string;
92
+ toolCallId: string;
93
+ toolArgs: unknown;
94
+ reason: string;
95
+ payload?: Record<string, unknown>;
96
+ }): Promise<ApprovalRequest>;
97
+ /**
98
+ * Request approval from an AI SDK tool call.
99
+ */
100
+ requestApprovalFromToolCall(toolCall: ToolCallInfo, context: {
101
+ sessionId: string;
102
+ agentId: string;
103
+ tenantId?: string;
104
+ reason?: string;
105
+ }): Promise<ApprovalRequest>;
106
+ /**
107
+ * Approve a pending request.
108
+ */
109
+ approve(id: string, reviewer: string, notes?: string): Promise<void>;
110
+ /**
111
+ * Reject a pending request.
112
+ */
113
+ reject(id: string, reviewer: string, notes?: string): Promise<void>;
114
+ /**
115
+ * Get approval status for a tool call.
116
+ */
117
+ getStatus(toolCallId: string): Promise<ApprovalStatus | null>;
118
+ /**
119
+ * Check if a tool call is approved.
120
+ */
121
+ isApproved(toolCallId: string): Promise<boolean>;
122
+ /**
123
+ * List pending approvals.
124
+ */
125
+ listPending(options?: {
126
+ agentId?: string;
127
+ tenantId?: string;
128
+ }): Promise<ApprovalRequest[]>;
129
+ /**
130
+ * Get approval request by ID.
131
+ */
132
+ get(id: string): Promise<ApprovalRequest | null>;
133
+ }
134
+ //#endregion
135
+ export { ApprovalRequest, ApprovalStatus, ApprovalStore, ApprovalWorkflow };
136
+ //# sourceMappingURL=workflow.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workflow.d.mts","names":[],"sources":["../../../../../ai-agent/src/approval/workflow.ts"],"sourcesContent":[],"mappings":";;;KAGY,cAAA;;AAAZ;AAQA;;;;AA0Be,UA1BE,eAAA,CA0BF;EAAI;EAQF,EAAA,EAAA,MAAA;EACC;EAAkB,SAAA,EAAA,MAAA;EACT;EAAR,OAAA,EAAA,MAAA;EAC4B;EAAR,QAAA,CAAA,EAAA,MAAA;EAGb;EAAL,QAAA,EAAA,MAAA;EAAR;EACR,UAAA,EAAA,MAAA;EAEQ;EAGC,QAAA,EAAA,OAAA;EAAR;EAAO,MAAA,EAAA,MAAA;EAyFA;EAEe,WAAA,EAvHb,IAuHa;EAcd;EACA,MAAA,EApIJ,cAoII;EAAR;EAuBQ,OAAA,CAAA,EAzJF,MAyJE,CAAA,MAAA,EAAA,OAAA,CAAA;EAOD;EAAR,QAAA,CAAA,EAAA,MAAA;EAe0D;EAYD,UAAA,CAAA,EAvL/C,IAuL+C;EAYf;EAAR,KAAA,CAAA,EAAA,MAAA;;;;;AA0Bd,UArNR,aAAA,CAqNQ;EAAO,MAAA,CAAA,OAAA,EApNd,eAoNc,CAAA,EApNI,OAoNJ,CAAA,IAAA,CAAA;mBAnNb,QAAQ;uCACY,QAAQ;8BAGlC,QAAQ,KAAK,wCACrB;;aAEQ;;;MAGP,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAyFD,gBAAA;;sBAEe;;;;;;;;;;;;cAcd;MACR,QAAQ;;;;wCAuBA;;;;;MAOT,QAAQ;;;;yDAekD;;;;wDAYD;;;;iCAYvB,QAAQ;;;;kCAQP;;;;;;;MAWlC,QAAQ;;;;mBAOW,QAAQ"}
@@ -0,0 +1,30 @@
1
+ import { ModelMessage, StepResult, ToolSet } from "ai";
2
+
3
+ //#region ../ai-agent/src/types.d.ts
4
+
5
+ /**
6
+ * Simplified tool call type for ContractSpec usage.
7
+ * Compatible with AI SDK v6 TypedToolCall.
8
+ */
9
+ interface ToolCallInfo {
10
+ type: 'tool-call';
11
+ toolCallId: string;
12
+ toolName: string;
13
+ args: unknown;
14
+ }
15
+ type AgentStatus = 'idle' | 'running' | 'waiting' | 'completed' | 'failed' | 'escalated';
16
+ interface AgentSessionState {
17
+ sessionId: string;
18
+ agentId: string;
19
+ tenantId?: string;
20
+ actorId?: string;
21
+ status: AgentStatus;
22
+ messages: ModelMessage[];
23
+ steps: StepResult<ToolSet>[];
24
+ createdAt: Date;
25
+ updatedAt: Date;
26
+ metadata?: Record<string, string>;
27
+ }
28
+ //#endregion
29
+ export { AgentSessionState, ToolCallInfo };
30
+ //# sourceMappingURL=types.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.mts","names":[],"sources":["../../../../ai-agent/src/types.ts"],"sourcesContent":[],"mappings":";;;;;AAWA;AAoCA;AA8DA;AAKU,UAvGO,YAAA,CAuGP;EACE,IAAA,EAAA,WAAA;EACQ,UAAA,EAAA,MAAA;EAAX,QAAA,EAAA,MAAA;EACI,IAAA,EAAA,OAAA;;KAtED,WAAA;UA8DK,iBAAA;;;;;UAKP;YACE;SACH,WAAW;aACP;aACA;aACA"}
@@ -0,0 +1,35 @@
1
+ import { IntentPattern, OperationMetricSample, OptimizationHint, SpecAnomaly, SpecUsageStats } from "../types.mjs";
2
+ import { Logger } from "@contractspec/lib.observability";
3
+ import { LifecycleStage } from "@contractspec/lib.lifecycle";
4
+
5
+ //#region src/analyzer/spec-analyzer.d.ts
6
+ interface SpecAnalyzerOptions {
7
+ logger?: Logger;
8
+ minSampleSize?: number;
9
+ errorRateThreshold?: number;
10
+ latencyP99ThresholdMs?: number;
11
+ throughputDropThreshold?: number;
12
+ }
13
+ declare class SpecAnalyzer {
14
+ private readonly logger?;
15
+ private readonly minSampleSize;
16
+ private readonly errorRateThreshold;
17
+ private readonly latencyP99ThresholdMs;
18
+ private readonly throughputDropThreshold;
19
+ constructor(options?: SpecAnalyzerOptions);
20
+ analyzeSpecUsage(samples: OperationMetricSample[]): SpecUsageStats[];
21
+ detectAnomalies(stats: SpecUsageStats[], baseline?: SpecUsageStats[]): SpecAnomaly[];
22
+ toIntentPatterns(anomalies: SpecAnomaly[], stats: SpecUsageStats[]): IntentPattern[];
23
+ suggestOptimizations(stats: SpecUsageStats[], anomalies: SpecAnomaly[], lifecycleContext?: {
24
+ stage: LifecycleStage;
25
+ }): OptimizationHint[];
26
+ private operationKey;
27
+ private buildUsageStats;
28
+ private toSeverity;
29
+ private mapMetricToIntent;
30
+ private groupByOperation;
31
+ private applyLifecycleContext;
32
+ }
33
+ //#endregion
34
+ export { SpecAnalyzer, SpecAnalyzerOptions };
35
+ //# sourceMappingURL=spec-analyzer.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spec-analyzer.d.mts","names":[],"sources":["../../src/analyzer/spec-analyzer.ts"],"sourcesContent":[],"mappings":";;;;;UAciB,mBAAA;WACN;EADM,aAAA,CAAA,EAAA,MAAmB;EAmBvB,kBAAY,CAAA,EAAA,MAAA;EAOF,qBAAA,CAAA,EAAA,MAAA;EAUK,uBAAA,CAAA,EAAA,MAAA;;AAoCjB,cArDE,YAAA,CAqDF;EACI,iBAAA,MAAA;EACV,iBAAA,aAAA;EAsFU,iBAAA,kBAAA;EACJ,iBAAA,qBAAA;EACN,iBAAA,uBAAA;EA8BM,WAAA,CAAA,OAAA,CAAA,EAtKY,mBAsKZ;EACI,gBAAA,CAAA,OAAA,EA7Ja,qBA6Jb,EAAA,CAAA,EA7JuC,cA6JvC,EAAA;EACiB,eAAA,CAAA,KAAA,EA1HrB,cA0HqB,EAAA,EAAA,QAAA,CAAA,EAzHjB,cAyHiB,EAAA,CAAA,EAxH3B,WAwH2B,EAAA;EAC3B,gBAAA,CAAA,SAAA,EAnCU,WAmCV,EAAA,EAAA,KAAA,EAlCM,cAkCN,EAAA,CAAA,EAjCA,aAiCA,EAAA;EAAgB,oBAAA,CAAA,KAAA,EAHV,cAGU,EAAA,EAAA,SAAA,EAFN,WAEM,EAAA,EAAA,iBAAA,EAAA;WADW;MAC3B"}
@@ -0,0 +1,305 @@
1
+ import { Logger } from "@contractspec/lib.observability";
2
+ import { randomUUID } from "node:crypto";
3
+ import { LifecycleStage } from "@contractspec/lib.lifecycle";
4
+
5
+ //#region src/analyzer/spec-analyzer.ts
6
+ const DEFAULT_OPTIONS = {
7
+ minSampleSize: 50,
8
+ errorRateThreshold: .05,
9
+ latencyP99ThresholdMs: 750
10
+ };
11
+ var SpecAnalyzer = class {
12
+ logger;
13
+ minSampleSize;
14
+ errorRateThreshold;
15
+ latencyP99ThresholdMs;
16
+ throughputDropThreshold;
17
+ constructor(options = {}) {
18
+ this.logger = options.logger;
19
+ this.minSampleSize = options.minSampleSize ?? DEFAULT_OPTIONS.minSampleSize;
20
+ this.errorRateThreshold = options.errorRateThreshold ?? DEFAULT_OPTIONS.errorRateThreshold;
21
+ this.latencyP99ThresholdMs = options.latencyP99ThresholdMs ?? DEFAULT_OPTIONS.latencyP99ThresholdMs;
22
+ this.throughputDropThreshold = options.throughputDropThreshold ?? .2;
23
+ }
24
+ analyzeSpecUsage(samples) {
25
+ if (!samples.length) {
26
+ this.logger?.debug("SpecAnalyzer.analyzeSpecUsage.skip", { reason: "no-samples" });
27
+ return [];
28
+ }
29
+ const groups = /* @__PURE__ */ new Map();
30
+ for (const sample of samples) {
31
+ const key = this.operationKey(sample);
32
+ const arr = groups.get(key) ?? [];
33
+ arr.push(sample);
34
+ groups.set(key, arr);
35
+ }
36
+ return [...groups.values()].filter((samplesForOp) => {
37
+ const valid = samplesForOp.length >= this.minSampleSize;
38
+ if (!valid) this.logger?.debug("SpecAnalyzer.analyzeSpecUsage.skipOperation", {
39
+ operation: this.operationKey(samplesForOp[0]),
40
+ sampleSize: samplesForOp.length,
41
+ minSampleSize: this.minSampleSize
42
+ });
43
+ return valid;
44
+ }).map((operationSamples) => this.buildUsageStats(operationSamples));
45
+ }
46
+ detectAnomalies(stats, baseline) {
47
+ const anomalies = [];
48
+ if (!stats.length) {
49
+ this.logger?.debug("SpecAnalyzer.detectAnomalies.skip", { reason: "no-stats" });
50
+ return anomalies;
51
+ }
52
+ const baselineByOp = new Map((baseline ?? []).map((item) => [this.operationKey(item.operation), item]));
53
+ for (const stat of stats) {
54
+ const evidence = [];
55
+ if (stat.errorRate >= this.errorRateThreshold) {
56
+ evidence.push({
57
+ type: "telemetry",
58
+ description: `Error rate ${stat.errorRate.toFixed(2)} exceeded threshold ${this.errorRateThreshold}`,
59
+ data: { errorRate: stat.errorRate }
60
+ });
61
+ anomalies.push({
62
+ operation: stat.operation,
63
+ severity: this.toSeverity(stat.errorRate / this.errorRateThreshold),
64
+ metric: "error-rate",
65
+ description: "Error rate spike",
66
+ detectedAt: /* @__PURE__ */ new Date(),
67
+ threshold: this.errorRateThreshold,
68
+ observedValue: stat.errorRate,
69
+ evidence
70
+ });
71
+ continue;
72
+ }
73
+ if (stat.p99LatencyMs >= this.latencyP99ThresholdMs) {
74
+ evidence.push({
75
+ type: "telemetry",
76
+ description: `P99 latency ${stat.p99LatencyMs}ms exceeded threshold ${this.latencyP99ThresholdMs}ms`,
77
+ data: { p99LatencyMs: stat.p99LatencyMs }
78
+ });
79
+ anomalies.push({
80
+ operation: stat.operation,
81
+ severity: this.toSeverity(stat.p99LatencyMs / this.latencyP99ThresholdMs),
82
+ metric: "latency",
83
+ description: "Latency regression detected",
84
+ detectedAt: /* @__PURE__ */ new Date(),
85
+ threshold: this.latencyP99ThresholdMs,
86
+ observedValue: stat.p99LatencyMs,
87
+ evidence
88
+ });
89
+ continue;
90
+ }
91
+ const baselineStat = baselineByOp.get(this.operationKey(stat.operation));
92
+ if (baselineStat) {
93
+ const drop = (baselineStat.totalCalls - stat.totalCalls) / baselineStat.totalCalls;
94
+ if (drop >= this.throughputDropThreshold) {
95
+ evidence.push({
96
+ type: "telemetry",
97
+ description: `Throughput dropped by ${(drop * 100).toFixed(1)}% compared to baseline`,
98
+ data: {
99
+ baselineCalls: baselineStat.totalCalls,
100
+ currentCalls: stat.totalCalls
101
+ }
102
+ });
103
+ anomalies.push({
104
+ operation: stat.operation,
105
+ severity: this.toSeverity(drop / this.throughputDropThreshold),
106
+ metric: "throughput",
107
+ description: "Usage drop detected",
108
+ detectedAt: /* @__PURE__ */ new Date(),
109
+ threshold: this.throughputDropThreshold,
110
+ observedValue: drop,
111
+ evidence
112
+ });
113
+ }
114
+ }
115
+ }
116
+ return anomalies;
117
+ }
118
+ toIntentPatterns(anomalies, stats) {
119
+ const statsByOp = new Map(stats.map((item) => [this.operationKey(item.operation), item]));
120
+ return anomalies.map((anomaly) => {
121
+ const stat = statsByOp.get(this.operationKey(anomaly.operation));
122
+ const confidence = {
123
+ score: Math.min(1, (anomaly.observedValue ?? 0) / (anomaly.threshold ?? 1)),
124
+ sampleSize: stat?.totalCalls ?? 0,
125
+ pValue: void 0
126
+ };
127
+ return {
128
+ id: randomUUID(),
129
+ type: this.mapMetricToIntent(anomaly.metric),
130
+ description: anomaly.description,
131
+ operation: anomaly.operation,
132
+ confidence,
133
+ metadata: {
134
+ observedValue: anomaly.observedValue,
135
+ threshold: anomaly.threshold
136
+ },
137
+ evidence: anomaly.evidence
138
+ };
139
+ });
140
+ }
141
+ suggestOptimizations(stats, anomalies, lifecycleContext) {
142
+ const anomaliesByOp = new Map(this.groupByOperation(anomalies));
143
+ const hints = [];
144
+ for (const stat of stats) {
145
+ const opKey = this.operationKey(stat.operation);
146
+ const opAnomalies = anomaliesByOp.get(opKey) ?? [];
147
+ for (const anomaly of opAnomalies) if (anomaly.metric === "latency") hints.push(this.applyLifecycleContext({
148
+ operation: stat.operation,
149
+ category: "performance",
150
+ summary: "Latency regression detected",
151
+ justification: `P99 latency at ${stat.p99LatencyMs}ms`,
152
+ recommendedActions: ["Add batching or caching layer", "Replay golden tests to capture slow inputs"]
153
+ }, lifecycleContext?.stage));
154
+ else if (anomaly.metric === "error-rate") {
155
+ const topError = Object.entries(stat.topErrors).sort((a, b) => b[1] - a[1])[0]?.[0];
156
+ hints.push(this.applyLifecycleContext({
157
+ operation: stat.operation,
158
+ category: "error-handling",
159
+ summary: "Error spike detected",
160
+ justification: topError ? `Dominant error code ${topError}` : "Increase in failures",
161
+ recommendedActions: ["Generate regression spec from failing payloads", "Add policy guardrails before rollout"]
162
+ }, lifecycleContext?.stage));
163
+ } else if (anomaly.metric === "throughput") hints.push(this.applyLifecycleContext({
164
+ operation: stat.operation,
165
+ category: "performance",
166
+ summary: "Throughput drop detected",
167
+ justification: "Significant traffic reduction relative to baseline",
168
+ recommendedActions: ["Validate routing + feature flag bucketing", "Backfill spec variant to rehydrate demand"]
169
+ }, lifecycleContext?.stage));
170
+ }
171
+ return hints;
172
+ }
173
+ operationKey(op) {
174
+ const coordinate = "operation" in op ? op.operation : op;
175
+ return `${coordinate.key}.v${coordinate.version}${coordinate.tenantId ? `@${coordinate.tenantId}` : ""}`;
176
+ }
177
+ buildUsageStats(samples) {
178
+ const durations = samples.map((s) => s.durationMs).sort((a, b) => a - b);
179
+ const errors = samples.filter((s) => !s.success);
180
+ const totalCalls = samples.length;
181
+ const successRate = (totalCalls - errors.length) / totalCalls;
182
+ const errorRate = errors.length / totalCalls;
183
+ const averageLatencyMs = durations.reduce((sum, value) => sum + value, 0) / totalCalls;
184
+ const topErrors = errors.reduce((acc, sample) => {
185
+ if (!sample.errorCode) return acc;
186
+ acc[sample.errorCode] = (acc[sample.errorCode] ?? 0) + 1;
187
+ return acc;
188
+ }, {});
189
+ const timestamps = samples.map((s) => s.timestamp.getTime());
190
+ const windowStart = new Date(Math.min(...timestamps));
191
+ const windowEnd = new Date(Math.max(...timestamps));
192
+ return {
193
+ operation: samples[0].operation,
194
+ totalCalls,
195
+ successRate,
196
+ errorRate,
197
+ averageLatencyMs,
198
+ p95LatencyMs: percentile(durations, .95),
199
+ p99LatencyMs: percentile(durations, .99),
200
+ maxLatencyMs: Math.max(...durations),
201
+ lastSeenAt: windowEnd,
202
+ windowStart,
203
+ windowEnd,
204
+ topErrors
205
+ };
206
+ }
207
+ toSeverity(ratio) {
208
+ if (ratio >= 2) return "high";
209
+ if (ratio >= 1.3) return "medium";
210
+ return "low";
211
+ }
212
+ mapMetricToIntent(metric) {
213
+ switch (metric) {
214
+ case "error-rate": return "error-spike";
215
+ case "latency": return "latency-regression";
216
+ case "throughput": return "throughput-drop";
217
+ default: return "schema-mismatch";
218
+ }
219
+ }
220
+ groupByOperation(items) {
221
+ const map = /* @__PURE__ */ new Map();
222
+ for (const item of items) {
223
+ const key = this.operationKey(item.operation);
224
+ const arr = map.get(key) ?? [];
225
+ arr.push(item);
226
+ map.set(key, arr);
227
+ }
228
+ return map;
229
+ }
230
+ applyLifecycleContext(hint, stage) {
231
+ if (stage === void 0) return hint;
232
+ const advice = LIFECYCLE_HINTS[mapStageBand(stage)]?.[hint.category];
233
+ if (!advice) return {
234
+ ...hint,
235
+ lifecycleStage: stage
236
+ };
237
+ return {
238
+ ...hint,
239
+ lifecycleStage: stage,
240
+ lifecycleNotes: advice.message,
241
+ recommendedActions: dedupeActions([...hint.recommendedActions, ...advice.supplementalActions])
242
+ };
243
+ }
244
+ };
245
+ function percentile(values, p) {
246
+ if (!values.length) return 0;
247
+ if (values.length === 1) return values[0];
248
+ return values[Math.min(values.length - 1, Math.floor(p * values.length))];
249
+ }
250
+ const mapStageBand = (stage) => {
251
+ if (stage <= 2) return "early";
252
+ if (stage === LifecycleStage.ProductMarketFit) return "pmf";
253
+ if (stage === LifecycleStage.GrowthScaleUp || stage === LifecycleStage.ExpansionPlatform) return "scale";
254
+ return "mature";
255
+ };
256
+ const LIFECYCLE_HINTS = {
257
+ early: {
258
+ performance: {
259
+ message: "Favor guardrails that protect learning velocity before heavy rewrites.",
260
+ supplementalActions: ["Wrap risky changes behind progressive delivery flags"]
261
+ },
262
+ "error-handling": {
263
+ message: "Make failures loud and recoverable so you can learn faster.",
264
+ supplementalActions: ["Add auto-rollbacks or manual kill switches"]
265
+ }
266
+ },
267
+ pmf: { performance: {
268
+ message: "Stabilize the core use case to avoid regressions while demand grows.",
269
+ supplementalActions: ["Instrument regression tests on critical specs"]
270
+ } },
271
+ scale: {
272
+ performance: {
273
+ message: "Prioritize resilience and multi-tenant safety as volumes expand.",
274
+ supplementalActions: ["Introduce workload partitioning or isolation per tenant"]
275
+ },
276
+ "error-handling": {
277
+ message: "Contain blast radius with policy fallbacks and circuit breakers.",
278
+ supplementalActions: ["Add circuit breakers to high-risk operations"]
279
+ }
280
+ },
281
+ mature: {
282
+ performance: {
283
+ message: "Optimize for margins and predictable SLAs.",
284
+ supplementalActions: ["Capture unit-cost impacts alongside latency fixes"]
285
+ },
286
+ "error-handling": {
287
+ message: "Prevent regressions with automated regression specs before deploy.",
288
+ supplementalActions: ["Run auto-evolution simulations on renewal scenarios"]
289
+ }
290
+ }
291
+ };
292
+ const dedupeActions = (actions) => {
293
+ const seen = /* @__PURE__ */ new Set();
294
+ const ordered = [];
295
+ for (const action of actions) {
296
+ if (seen.has(action)) continue;
297
+ seen.add(action);
298
+ ordered.push(action);
299
+ }
300
+ return ordered;
301
+ };
302
+
303
+ //#endregion
304
+ export { SpecAnalyzer };
305
+ //# sourceMappingURL=spec-analyzer.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spec-analyzer.mjs","names":[],"sources":["../../src/analyzer/spec-analyzer.ts"],"sourcesContent":["import { Logger } from '@contractspec/lib.observability';\nimport { randomUUID } from 'node:crypto';\nimport { LifecycleStage } from '@contractspec/lib.lifecycle';\nimport {\n type IntentPattern,\n type OperationCoordinate,\n type OperationMetricSample,\n type OptimizationHint,\n type PatternConfidence,\n type SpecAnomaly,\n type SpecUsageStats,\n type SuggestionEvidence,\n} from '../types';\n\nexport interface SpecAnalyzerOptions {\n logger?: Logger;\n minSampleSize?: number;\n errorRateThreshold?: number;\n latencyP99ThresholdMs?: number;\n throughputDropThreshold?: number;\n}\n\nconst DEFAULT_OPTIONS: Required<\n Pick<\n SpecAnalyzerOptions,\n 'minSampleSize' | 'errorRateThreshold' | 'latencyP99ThresholdMs'\n >\n> = {\n minSampleSize: 50,\n errorRateThreshold: 0.05,\n latencyP99ThresholdMs: 750,\n};\n\nexport class SpecAnalyzer {\n private readonly logger?: Logger;\n private readonly minSampleSize: number;\n private readonly errorRateThreshold: number;\n private readonly latencyP99ThresholdMs: number;\n private readonly throughputDropThreshold: number;\n\n constructor(options: SpecAnalyzerOptions = {}) {\n this.logger = options.logger;\n this.minSampleSize = options.minSampleSize ?? DEFAULT_OPTIONS.minSampleSize;\n this.errorRateThreshold =\n options.errorRateThreshold ?? DEFAULT_OPTIONS.errorRateThreshold;\n this.latencyP99ThresholdMs =\n options.latencyP99ThresholdMs ?? DEFAULT_OPTIONS.latencyP99ThresholdMs;\n this.throughputDropThreshold = options.throughputDropThreshold ?? 0.2;\n }\n\n analyzeSpecUsage(samples: OperationMetricSample[]): SpecUsageStats[] {\n if (!samples.length) {\n this.logger?.debug('SpecAnalyzer.analyzeSpecUsage.skip', {\n reason: 'no-samples',\n });\n return [];\n }\n\n const groups = new Map<string, OperationMetricSample[]>();\n for (const sample of samples) {\n const key = this.operationKey(sample);\n const arr = groups.get(key) ?? [];\n arr.push(sample);\n groups.set(key, arr);\n }\n\n const entries = [...groups.values()];\n const usable = entries.filter((samplesForOp) => {\n const valid = samplesForOp.length >= this.minSampleSize;\n if (!valid) {\n this.logger?.debug('SpecAnalyzer.analyzeSpecUsage.skipOperation', {\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n operation: this.operationKey(samplesForOp[0]!),\n sampleSize: samplesForOp.length,\n minSampleSize: this.minSampleSize,\n });\n }\n return valid;\n });\n\n return usable.map((operationSamples) =>\n this.buildUsageStats(operationSamples)\n );\n }\n\n detectAnomalies(\n stats: SpecUsageStats[],\n baseline?: SpecUsageStats[]\n ): SpecAnomaly[] {\n const anomalies: SpecAnomaly[] = [];\n if (!stats.length) {\n this.logger?.debug('SpecAnalyzer.detectAnomalies.skip', {\n reason: 'no-stats',\n });\n return anomalies;\n }\n const baselineByOp = new Map(\n (baseline ?? []).map((item) => [this.operationKey(item.operation), item])\n );\n\n for (const stat of stats) {\n const evidence: SuggestionEvidence[] = [];\n\n if (stat.errorRate >= this.errorRateThreshold) {\n evidence.push({\n type: 'telemetry',\n description: `Error rate ${stat.errorRate.toFixed(2)} exceeded threshold ${this.errorRateThreshold}`,\n data: { errorRate: stat.errorRate },\n });\n anomalies.push({\n operation: stat.operation,\n severity: this.toSeverity(stat.errorRate / this.errorRateThreshold),\n metric: 'error-rate',\n description: 'Error rate spike',\n detectedAt: new Date(),\n threshold: this.errorRateThreshold,\n observedValue: stat.errorRate,\n evidence,\n });\n continue;\n }\n\n if (stat.p99LatencyMs >= this.latencyP99ThresholdMs) {\n evidence.push({\n type: 'telemetry',\n description: `P99 latency ${stat.p99LatencyMs}ms exceeded threshold ${this.latencyP99ThresholdMs}ms`,\n data: { p99LatencyMs: stat.p99LatencyMs },\n });\n anomalies.push({\n operation: stat.operation,\n severity: this.toSeverity(\n stat.p99LatencyMs / this.latencyP99ThresholdMs\n ),\n metric: 'latency',\n description: 'Latency regression detected',\n detectedAt: new Date(),\n threshold: this.latencyP99ThresholdMs,\n observedValue: stat.p99LatencyMs,\n evidence,\n });\n continue;\n }\n\n const baselineStat = baselineByOp.get(this.operationKey(stat.operation));\n if (baselineStat) {\n const drop =\n (baselineStat.totalCalls - stat.totalCalls) / baselineStat.totalCalls;\n if (drop >= this.throughputDropThreshold) {\n evidence.push({\n type: 'telemetry',\n description: `Throughput dropped by ${(drop * 100).toFixed(1)}% compared to baseline`,\n data: {\n baselineCalls: baselineStat.totalCalls,\n currentCalls: stat.totalCalls,\n },\n });\n anomalies.push({\n operation: stat.operation,\n severity: this.toSeverity(drop / this.throughputDropThreshold),\n metric: 'throughput',\n description: 'Usage drop detected',\n detectedAt: new Date(),\n threshold: this.throughputDropThreshold,\n observedValue: drop,\n evidence,\n });\n }\n }\n }\n\n return anomalies;\n }\n\n toIntentPatterns(\n anomalies: SpecAnomaly[],\n stats: SpecUsageStats[]\n ): IntentPattern[] {\n const statsByOp = new Map(\n stats.map((item) => [this.operationKey(item.operation), item])\n );\n return anomalies.map((anomaly) => {\n const stat = statsByOp.get(this.operationKey(anomaly.operation));\n const confidence: PatternConfidence = {\n score: Math.min(\n 1,\n (anomaly.observedValue ?? 0) / (anomaly.threshold ?? 1)\n ),\n sampleSize: stat?.totalCalls ?? 0,\n pValue: undefined,\n };\n return {\n id: randomUUID(),\n type: this.mapMetricToIntent(anomaly.metric),\n description: anomaly.description,\n operation: anomaly.operation,\n confidence,\n metadata: {\n observedValue: anomaly.observedValue,\n threshold: anomaly.threshold,\n },\n evidence: anomaly.evidence,\n };\n });\n }\n\n suggestOptimizations(\n stats: SpecUsageStats[],\n anomalies: SpecAnomaly[],\n lifecycleContext?: { stage: LifecycleStage }\n ): OptimizationHint[] {\n const anomaliesByOp = new Map<string, SpecAnomaly[]>(\n this.groupByOperation(anomalies)\n );\n const hints: OptimizationHint[] = [];\n\n for (const stat of stats) {\n const opKey = this.operationKey(stat.operation);\n const opAnomalies = anomaliesByOp.get(opKey) ?? [];\n for (const anomaly of opAnomalies) {\n if (anomaly.metric === 'latency') {\n hints.push(\n this.applyLifecycleContext(\n {\n operation: stat.operation,\n category: 'performance',\n summary: 'Latency regression detected',\n justification: `P99 latency at ${stat.p99LatencyMs}ms`,\n recommendedActions: [\n 'Add batching or caching layer',\n 'Replay golden tests to capture slow inputs',\n ],\n },\n lifecycleContext?.stage\n )\n );\n } else if (anomaly.metric === 'error-rate') {\n const topError = Object.entries(stat.topErrors).sort(\n (a, b) => b[1] - a[1]\n )[0]?.[0];\n hints.push(\n this.applyLifecycleContext(\n {\n operation: stat.operation,\n category: 'error-handling',\n summary: 'Error spike detected',\n justification: topError\n ? `Dominant error code ${topError}`\n : 'Increase in failures',\n recommendedActions: [\n 'Generate regression spec from failing payloads',\n 'Add policy guardrails before rollout',\n ],\n },\n lifecycleContext?.stage\n )\n );\n } else if (anomaly.metric === 'throughput') {\n hints.push(\n this.applyLifecycleContext(\n {\n operation: stat.operation,\n category: 'performance',\n summary: 'Throughput drop detected',\n justification:\n 'Significant traffic reduction relative to baseline',\n recommendedActions: [\n 'Validate routing + feature flag bucketing',\n 'Backfill spec variant to rehydrate demand',\n ],\n },\n lifecycleContext?.stage\n )\n );\n }\n }\n }\n return hints;\n }\n\n private operationKey(\n op:\n | OperationCoordinate\n | SpecUsageStats\n | SpecAnomaly\n | OperationMetricSample\n ) {\n const coordinate =\n 'operation' in op ? (op.operation as OperationCoordinate) : op;\n return `${coordinate.key}.v${coordinate.version}${\n coordinate.tenantId ? `@${coordinate.tenantId}` : ''\n }`;\n }\n\n private buildUsageStats(samples: OperationMetricSample[]): SpecUsageStats {\n const durations = samples.map((s) => s.durationMs).sort((a, b) => a - b);\n const errors = samples.filter((s) => !s.success);\n const totalCalls = samples.length;\n const successRate = (totalCalls - errors.length) / totalCalls;\n const errorRate = errors.length / totalCalls;\n const averageLatencyMs =\n durations.reduce((sum, value) => sum + value, 0) / totalCalls;\n\n const topErrors = errors.reduce<Record<string, number>>((acc, sample) => {\n if (!sample.errorCode) return acc;\n acc[sample.errorCode] = (acc[sample.errorCode] ?? 0) + 1;\n return acc;\n }, {});\n\n const timestamps = samples.map((s) => s.timestamp.getTime());\n const windowStart = new Date(Math.min(...timestamps));\n const windowEnd = new Date(Math.max(...timestamps));\n\n return {\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n operation: samples[0]!.operation,\n totalCalls,\n successRate,\n errorRate,\n averageLatencyMs,\n p95LatencyMs: percentile(durations, 0.95),\n p99LatencyMs: percentile(durations, 0.99),\n maxLatencyMs: Math.max(...durations),\n lastSeenAt: windowEnd,\n windowStart,\n windowEnd,\n topErrors,\n };\n }\n\n private toSeverity(ratio: number): SpecAnomaly['severity'] {\n if (ratio >= 2) return 'high';\n if (ratio >= 1.3) return 'medium';\n return 'low';\n }\n\n private mapMetricToIntent(\n metric: SpecAnomaly['metric']\n ): IntentPattern['type'] {\n switch (metric) {\n case 'error-rate':\n return 'error-spike';\n case 'latency':\n return 'latency-regression';\n case 'throughput':\n return 'throughput-drop';\n default:\n return 'schema-mismatch';\n }\n }\n\n private groupByOperation<T extends { operation: OperationCoordinate }>(\n items: T[]\n ): Map<string, T[]> {\n const map = new Map<string, T[]>();\n for (const item of items) {\n const key = this.operationKey(item.operation);\n const arr = map.get(key) ?? [];\n arr.push(item);\n map.set(key, arr);\n }\n return map;\n }\n\n private applyLifecycleContext(\n hint: OptimizationHint,\n stage?: LifecycleStage\n ): OptimizationHint {\n if (stage === undefined) return hint;\n const band = mapStageBand(stage);\n const advice = LIFECYCLE_HINTS[band]?.[hint.category];\n if (!advice) {\n return { ...hint, lifecycleStage: stage };\n }\n return {\n ...hint,\n lifecycleStage: stage,\n lifecycleNotes: advice.message,\n recommendedActions: dedupeActions([\n ...hint.recommendedActions,\n ...advice.supplementalActions,\n ]),\n };\n }\n}\n\nfunction percentile(values: number[], p: number): number {\n if (!values.length) return 0;\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n if (values.length === 1) return values[0]!;\n const idx = Math.min(values.length - 1, Math.floor(p * values.length));\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n return values[idx]!;\n}\n\ntype StageBand = 'early' | 'pmf' | 'scale' | 'mature';\n\nconst mapStageBand = (stage: LifecycleStage): StageBand => {\n if (stage <= 2) return 'early';\n if (stage === LifecycleStage.ProductMarketFit) return 'pmf';\n if (\n stage === LifecycleStage.GrowthScaleUp ||\n stage === LifecycleStage.ExpansionPlatform\n ) {\n return 'scale';\n }\n return 'mature';\n};\n\nconst LIFECYCLE_HINTS: Record<\n StageBand,\n Partial<\n Record<\n OptimizationHint['category'],\n { message: string; supplementalActions: string[] }\n >\n >\n> = {\n early: {\n performance: {\n message:\n 'Favor guardrails that protect learning velocity before heavy rewrites.',\n supplementalActions: [\n 'Wrap risky changes behind progressive delivery flags',\n ],\n },\n 'error-handling': {\n message: 'Make failures loud and recoverable so you can learn faster.',\n supplementalActions: ['Add auto-rollbacks or manual kill switches'],\n },\n },\n pmf: {\n performance: {\n message:\n 'Stabilize the core use case to avoid regressions while demand grows.',\n supplementalActions: ['Instrument regression tests on critical specs'],\n },\n },\n scale: {\n performance: {\n message:\n 'Prioritize resilience and multi-tenant safety as volumes expand.',\n supplementalActions: [\n 'Introduce workload partitioning or isolation per tenant',\n ],\n },\n 'error-handling': {\n message:\n 'Contain blast radius with policy fallbacks and circuit breakers.',\n supplementalActions: ['Add circuit breakers to high-risk operations'],\n },\n },\n mature: {\n performance: {\n message: 'Optimize for margins and predictable SLAs.',\n supplementalActions: [\n 'Capture unit-cost impacts alongside latency fixes',\n ],\n },\n 'error-handling': {\n message:\n 'Prevent regressions with automated regression specs before deploy.',\n supplementalActions: [\n 'Run auto-evolution simulations on renewal scenarios',\n ],\n },\n },\n};\n\nconst dedupeActions = (actions: string[]): string[] => {\n const seen = new Set<string>();\n const ordered: string[] = [];\n for (const action of actions) {\n if (seen.has(action)) continue;\n seen.add(action);\n ordered.push(action);\n }\n return ordered;\n};\n"],"mappings":"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"}