autotel-eventcatalog 10.0.0 → 11.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +48 -0
- package/package.json +2 -3
- package/src/__fixtures__/drift-report-all.golden.json +0 -33
- package/src/__fixtures__/drift-summary-clean.golden.json +0 -17
- package/src/__fixtures__/drift-summary-drifty.golden.json +0 -17
- package/src/__fixtures__/stamp-summary-noop.golden.json +0 -10
- package/src/catalog.test.ts +0 -63
- package/src/catalog.ts +0 -169
- package/src/cli.e2e.test.ts +0 -513
- package/src/cli.ts +0 -668
- package/src/contract.test.ts +0 -483
- package/src/diff-vs-base.test.ts +0 -145
- package/src/diff-vs-base.ts +0 -242
- package/src/diff.test.ts +0 -384
- package/src/diff.ts +0 -291
- package/src/eventcatalog-snapshot-diff.test.ts +0 -61
- package/src/eventcatalog-snapshot-diff.ts +0 -170
- package/src/generate.test.ts +0 -195
- package/src/generate.ts +0 -559
- package/src/index.ts +0 -100
- package/src/policy.test.ts +0 -81
- package/src/policy.ts +0 -41
- package/src/renderers/eventcatalog-snapshot-diff.ts +0 -46
- package/src/renderers/index.ts +0 -89
- package/src/renderers/json.ts +0 -33
- package/src/renderers/markdown.ts +0 -223
- package/src/renderers/renderers.test.ts +0 -90
- package/src/renderers/terminal.ts +0 -30
- package/src/renderers/types.ts +0 -26
- package/src/report.test.ts +0 -244
- package/src/report.ts +0 -31
- package/src/snapshot.test.ts +0 -131
- package/src/snapshot.ts +0 -127
- package/src/stamp.test.ts +0 -283
- package/src/stamp.ts +0 -232
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,53 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 11.0.0
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- ec47ec8: Google Secure AI Agents observability plus MCP protocol-boundary security observability — additive defense-in-depth across planning, tool use, MCP traffic, triage, and UI surfaces.
|
|
8
|
+
|
|
9
|
+
**autotel-mcp-instrumentation**
|
|
10
|
+
- Annotation hints captured as `mcp.tool.*` span attributes (`readOnlyHint`, `destructiveHint`, `idempotentHint`, `openWorldHint`, `untrustedContentHint`) to surface malicious-manifest vectors and tool trust profiles.
|
|
11
|
+
- Payload-size signals (`mcp.tool.arguments.size` / `mcp.tool.result.size`) for token-exhaustion and contaminated-output detection without logging content.
|
|
12
|
+
- Output character budgets (`outputCharBudget` + `MCP_CHAR_BUDGETS`) that emit `mcp.security.budget_exceeded` signals and can bridge to unified `security.*` events.
|
|
13
|
+
- Pluggable injection classifier (`securityClassifier`) scanning arguments and results on both client and server, recording `mcp.security.injection.*` signals and bridging suspicious verdicts to `security.*` events without breaking traced calls.
|
|
14
|
+
- `heuristicInjectionClassifier()` as a dependency-free first-pass detector.
|
|
15
|
+
- `spotlight()` to delimit/base64 untrusted content across Node and edge runtimes.
|
|
16
|
+
- `validateToolBudget()` for WebMCP-style text-surface limits.
|
|
17
|
+
- Guard bridge via `guard` config so MCP tool calls count against an `autotel-genai` guard.
|
|
18
|
+
- `applyManifestAssessment()` bridges suspicious manifest verdicts to unified `security.*` events when `bridgeSecurityEvents` is enabled.
|
|
19
|
+
- New `mcp.security.events` counter and `autotel-mcp-instrumentation/security` subpath export.
|
|
20
|
+
|
|
21
|
+
**autotel-cli**
|
|
22
|
+
- Add `autotel security mcp` to aggregate MCP security signals: injection verdicts, output-budget breaches, and untrusted-content tool calls.
|
|
23
|
+
|
|
24
|
+
**autotel-genai/agent**
|
|
25
|
+
- `AgentPlanClassifier` + `runAgentPlanClassifier()` / `recordPlanRiskAssessment()` with `agent.plan.risk.*` attrs and optional `llm.plan.risk.elevated` security event.
|
|
26
|
+
- `heuristicPlanRiskClassifier()` as a dependency-free first-pass plan-risk tripwire.
|
|
27
|
+
- Export `agentContextFromSpan()` from the agent subpath.
|
|
28
|
+
|
|
29
|
+
**autotel-audit**
|
|
30
|
+
- Passive action-chain processor emits `llm.action_chain.suspicious` and stamps unified `security.*` attributes on the destructive span.
|
|
31
|
+
- `llm.manifest.suspicious` and `llm.plan.risk.elevated` added to the suggested security event catalogue.
|
|
32
|
+
|
|
33
|
+
**autotel-cloudflare/agents**
|
|
34
|
+
- `tool:approval` events use `recordHumanApproval()` (optional `autotel-genai` peer dependency).
|
|
35
|
+
|
|
36
|
+
**autotel-devtools**
|
|
37
|
+
- Agent timeline surfaces consent, policy, injection, guard, security-event, and plan-step badges from the new agent security attributes.
|
|
38
|
+
|
|
39
|
+
**autotel-schema**
|
|
40
|
+
- Agent security contract snapshot extended with `agent.plan.risk.*` attributes.
|
|
41
|
+
|
|
42
|
+
**autotel**
|
|
43
|
+
- Core `security-schema` remains the shared sink for unified `security.*` events consumed by the agent and MCP observability layers.
|
|
44
|
+
|
|
45
|
+
**Packaging**
|
|
46
|
+
- Drop the duplicated `src/` directory from published tarballs across all packages. The shipped `.js.map` sourcemaps already embed original source via `sourcesContent`, so source-level debugging is unchanged while install footprint shrinks ~20–30%.
|
|
47
|
+
|
|
48
|
+
- Updated dependencies [ec47ec8]
|
|
49
|
+
- autotel-subscribers@41.0.0
|
|
50
|
+
|
|
3
51
|
## 10.0.0
|
|
4
52
|
|
|
5
53
|
### Patch Changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "autotel-eventcatalog",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "11.0.0",
|
|
4
4
|
"description": "Diff your autotel architecture snapshot against an EventCatalog and report drift. Same model as Pact, for event architectures.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -23,7 +23,6 @@
|
|
|
23
23
|
},
|
|
24
24
|
"files": [
|
|
25
25
|
"dist",
|
|
26
|
-
"src",
|
|
27
26
|
"schemas",
|
|
28
27
|
"docs",
|
|
29
28
|
"action.yml",
|
|
@@ -56,7 +55,7 @@
|
|
|
56
55
|
"tsdown": "^0.22.2",
|
|
57
56
|
"typescript": "^6.0.3",
|
|
58
57
|
"vitest": "^4.1.8",
|
|
59
|
-
"autotel-subscribers": "
|
|
58
|
+
"autotel-subscribers": "41.0.0"
|
|
60
59
|
},
|
|
61
60
|
"dependencies": {
|
|
62
61
|
"@eventcatalog/sdk": "^2.24.1"
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"spec": "autotel-eventcatalog-report/v0.2.0",
|
|
3
|
-
"mode": "all",
|
|
4
|
-
"report": {
|
|
5
|
-
"snapshotGeneratedAt": "2026-05-22T00:00:00.000Z",
|
|
6
|
-
"snapshotService": "fixture",
|
|
7
|
-
"events": {
|
|
8
|
-
"observedButUndocumented": [
|
|
9
|
-
"order.cancelled"
|
|
10
|
-
],
|
|
11
|
-
"documentedButUnseen": [
|
|
12
|
-
"LegacyEvent"
|
|
13
|
-
],
|
|
14
|
-
"fieldDrift": [
|
|
15
|
-
{
|
|
16
|
-
"event": "recommendation.generated",
|
|
17
|
-
"extra": [
|
|
18
|
-
"personalization_seed"
|
|
19
|
-
],
|
|
20
|
-
"missing": []
|
|
21
|
-
}
|
|
22
|
-
],
|
|
23
|
-
"typeDrift": [],
|
|
24
|
-
"valueDrift": []
|
|
25
|
-
},
|
|
26
|
-
"services": {
|
|
27
|
-
"observedButUndocumented": []
|
|
28
|
-
},
|
|
29
|
-
"channels": {
|
|
30
|
-
"observedButUndocumented": []
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
}
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"spec": "autotel-eventcatalog-drift-summary/v0.2.0",
|
|
3
|
-
"mode": "all",
|
|
4
|
-
"shouldFail": false,
|
|
5
|
-
"reason": "No drift detected.",
|
|
6
|
-
"counts": {
|
|
7
|
-
"observedButUndocumentedEvents": 0,
|
|
8
|
-
"documentedButUnseenEvents": 0,
|
|
9
|
-
"fieldDriftEvents": 0,
|
|
10
|
-
"fieldDriftPaths": 0,
|
|
11
|
-
"typeDriftPaths": 0,
|
|
12
|
-
"valueDriftPaths": 0,
|
|
13
|
-
"undocumentedServices": 0,
|
|
14
|
-
"undocumentedChannels": 0,
|
|
15
|
-
"total": 0
|
|
16
|
-
}
|
|
17
|
-
}
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"spec": "autotel-eventcatalog-drift-summary/v0.2.0",
|
|
3
|
-
"mode": "all",
|
|
4
|
-
"shouldFail": true,
|
|
5
|
-
"reason": "Drift detected in current snapshot.",
|
|
6
|
-
"counts": {
|
|
7
|
-
"observedButUndocumentedEvents": 1,
|
|
8
|
-
"documentedButUnseenEvents": 1,
|
|
9
|
-
"fieldDriftEvents": 1,
|
|
10
|
-
"fieldDriftPaths": 1,
|
|
11
|
-
"typeDriftPaths": 0,
|
|
12
|
-
"valueDriftPaths": 0,
|
|
13
|
-
"undocumentedServices": 0,
|
|
14
|
-
"undocumentedChannels": 0,
|
|
15
|
-
"total": 3
|
|
16
|
-
}
|
|
17
|
-
}
|
package/src/catalog.test.ts
DELETED
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import { extractDeclaredFieldPaths } from './catalog';
|
|
3
|
-
|
|
4
|
-
describe('extractDeclaredFieldPaths', () => {
|
|
5
|
-
it('extracts scalar properties', () => {
|
|
6
|
-
expect(
|
|
7
|
-
extractDeclaredFieldPaths({
|
|
8
|
-
type: 'object',
|
|
9
|
-
properties: {
|
|
10
|
-
orderId: { type: 'string' },
|
|
11
|
-
totalCents: { type: 'integer' },
|
|
12
|
-
},
|
|
13
|
-
}),
|
|
14
|
-
).toEqual(['orderId', 'totalCents']);
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
it('collapses array items under `[]`', () => {
|
|
18
|
-
expect(
|
|
19
|
-
extractDeclaredFieldPaths({
|
|
20
|
-
type: 'object',
|
|
21
|
-
properties: {
|
|
22
|
-
items: {
|
|
23
|
-
type: 'array',
|
|
24
|
-
items: {
|
|
25
|
-
type: 'object',
|
|
26
|
-
properties: {
|
|
27
|
-
sku: { type: 'string' },
|
|
28
|
-
quantity: { type: 'integer' },
|
|
29
|
-
},
|
|
30
|
-
},
|
|
31
|
-
},
|
|
32
|
-
},
|
|
33
|
-
}),
|
|
34
|
-
).toEqual(['items', 'items[].quantity', 'items[].sku']);
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
it('walks nested objects', () => {
|
|
38
|
-
expect(
|
|
39
|
-
extractDeclaredFieldPaths({
|
|
40
|
-
type: 'object',
|
|
41
|
-
properties: {
|
|
42
|
-
usage: {
|
|
43
|
-
type: 'object',
|
|
44
|
-
properties: {
|
|
45
|
-
promptTokens: { type: 'integer' },
|
|
46
|
-
completionTokens: { type: 'integer' },
|
|
47
|
-
},
|
|
48
|
-
},
|
|
49
|
-
},
|
|
50
|
-
}),
|
|
51
|
-
).toEqual(['usage', 'usage.completionTokens', 'usage.promptTokens']);
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
it('returns an empty list for non-object schemas', () => {
|
|
55
|
-
expect(extractDeclaredFieldPaths({ type: 'string' })).toEqual([]);
|
|
56
|
-
expect(extractDeclaredFieldPaths(null)).toEqual([]);
|
|
57
|
-
expect(extractDeclaredFieldPaths()).toEqual([]);
|
|
58
|
-
});
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
// Path classification and frontmatter parsing now live in @eventcatalog/sdk
|
|
62
|
-
// (since v2.21); the cross-platform path tests that used to live here are
|
|
63
|
-
// covered by the SDK's own test suite.
|
package/src/catalog.ts
DELETED
|
@@ -1,169 +0,0 @@
|
|
|
1
|
-
// Read the current state of an EventCatalog using @eventcatalog/sdk. We
|
|
2
|
-
// re-use SDK types (`Event`, `Service`, `Channel`) verbatim and only add the
|
|
3
|
-
// two fields the SDK doesn't expose: the resolved on-disk `filePath`, and the
|
|
4
|
-
// dotted field-path + schema-constraint extractions our drift diff consumes.
|
|
5
|
-
// Inventing our own catalog types would silently drift from the SDK as it
|
|
6
|
-
// evolves.
|
|
7
|
-
|
|
8
|
-
import utils from '@eventcatalog/sdk';
|
|
9
|
-
import type { Channel, Event, Service } from '@eventcatalog/sdk';
|
|
10
|
-
|
|
11
|
-
export type SchemaConstraint = {
|
|
12
|
-
types?: string[];
|
|
13
|
-
enumValues?: unknown[];
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
export type CatalogEvent = Event & {
|
|
17
|
-
/** Absolute path to the event's `index.mdx`, resolved via SDK. */
|
|
18
|
-
filePath: string;
|
|
19
|
-
/** Field paths declared in the event's JSON Schema (if present). */
|
|
20
|
-
declaredFieldPaths?: string[];
|
|
21
|
-
declaredSchemaConstraints?: Record<string, SchemaConstraint>;
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
export type CatalogService = Service & {
|
|
25
|
-
filePath: string;
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
export type CatalogChannel = Channel & {
|
|
29
|
-
filePath: string;
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
export type CatalogState = {
|
|
33
|
-
events: Map<string, CatalogEvent>;
|
|
34
|
-
services: Map<string, CatalogService>;
|
|
35
|
-
channels: Map<string, CatalogChannel>;
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
export async function readCatalogState(
|
|
39
|
-
catalogPath: string,
|
|
40
|
-
): Promise<CatalogState> {
|
|
41
|
-
const sdk = utils(catalogPath);
|
|
42
|
-
|
|
43
|
-
const [events, services, channels] = await Promise.all([
|
|
44
|
-
sdk.getEvents({ latestOnly: true, attachSchema: true }),
|
|
45
|
-
sdk.getServices({ latestOnly: true }),
|
|
46
|
-
sdk.getChannels({ latestOnly: true }),
|
|
47
|
-
]);
|
|
48
|
-
|
|
49
|
-
const state: CatalogState = {
|
|
50
|
-
events: new Map(),
|
|
51
|
-
services: new Map(),
|
|
52
|
-
channels: new Map(),
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
// SDK quirk (as of 2.21.2): unlike the other helpers, `getResourcePath` is
|
|
56
|
-
// not curried with `catalogDir`, so we pass the catalog path explicitly.
|
|
57
|
-
const resolveFilePath = async (id: string, version?: string) => {
|
|
58
|
-
const paths = await sdk.getResourcePath(catalogPath, id, version);
|
|
59
|
-
return paths?.fullPath ?? '';
|
|
60
|
-
};
|
|
61
|
-
|
|
62
|
-
for (const e of events ?? []) {
|
|
63
|
-
const filePath = await resolveFilePath(e.id, e.version);
|
|
64
|
-
const schemaExtractions = e.schema
|
|
65
|
-
? {
|
|
66
|
-
declaredFieldPaths: extractDeclaredFieldPaths(e.schema),
|
|
67
|
-
declaredSchemaConstraints: extractDeclaredSchemaConstraints(e.schema),
|
|
68
|
-
}
|
|
69
|
-
: {};
|
|
70
|
-
state.events.set(e.id, { ...e, filePath, ...schemaExtractions });
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
for (const s of services ?? []) {
|
|
74
|
-
const filePath = await resolveFilePath(s.id, s.version);
|
|
75
|
-
state.services.set(s.id, { ...s, filePath });
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
for (const c of channels ?? []) {
|
|
79
|
-
const filePath = await resolveFilePath(c.id, c.version);
|
|
80
|
-
state.channels.set(c.id, { ...c, filePath });
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
return state;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* Extract field paths from a JSON Schema. Mirrors the dotted-path convention
|
|
88
|
-
* used by the snapshot subscriber: arrays collapse to `[]`, nested objects
|
|
89
|
-
* use `.`. We walk `properties` (objects) and `items` (arrays).
|
|
90
|
-
*/
|
|
91
|
-
export function extractDeclaredFieldPaths(
|
|
92
|
-
schema: unknown,
|
|
93
|
-
prefix = '',
|
|
94
|
-
): string[] {
|
|
95
|
-
const out = new Set<string>();
|
|
96
|
-
walkSchema(schema, prefix, out);
|
|
97
|
-
return [...out].toSorted();
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
function walkSchema(schema: unknown, prefix: string, out: Set<string>): void {
|
|
101
|
-
if (!schema || typeof schema !== 'object') return;
|
|
102
|
-
const s = schema as Record<string, unknown>;
|
|
103
|
-
|
|
104
|
-
if (s.properties && typeof s.properties === 'object') {
|
|
105
|
-
for (const [key, sub] of Object.entries(
|
|
106
|
-
s.properties as Record<string, unknown>,
|
|
107
|
-
)) {
|
|
108
|
-
const path = prefix === '' ? key : `${prefix}.${key}`;
|
|
109
|
-
out.add(path);
|
|
110
|
-
walkSchema(sub, path, out);
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
if (s.items) {
|
|
115
|
-
const arrayPrefix = prefix + '[]';
|
|
116
|
-
walkSchema(s.items, arrayPrefix, out);
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
export function extractDeclaredSchemaConstraints(
|
|
121
|
-
schema: unknown,
|
|
122
|
-
prefix = '',
|
|
123
|
-
): Record<string, SchemaConstraint> {
|
|
124
|
-
const out = new Map<string, SchemaConstraint>();
|
|
125
|
-
walkSchemaConstraints(schema, prefix, out);
|
|
126
|
-
const obj: Record<string, SchemaConstraint> = {};
|
|
127
|
-
for (const [path, c] of out) obj[path] = c;
|
|
128
|
-
return obj;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
function walkSchemaConstraints(
|
|
132
|
-
schema: unknown,
|
|
133
|
-
prefix: string,
|
|
134
|
-
out: Map<string, SchemaConstraint>,
|
|
135
|
-
): void {
|
|
136
|
-
if (!schema || typeof schema !== 'object') return;
|
|
137
|
-
const s = schema as Record<string, unknown>;
|
|
138
|
-
const typeVal = s.type;
|
|
139
|
-
const enumVal = s.enum;
|
|
140
|
-
if (prefix !== '' && (typeVal !== undefined || enumVal !== undefined)) {
|
|
141
|
-
const types = toTypeArray(typeVal);
|
|
142
|
-
const enumValues = Array.isArray(enumVal) ? [...enumVal] : undefined;
|
|
143
|
-
out.set(prefix, {
|
|
144
|
-
...(types.length > 0 ? { types } : {}),
|
|
145
|
-
...(enumValues ? { enumValues } : {}),
|
|
146
|
-
});
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
if (s.properties && typeof s.properties === 'object') {
|
|
150
|
-
for (const [key, sub] of Object.entries(
|
|
151
|
-
s.properties as Record<string, unknown>,
|
|
152
|
-
)) {
|
|
153
|
-
const path = prefix === '' ? key : `${prefix}.${key}`;
|
|
154
|
-
walkSchemaConstraints(sub, path, out);
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
if (s.items) {
|
|
158
|
-
const arrayPrefix = prefix + '[]';
|
|
159
|
-
walkSchemaConstraints(s.items, arrayPrefix, out);
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
function toTypeArray(typeVal: unknown): string[] {
|
|
164
|
-
if (typeof typeVal === 'string') return [typeVal];
|
|
165
|
-
if (Array.isArray(typeVal)) {
|
|
166
|
-
return typeVal.filter((t): t is string => typeof t === 'string').toSorted();
|
|
167
|
-
}
|
|
168
|
-
return [];
|
|
169
|
-
}
|