<!-- source-hash: e259d6e958fa331a13973e9d2749ffbc -->
Resolves the "what does clicking this row do?" CTA contract for source rows across chat-adjacent surfaces (chips, inline cards, search results). Pure function — no DOM, no fetch, no side effects.

## Key Components

### Interfaces

| Interface | Purpose |
|---|---|
| `SourceRowInput` | Row data — `sourceRepo`, `documentType`, `id`, `title`, `externalUrl`, `path` |
| `SourceRowContext` | Host-injected context — `baseRoute`, `chipBasePlatform`, `currentPlatform`, `tableIdForDocumentType` |
| `SourceRowCTA` | Resolved contract — `icon`, `iconLabel`, `href`, `targetPlatform`, `askable`, `chatRef` |

### Exports

- **`resolveSourceRowCTA(row, ctx)`** — Primary resolver. Returns the full CTA contract for a row. Handles icon resolution, URL safety (`safeHref`), path sanitization, and Ask drill-in viability.
- **`resolveSourceIcon(input, ctx)`** — Lightweight helper for surfaces that only need the icon + label (inline cards, search dropdown). Accepts `{ sourceRepo, documentType }` or a bare `string` (treated as `documentType`) for backward compatibility.

### Internal Helpers

- **`sanitizePath(path)`** — Strips non-alphanumeric characters except `/`, `-`, `.`, `_` to prevent path traversal or query injection.
- **`shouldFallbackToPathNav(row)`** — Limits in-app path fallback to `markdown` and `data_room_doc` document types only.
- **`pickSourceIcon(sourceRepo, documentType)`** — Maps `sourceRepo` → icon name → React component via `SOURCE_ICON_NAMES` + `ICON_REGISTRY`; falls back to `FileText`.

## Usage Example

```typescript
import { resolveSourceRowCTA, resolveSourceIcon } from './source-row-cta'

// Full CTA resolution for a chip or search result
const cta = resolveSourceRowCTA(
  {
    sourceRepo: 'blog-posts',
    documentType: 'blog_post',
    id: 'post-42',
    title: 'Series A Playbook',
    externalUrl: 'https://example.com/posts/42',
    targetPlatform: 'hub',
  },
  {
    baseRoute: '/knowledge-base',
    currentPlatform: 'hub',
    tableIdForDocumentType: (type) => type === 'blog_post' ? 'blog-posts' : null,
  },
)
// cta.href       → 'https://example.com/posts/42'  (safeHref-validated)
// cta.askable   → true
// cta.chatRef   → { type: 'blog_post', sourceRepo: 'blog-posts', id: 'post-42', ... }

// Icon-only resolution for an inline card tracking strip
const { Icon, label } = resolveSourceIcon({ documentType: 'cap_table' }, { tableIdForDocumentType })
```

> `href` is always `safeHref()`-sanitized — a hostile RAG row cannot inject `javascript:` or `data:` URIs into the DOM. Ask drill-in (`askable`) requires both a non-empty `id` and a `documentType`.