# Source

inseam's durable identity-plus-address layer: one row per indexed thing, addressed by a plugin-owned `LocationKind`, decoupled from any specific `Connection`. A plugin contributes a `LocationKindDefinition` per kind it wants inseam to index; core registers it, persists Source rows under the kind's canonical `pathId`, and emits `source.*` events on every mutation. Plugins compile against `@inseam/source-contract` alone.

**See also:** [design](../design/source.md) · [spec](../spec/source.md)

## What it does

Two surfaces, split by package:

- **Contract surface** (`@inseam/source-contract`) — `LocationKindDefinition`, `Source`, `SourceInput`, `SourcePatch`, `PathMutationInput`, `RetrievalMetadata`, the four `Source*Event` payloads, and the typed error hierarchy. Zero runtime deps; safe for any plugin or event consumer.
- **Core surface** (`@inseam/core`, Source contribution) — `openSourceRegistry`, `SourceRegistry`. Owns the in-memory Location Kind table, persistence, the `(location_kind, path_id)` uniqueness rule, and a single-batch coupling between each row mutation and its corresponding outbox event.

A Source row carries first-class retrieval metadata (`contentType`, `contentSize`, `contentHash`, `displayName`, upstream timestamps) plus a plugin-namespaced `metadata` blob. The `pathId` is the plugin's stable identifier within its kind — `normalize` is called before every write so non-canonical forms cannot reach storage. Path mutations swap the address atomically and emit `source.path_changed`; the row's `id` never changes. Path history is the stream of those events, not a sibling table.

Writes are core-internal. Plugins do not call `createSource` / `updateSource` / etc. directly — those compose through the future `ingest` package, which pairs every Source write with an access-layer Envelope in the same Store batch.

## How to use it

A host wires a Store and an events registry, then opens the Source registry:

```ts
import { openStore, openEventsRegistry, openSourceRegistry } from "@inseam/core";
import { bunSqliteAdapter } from "@inseam/adapter-store-bun-sqlite";
import { gdriveFile } from "inseam-plugin-gdrive";

const store = await openStore(bunSqliteAdapter("./inseam.sqlite"));
const events = await openEventsRegistry({ store });
const registry = await openSourceRegistry({ store, events });

registry.registerLocationKind(gdriveFile);

const existing = await registry.findSourceByAddress("gdrive.file", "FILE_ID");
const source = existing ?? (await registry.createSource({
  locationKind: "gdrive.file",
  pathId: "FILE_ID",
  ownerHostId: myHostId,
  retrieval: { contentType: "application/pdf", displayName: "Q3.pdf" },
}));
```

Full type signatures: [spec — Public API](../spec/source.md#public-api).

## How to extend it

A new Location Kind is a one-object contribution. A plugin package depends on `@inseam/source-contract` only:

```ts
import type { LocationKindDefinition } from "@inseam/source-contract";
import { InvalidPathIdError } from "@inseam/source-contract";

export const gdriveFile: LocationKindDefinition = {
  name: "gdrive.file",
  normalize(pathId) {
    const trimmed = pathId.trim();
    if (trimmed.length === 0) {
      throw new InvalidPathIdError("gdrive.file", pathId);
    }
    return trimmed;
  },
  format: (pathId) => `gdrive://drive.google.com/${pathId}`,
  parse: (uri) => uri.match(/^gdrive:\/\/drive\.google\.com\/(.+)$/)?.[1] ?? null,
};
```

The minimum a plugin author needs:

1. Pick a stable `name` (`"gdrive.file"`, `"gmail.message"`, `"fs.local"`).
2. Implement `normalize(pathId)`: deterministic, idempotent, throws `InvalidPathIdError` for inputs with no canonical form. Pure; no I/O.
3. Optionally implement `format` / `parse` — convenience helpers for rendering URIs at boundaries; core never calls them.

A host registers the kind on startup via `registry.registerLocationKind(def)`. Registrations are process-local — rows persist, kinds re-register on every boot.

## Where the code lives

- Contract package: `pkgs/source-contract/src/index.ts`
- Core surface (registry, writes, normalize seam): `pkgs/core/src/source/registry.ts`
- Row codec (column list, retrieval JSON, Date round-trip): `pkgs/core/src/source/rows.ts`
- Migrations (table, unique + filter indices): `pkgs/core/src/source/migrations.ts`
- Public exports: `pkgs/core/src/source/index.ts`
- Behavior tests, one file per concern: `pkgs/core/src/source/*.test.ts`

## Related

- [Spec — source](../spec/source.md) — types, behavior, errors, acceptance criteria
- [Design — source](../design/source.md) — why Source is decoupled from Connection and what `pathId` is for
- [Connection](./connection.md) — declares `resolves: LocationKind[]` against the vocabulary defined here; no FK between the two
- [Events](./events.md) — owns the outbox the `source.*` events ride; this port writes one event per mutation in the same batch
- [Access](./access.md) — Envelope coupling that the future `ingest` will stitch alongside every Source write
- [Store](./store.md) — `source` table and the outbox both live here
