<!-- Canonical Fabric Harness channel connector contract. Shipped with @fabric-harness/sdk. -->

# Fabric Harness Channel Connector Spec

A channel turns an authenticated provider event into a durable dispatch to a persistent agent. Produce a TypeScript module under `.fabricharness/channels/<provider>.ts` that exports a `Channel` created with `defineChannel()`.

## Required shape

```ts
import { conversationKey, defineChannel, parseConversationKey } from '@fabric-harness/sdk';

export const channel = defineChannel({
  name: '<provider>',
  routes: [{
    method: 'POST',
    path: '/events',
    async handler(request, context) {
      // 1. Read the exact body bytes once.
      // 2. Verify the provider signature and timestamp before parsing/dispatching.
      // 3. Normalize the event and ignore provider-owned bot messages.
      // 4. Dispatch with stable instance, dedupe, tenant, and actor identity.
      const receipt = await context.dispatch('<agent>', {
        instanceId: conversationKey('<provider>', 'v1', '<conversation-id>'),
        message: { kind: 'signal', signal: '<provider>.event', data: {} },
        dedupeKey: '<provider-event-id>',
        tenantId: '<workspace-or-account-id>',
        actor: { id: '<provider-user-id>', type: 'user' },
      });
      return Response.json(receipt, { status: 202 });
    },
  }],
  conversationKey(ref) {
    const value = ref as { id: string };
    return conversationKey('<provider>', 'v1', value.id);
  },
  parseConversationKey(id) {
    return { id: parseConversationKey(id).segments[0] };
  },
});
```

## Security requirements

1. Fail closed when the signing secret, public key, OAuth configuration, or expected bearer token is absent.
2. Verify the exact request bytes. Use `readJsonBody()` when an HMAC provider requires both raw bytes and parsed JSON.
3. Enforce the provider timestamp window or challenge protocol to prevent replay attacks.
4. Use constant-time signature verification. Prefer Fabric's `verifyHmacSha256()` when the provider uses HMAC-SHA256.
5. Use the provider event/delivery ID as `dedupeKey`. Do not invent a random value.
6. Derive `instanceId` from the stable thread, conversation, ticket, order, or resource identifier.
7. Propagate organization/account identity as `tenantId` and the initiating user as `actor`.
8. Ignore messages/actions created by the integration itself to prevent response loops.
9. Keep credentials in environment variables or a secret manager. Never put secrets in dispatch input, model context, errors, logs, or session artifacts.
10. Apply a `CapabilityPolicy` to outbound write tools. Require approval for destructive, financial, public-posting, or customer-data mutations.

## Outbound tools

Expose provider writes through `defineTool()` with `effect: 'write'`. Bind tools to the addressed conversation/resource at persistent-agent initialization, and restrict OAuth scopes to the exact actions required. Include idempotency keys when the provider supports them.

## Tests

Add unit tests for valid and invalid signatures, stale timestamps, duplicate event IDs, bot-loop suppression, tenant/actor propagation, stable conversation keys, body-size limits, and outbound policy classification. Add an opt-in live test for provider webhook and permission changes.
