From 9e5a945efb4bc24158b31b8884ec985580bcbb66 Mon Sep 17 00:00:00 2001 From: "Bender (Paperclip)" Date: Wed, 23 Sep 2026 18:29:24 +0000 Subject: [PATCH] Rebuild on the rebased proposal; sync the generated contract from backend draft 48a4cce MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Provenance: paperclipai/telemetry-backend PR #145 commit 48a4ccea6ce89c17f74841fa2022c25f96410c09, artifact dist/client-sdk/paperclip-telemetry.ts, copied verbatim (never hand edited; sha256 0a875a739023f3a776ac9ba4dbed4114096ba5e71753c31bf01e366372d68faf) into packages/shared/src/telemetry/generated/paperclip-telemetry.ts. The stack was rebuilt on proposal commit 0f614f9ab (rebased onto master b41ccf097, where #13758 retired the legacy Composio broker). The three prior adoption commits were cherry-picked unchanged; the rename edits that lived in the former merge commit e9b0ffcb6 (typed wrappers, tests, README, retention map) are restored here. Contract delta versus the previous artifact: `composio_sync` removed from `setup_flow` and `change_source` (no emission site remains), and two enum descriptions widened to name the OAuth access-finalization and Cloud/Vercel callback variants the proposal wires. README Connector Events section: direct Composio MCP connections report `composio`; retained legacy broker records can only be removed and report `composio` (`rest_api` parent) or `custom` (child). Draft — DO NOT MERGE; stacked on proposal/connector-telemetry-events. Co-Authored-By: Claude Fable 5.1 --- packages/shared/src/telemetry/README.md | 26 ++-- .../generated/paperclip-telemetry.ts | 114 +++++++++--------- .../shared/src/telemetry/retention.test.ts | 6 +- packages/shared/src/telemetry/retention.ts | 10 +- 4 files changed, 80 insertions(+), 76 deletions(-) diff --git a/packages/shared/src/telemetry/README.md b/packages/shared/src/telemetry/README.md index cc0313d1dd0..444acfc929d 100644 --- a/packages/shared/src/telemetry/README.md +++ b/packages/shared/src/telemetry/README.md @@ -110,7 +110,8 @@ data paths document their own contract in their own file: ## Connector Events -The three `connector.*` events record tool-connector lifecycle and usage for +The three connection events (`connection.created`, `connection.updated`, +`connection.invoked`) record tool-connector lifecycle and usage for connections whose `connection_purpose` is `tool`. The generated contract remains the authority for their exact dimensions and optionality. These durable rules are not expressible in the generated `string`/`number` types: @@ -119,15 +120,20 @@ durable rules are not expressible in the generated `string`/`number` types: connection's `sourceTemplateKey` only when that key resolves to a reviewed first-party catalog definition; every other connection emits the literal `custom`. `connectorKeyForConnection()` in - `server/src/services/connector-telemetry.ts` owns this rule. Composio-synced - child connections persist a `toolkitSlug` but no catalog - `sourceTemplateKey`, so they report `custom` too; raw toolkit or provider - strings are never exported. -- `connector.connection_created` and `connector.connection_updated` are + `server/src/services/connector-telemetry.ts` owns this rule. Direct + Composio MCP connections (catalog slug `composio`, method `mcp`) therefore + report `composio`; session URLs, headers, and the tools reached through the + aggregator never appear in any dimension. Records left behind by the retired + Composio broker (#13758) can only be removed: a legacy parent reports + `composio` with `transport: rest_api`, and a legacy child — which has a + `toolkitSlug` but no catalog `sourceTemplateKey` — reports `custom`; raw + toolkit or provider strings are never exported. +- `connection.created` and `connection.updated` are emitted only after the lifecycle write commits, from returned rows. Writes that affect no rows and no-op changes do not emit. -- `connector.invocation_completed` is emitted when an invocation reaches a - terminal status — best-effort, so duplicate and lost events are possible +- `connection.invoked` is emitted when an invocation reaches a + terminal status: it records completed invocation attempts and their terminal + status, never invocation starts — best-effort, so duplicate and lost events are possible (see the delivery rule below); do not read it as exactly one event per invocation. Its `origin` dimension separates setup tests (the Apps → Test tab) from agent and user calls using the durable test-origin columns. @@ -138,8 +144,8 @@ durable rules are not expressible in the generated `string`/`number` types: bookkeeping itself fails, and a crash between the product write and emission loses that event. Do not treat these events as an exactly-once ledger. -Use `trackConnectorConnectionCreated()`, `trackConnectorConnectionUpdated()`, -and `trackConnectorInvocationCompleted()` from `events.ts` to emit these +Use `trackConnectionCreated()`, `trackConnectionUpdated()`, +and `trackConnectionInvoked()` from `events.ts` to emit these events. ## Dimension Values diff --git a/packages/shared/src/telemetry/generated/paperclip-telemetry.ts b/packages/shared/src/telemetry/generated/paperclip-telemetry.ts index b552647ce80..fc206923108 100644 --- a/packages/shared/src/telemetry/generated/paperclip-telemetry.ts +++ b/packages/shared/src/telemetry/generated/paperclip-telemetry.ts @@ -37,18 +37,26 @@ source_ref?: string source_ref_hashed?: boolean } -export interface PaperclipConnectorConnectionCreatedDimensions { +export interface PaperclipConnectionCreatedDimensions { auth_kind: ("oauth" | "api_key" | "none") connector_key: string enabled: boolean -setup_flow: ("gallery" | "api" | "example" | "composio_sync") +setup_flow: ("gallery" | "api" | "example") status: ("draft" | "active" | "disabled" | "archived") transport: ("mcp_remote" | "rest_api" | "local_stdio") } -export interface PaperclipConnectorConnectionUpdatedDimensions { +export interface PaperclipConnectionInvokedDimensions { +connector_key: string +duration_seconds?: number +origin: ("setup_test" | "agent" | "user" | "system" | "plugin") +status: ("succeeded" | "failed" | "denied" | "cancelled" | "timed_out" | "rate_limited") +transport: ("mcp_remote" | "rest_api" | "local_stdio") +} + +export interface PaperclipConnectionUpdatedDimensions { auth_kind: ("oauth" | "api_key" | "none") -change_source: ("update_api" | "gallery_setup" | "oauth_callback" | "credential_refresh" | "archive" | "example" | "composio_sync") +change_source: ("api" | "gallery" | "oauth_callback" | "credential_refresh" | "archive" | "example") connector_key: string enabled: boolean previous_enabled: boolean @@ -57,14 +65,6 @@ status: ("draft" | "active" | "disabled" | "archived") transport: ("mcp_remote" | "rest_api" | "local_stdio") } -export interface PaperclipConnectorInvocationCompletedDimensions { -connector_key: string -duration_seconds?: number -origin: ("setup_test" | "agent" | "user" | "system" | "plugin") -status: ("succeeded" | "failed" | "denied" | "cancelled" | "timed_out" | "rate_limited") -transport: ("mcp_remote" | "rest_api" | "local_stdio") -} - export interface PaperclipErrorHandlerCrashDimensions { error_code: string } @@ -133,9 +133,9 @@ export type PaperclipEventName = | "agent.task_completed" | "agent.task_run" | "company.imported" - | "connector.connection_created" - | "connector.connection_updated" - | "connector.invocation_completed" + | "connection.created" + | "connection.invoked" + | "connection.updated" | "error.handler_crash" | "goal.created" | "install.completed" @@ -153,9 +153,9 @@ export interface EventDimensionsMap { "agent.task_completed": PaperclipAgentTaskCompletedDimensions; "agent.task_run": PaperclipAgentTaskRunDimensions; "company.imported": PaperclipCompanyImportedDimensions; - "connector.connection_created": PaperclipConnectorConnectionCreatedDimensions; - "connector.connection_updated": PaperclipConnectorConnectionUpdatedDimensions; - "connector.invocation_completed": PaperclipConnectorInvocationCompletedDimensions; + "connection.created": PaperclipConnectionCreatedDimensions; + "connection.invoked": PaperclipConnectionInvokedDimensions; + "connection.updated": PaperclipConnectionUpdatedDimensions; "error.handler_crash": PaperclipErrorHandlerCrashDimensions; "goal.created": PaperclipGoalCreatedDimensions; "install.completed": PaperclipInstallCompletedDimensions; @@ -174,9 +174,9 @@ export const PAPERCLIP_EVENTS = { "agent.task_completed": "agent.task_completed", "agent.task_run": "agent.task_run", "company.imported": "company.imported", - "connector.connection_created": "connector.connection_created", - "connector.connection_updated": "connector.connection_updated", - "connector.invocation_completed": "connector.invocation_completed", + "connection.created": "connection.created", + "connection.invoked": "connection.invoked", + "connection.updated": "connection.updated", "error.handler_crash": "error.handler_crash", "goal.created": "goal.created", "install.completed": "install.completed", @@ -311,7 +311,7 @@ export const PAPERCLIP_ENUM_DESCRIPTIONS = { "unknown": "Source type could not be classified." } }, - "connector.connection_created": { + "connection.created": { "auth_kind": { "oauth": "Connection authenticates with an OAuth flow.", "api_key": "Connection authenticates with a stored API key.", @@ -320,8 +320,7 @@ export const PAPERCLIP_ENUM_DESCRIPTIONS = { "setup_flow": { "gallery": "Connection was created through the app-gallery setup flow.", "api": "Connection was created through the connections API.", - "example": "Connection was created or upserted as a bundled example connection.", - "composio_sync": "Connection was created by syncing a Composio child connection." + "example": "Connection was created or upserted as a bundled example connection." }, "status": { "draft": "Connection row exists but setup has not produced a usable configuration yet.", @@ -335,40 +334,7 @@ export const PAPERCLIP_ENUM_DESCRIPTIONS = { "local_stdio": "Connection runs a local MCP server over stdio." } }, - "connector.connection_updated": { - "auth_kind": { - "oauth": "Connection authenticates with an OAuth flow.", - "api_key": "Connection authenticates with a stored API key.", - "none": "Connection requires no authentication." - }, - "change_source": { - "update_api": "A connections API update changed the persisted lifecycle state.", - "gallery_setup": "An app-gallery setup step changed the persisted lifecycle state.", - "oauth_callback": "An OAuth callback completion changed the persisted lifecycle state.", - "credential_refresh": "A credential refresh changed the persisted lifecycle state.", - "archive": "An archive operation changed the persisted lifecycle state.", - "example": "An example-connection upsert changed the persisted lifecycle state.", - "composio_sync": "A Composio child-connection sync changed the persisted lifecycle state." - }, - "previous_status": { - "draft": "Connection row exists but setup has not produced a usable configuration yet.", - "active": "Connection is fully configured and available for use.", - "disabled": "Connection is disabled and excluded from tool use.", - "archived": "Connection is archived and excluded from tool use." - }, - "status": { - "draft": "Connection row exists but setup has not produced a usable configuration yet.", - "active": "Connection is fully configured and available for use.", - "disabled": "Connection is disabled and excluded from tool use.", - "archived": "Connection is archived and excluded from tool use." - }, - "transport": { - "mcp_remote": "Connection talks to a remote MCP server over HTTP.", - "rest_api": "Connection calls the provider's REST API directly.", - "local_stdio": "Connection runs a local MCP server over stdio." - } - }, - "connector.invocation_completed": { + "connection.invoked": { "origin": { "setup_test": "A human ran the connection's setup test, identified by durable invocation columns: user actor with no run, issue, or gateway reference and a connection id.", "agent": "An agent run invoked the tool.", @@ -390,6 +356,38 @@ export const PAPERCLIP_ENUM_DESCRIPTIONS = { "local_stdio": "Connection runs a local MCP server over stdio." } }, + "connection.updated": { + "auth_kind": { + "oauth": "Connection authenticates with an OAuth flow.", + "api_key": "Connection authenticates with a stored API key.", + "none": "Connection requires no authentication." + }, + "change_source": { + "api": "A connections API update changed the persisted lifecycle state.", + "gallery": "An app-gallery setup step (finish, OAuth access finalization, or reconnect; Apps gallery and inline task cards) changed the persisted lifecycle state.", + "oauth_callback": "An OAuth callback completion (provider OAuth, Paperclip Cloud connector, or Vercel Connect) changed the persisted lifecycle state.", + "credential_refresh": "A credential refresh changed the persisted lifecycle state.", + "archive": "An archive operation changed the persisted lifecycle state.", + "example": "An example-connection upsert changed the persisted lifecycle state." + }, + "previous_status": { + "draft": "Connection row exists but setup has not produced a usable configuration yet.", + "active": "Connection is fully configured and available for use.", + "disabled": "Connection is disabled and excluded from tool use.", + "archived": "Connection is archived and excluded from tool use." + }, + "status": { + "draft": "Connection row exists but setup has not produced a usable configuration yet.", + "active": "Connection is fully configured and available for use.", + "disabled": "Connection is disabled and excluded from tool use.", + "archived": "Connection is archived and excluded from tool use." + }, + "transport": { + "mcp_remote": "Connection talks to a remote MCP server over HTTP.", + "rest_api": "Connection calls the provider's REST API directly.", + "local_stdio": "Connection runs a local MCP server over stdio." + } + }, "goal.created": { "goal_level": { "company": "Goal applies at company scope.", diff --git a/packages/shared/src/telemetry/retention.test.ts b/packages/shared/src/telemetry/retention.test.ts index 24928a49d73..5aacf461acb 100644 --- a/packages/shared/src/telemetry/retention.test.ts +++ b/packages/shared/src/telemetry/retention.test.ts @@ -25,8 +25,8 @@ describe("telemetry retention contract", () => { }); it("connector telemetry uses enum/count retention", () => { - expect(EVENT_RETENTION_CLASS["connector.connection_created"]).toBe("operational_enum_count"); - expect(EVENT_RETENTION_CLASS["connector.connection_updated"]).toBe("operational_enum_count"); - expect(EVENT_RETENTION_CLASS["connector.invocation_completed"]).toBe("operational_enum_count"); + expect(EVENT_RETENTION_CLASS["connection.created"]).toBe("operational_enum_count"); + expect(EVENT_RETENTION_CLASS["connection.updated"]).toBe("operational_enum_count"); + expect(EVENT_RETENTION_CLASS["connection.invoked"]).toBe("operational_enum_count"); }); }); diff --git a/packages/shared/src/telemetry/retention.ts b/packages/shared/src/telemetry/retention.ts index 45231d3be74..0a0e83ec6cc 100644 --- a/packages/shared/src/telemetry/retention.ts +++ b/packages/shared/src/telemetry/retention.ts @@ -43,8 +43,8 @@ export type RetentionClass = keyof typeof RETENTION_DAYS; * External-system event (Codex CLI) — not yet in PaperclipEventName. * Class: operational_enum_count — 90-day window. * - * connector.connection_created / connector.connection_updated / - * connector.invocation_completed + * connection.created / connection.updated / + * connection.invoked * Carry only closed enums, booleans, a coarse rounded-seconds duration, and * the reviewed catalog slug (or the literal "custom") as connector_key. No * token material and no PII. @@ -54,7 +54,7 @@ export const EVENT_RETENTION_CLASS: Partial> = { "codex.credential_health": "operational_enum_count", "interaction.created": "operational_enum_count", "interaction.resolved": "operational_enum_count", - "connector.connection_created": "operational_enum_count", - "connector.connection_updated": "operational_enum_count", - "connector.invocation_completed": "operational_enum_count", + "connection.created": "operational_enum_count", + "connection.updated": "operational_enum_count", + "connection.invoked": "operational_enum_count", };