# Privacy-First Observability

`@ai-node-editor/core` does not transmit analytics, workflow content, prompts, credentials, files, or editor activity to the package author.

The package contains no analytics provider and has no telemetry endpoint. `AINodeEditor` emits typed events only to callbacks in the same JavaScript process. If an application does not provide `onEvent`, the events are discarded.

```text
@ai-node-editor/core
        |
        | emits local typed events
        v
consumer application
       / \
      /   \
 nothing  consumer's analytics provider
```

## Subscribe Locally

```tsx
import { AINodeEditor, type EditorEvent } from "@ai-node-editor/core";

function handleEditorEvent(event: EditorEvent) {
  console.log(event);

  // Optional. The consuming application owns this decision and configuration.
  analytics.track(event.type, event);
}

<AINodeEditor onEvent={handleEditorEvent} />;
```

Event callbacks are isolated from editor behavior. If `onEvent` or `onDiagnostic` throws, the editor continues to work. In `debug` mode the package logs only the same sanitized event/diagnostic objects and static callback-failure messages.

```tsx
<AINodeEditor
  onDiagnostic={(diagnostic) => console.warn(diagnostic.code, diagnostic.message)}
  options={{ debug: true }}
/>
```

Debug mode never performs a network request.

## Privacy Boundary

Editor events contain structure and behavior, not workflow content. They may contain:

- event type and ISO timestamp
- node type and registered category
- port data types
- node and edge counts
- stable diagnostic codes
- template catalog ID/category
- execution duration, attempt number, and status

Editor events never contain:

- graphs or serialized graphs
- node IDs, user-entered node labels, or workflow names
- node configuration values
- prompts or LLM responses
- API keys, tokens, passwords, or other secret values
- uploaded files, document contents, database queries, or webhook payloads
- private URLs
- execution inputs, outputs, logs, or raw thrown errors

The low-level `GraphEngine.on()` API is an execution API rather than an analytics API. Its local lifecycle events can include raw `data`, `error`, validation issues, and logs so an application can implement execution. Do not forward those objects to analytics without an application-specific sanitizer. `AINodeEditor.onEvent` never forwards those fields.

## Event Contract

Every event has:

| Property | Type | Meaning |
| --- | --- | --- |
| `type` | `EditorEventType` | Discriminant for the typed union |
| `timestamp` | `string` | ISO 8601 event time |

Event-specific properties:

| Event | Properties |
| --- | --- |
| `editor:ready` | `nodeCount`, `edgeCount` |
| `node:add` | `nodeType`, optional `category`, `source`, `nodeCount` |
| `node:remove` | optional `nodeType`, optional `category`, `source`, `nodeCount` |
| `node:duplicate` | `nodeTypes`, `source`, `nodeCount` |
| `connection:start` | optional `sourcePortType`, `reconnecting` |
| `connection:success` | `sourcePortType`, `targetPortType`, `edgeCount` |
| `connection:rejected` | `code`, structural `message`, optional port types, `expectedPortTypes`, `suggestedNodeTypes` |
| `connection:reconnect` | `sourcePortType`, `targetPortType`, `edgeCount` |
| `template:open` | `templateCount` |
| `template:use` | `templateId`, optional `category`, `nodeCount`, `edgeCount` |
| `validation:start` | `nodeCount`, `edgeCount` |
| `validation:complete` | `valid`, `errorCount`, `warningCount`, `issueCodes` |
| `validation:suggestion_shown` | `issueCode`, `suggestionType` |
| `validation:suggestion_applied` | `issueCode`, `suggestionType` |
| `execution:start` | `nodeCount`, `edgeCount` |
| `execution:success` | `nodeCount`, `edgeCount`, optional `durationMs` |
| `execution:error` | `code`, optional `failedNodeType`, optional `durationMs` |
| `execution:cancel` | optional `durationMs` |
| `execution:retry` | optional `nodeType`, `attempt` |
| `workflow:save` | `nodeCount`, `edgeCount`, optional `format` |
| `workflow:load` | `nodeCount`, `edgeCount`, optional `format` |
| `workflow:serialize` | `nodeCount`, `edgeCount`, `schemaVersion` |
| `library:open` | `nodeTypeCount` |
| `inspector:open` | optional `nodeType` |
| `editor:error` | `code`, static `message`, optional `operation` |
| `editor:diagnostic` | sanitized `diagnostic` |

`source` is one of `api`, `canvas`, `context-menu`, `keyboard`, `library`, `template`, `toolbar`, or `unknown`.

These events let an application calculate activation funnels, rejection rates, validation friction, execution failure rates, feature discovery, and time to first value. Storage, session correlation, user identity, aggregation, and dashboards remain the consumer's responsibility.

## Stable Diagnostic Codes

Core validation and connection results retain their existing dotted `code` for backward compatibility and also expose `diagnosticCode` when a stable code is available.

| Code | Typical meaning |
| --- | --- |
| `PORT_TYPE_MISMATCH` | Source and target port types are incompatible |
| `INPUT_MAX_CONNECTIONS` | A single-input port already has a connection |
| `MISSING_REQUIRED_INPUT` | A required input has no connection/default |
| `UNKNOWN_NODE_TYPE` | A node definition is not registered |
| `UNKNOWN_PORT` | An edge references a missing port |
| `CYCLE_NOT_ALLOWED` | The graph contains a disallowed cycle |
| `SELF_LINK_NOT_ALLOWED` | A node links to itself when disabled |
| `CONNECTION_DIRECTION_MISMATCH` | The gesture does not connect output to input |
| `DUPLICATE_CONNECTION` | The same connection already exists |
| `INVALID_CONFIG` | Config does not match its schema |
| `MISSING_CONFIG` | Required config is absent |
| `DANGLING_EDGE` | An edge references a missing node |
| `EXECUTION_TIMEOUT` | Graph or node execution timed out |
| `NODE_EXECUTION_FAILED` | A node failed during execution |
| `EXECUTION_ENGINE_MISSING` | Run was requested without an engine/callback |
| `EXECUTION_CANCELLED` | Execution was cancelled |
| `EDITOR_ZERO_HEIGHT` | The editor container has no usable height |
| `THEME_STYLES_MISSING` | Base/theme CSS does not appear to be loaded |
| `EDITOR_CALLBACK_FAILED` | A consumer callback threw |

Diagnostics use static messages and structural fields only. They do not copy raw validation/config values or execution errors into telemetry events.

## Secret Serialization

Pass the same registry used by the editor when serializing. Schema fields with `type: "secret"` are omitted by default, and any field can opt out with `serialize: false`.

```ts
const configSchema = {
  apiKey: { type: "secret", label: "API key" },
  transient: { type: "string", label: "Transient", serialize: false }
} satisfies ConfigSchema;

const json = serializeGraph(graph, {
  registry,
  pretty: true
});
```

Secret references are preserved without serializing raw credentials:

```ts
node.config.apiKey = { $secretRef: "openai-production" };
```

Raw secret serialization requires the explicit `secretPolicy: "preserve"` option. Registry-free serialization keeps the legacy behavior because a graph alone does not contain config schemas; use a registry whenever graphs may contain secrets.