API Reference

Hooks, providers, and core APIs.

API Reference

Packages: `@fortemi/core` · `@fortemi/graph` · `@fortemi/react` Version: 2026.9.9


Table of Contents


@fortemi/core

Core Utilities

`VERSION`

const VERSION: string

The current package version string. Value: `'2026.9.9'`.


`generateId()`

function generateId(): string

Generates a UUIDv7 identifier. UUIDv7 values are time-ordered, making them suitable as primary keys in sorted indexes.

Returns: A UUIDv7 string, e.g. `'018f1e2d-3b4c-7a5d-8e9f-0a1b2c3d4e5f'`.


`computeHash(data)`

function computeHash(data: Uint8Array): string

Computes a SHA-256 digest of the provided binary data.

ParameterTypeDescription
`data``Uint8Array`Raw bytes to hash

Returns: A prefixed hex string in the form `'sha256:<hex>'`.


`createPGliteInstance(persistence, archiveName)`

function createPGliteInstance(
  persistence: PersistenceMode,
  archiveName: string
): Promise<PGlite>

Creates and initializes a PGlite database instance using the specified storage backend.

ParameterTypeDescription
`persistence``PersistenceMode`Storage backend: `'opfs'`, `'idb'`, or `'memory'`
`archiveName``string`Logical name for the archive; used to scope the on-disk path

Returns: A `Promise` that resolves to an initialized `PGlite` instance.


`createFortemi(config)`

function createFortemi(config: FortemiConfig): FortemiCore

Lightweight factory for the non-React runtime shell. It creates the shared `TypedEventBus`, stores the provided config, and exposes a `destroy()` cleanup hook. Use `ArchiveManager.open()` to create the PGlite database before constructing repositories.

ParameterTypeDescription
`config``FortemiConfig`Configuration object (see FortemiConfig)

Returns: A `FortemiCore` instance.


`createBlobStore(archiveName)`

function createBlobStore(archiveName: string): BlobStore

Creates a `BlobStore` backed by OPFS when available, falling back to IndexedDB. Used for binary attachment storage.

ParameterTypeDescription
`archiveName``string`Archive name used to scope the storage namespace

Returns: A `BlobStore` instance.


`MemoryBlobStore`

class MemoryBlobStore implements BlobStore {
  write(key: string, data: Uint8Array): Promise<void>
  read(key: string): Promise<Uint8Array | null>
  remove(key: string): Promise<void>
  exists(key: string): Promise<boolean>
}

An in-memory implementation of `BlobStore` intended for use in test environments. Data does not persist between instantiations.


Types

`PersistenceMode`

type PersistenceMode = 'opfs' | 'idb' | 'memory'

Controls where PGlite stores data.

ValueDescription
`'opfs'`Origin Private File System — best performance on supported browsers
`'idb'`IndexedDB — broader compatibility fallback
`'memory'`No persistence; suitable for tests

`BlobStore`

interface BlobStore {
  put(bytes: Uint8Array): Promise<string>
  read(checksum: string): Promise<Uint8Array | null>
  has(checksum: string): Promise<boolean>
  delete?(checksum: string): Promise<boolean>
  reconcile(liveChecksums: Iterable<string>, opts?: BlobReconcileOptions): Promise<BlobReconcileResult>
  gc(opts?: BlobGcOptions): Promise<BlobGcResult>
  diagnostics(): Promise<BlobStoreDiagnostics>
  close(): Promise<void>
}

Content-addressed binary storage keyed by canonical `blake3:<hex>` checksums. Built-in stores implement the optional `delete` capability for rollback-safe shard sidecar promotion; custom stores without it remain API-compatible but cannot import sidecar bytes atomically.


`FortemiConfig`

interface FortemiConfig {
  persistence: PersistenceMode
  archiveName?: string
}

Configuration passed to `createFortemi`.

PropertyTypeRequiredDescription
`persistence``PersistenceMode`YesStorage backend
`archiveName``string`NoArchive identifier; defaults to a standard name when omitted

`FortemiCore`

interface FortemiCore {
  events: TypedEventBus
  config: FortemiConfig
  destroy(): void
}

The runtime shell returned by `createFortemi`. React apps usually get the richer `{ db, events, archiveManager, capabilityManager, blobStore }` surface from `FortemiProvider` instead.


Dataset Execution

The dataset APIs are versioned, storage-neutral contracts. Their descriptors, plans, receipts, and lineage records must be bound to the concrete runtime and evidence that produced them; method presence or a successful cache query is not a capability claim.

Capability negotiation

function validateDatasetExecutionDescriptor(
  descriptor: DatasetExecutionCapabilityDescriptor,
): DatasetCapabilityDiagnostic[]

function negotiateDatasetExecutionCapabilities(
  descriptor: DatasetExecutionCapabilityDescriptor,
  request: DatasetCapabilityNegotiationRequest,
): DatasetCapabilityNegotiationResult

`fortemi.dataset-execution-capabilities/v1` distinguishes browser-local archives, static caches, portable shards, server processes, and remote persistence. Required capability/version/limit mismatches return `accepted: false`; optional mismatches return explicit degradation and fallback details. Negotiation is pure and performs no probe, network request, mutation, or fallback execution.

Ingest runs

class DatasetIngestExecutor {
  constructor(store: DatasetIngestStore)
  executeBatch(
    plan: DatasetProcessingPlan,
    batch: DatasetMutationBatch,
    options?: ExecuteDatasetBatchOptions,
  ): Promise<DatasetRunReceipt>
  resolveAmbiguousCommit(
    plan: DatasetProcessingPlan,
    batch: DatasetMutationBatch,
  ): Promise<DatasetRunReceipt | undefined>
}

class MemoryDatasetIngestStore implements DatasetIngestStore {}

`fortemi.dataset-ingest/v1` binds processing plans to source revisions, normalized configuration, transformation profiles, destination scope, rejection policy, and reconciliation limits. Each transaction commits record effects, redacted rejection accounting, checkpoint advancement, and its receipt together. Exact replay returns the stored receipt; an idempotency key reused for different canonical request content fails.

Evidence-bearing lineage

class DatasetLineageLedger {
  constructor(options?: DatasetLineageLedgerOptions)
  traverse(
    request: LineageTraversalRequest,
    policy: LineageAuthorizationPolicy,
  ): LineageTraversalResult
  exportArchive(snapshot?: number): LineageLedgerArchive
  project(
    capabilities: LineageProjectionCapabilities,
    snapshot?: number,
  ): LineageProjection
}

`fortemi.dataset-lineage/v1` is an append-only authority for entities, agents, activities, evidence revisions, assertions, and corrections. Traversal applies authorization before graph expansion, requires explicit depth/result/page bounds, and returns snapshot-bound cursors. External projections are regenerable and include explicit loss receipts; W3C PROV and OpenLineage are adapter targets rather than the native storage schema.

Materialization and retrieval

function negotiateDatasetMaterializationProfile(
  request: DatasetProfileNegotiationRequest,
): DatasetProfileNegotiationResult

function executeDatasetMaterialization(
  request: DatasetMaterializationRequest,
  runtime: DatasetExecutionCapabilityDescriptor,
  adapter: DatasetMaterializationAdapter,
  authorize: DatasetRecordAuthorizer,
  options?: {
    fallbackProfiles?: DatasetMaterializationProfile[]
    now?: () => string
  },
): Promise<{
  artifacts: DatasetMaterializationArtifact[]
  receipt: DatasetMaterializationReceipt
}>

function executeDatasetRetrieval(
  request: DatasetRetrievalRequest,
  runtime: DatasetExecutionCapabilityDescriptor,
  adapter: DatasetMaterializationAdapter,
  receiptId: string,
  fallbackProfiles?: DatasetMaterializationProfile[],
): Promise<DatasetRetrievalResponse>

`fortemi.dataset-materialization-profile/v1` defines composable canonical, lexical, vector, hybrid, embedding, graph, and community profiles. Receipts bind source/configuration digests, processing run, profile, implementation/model, freshness, privacy decisions, degradation, and measurements. This contract does not alter Knowledge Shard profiles or establish live-server parity.

See the architecture notes in `docs/architecture/dataset-*.md` and the schemas published from the package's `schemas` exports for the complete wire contracts.


Event Bus

`TypedEventBus`

class TypedEventBus {
  on(event: FortemiEvent, handler: EventHandler): () => void
  once(event: FortemiEvent, handler: EventHandler): () => void
  emit(event: FortemiEvent, payload?: unknown): void
  bridge(port: MessagePort): void
}

Typed publish/subscribe bus. All internal subsystems communicate through this bus. `bridge` connects a `MessagePort`, allowing events to be relayed across Worker boundaries.

MethodDescription
`on(event, handler)`Subscribe to an event. Returns an unsubscribe function.
`once(event, handler)`Subscribe for a single emission, then auto-unsubscribe. Returns an unsubscribe function.
`emit(event, payload?)`Publish an event with an optional payload.
`bridge(port)`Forward all events bidirectionally over a `MessagePort`.

Event names:

EventWhen emitted
`note.created`A note was successfully persisted
`note.updated`A note's content or metadata changed
`note.deleted`A note was soft-deleted
`note.restored`A soft-deleted note was restored
`job.completed`A background job finished successfully
`job.failed`A background job terminated with an error
`capability.ready`A capability finished loading and is available
`capability.disabled`A capability was explicitly disabled
`capability.loading`A capability loader started

Capability Manager

`CapabilityManager`

class CapabilityManager {
  enable(name: CapabilityName): Promise<void>
  disable(name: CapabilityName): void
  isReady(name: CapabilityName): boolean
  getState(name: CapabilityName): CapabilityState
  registerLoader(name: CapabilityName, fn: () => Promise<void>): void
  getError(name: CapabilityName): Error | null
  getProgress(name: CapabilityName): string | null
  setProgress(name: CapabilityName, msg: string): void
  listAll(): Array<{ name: CapabilityName; state: CapabilityState }>
}

Manages optional runtime capabilities (ML models, hardware features). Each capability has an independently tracked lifecycle.

MethodDescription
`enable(name)`Invoke the registered loader for the capability. Resolves when ready. Emits `capability.loading` then `capability.ready`.
`disable(name)`Unload a capability and mark it disabled. Emits `capability.disabled`.
`isReady(name)`Returns `true` if the capability state is `'ready'`.
`getState(name)`Returns the current `CapabilityState` for the capability.
`registerLoader(name, fn)`Register the async function that initializes the capability. Must be called before `enable`.
`getError(name)`Returns the `Error` that caused the last load failure, or `null`.
`getProgress(name)`Returns the current progress message string, or `null`.
`setProgress(name, msg)`Update the progress message during a long-running load.
`listAll()`Returns an array of all registered capabilities and their states.

`CapabilityName`

type CapabilityName = 'semantic' | 'llm' | 'audio' | 'vision' | 'pdf'
ValueDescription
`'semantic'`Embedding model for semantic search
`'llm'`Language model for title generation and tagging
`'audio'`Audio transcription
`'vision'`Image understanding
`'pdf'`PDF text extraction

`CapabilityState`

type CapabilityState = 'unloaded' | 'loading' | 'ready' | 'error' | 'disabled'
ValueDescription
`'unloaded'`Loader registered but not yet started
`'loading'`Loader is running
`'ready'`Capability is available for use
`'error'`Loader failed; inspect via `getError()`
`'disabled'`Explicitly disabled via `disable()`

Repositories

All repositories share a common constructor signature unless noted:

constructor(db: PGlite, events?: TypedEventBus)

`events` is optional. When provided, state-changing operations emit the corresponding events on the bus.


`NotesRepository`

class NotesRepository {
  constructor(db: PGlite, events?: TypedEventBus)

  create(input: NoteCreateInput): Promise<NoteFull>
  get(id: string): Promise<NoteFull | null>
  list(options?: NoteListOptions): Promise<PaginatedResult<NoteSummary>>
  update(id: string, input: NoteUpdateInput): Promise<NoteFull>
  delete(id: string): Promise<void>
  restore(id: string): Promise<void>
  star(id: string, starred: boolean): Promise<void>
  pin(id: string, pinned: boolean): Promise<void>
  archive(id: string, archived: boolean): Promise<void>
  addTags(id: string, tags: string[]): Promise<void>
  removeTags(id: string, tags: string[]): Promise<void>
  getRevisions(id: string): Promise<NoteRevision[]>
  getOriginalHistory(id: string): Promise<OriginalContentRevision[]>
}
MethodDescription
`create(input)`Insert a new note. Emits `note.created`.
`get(id)`Retrieve a single note by ID including full content and tags. Returns `null` if not found.
`list(options?)`Retrieve a paginated, filtered list of note summaries.
`update(id, input)`Apply partial updates to a note. Creates a revision snapshot. Emits `note.updated`.
`delete(id)`Soft-delete a note. Emits `note.deleted`.
`restore(id)`Undo a soft-delete. Emits `note.restored`.
`star(id, starred)`Set the starred flag.
`pin(id, pinned)`Set the pinned flag.
`archive(id, archived)`Set the archived flag.
`addTags(id, tags)`Append tags to a note without duplicates.
`removeTags(id, tags)`Remove specific tags from a note.
`getRevisions(id)`Return the ordered revision history for a note.
`getOriginalHistory(id)`Return user-authored original history, newest version first.

Native history reads include original version/user timestamps, revision parent, summary/rationale, generation/user-edit fields and current `last_revision_id`. `NoteFull.original.id` may be null; original identity is scoped by note owner.

`NoteFull.metadata` and native-backend `BackendNoteFull.metadata` expose note metadata separately from `current.ai_metadata`. `NoteCreateInput.metadata` and `NoteUpdateInput.metadata` accept JSON values, including null, arrays and scalar values. Explicit metadata writes and source imports remain independent of AI revision changes. Legacy current-metadata writers retain their prior projection until independent note metadata is authored. Registered metadata predicates use the note field. This does not complete public full-v1 restore/export acceptance. `OriginalContentRevision` is exported by Core and preserves scalar timestamp precision. The public `2.0.0/full-v1` dispatcher restores this native state; released and cross-runtime qualification remains tracked in #424.


`SearchRepository`

class SearchRepository {
  constructor(db: PGlite, semanticAvailable?: boolean)

  search(query: string, options?: SearchOptions, queryEmbedding?: number[]): Promise<SearchResponse>
  semanticSearch(queryEmbedding: number[], options?: SearchOptions): Promise<SearchResponse>
  hybridSearch(query: string, queryEmbedding: number[], options?: SearchOptions): Promise<SearchResponse>
}
MethodDescription
`search(query, options?, queryEmbedding?)`Main entry point. Dispatches to text, semantic, or hybrid based on inputs. Empty query returns recent notes. Quoted phrases use `phraseto_tsquery`.
`semanticSearch(queryEmbedding, options?)`Pure vector similarity search via pgvector cosine distance.
`hybridSearch(query, queryEmbedding, options?)`Combines BM25 text ranking and vector similarity using Reciprocal Rank Fusion (k=60).

Tenant, archive, deletion, and metadata predicates are applied before ranking for text, semantic, hybrid, and recent-note modes. Result locators expose source namespace, schema/import-run metadata, and an external-id hash; they do not return raw external source keys.

The `search()` method routing logic:

  • Query text + embedding = hybrid (RRF fusion)
  • Embedding only (empty query) = semantic (vector cosine)
  • Query text without embedding = text (BM25 tsvector)
  • Empty query, no embedding = recent notes (ordered by created_at DESC)

`buildNoteConditions()`

function buildNoteConditions(
  options: Pick<SearchOptions, 'tags' | 'collection_id' | 'date_from' | 'date_to' | 'is_starred' | 'is_archived' | 'format' | 'source' | 'visibility'>,
  startIdx: number,
  includeDeleted?: boolean,
): { conditions: string[]; params: unknown[]; nextIdx: number }

Shared SQL condition builder used by both `SearchRepository` and `NotesRepository`. Generates parameterized WHERE clause conditions for all filter fields.


`TagsRepository`

class TagsRepository {
  constructor(db: DatabaseClient)
  addTag(noteId: string, tag: string): Promise<void>
  removeTag(noteId: string, tag: string): Promise<void>
  getTagsForNote(noteId: string): Promise<string[]>
  getNotesForTag(tag: string): Promise<string[]>
  listAllTags(): Promise<Array<{ tag: string; count: number }>>
  listRecords(): Promise<TagRecord[]>
}
MethodDescription
`addTag` / `removeTag`Add or remove a note membership. Adding declares the native tag atomically.
`getTagsForNote` / `getNotesForTag`Query native membership in either direction.
`listAllTags()`Return tags used by notes with usage counts.
`listRecords()`Return declared tag records and exact creation timestamps, including unused tags.

`CollectionsRepository`

class CollectionsRepository {
  constructor(db: DatabaseClient)

  create(input: { name: string; description?: string }): Promise<CollectionRow>
  get(id: string): Promise<CollectionRow>
  getRecord(id: string): Promise<CollectionRecord | null>
  list(): Promise<CollectionRow[]>
  update(id: string, input: { name?: string; description?: string }): Promise<CollectionRow>
  delete(id: string): Promise<void>
  assignNote(collectionId: string, noteId: string): Promise<void>
  unassignNote(collectionId: string, noteId: string): Promise<void>
}
MethodDescription
`create(input)`Create a new named collection.
`get(id)`Retrieve an active collection by ID; throws if not found.
`getRecord(id)`Read the full-v1 record with precise timestamp and imported snapshot count, or null if absent.
`list()`List all collections.
`update(id, input)`Update name or description.
`delete(id)`Delete a collection. Does not delete member notes.
`assignNote(collectionId, noteId)`Add membership; an existing membership is unchanged.
`unassignNote(collectionId, noteId)`Remove membership. Membership mutations invalidate imported snapshot counts.

`LinksRepository`

class LinksRepository {
  constructor(db: DatabaseClient)
  create(sourceNoteId: string, targetNoteId: string, linkType?: string): Promise<LinkRow>
  get(id: string): Promise<LinkRow>
  getRecord(id: string): Promise<LinkRecord | null>
  listForNote(noteId: string): Promise<{ outbound: LinkRow[]; inbound: LinkRow[] }>
  getBacklinks(noteId: string): Promise<string[]>
  delete(id: string): Promise<void>
}
MethodDescription
`create(sourceNoteId, targetNoteId, linkType?)`Create a note-target link, reusing an existing active pair/type.
`get(id)`Retrieve a note-target link; throws if absent.
`getRecord(id)`Read either target kind with full-v1 metadata/score/timestamp; rejects unrepresentable values such as a null score.
`listForNote` / `getBacklinks`Read directional note-target relationships.
`delete(id)`Soft-delete either target kind atomically.

`TemplatesRepository`

class TemplatesRepository {
  constructor(db: DatabaseClient)
  create(input: TemplateCreateInput): Promise<TemplateRecord>
  get(id: string): Promise<TemplateRecord>
  list(): Promise<TemplateRecord[]>
  update(id: string, input: Partial<TemplateCreateInput>): Promise<TemplateRecord>
  delete(id: string): Promise<void>
}

`TemplateCreateInput` requires `name` and `content`; optional fields are `description`, `format`, `default_tags` and `collection_id`. Records preserve native identities, ordered default tags and precise timestamps. Invalid records reject before mutation. `get` throws when absent; deletion removes the record.


`SkosRepository`

class SkosRepository {
  constructor(db: DatabaseClient)

  createScheme(title: string, description?: string): Promise<SkosScheme>
  createConcept(schemeId: string, prefLabel: string, options?: { altLabels?: string[]; definition?: string }): Promise<SkosConcept>
  createRelation(sourceConceptId: string, targetConceptId: string, relationType: 'broader' | 'narrower' | 'related'): Promise<SkosRelation>
  tagNote(noteId: string, conceptId: string): Promise<NoteSkosTag>
  untagNote(noteId: string, conceptId: string): Promise<void>
  conceptsForNote(noteId: string): Promise<SkosConcept[]>
  listSchemes(): Promise<SkosScheme[]>
  listConcepts(schemeId: string): Promise<SkosConcept[]>
  getRelations(conceptId: string): Promise<SkosRelation[]>
  deleteScheme(id: string): Promise<void>
  deleteConcept(id: string): Promise<void>
  getSchemeRecord(id: string): Promise<SkosSchemeRecord | null>
  getConceptRecord(id: string): Promise<SkosConceptRecord | null>
  getLabels(conceptId: string): Promise<SkosLabel[]>
  getNotes(conceptId: string): Promise<SkosNote[]>
  getMappings(conceptId: string): Promise<SkosMapping[]>
  getSchemeMemberships(conceptId: string): Promise<SkosMembership[]>
  getAssignments(noteId: string): Promise<NoteSkosAssignment[]>
  listCollections(schemeId?: string): Promise<SkosCollection[]>
  getCollectionMembers(collectionId: string): Promise<SkosCollectionMember[]>
}

Native records preserve rich scheme/concept fields, multilingual labels and notes, semantic relations, external mappings, memberships, assignments and collections. Rich record methods return timestamp strings with retained source precision; existing display methods keep their `Date` timestamp contract. Collection members sort by position, with null positions last. Scheme/concept record reads exclude soft-deleted rows.

Concept creation writes English labels, an optional definition and a primary scheme membership transactionally. Label/note edits update flattened display fields, which are projections rather than rich record authority. These native reads participate in public full-v1 native restoration; released qualification remains tracked in #424.

MethodDescription
`createScheme(title, description)`Create a scheme; its initial notation is its generated ID.
`createConcept(schemeId, prefLabel, options)`Create a concept and native labels, definition and membership.
`createRelation(sourceId, targetId, type)`Assert a `'broader'`, `'narrower'`, or `'related'` relation.
`tagNote(noteId, conceptId)`Attach a SKOS concept to a note idempotently.
`untagNote(noteId, conceptId)`Remove a note-to-concept assignment.
`conceptsForNote(noteId)`Read active SKOS concepts assigned to a note.
`getSchemeRecord(id)`Retrieve a rich active scheme record by ID.
`getConceptRecord(id)`Retrieve a rich active concept record by ID.
`listConcepts(schemeId)`List all concepts belonging to a scheme.

`ProvenanceRepository`

class ProvenanceRepository {
  constructor(db: PGlite)

  recordProvenance(
    entityType: string,
    entityId: string,
    input: {
      activity: string
      agent: string | null
      startedAt?: Date | string
      endedAt?: Date | string | null
      attributes?: unknown
    }
  ): Promise<ProvenanceEdge>

  forEntity(entityType: string, entityId: string): Promise<ProvenanceEdge[]>
  getActivity(id: string): Promise<ProvenanceActivity | null>
  activitiesForNote(noteId: string): Promise<ProvenanceActivity[]>
  derivationsForRevision(revisionId: string): Promise<ProvenanceDerivation[]>
  getNamedLocation(id: string): Promise<NamedLocation | null>
  getLocation(id: string): Promise<ProvenanceLocation | null>
  getDevice(id: string): Promise<ProvenanceDevice | null>
  getCapture(id: string): Promise<ProvenanceCapture | null>
  captureForNote(noteId: string): Promise<ProvenanceCapture | null>
}

First-class W3C PROV write/read surface over `provenance_edge`, so consumers do not need raw SQL to record lifecycle events.

The historical `ProvenanceEdge` name denotes a local activity. Rich derivations are separate `ProvenanceDerivation` records. Note reads include activities owned by that note's revisions. Agents may be null and metadata is arbitrary JSON; narrow `unknown` before object-field access. JSON strings are values, not encoded objects to parse a second time. `useNoteProvenance` retains the same values.

Capture records expose exact structured time ranges. Geometry getters emit EWKB from current native GeoJSON, retaining original byte order only for unchanged values. These APIs expose the native provenance restored by public `full-v1` import. Released producer/consumer qualification remains tracked in #424.


`AttachmentsRepository`

class AttachmentsRepository {
  constructor(db: PGlite, blobStore: BlobStore)

  attach(input: { noteId: string; filename: string; mimeType: string; data: Uint8Array }): Promise<AttachmentRow>
  get(id: string): Promise<AttachmentRow | null>
  getBlob(id: string): Promise<Uint8Array | null>
  list(noteId: string): Promise<AttachmentRow[]>
  delete(id: string): Promise<void>
}

Unlike other repositories, `AttachmentsRepository` takes a `BlobStore` instead of `TypedEventBus` as its second argument, because attachment data is stored outside the database.

MethodDescription
`attach(input)`Write binary data to the blob store and record metadata in the database.
`get(id)`Retrieve attachment metadata without the binary payload.
`getBlob(id)`Retrieve the raw binary data for an attachment.
`list(noteId)`List all attachments for a note (metadata only).
`delete(id)`Remove metadata from the database and delete the blob.

`EmbeddingSetsRepository`

class EmbeddingSetsRepository {
  constructor(db: PGlite, events?: TypedEventBus)

  getConfig(id: string): Promise<EmbeddingConfigRow>
  listConfigs(): Promise<EmbeddingConfigRow[]>
  listMembers(setId: string): Promise<EmbeddingMemberRow[]>
  listEmbeddings(setId: string): Promise<EmbeddingRow[]>
  create(input: EmbeddingSetCreateInput): Promise<EmbeddingSetRow>
  ensureDefault(): Promise<EmbeddingSetRow>
  get(id: string): Promise<EmbeddingSetRow>
  list(): Promise<EmbeddingSetRow[]>
  listDescriptors(): Promise<EmbeddingSetDescriptor[]>
  createVirtualDefinition(input: VirtualEmbeddingSetDefinition): Promise<EmbeddingSetRow>
  putEmbedding(input: EmbeddingSetEmbeddingInput): Promise<{ id: string }>
  resolveSelector(selector: EmbeddingSetSelector): Promise<ResolvedEmbeddingSet>
}

Manages physical and virtual embedding sets. Selectors can target explicit sets, criteria-based sets, set operations, latest-compatible sets, snapshots, or fallback chains. Virtual definitions are durable metadata until materialized by application code.

Configuration reads include native provider, MRL and document-composition metadata. `listMembers` includes declared memberships even without a vector. `listEmbeddings` includes all chunks and metadata-only records with null vectors; selector resolution excludes rows without usable note vectors. `EmbeddingRow` exposes nullable owners/timestamps and explicit contract-fingerprint presence. `EmbeddingSetRow` also exposes stored index/refresh and agent metadata. These native reads expose public full-v1 restored state; released qualification remains tracked in #424.

Semantic and hybrid queries compare vectors of the query dimension, exclude null vectors and rank each note once using its best matching chunk. An explicit set selector retains all chunks in the source set chosen for each note. Native linking also requires the same set/model; 384- and 768-dimensional data can coexist.


`GraphRepository`

class GraphRepository {
  constructor(db: PGlite)

  buildLinkGraph(): Promise<CommunityGraph>
  buildSimilarityGraph(embeddingSet: string | EmbeddingSetSelector, options?: SimilarityGraphOptions): Promise<CommunityGraph>
  buildOrLoadSimilarityGraph(request: SimilarityGraphRequest): Promise<SimilarityGraphResult>
  saveSimilarityGraphArtifact(input: {
    graph: CommunityGraph
    request: Required<Pick<SimilarityGraphRequest, 'selector' | 'k' | 'minSimilarity' | 'metric' | 'source'>>
    resolved: ResolvedEmbeddingSet
    cacheKey: SimilarityGraphCacheKey
    freshness?: 'fresh' | 'stale' | 'unknown'
  }): Promise<SimilarityGraphResult['graphSource']>
  markSimilarityGraphStale(graphSourceId: string, reason: string): Promise<void>
  getSourceRecord(graphSourceId: string): Promise<GraphSourceRecord | null>
  getEdgeRecords(graphSourceId: string): Promise<GraphEdgeRecord[]>
  loadGraphArtifact(graphSourceId: string, noteIds?: string[], communitySetId?: string): Promise<CommunityGraph>
}

Builds citation and embedding-similarity graphs, detects communities, and persists precomputed graph artifacts for shard export/import and UI reuse. Cached graph results include source metadata, freshness, and cache status.

Passing `communitySetId` loads that set's stored communities and assignments, including declared empty communities, without recomputing memberships. The set must belong to the requested graph source. Omitting it retains the existing computed-community behavior. Rich getters preserve nullable metadata, ranks, exact scalar timestamps and graph-scoped edge identities.


`CommunitiesRepository`

class CommunitiesRepository {
  constructor(db: PGlite)

  previewDynamicCommunity(filters: CommunityFilterDefinition): Promise<CommunityAssignmentView[]>
  saveCommunity(input: CommunityCreateInput): Promise<CommunitySourceDescriptor>
  rerunDynamicCommunity(sourceId: string): Promise<CommunityAssignmentView[]>
  listCommunitySources(): Promise<CommunitySourceDescriptor[]>
  getCommunityAssignments(sourceId: string): Promise<CommunityAssignmentView[]>
  listCommunitySummaries(sourceId: string): Promise<CommunitySummary[]>
  getCommunitySet(sourceId: string): Promise<CommunitySetRecord | null>
  getCommunityRecords(sourceId: string): Promise<CommunityRecord[]>
  getAssignmentRecords(sourceId: string): Promise<CommunityAssignmentRecord[]>
}

Provides runtime-only dynamic community previews plus persisted dynamic snapshots and user-authored communities. Saved communities use the graph/community artifact tables so they can round-trip through Knowledge Shards.

Rich getters retain null/empty/value distinctions and the nested community array order, independently of display rank. Graph, set and community IDs are case-sensitive opaque strings. `saveCommunity` writes its source, set, child and assignments in one transaction. These native APIs do not by themselves complete public full-v1 restore/export acceptance; #424 remains the integration gate.


Repository Types

`NoteSummary`

interface NoteSummary {
  id: string
  title: string | null
  snippet: string
  tags: string[]
  starred: boolean
  pinned: boolean
  archived: boolean
  deletedAt: string | null
  createdAt: string
  updatedAt: string
}

Lightweight note projection used in list results.


`NoteFull`

interface NoteFull extends NoteSummary {
  content: string
  embedding: number[] | null
}

Full note including raw content and optional embedding vector.


`NoteCreateInput`

interface NoteCreateInput {
  content: string
  title?: string
  tags?: string[]
}

`NoteUpdateInput`

interface NoteUpdateInput {
  content?: string
  title?: string
  tags?: string[]
  starred?: boolean
  pinned?: boolean
  archived?: boolean
}

All fields are optional; only provided fields are written.


`NoteListOptions`

interface NoteListOptions {
  page?: number
  pageSize?: number
  tags?: string[]
  starred?: boolean
  pinned?: boolean
  archived?: boolean
  includeDeleted?: boolean
  orderBy?: 'createdAt' | 'updatedAt' | 'title'
  orderDir?: 'asc' | 'desc'
}

`PaginatedResult<T>`

interface PaginatedResult<T> {
  items: T[]
  total: number
  page: number
  pageSize: number
  hasNext: boolean
}

`SearchResult`

interface SearchResult {
  note: NoteSummary
  score: number
  highlights?: string[]
  has_embedding?: boolean  // whether the note has a vector embedding (for semantic search readiness)
}

`SearchFacets`

interface SearchFacets {
  tags: { tag: string; count: number }[]
  collections: { id: string; name: string; count: number }[]
}

Aggregate counts from the full (unpaginated) result set. Present on `SearchResponse` when `include_facets: true`.


`SearchResponse`

interface SearchResponse {
  results: SearchResult[]
  total: number
  query: string
  mode: 'text' | 'semantic' | 'hybrid'
  semantic_available: boolean
  limit: number
  offset: number
  facets?: SearchFacets
}

The `mode` field reflects the actual search mode used. `facets` is present when `include_facets: true` was requested.


`SearchOptions`

interface SearchOptions {
  limit?: number           // 1-100, default: 20
  offset?: number          // default: 0
  tags?: string[]          // filter: notes with ANY of these tags
  collection_id?: string   // filter: notes in this collection
  date_from?: Date         // filter: created on or after
  date_to?: Date           // filter: created on or before
  is_starred?: boolean     // filter: starred status
  is_archived?: boolean    // filter: archived status
  format?: string          // filter: 'markdown' | 'plain' | 'html'
  source?: string          // filter: 'user' | 'mcp' | 'import' | 'api'
  visibility?: string      // filter: 'private' | 'shared' | 'public'
  tenant_id?: string       // source-identity tenant scope
  archive_id?: string | null // source-identity archive scope
  metadataPredicates?: MetadataPredicate[] // indexed metadata filters
  include_facets?: boolean // include tag/collection aggregate counts (default: false)
  mode?: 'text' | 'semantic' | 'hybrid' | 'auto'  // override search mode (default: 'auto')
}

All filters apply uniformly across text, semantic, hybrid, and recent-note search modes. Metadata predicates are bounded and allowlisted to indexed fields: `provider`, `model`, `role`, `event_kind`, `sensitivity`, and `import_run_id`. Unsupported paths fail clearly rather than running unbounded scans.

`SourceUpsertRepository`

class SourceUpsertRepository {
  constructor(db: DatabaseClient, events?: TypedEventBus)
  upsertBatch(items: SourceUpsertItem[], options?: SourceUpsertOptions): Promise<SourceUpsertBatchResult>
}

type SourceUpsertPolicy = 'replace' | 'version' | 'conflict'
type SourceUpsertOutcome = 'inserted' | 'unchanged' | 'versioned' | 'replaced' | 'conflict' | 'rejected'

Atomically imports source-addressed notes by tenant, archive, source namespace, and external id. Exact replays return `unchanged` without duplicating notes, revisions, jobs, source identities, or import runs. Changed content follows the per-item `policy`: replace current content, create a versioned revision, or report a conflict without mutation. Results include source-key hashes and content digests, not raw external ids.

`LifecyclePurgeRepository`

class LifecyclePurgeRepository {
  constructor(db: DatabaseClient, events?: TypedEventBus, blobStore?: BlobStore)
  preview(selector: PurgeSelector): Promise<PurgePreview>
  begin(request: PurgeRequest): Promise<PurgeStatus>
  status(operationId: string): Promise<PurgeStatus | null>
  resume(operationId: string): Promise<PurgeStatus>
  purge(selector: PurgeSelector, operationId: string): Promise<DeletionReceipt>
}

Provides authority-bound terminal purge for note ids or source-identity selectors. Preview counts match terminal receipt counts; begin removes note rows, revisions, links, tags, embeddings, graph edges, provenance edges, attachments, and source identity rows atomically. Configured BlobStore bytes are observable as `cleanup_pending` and removed idempotently by `resume`. Re-running the same operation returns the same content-free deletion receipt.


`NoteRevision`

interface NoteRevision {
  id: string
  noteId: string
  content: string
  title: string | null
  createdAt: string
}

Immutable snapshot of a note's content at the time of an update.


`CollectionRow`

interface CollectionRow {
  id: string
  name: string
  description: string | null
  noteCount: number
  createdAt: string
  updatedAt: string
}

`LinkRow`

interface LinkRow {
  id: string
  sourceId: string
  targetId: string
  relation: string | null
  confidence: number | null   // similarity score (1 - cosine distance) for semantic links
  updated_at: Date | null     // last modification timestamp
  createdAt: string
}

`AttachmentRow`

interface AttachmentRow {
  id: string
  noteId: string
  filename: string
  mimeType: string
  size: number
  createdAt: string
}

`AttachmentBlobRow`

interface AttachmentBlobRow extends AttachmentRow {
  data: Uint8Array
}

Tool Functions

Tool functions accept plain input objects, validate them with Zod, and return structured results. `FortemiToolManifest` currently registers 10 bridge-visible Fortemi tools; `@fortemi/core` also exports `manageAttachments` as a direct helper because it requires both `db` and `blobStore`.


`captureKnowledge(db, events, input)`

function captureKnowledge(
  db: PGlite,
  events: TypedEventBus,
  input:
    | { action: 'create'; content: string; title?: string; tags?: string[] }
    | { action: 'bulk_create'; notes: NoteCreateInput[] }
    | { action: 'from_template'; templateId: string; variables: Record<string, string> }
): Promise<NoteFull | NoteFull[]>

Create one or more notes. `from_template` expands a stored template with variable substitution.


`manageNote(db, events, input)`

function manageNote(
  db: PGlite,
  events: TypedEventBus,
  input:
    | { action: 'update'; id: string } & NoteUpdateInput
    | { action: 'delete'; id: string }
    | { action: 'restore'; id: string }
    | { action: 'archive'; id: string; archived: boolean }
    | { action: 'star'; id: string; starred: boolean }
): Promise<NoteFull | void>

Mutate an existing note's state or metadata.


`searchTool(db, input)`

function searchTool(
  db: PGlite,
  input:
    | { mode: 'text'; query: string; options?: SearchOptions }
    | { mode: 'semantic'; embedding: number[]; options?: SearchOptions }
    | { mode: 'hybrid'; query: string; embedding: number[]; options?: SearchOptions }
): Promise<SearchResponse>

Unified search entry point covering all three search modes.


`getNote(db, input)`

function getNote(
  db: PGlite,
  input: { id: string }
): Promise<NoteFull | null>

Retrieve a single note by ID.


`listNotes(db, input)`

function listNotes(
  db: PGlite,
  input: NoteListOptions
): Promise<PaginatedResult<NoteSummary>>

Retrieve a paginated, filtered note list.


`createRemoteBackend(config)`

function createRemoteBackend(config: {
  baseUrl: string
  id?: string
  fetchImpl?: typeof fetch
  headers?: HeadersInit | (() => HeadersInit | Promise<HeadersInit>)
  authToken?: string
  paths?: Partial<RemoteBackendPaths>
}): RemoteDataBackend

Creates the network-backed Fortemi server tier for `selectBackend`. The returned backend advertises `read`, `write`, `multiUser`, `semantic: 'server'`, `merge: false`, and `startupCost: 'network'`. These describe implemented dispatch, not authorization or provider availability for a particular server request.

A fourth seam adapter, `createRecordBackend`, serves the writable canonical record tier with no PGlite at all — see Canonical Records.

Readable `DataBackend` implementations expose `listNotes`, `getNote` and `search`, with optional full-note and relationship methods. Remote `getNoteFull` includes `provenanceGraph`, preserving server activities, edges, current chain and derived note IDs. Use `provenanceGraphOf(id)` for that graph. Remote `provenanceOf` throws `RemoteBackendError` with `kind: 'unsupported-operation'`; it cannot represent the server graph as local PGlite edges. PGlite and shard methods are unchanged.

Remote links include `direction` relative to the requested note and retain both endpoints. Remote concepts mark `altLabels` and `definition` in `unavailableFields`: their empty/null placeholders do not assert absence. `getNote`/`getNoteFull` return null only for a recognized note-not-found response. Authorization, transport, malformed response and enrichment errors throw `RemoteBackendError` with bounded status/problem metadata and no raw body text.

Remote `search(query, options?)` uses `q` and defaults to `mode: 'fts'` with a 20-hit limit. Supported modes are `fts`, `semantic`, and `hybrid`; the adapter admits integer limits 1-100. `tags` is an AND-filter encoded as one comma-separated value; empty, padded or comma-bearing tag values reject. Nonzero offset and nonempty source filters are unsupported and reject before dispatch. This endpoint does not provide offset pagination or facets. `totalKind: 'returned-hits'` makes the producer's count semantics explicit.

Search metadata preserves actual EnhancedSearchHit scores, snippets and optional chain/embedding fields. Timestamps are enriched with at most 100 sequential validated note-detail reads, preserving result order and reusing duplicate IDs. This is not an atomic snapshot: an enrichment failure fails the whole operation, and detail metadata can be newer than the search result. No empty timestamps or partial-success records are manufactured.

`RemoteSearchResult` includes `requestedMode`, `effectiveMode`, `degraded` and optional `degradation` (`code`, `effective_mode`). `semanticWithReport(query, k?)` retains explicit fallback results. The legacy array-returning `semantic(query, k?)` throws `RemoteBackendError` with `kind: 'degraded-search'` on fallback, before detail enrichment; it does not present FTS results as vector retrieval.

`manageNote(input)` accepts only these validated shapes:

ActionFields besides actionREST operation
`create``content`, optional `title`, `tags`, `source`POST notes, revision mode none, empty pipeline
`update``note_id`, `content` and/or `tags`PATCH note; content uses revision mode none
`star` / `unstar``note_id`PATCH starred boolean
`archive` / `unarchive``note_id`PATCH archived boolean
`delete``note_id`DELETE note, body-free 204
`restore``note_id`POST note/restore with revision mode none

Tag updates replace the tag set. Unknown actions/fields, including unsupported title/format/visibility updates, reject before dispatch as `invalid-request`. The result contains `action` and `note_id`; only PATCH responses include the projected `note`. Create/restore acknowledgements are identity-validated without an additional detail fetch. A failed transport or response validation does not prove that a dispatched mutation rolled back. Restore may queue server indexing work even with AI revision disabled. No automatic mutation retries are performed.

Legacy `paths.manageNote` and `paths.semantic` overrides reject as unsupported; use operation-specific `notes`, `note`, `restore` and `search` paths. A path override cannot translate a tool-intent or MCP envelope into a REST contract.

Qualification boundary: pinned producer fixtures and source tests do not establish released-consumer parity, successful vector retrieval or auth qualification. Those acceptance gates remain tracked by #417-421 and producer #1146.


`manageTags(db, input)`

function manageTags(
  db: PGlite,
  input:
    | { action: 'list' }
    | { action: 'frequency' }
    | { action: 'suggest'; partial: string }
): Promise<string[] | Array<{ tag: string; count: number }>>

`manageCollections(db, input)`

function manageCollections(
  db: PGlite,
  input:
    | { action: 'create'; name: string; description?: string }
    | { action: 'get'; id: string }
    | { action: 'list' }
    | { action: 'update'; id: string; name?: string; description?: string }
    | { action: 'delete'; id: string }
    | { action: 'add_notes'; collectionId: string; noteIds: string[] }
    | { action: 'remove_notes'; collectionId: string; noteIds: string[] }
): Promise<CollectionRow | CollectionRow[] | void>

`manageLinks(db, input)`

function manageLinks(
  db: PGlite,
  input:
    | { action: 'create'; sourceId: string; targetId: string; relation?: string }
    | { action: 'get'; id: string }
    | { action: 'list'; noteId?: string }
    | { action: 'delete'; id: string }
    | { action: 'get_related'; noteId: string }
): Promise<LinkRow | LinkRow[] | NoteSummary[] | void>

`manageArchive(manager, input)`

function manageArchive(
  manager: ArchiveManager,
  input:
    | { action: 'open'; name: string }
    | { action: 'list' }
    | { action: 'create'; name: string; persistence: PersistenceMode }
    | { action: 'switch'; name: string }
    | { action: 'delete'; name: string }
): Promise<unknown>

`manageCapabilities(manager, input)`

function manageCapabilities(
  manager: CapabilityManager,
  input:
    | { action: 'enable'; name: CapabilityName }
    | { action: 'disable'; name: CapabilityName }
    | { action: 'status'; name?: CapabilityName }
): Promise<CapabilityState | Array<{ name: CapabilityName; state: CapabilityState }> | void>

`manageAttachments(db, blobStore, input)`

function manageAttachments(
  db: PGlite,
  blobStore: BlobStore,
  input:
    | { action: 'attach'; noteId: string; filename: string; mimeType: string; data: Uint8Array }
    | { action: 'list'; noteId: string }
    | { action: 'get'; id: string }
    | { action: 'get_blob'; id: string }
    | { action: 'delete'; id: string }
): Promise<AttachmentRow | AttachmentBlobRow | AttachmentRow[] | void>

Job Queue

The job queue processes background tasks (embedding generation, title generation, tagging, linking) asynchronously inside a Worker or on the main thread.


`JobQueueWorker`

class JobQueueWorker {
  constructor(
    db: PGlite,
    events: TypedEventBus,
    options: { pollIntervalMs?: number; batchSize?: number },
    capabilityManager: CapabilityManager
  )

  registerHandler(jobType: JobType, handler: JobHandler): void
  start(): void
  stop(): void
  processOnce(): Promise<number>
}
MethodDescription
`registerHandler(jobType, handler)`Register an async function to handle a specific job type.
`start()`Begin polling the job queue at the configured interval.
`stop()`Stop polling. In-flight jobs complete before the worker halts.
`processOnce()`Process one batch of pending jobs immediately. Returns the count processed.

`enqueueJob(db, input)`

function enqueueJob(
  db: PGlite,
  input: { noteId: string; jobType: JobType; priority?: number; requiredCapability?: string | null }
): Promise<string>

Insert a job into the queue. Returns the job ID.


`enqueueNoteCreationJobs(db, noteId, hasTitle)`

function enqueueNoteCreationJobs(
  db: PGlite,
  noteId: string,
  hasTitle: boolean
): Promise<void>

Convenience function that enqueues the standard set of post-creation jobs: embedding, concept tagging, linking, and optionally title generation (when `hasTitle` is `false`).


`enqueueFullWorkflow(db, noteId)`

async function enqueueFullWorkflow(db: PGlite, noteId: string): Promise<void>

Enqueues the complete 5-job pipeline for a note in correct dependency order: AI revision (1) → title generation (2) → embedding (3) → concept tagging (4) → linking (5).


`getJobQueueStatus(db, noteId?)`

function getJobQueueStatus(
  db: PGlite,
  noteId?: string
): Promise<Array<{ jobId: string; jobType: JobType; status: string; createdAt: string }>>

Return current queue status. Pass `noteId` to filter to jobs for a specific note.


Built-in Job Handlers

The following handler functions are registered on a `JobQueueWorker` to implement background processing. Each conforms to the `JobHandler` type.

ExportJob TypeRequires Capability
`titleGenerationHandler``'title_generation'``'llm'`
`aiRevisionHandler``'ai_revision'``'llm'`
`conceptTaggingHandler``'concept_tagging'``'llm'`
`linkingHandler``'linking'``'llm'`
`embeddingGenerationHandler``'embedding'``'semantic'`

`embeddingGenerationHandler` is exported from the capabilities module rather than the core job queue module.


`JobType`

type JobType =
  | 'title_generation'
  | 'ai_revision'
  | 'embedding'
  | 'concept_tagging'
  | 'linking'

`JOB_PRIORITIES`

const JOB_PRIORITIES: Record<JobType, number>

Default numeric priority values for each job type. Lower numbers run first.

Job TypePriority
`ai_revision`1
`title_generation`2
`embedding`3
`concept_tagging`4
`linking`5

`JOB_CAPABILITIES`

const JOB_CAPABILITIES: Record<JobType, CapabilityName>

Maps each job type to the capability that must be ready before the job can run. If a pending job's required capability is not ready, the worker defers dispatch, keeps the job pending with a `requires capability '<name>' — not ready` message, and emits both `job.blocked` and `capability.required` events.


Capabilities

Utility functions for hardware detection, model selection, and ML function registration.


`detectGpuCapabilities()`

function detectGpuCapabilities(): Promise<GpuCapabilities>

Queries the WebGPU adapter (if available) to detect GPU memory and features.

Returns: A `GpuCapabilities` object with detected hardware details.


`estimateVramTier(caps)`

function estimateVramTier(caps: GpuCapabilities): VramTier

Maps detected GPU capabilities to a discrete VRAM tier used for model selection.

ParameterTypeDescription
`caps``GpuCapabilities`Output of `detectGpuCapabilities()`

Returns: A `VramTier` value.


`selectLlmModel(tier, supportsF16?)`

function selectLlmModel(tier: VramTier, supportsF16?: boolean): string

Returns the recommended model identifier for the given hardware tier.

ParameterTypeDescription
`tier``VramTier`Hardware tier from `estimateVramTier`
`supportsF16``boolean`Optional; prefer F16 quantization when `true`

Returns: A model identifier string (e.g. `'smollm2-135m-instruct-q4_k_m'`).


`setEmbedFunction(fn)` / `getEmbedFunction()`

function setEmbedFunction(
  fn: ((texts: string[], options?: { task?: InferenceTask; model?: string }) => Promise<number[][]>) | null,
): void
function getEmbedFunction(): ((texts: string[], options?: { task?: InferenceTask; model?: string }) => Promise<number[][]>) | null

Register or retrieve the active embedding function. The embedding function is called by `embeddingGenerationHandler` and `semanticSearch`. Must be set before semantic features are used.


`setLlmFunction(fn)` / `getLlmFunction()`

function setLlmFunction(
  fn: ((prompt: string, options?: { maxTokens?: number; temperature?: number; task?: InferenceTask; model?: string }) => Promise<string>) | null,
): void
function getLlmFunction(): ((prompt: string, options?: { maxTokens?: number; temperature?: number; task?: InferenceTask; model?: string }) => Promise<string>) | null

Register or retrieve the active LLM inference function. Called by title generation, tagging, and revision handlers.


`registerSemanticCapability(manager)` / `unregisterSemanticCapability(manager)`

function registerSemanticCapability(manager: CapabilityManager): void
function unregisterSemanticCapability(manager: CapabilityManager): void

Attach or detach the default semantic capability loader from a `CapabilityManager` instance.


`registerSemanticCapabilityWorker(manager, port, options?)`

function registerSemanticCapabilityWorker(
  manager: CapabilityManager,
  port: EmbedTransportPort, // Worker | MessagePort
  options?: EmbedWorkerOptions, // { timeoutMs?: number } — default 30000, 0 disables
): void

Register the semantic capability backed by an off-main-thread transport, so embedding model load and per-query inference never touch the main thread (#180). Core round-trips each `{ texts }` request to the host-owned worker and awaits `number[][]`. Pairs with `handleEmbedRequests(port, embed)` (worker side) and `createWorkerEmbedFunction(port, options?)` (the lower-level primitive). Additive and opt-in; the main-thread `registerSemanticCapability` path is unchanged. See Integration → Off-main-thread embedding transport.


`configureInferenceRuntime(options?)`

function configureInferenceRuntime(options?: {
  providers?: ConfiguredInferenceProvider[]
  routes?: Partial<Record<InferenceTask, ProviderRoutePolicy>>
  activeProviderId?: string
  bridgeHost?: FortemiBridgeHost
  includeBridgeProviders?: boolean
  discoverLocal?: boolean | DiscoveryOptions
  embeddingTaskSelection?: {
    largeDocumentChars?: number
    largeDocumentChunks?: number
  }
  registry?: ProviderRegistry
  events?: TypedEventBus
  capabilityManager?: CapabilityManager
}): Promise<{
  registry: ProviderRegistry
  providers: InferenceProvider[]
  routeValidation: ProviderRouteValidation[]
  routeIssues: ProviderRouteValidationIssue[]
}>

Registers configured inference providers, adapts host bridge providers, optionally discovers local OpenAI-compatible servers, applies task routes, validates the configured routes, and wires `semantic`/`llm` capability loaders when a `CapabilityManager` is provided.

Supported task hints are `embedding.query`, `embedding.document`, `embedding.large-document`, `chat.general`, `chat.revision`, `chat.tagging`, `chat.linking`, and `vision.general`.

OpenAI-compatible `/models` listings and local discovery use the same lightweight model-name classifier for chat, embedding, and vision capability hints.

`embeddingTaskSelection` configures the built-in document embedding pipeline's threshold for switching from `embedding.document` to `embedding.large-document`.

`routeValidation` and `routeIssues` summarize configuration problems immediately after route application. They do not probe providers and do not include prompt, note, embedding, generated text, or key material.

Use the additive definition helpers when a deployment wants to compose provider packs, route presets, and environment-specific overrides:

function defineInferenceRuntime(config: InferenceRuntimeConfig): InferenceRuntimeConfig
function defineInferenceProvider(provider: InferenceProvider): ConfiguredInferenceProvider
function defineOpenAICompatibleProvider(config: OpenAIProviderConfig): ConfiguredInferenceProvider
function defineLegacyInferenceProvider(config: LegacyInferenceProviderConfig): ConfiguredInferenceProvider
function getConfiguredInferenceProviderId(config: ConfiguredInferenceProvider): string
function mergeInferenceRuntimeConfigs(
  ...configs: Array<InferenceRuntimeConfig | null | undefined>
): InferenceRuntimeConfig

`mergeInferenceRuntimeConfigs()` merges providers by configured provider ID with later fragments overriding earlier ones, merges route maps by task, merges `embeddingTaskSelection`, and lets later scalar options such as `activeProviderId`, `discoverLocal`, and `includeBridgeProviders` override earlier fragments.


`ProviderRegistry`

class ProviderRegistry {
  add(provider: InferenceProvider): void
  remove(id: string): void
  setActive(id: string): void
  setRoute(task: InferenceTask, policy: ProviderRoutePolicy): void
  getRoute(task: InferenceTask): ProviderRoutePolicy | undefined
  clearRoute(task: InferenceTask): void
  clearRoutes(): void
  previewRoute(
    task: InferenceTask | undefined,
    capability: keyof ProviderCapabilities,
    requestModel?: string,
  ): ProviderRouteSelection
  probeRoute(
    task: InferenceTask | undefined,
    capability: keyof ProviderCapabilities,
    requestModel?: string,
  ): Promise<ProviderRouteProbeResult>
  validateRoute(task: InferenceTask): ProviderRouteValidation
  validateRoutes(): ProviderRouteValidation[]
  embed(request: EmbedRequest): Promise<EmbedResponse>
  complete(request: CompletionRequest): Promise<CompletionResponse>
  stream(request: CompletionRequest): AsyncIterable<StreamChunk>
}

`ProviderRegistry` keeps the legacy `setEmbedFunction` and `setLlmFunction` bridges in sync while allowing task-specific provider/model selection.

`setRoute()` and `getRoute()` copy route policy arrays and nested requirement arrays at the registry boundary. Hosts can reuse or mutate their own config objects after applying routes without mutating the active registry route.

interface ProviderRoutePolicy {
  providerIds?: string[]
  tiers?: ProviderTier[]
  model?: string
  fallback?: boolean
  requirements?: ProviderRouteRequirements
}

interface ProviderRouteRequirements {
  privacyTiers?: Array<'local' | 'host-managed' | 'external'>
  maxCostTier?: 'free' | 'low' | 'medium' | 'high'
  minContextTokens?: number
  minEmbeddingDimensions?: number
  dataClass?: 'public' | 'private' | 'sensitive' | 'regulated'
  maxInputChars?: number
}

Providers can expose optional runtime profile metadata:

interface ProviderProfile {
  privacyTier?: 'local' | 'host-managed' | 'external'
  costTier?: 'free' | 'low' | 'medium' | 'high'
  maxInputChars?: number
  embeddingDimensions?: number[]
  dataClasses?: Array<'public' | 'private' | 'sensitive' | 'regulated'>
}

Route requirements are enforced before capability dispatch. If no configured provider satisfies the route, capability, and requirement set, the request rejects instead of falling back across that boundary.

Use `validateRoute()` or `validateRoutes()` to check configured routes without probing providers or sending inference payloads. Validation reports missing providers, unsupported capabilities, missing handlers, profile requirement failures, empty explicit chains, and routes with no eligible provider.

For `embed()` and `complete()`, routes with fallback enabled retry the next eligible provider when the selected provider throws. When `providerIds` is present, fallback is limited to that explicit ordered chain; it does not try unlisted registered providers. `provider.fallback` is emitted with provider IDs, error category, and error message only. Streaming requests resolve one provider up front and do not retry mid-stream.

Use the exported validators when building route configuration UI:

function providerSatisfiesRouteRequirements(
  provider: InferenceProvider,
  requirements: ProviderRouteRequirements | undefined,
  capability: keyof ProviderCapabilities,
): boolean

function getProviderRouteRequirementIssue(
  provider: InferenceProvider,
  requirements: ProviderRouteRequirements | undefined,
  capability: keyof ProviderCapabilities,
): string | undefined

`previewRoute()` resolves the provider/model that a task would use without sending inference payloads. `probeRoute()` resolves the same route and calls the selected provider's health probe.

Route configuration changes emit `provider.route.configured` and `provider.route.cleared` with task/provider/model metadata only. Each routed request emits `provider.route.selected` with provider ID/name, tier, capability, task, optional model, and whether a task route matched. Completed requests emit `provider.route.completed` with attempt count, fallback count, and latency. Failed requests emit `provider.route.failed` with attempt count, fallback count, latency, error category, and error message. These events never include prompts, input text, output text, vectors, or API keys.


`registerLlmCapability(manager)` / `unregisterLlmCapability(manager)`

function registerLlmCapability(manager: CapabilityManager): void
function unregisterLlmCapability(manager: CapabilityManager): void

Attach or detach the default LLM capability loader from a `CapabilityManager` instance.


`chunkText(content)`

function chunkText(content: string): string[]

Split a document into overlapping chunks suitable for embedding. Used internally before calling the embed function on long notes.

`selectEmbeddingTask(content, chunks, options?)`

function selectEmbeddingTask(
  content: string,
  chunks: string[],
  options?: {
    largeDocumentChars?: number
    largeDocumentChunks?: number
  },
): 'embedding.document' | 'embedding.large-document'

Returns `embedding.large-document` when content length or chunk count crosses the configured threshold. The default thresholds are exported as `DEFAULT_LARGE_DOCUMENT_CHARS` and `DEFAULT_LARGE_DOCUMENT_CHUNKS`.

Non-React hosts can configure the built-in embedding job directly:

function setEmbeddingTaskSelectionOptions(options?: {
  largeDocumentChars?: number
  largeDocumentChunks?: number
}): void

function getEmbeddingTaskSelectionOptions(): {
  largeDocumentChars?: number
  largeDocumentChunks?: number
}

`cosineSimilarity(a, b)`

function cosineSimilarity(a: number[], b: number[]): number

Compute the cosine similarity between two equal-length embedding vectors.

Returns: A float in the range `[-1, 1]`.


`suggestTags(embedding, tagEmbeddings)`

function suggestTags(
  embedding: number[],
  tagEmbeddings: Array<{ tag: string; embedding: number[] }>
): string[]

Return a ranked list of tags whose embeddings are most similar to the input embedding. Used by the concept tagging job handler.


Migrations and Archive

`MigrationRunner`

class MigrationRunner {
  constructor(db: PGlite)

  run(): Promise<void>
}

Applies all pending schema migrations in order. Safe to call on each startup; already-applied migrations are skipped.


`allMigrations`

const allMigrations: Migration[]

The ordered array of all schema migration definitions used by `MigrationRunner`.


`ArchiveManager`

class ArchiveManager {
  constructor(persistence: PersistenceMode, events?: TypedEventBus)

  open(name: string): Promise<PGlite>
  list(): Promise<string[]>
  create(name: string, persistence?: PersistenceMode): Promise<void>
  switch(name: string): Promise<PGlite>
  delete(name: string): Promise<void>
}

Manages multiple named archives (databases). Each archive is an independent PGlite instance.

MethodDescription
`open(name)`Open an existing archive and return its `PGlite` instance.
`list()`Return names of all known archives for the current persistence mode.
`create(name, persistence?)`Create a new empty archive with optional override persistence mode.
`switch(name)`Open a different archive, replacing the currently active instance.
`delete(name)`Permanently delete an archive and all its data.
`adopt(backend, name?)`Adopt an already-created backend without running migrations — e.g. a PGlite restored from a physical snapshot (the restored data dir already carries the schema + indexes).

Physical DB snapshot — `dumpDbSnapshot` / `restoreDbSnapshot`

A snapshot is a binary dump of a populated PGlite data directory — schema + rows + indexes (including the HNSW vector index) — that restores in a single load with no migration, no shard import, and no client-side HNSW build. It is the fast, pre-indexed, single-version restore option, complementary to logical Knowledge Shards (portable + mergeable, but they pay the import + reindex cost on every load).

Distinct from `ArchiveManager`/`archiveName` (a named persistence store) and from Knowledge Shards (logical, mergeable interchange). "Snapshot" = the physical data-dir image.

// Build-time (Node), after the corpus is populated and the HNSW index built once:
const { data, meta } = await dumpDbSnapshot(db, { compression: 'gzip' })
// Write `data` to e.g. corpus.pgdata and `meta` to corpus.pgdata.meta.json (the sidecar).

// Browser restore — verifies the version stamp BEFORE loading, then no migration/import:
const pglite = await restoreDbSnapshot('/corpus/corpus.pgdata', { persistence: 'memory' })
ExportDescription
`dumpDbSnapshot(db, options?)`Dump a `{ data, meta }` snapshot. `meta` stamps `{ pglite_version, pgvector_version, migration_head, created_at }`.
`restoreDbSnapshot(source, options?)`Restore a `PGlite` from a snapshot (`{data,meta}`, a URL, or `{dataUrl, metaUrl?}`). Verifies the stamp first; throws `DbSnapshotVersionError` on mismatch (catch it to fall back to a shard import).
`verifyDbSnapshotMeta(meta, expected?)`Pure compatibility check → `{ compatible, reasons, warnings }`. Hard gates: snapshot schema, migration head (exact), PGlite major.minor; pgvector is advisory.
`DbSnapshotVersionError`Thrown when a snapshot is incompatible with this build; carries `reasons` + `meta`.
`SUPPORTED_PGLITE_VERSION` / `CURRENT_MIGRATION_HEAD` / `DB_SNAPSHOT_SCHEMA_VERSION`The version values this build restores against.

`createPGliteInstance(persistence, archiveName?, { loadDataDir })` accepts a snapshot blob directly for lower-level use. The React `FortemiProvider` exposes a `snapshotUrl` prop (main execution mode) as the turnkey path.


Canonical Records

The canonical record layer (ADR-013, `records/`) is the writable structured-record tier that exists independently of PGlite: records mirror the SQL rows one-to-one, every mutation commits atomically with a change-journal entry, and the PGlite tables become an optional, rebuildable projection. All exports below come from the `@fortemi/core` root.

`RecordStore` / `createRecordStore(options?)` / `MemoryRecordStore`

interface RecordStore {
  get<C extends RecordCollectionName>(collection: C, id: string): Promise<RecordCollections[C] | null>
  put<C extends RecordCollectionName>(collection: C, record: RecordCollections[C]): Promise<JournalEntry>
  remove(collection: RecordCollectionName, id: string): Promise<JournalEntry>
  applyBatch?(mutations: readonly RecordMutation[]): Promise<JournalEntry[]>
  applyPurgeBatch?(
    mutations: readonly RecordMutation[],
    scrubJournal: readonly RecordJournalKey[],
  ): Promise<JournalEntry[]>
  list<C extends RecordCollectionName>(collection: C, opts?: RecordListOptions): Promise<RecordCollections[C][]>
  journalSince(sinceSeq: number, limit?: number): Promise<JournalEntry[]>
  headSeq(): Promise<number>
  readonly capabilities: RecordStoreCapabilities
  close(): Promise<void>
}

The store contract plus the durable IndexedDB implementation (`createRecordStore` → `IdbRecordStore`, with schema versioning and a newer-schema guard) and the in-memory test double. Collections include canonical note/source records plus private lifecycle previews, operations, cleanup work, erasure targets, and content-free receipts. The built-in stores implement `applyBatch` and `applyPurgeBatch`; the latter removes prior deleted-record journal snapshots in the same transaction. Custom stores without that capability cannot claim terminal purge. Query capabilities remain explicit (`boundedTextScan: true`, `fullTextSearch: false`, `vectorSearch: false`, `sqlJoins: false`).

`CanonicalNotesRepository` / `CanonicalAttachmentsRepository`

DB-free note workflows (CRUD, soft-delete/restore, tags, links, nested collections, recent/by-tag queries, bounded text scan) and attachment workflows (bytes-first attach through the Bytecask `BlobStore`, dedupe, reference-only reads, manifest-derived `reconcileBlobs`/`gcBlobs`). `createCollection(name, description?, parentId?)` validates the optional parent before writing.

Source upsert and RecordStore lifecycle purge

RecordStore equivalents for source-addressed import and terminal purge use journal-compacting atomic mutation, preserve replay idempotence, expose `beginRecordStorePurge`, `recordStorePurgeStatus`, and `resumeRecordStorePurge`, and emit deletion receipts that omit content and raw external source ids. RecordStore Knowledge Shard export reports source identity mappings as typed loss because `record-v1` does not declare that operational mapping.

`createRecordBackend(store, options?)`

function createRecordBackend(store: RecordStore, options?: { id?: string }): DataBackend

Wraps a canonical `RecordStore` as a writable `DataBackend` for `selectBackend`. Advertises `read`, `write`, `merge`, `semantic: 'none'`, and `startupCost: 'instant'`; `manageNote` accepts the same Zod-validated input as the PGlite tool (update / delete / restore / archive / unarchive / star / unstar). Search is a bounded unranked scan, and `conceptsOf`/`provenanceOf` are absent (feature-detect via the optional methods) because the canonical tier does not persist SKOS or provenance records.

`exportShardFromRecords(store, options?)` / `importShardToRecords(store, data, options?)`

function exportShardFromRecords(store: RecordStore, options?: ExportOptions): Promise<Uint8Array>
function importShardToRecords(store: RecordStore, data: Uint8Array | ArrayBuffer, options?: ImportOptions): Promise<ImportResult>

Knowledge Shard handling with zero PGlite. Export honors `collectionId`/`tag` filters, `clusterNotesSize`, and the `includeBlobs` + `blobStore` byte sidecar. Import runs the same ADR-014 signature policy before any write, validates checksums, stages verified sidecar bytes, commits all record and journal mutations through `applyBatch`, and rolls newly promoted bytes back if that batch fails. Legacy unprofiled replace import reconciles tags, memberships, attachments, and note links for imported notes while preserving explicit null revisions and tombstone ordering. RecordStore advertises only the authority-owned `record-v1` subset; its import/export preserves collection hierarchy and reports profile-specific losses.

`projectNotes(db, store)` / `projectRecords(db, store)` / `dropNoteProjection(db)`

function projectNotes(db: DatabaseClient, store: RecordStore): Promise<NoteProjectionResult>
function projectRecords(db: DatabaseClient, store: RecordStore): Promise<RecordProjectionResult>
function dropNoteProjection(db: DatabaseClient): Promise<void>

Project canonical state into the optional PGlite tables. `projectNotes` covers the note tier (notes, originals, current revisions, tags, links, collections, memberships); `projectRecords` composes it with `projectAttachments` (migration 0017) for a full rebuild. Idempotent upserts, reconciliation of canonically hard-removed rows, and drop + rebuild with row-for-row parity — canonical records and Bytecask bytes are never modified. `projectAttachments` / `dropAttachmentProjection` are exported alongside.


Knowledge Shards

A Knowledge Shard is a gzip-compressed tar archive with a declared portability profile. Compatibility with the Rust/PostgreSQL Fortemi server is limited to profiles verified by the server-owned schema and cross-repository import/re-export fixtures; it is not implied for every component. All shard APIs are exported from the `@fortemi/core` root.

`exportShard(db, options?)`

function exportShard(db: DatabaseClient, options?: ExportOptions): Promise<Uint8Array>

interface ExportOptions {
  profile?: 'core-v1' | 'full-v1'
  schemaVersion?: '1.2.0' | '2.0.0'
  includeEmbeddings?: boolean
  collectionId?: string          // export only notes in this collection
  tag?: string                   // export only notes with this tag (e.g. 'app:research')
  embeddingSetIds?: string[]     // export only these sets + member/vector rows
  includeMaterializedSelectors?: boolean
  clusterNotesSize?: number      // emit clustered notes/000.jsonl files for in-place readers
  includeBlobs?: boolean         // pack attachment bytes into a content-addressed blobs/<blake3-hex> sidecar
  blobStore?: BlobStore          // byte source for the sidecar; required when includeBlobs is set
}

Produces the `.shard` archive bytes. With `includeBlobs`, attachment bytes are read from `blobStore` by `content_hash` and packed as BLAKE3-addressed `blobs/<hex>` sidecar entries, making the shard self-contained; a blob the store cannot return is skipped (that attachment stays reference-only) rather than failing the export.

Named profiles require `exportShardWithReport`; `exportShard` rejects a named profile so capability and loss evidence cannot be discarded. The exact `2.0.0/full-v1` PGlite path produces an archive from current native state and requires a `BlobStore` for mandatory attachment bytes. The delivered local implementation receipt makes this exact-tuple path callable for producer and conformance use. It is not included in backend capability advertisements until the independent cross-repository receipt tracked by #382 is delivered. Schema 1.x `full-v1` is not accepted.

For live `2.0.0/full-v1`, select notes by a nonempty `tag` or `collectionId`; supplying both is rejected before producing an archive. `embeddingSetIds` narrows sets and their member/vector rows, not the note set; use it with a note selector to limit both. Empty selectors/lists are rejected. A nonmatching note selector produces no notes or attachment bytes. A nonmatching embedding selector produces no embedding sets but does not remove selected notes. Stored archival snapshots never participate in this dispatcher. Explicit archival export has no scope selectors and returns the stored logical files.

Scoped note exports include their declared tags, histories, revisions, attachment projections and mandatory bytes. Note links require both endpoints to be selected; URL links require their owning note, and provenance requires its selected note or revision. Graph edges and assignments are limited to selected notes and applicable embedding sets. Supporting collection ancestry, referenced graph/SKOS metadata, and shared templates remain profile dependencies, not additional selected notes. This is not an entire-database backup or an authorization boundary for shared metadata. Archival byte roundtrip is distinct from native restore (#424); released consumer qualification and suite NO-GO remain separately governed.

`importShard(db, data, options?)`

For `2.0.0/full-v1`, this dispatcher validates the complete archive and restores all 33 declared components to native tables in one transaction. Native repository CRUD is visible to subsequent exports; archival records cannot shadow it. `skip` preserves existing identities and their owned state, but permits new records to reference existing targets. `replace` reconciles selected owners; `error` rejects an existing identity. Required sidecars are verified and newly promoted bytes are compensated after transaction failure. Released producer/ consumer and platform qualification remains the separate #424 acceptance gate.

Native migration lineage follows record identity and survives ordinary edits. Empty imports and no-op skips do not overwrite existing lineage. An export with incompatible selected histories returns `incompatible-native-migration-lineage` instead of silently replacing metadata; a coherent scope retains exact optional manifest key presence. Native exports regenerate producer/time/checksums and do not carry an imported signature over changed bytes.

For intentionally archival operations, use the separately exported `importFullV1Snapshot(db, data, options?)` and `exportFullV1Snapshot(db, blobStore)` from `@fortemi/core`. The exact tuple must be `2.0.0/full-v1`; both return existing report-bearing result types. Snapshot import options are `conflictStrategy`, `blobStore`, `verifySignature`, and `trustStore` (`FullV1SnapshotImportOptions`). Required attachment bytes must be preserved. Malformed input returns a failure before storage mutation. The conflict unit is the persisted archive tuple: `skip` accepts identical archive bytes only, `replace` replaces that snapshot, and `error` rejects an existing snapshot. These operations do not merge native notes, index imported content, or include later native changes. Archival counts are in `component_counts`; native `counts.notes` remains zero. This separation does not close #424 or widen the historical cross-repository receipt.

function importShard(
  db: DatabaseClient,
  data: Uint8Array | ArrayBuffer,
  options?: ImportOptions,
): Promise<ImportResult>

type ConflictStrategy = 'skip' | 'replace' | 'error'

interface ImportOptions {
  conflictStrategy?: ConflictStrategy      // default 'skip'
  batchSize?: number                       // applied component rows between yields; default 250, zero disables yields
  onProgress?: (progress: ImportProgress) => void
  blobStore?: BlobStore                    // destination for hydrating sidecar attachment bytes
}

interface ImportResult {
  success: boolean
  counts: ImportCounts                     // per-component imported-row counts
  skipped: Partial<ImportCounts>
  warnings: string[]
  errors: string[]
  duration_ms: number
}

Native progress counts declared component rows, including skipped rows as done; nested attachments and communities are fields of their owning row. Callbacks are awaited. Invalid batch sizes and precommit callback failures return a failed import without committed state. The final `index` notification follows commit; its rejection retains `success: true` and adds a warning. Unsigned imports under `verifySignature: 'prefer'` also warn that publisher provenance was not verified.

Structured-error contract: a malformed manifest or component resolves to `{ success: false, errors: [...] }` — the promise does not reject. With `blobStore`, verified sidecar entries are promoted before the logical transaction; a failure rolls back newly promoted hashes and logical writes, while hashes that existed before import remain untouched. Custom stores without the optional `delete` capability fail before promotion. Without a blob store, attachments import as reference-only metadata. In legacy unprofiled `replace` mode, imported-note relationships converge to the archive and older live records cannot revive newer destination tombstones. These legacy semantics do not expand the named `core-v1` contract.

`openShard(source, options?)`

function openShard(source: ShardReaderSource, options?: OpenShardOptions): Promise<ShardReader>

interface OpenShardOptions {
  baseUrl?: string
  fetchImpl?: typeof fetch
  semantic?: StaticSemanticProvider
  maxCachedMatches?: number
  maxComponentBytes?: number
}

interface ShardReader {
  readonly manifest: ShardManifest
  listNotes(options?: ShardListOptions): Promise<{ items: ShardReaderNote[]; total: number }>
  getNote(id: string): Promise<ShardReaderNote | null>
  getNoteFull(id: string): Promise<ShardNoteFull | null>
  search(query: string, options?: ShardSearchOptions): Promise<ShardSearchResult>
  semantic(query: string, k?: number): Promise<Array<{ note: ShardReaderNote; score: number }>>
  linksOf(id: string): Promise<ShardLink[]>
  conceptsOf(id: string): Promise<ShardSkosConcept[]>
  relationsOf(conceptId: string): Promise<ShardSkosRelation[]>
  provenanceOf(id: string): Promise<ShardProvenanceEdge[]>
  close(): void
}

Read a shard in place — no database, no import. Components are fetched and checksum-validated lazily as each is first read (not up front), so opening is cheap even for large shards; clustered-note layouts (`clusterNotesSize` at export) fetch only the clusters they need. Throws when the shard's `min_reader_version` exceeds this build — fall back to `importShard`.

`createCosineSemanticProvider(options)`

function createCosineSemanticProvider(options: {
  embedQuery: (query: string) => Promise<number[]> | number[]
  vectorsFile?: string           // default 'vectors.jsonl'
  vectors?: VectorEntry[]        // supply vectors directly instead of reading a file
}): StaticSemanticProvider

Brute-force cosine provider for `openShard`'s `semantic` option. Fine for small/demo corpora; supply a prebuilt-ANN `StaticSemanticProvider` for full-size corpora.

Prefetch / warm API

function prefetchShard(url: string, options?: PrefetchOptions): Promise<PrefetchResult>
function fromPrefetched(url: string): Uint8Array   // throws if not prefetched
function isShardPrefetched(url: string): boolean
function getPrefetchedSha256(url: string): string | undefined
function clearPrefetchedShard(url?: string): void  // omit url to clear all

`prefetchShard(url, { expectedSha256 })` downloads (optionally via Cache Storage) and verifies the whole-archive SHA-256, warming the bytes for a later `openShard(fromPrefetched(url))` or `importShard`.

Schema validation and low-level helpers

ExportPurpose
`validateShardArchive` / `validateShardManifest` / `validateShardComponentRecord` / `assertShardComponentRecord`Validate against the local `knowledge-shard.schema.json` copy; this is a structural gate, not server-compatibility evidence, until the copy has a pinned server receipt and cross-repository fixtures; returns `ShardSchemaValidationResult`
`getKnowledgeShardSchema()`The parsed JSON Schema object
`packTarGz` / `unpackTarGz`Tar + gzip primitives used by the pipelines
`sha256Hex` / `validateChecksums`Component checksum helpers
`enforceSignaturePolicy`The ADR-014 verify-before-persist gate shared by `importShard` and `importShardToRecords`
`noteToShard`, `noteFromShard`, `linkToShard`, `collectionToShard`, …Per-entity field mappers between browser rows and server-parity shard JSON (one `xToShard`/`xFromShard` pair per component; see `shard/field-mapper.ts`)
`CURRENT_SHARD_VERSION` / `SHARD_FORMAT`Format constants

Service Worker

`registerServiceWorker(options)`

function registerServiceWorker(options?: {
  scriptUrl?: string
  scope?: string
}): Promise<ServiceWorkerRegistration>

Register the Fortemi service worker. The service worker intercepts requests to serve cached assets and route API-style requests to the in-process PGlite instance.


`createRoutes(db, events)`

function createRoutes(
  db: PGlite,
  events: TypedEventBus
): Route[]

Build the route table used by the service worker to dispatch incoming `fetch` events to the appropriate repository method.


`matchRoute(routes, request)`

function matchRoute(
  routes: Route[],
  request: Request
): RouteHandler | null

Find the handler for an incoming request by matching against the route table. Returns `null` if no route matches.


Worker Utilities

`PGliteWorkerClient`

class PGliteWorkerClient {
  constructor(worker: Worker)

  query<T>(sql: string, params?: unknown[]): Promise<T[]>
  exec(sql: string): Promise<void>
  transaction<T>(fn: (tx: TransactionProxy) => Promise<T>): Promise<T>
}

A `PGlite`-compatible client that proxies queries over a `Worker` message channel. Use this on the main thread when PGlite is running in a dedicated worker.


`TransactionProxy`

interface TransactionProxy {
  query<T>(sql: string, params?: unknown[]): Promise<T[]>
  exec(sql: string): Promise<void>
}

Handle passed to transaction callbacks in `PGliteWorkerClient.transaction`. Scoped to the in-flight transaction.


AIWG Index (`@fortemi/core/aiwg-index`)

Database-free tooling for AIWG Fortemi index exports (`aiwg.fortemi.index.export.v1`/`v2`): validate, query, chunk, embed, and project static index files without PGlite. Import from the subpath:

import { createAiwgIndexController, queryAiwgFortemiIndex } from '@fortemi/core/aiwg-index'

The schema authority is vendored at `packages/core/schemas/aiwg-fortemi-index-export.schema.json` (pinned with a provenance receipt).

This subpath is intentionally limited to dependency-free static-index helpers. Build pipelines that convert an AIWG export into a validated Knowledge Shard must import `aiwgFortemiIndexToKnowledgeShard` or `aiwgFortemiIndexToKnowledgeShardWithReport` from `@fortemi/core/aiwg-index-shard`. Full-runtime consumers may continue importing the converters from the top-level `@fortemi/core` entry. The archive-only entry emits the reversible schema 1.2.0 `core-v1` profile. The report-bearing entry emits exact `2.0.0/full-v1` only for lossless input and validates all 33 component files. Defaulted or unavailable AIWG concepts return `archive: null` plus typed loss entries. PGlite import/export support for that exact tuple does not imply unqualified server, RecordStore, or AIWG semantic parity.

`createAiwgIndexController(initialIndex?)`

function createAiwgIndexController(initialIndex?: AiwgFortemiIndexExport): AiwgIndexController

interface AiwgIndexController {
  // Loading
  loadIndex(value: unknown): AiwgFortemiIndexExport
  loadChunkedIndex(manifest: unknown, loader: AiwgChunkedIndexLoader, options?: AiwgChunkedIndexLoadOptions): AiwgFortemiChunkManifest
  getIndex(): AiwgFortemiIndexExport | null
  getChunkedManifest(): AiwgFortemiChunkManifest | null
  getSnapshot(): AiwgIndexControllerSnapshot
  // Query
  query(query?: string, options?: AiwgIndexQueryOptions): AiwgIndexQueryResult
  queryChunked(query?: string, options?: AiwgChunkedIndexQueryOptions): Promise<AiwgChunkedIndexQueryResult>
  getRecord(id: string): Promise<AiwgFortemiRecord>   // resolves projected records via the detail loader
  // Relationship traversal
  neighbors(id: string, options?: AiwgRelationshipTraversalOptions): Promise<AiwgRelationshipTraversalResult>
  relationshipQuery(options?: AiwgRelationshipQueryOptions): Promise<AiwgRelationshipTraversalResult>
  relationshipSet(options: AiwgRelationshipSetOptions): Promise<AiwgRelationshipSetResult>
  // Graph projection
  toCommunityGraph(options?: AiwgIndexGraphOptions): CommunityGraph
  toCommunityGraphChunked(options?: AiwgIndexGraphOptions & { onProgress?: (p: AiwgChunkedIndexProgress) => void }): Promise<CommunityGraph>
  // Review workflow
  setReviewDecision(input: AiwgReviewInput): AiwgReviewDecision
  clearReviewDecision(itemId: string): void
  createReviewDecisionExport(generatedAt?: string): AiwgReviewDecisionExport
  // Lifecycle
  clearChunkCache(): void
  subscribe(listener: AiwgIndexControllerListener): () => void
}

Stateful controller over a whole or chunked index: load once, then query, traverse relationships, project to a `CommunityGraph`, and record review decisions. `subscribe` notifies on load/decision changes.

Query functions

function queryAiwgFortemiIndex(
  index: AiwgFortemiIndexExport,
  query?: string,
  options?: AiwgIndexQueryOptions,
): AiwgIndexQueryResult

interface AiwgIndexQueryOptions {
  types?: string[]
  facets?: Record<string, string[]>
  tags?: string[]
  concepts?: string[]
  privacy?: AiwgPrivacyClassification[]      // 'private' | 'sanitized' | 'public'
  relationshipTargetId?: string
  limit?: number
  offset?: number
  rank?: boolean
  snippets?: boolean
  snippetLength?: number
  weights?: Partial<AiwgIndexQueryWeights>   // title/text/tag/concept/facet/id/source
  includeMatches?: boolean
  searchProfile?: 'default' | 'aiwg-discovery'
}

interface AiwgIndexQueryResult {
  items: AiwgFortemiRecord[]
  total: number
  facets: Record<string, Record<string, number>>
  rankedItems?: AiwgIndexQueryRankedItem[]   // when rank: true — rank, snippet, matches
}

// Semantic and hybrid variants over a static embedding set
function queryAiwgSemanticIndex(index, embeddingSet, queryEmbedding: number[], options?): AiwgStaticSemanticResult[]
function queryAiwgHybridIndex(index, embeddingSet, query: string, queryEmbedding: number[], options?): AiwgStaticHybridResult[]

Chunked indexes

function buildAiwgChunkedIndex(index: AiwgFortemiIndexExport, options?: { partSize?: number /* default 500 */, ... }): AiwgChunkedIndexBuildResult
function createAiwgFetchChunkLoader(baseUrl?: string | URL): AiwgChunkedIndexLoader
function createAiwgFetchDetailLoader(baseUrl?: string | URL): AiwgChunkedIndexDetailLoader

`buildAiwgChunkedIndex` splits a whole index into a manifest + fixed-size parts (optionally search-projected records with per-id detail files) for static hosting. The fetch loaders resolve part/detail `href`s against `baseUrl`. v2 exports with `source.graph` are supported end-to-end.

Static embeddings

function buildAiwgStaticEmbeddingSet(
  index: AiwgFortemiIndexExport,
  options: BuildAiwgStaticEmbeddingSetOptions,   // embed callback + granularity ('body' default)
): Promise<AiwgStaticEmbeddingSet>

function findAiwgStaticDuplicatePairs(index, embeddingSet, threshold = 0.9, options?): AiwgStaticDuplicatePair[]

Validators

Every validator has a `validate` form returning `{ valid, errors }` — total on hostile input, never throws — and an `assert` form that throws on invalid input:

Validate (total)Assert (throwing)Target
`validateAiwgFortemiIndexExport``assertAiwgFortemiIndexExport`Whole index export (v1/v2)
`validateAiwgFortemiChunkManifest``assertAiwgFortemiChunkManifest`Chunk manifest
`validateAiwgFortemiChunkPart``assertAiwgFortemiChunkPart`Chunk part (checked against source export version)
`validateAiwgStaticEmbeddingSet``assertAiwgStaticEmbeddingSet`Static embedding set

Projection and utilities

ExportPurpose
`aiwgFortemiIndexToCommunityGraph(index, options?)`Project records + relationships to a `@fortemi/graph` `CommunityGraph` (relationship weights, community strategy via `AiwgIndexGraphOptions`)
`filterAiwgRecordsByPrivacy(records, options?)`Drop records above the allowed privacy classification (fails closed on unknown values)
`getAiwgFortemiFacets(items)`Facet-name → value → count aggregation
`createAiwgReviewDecisionExport(source, decisions, generatedAt?)`Serialize review decisions for round-trip to AIWG
`resolveAiwgFetchUrl` / `encodeAiwgDetailId` / `aiwgDetailHrefForId`URL/id helpers used by the fetch loaders
`AIWG_SCAN_REQUIRED_FIELDS` / `DEFAULT_AIWG_DUPLICATE_SCAN_MAX_EMBEDDINGS`Constants

@fortemi/graph

Framework-agnostic graph tooling. The projection helpers operate on plain `CommunityGraph` data (structurally identical to what `@fortemi/core` produces), so a graph from `GraphRepository` or `aiwgFortemiIndexToCommunityGraph` drops straight in. `@fortemi/react`'s `GraphView` is built on these helpers, and JS-only hosts can use them to render their own SVG/canvas views without React or PGlite. All projection helpers are pure (no input mutation), deterministic, and database-free, so they tree-shake cleanly. The package depends on `@fortemi/core` for `GraphController` (the graph-source state machine), which reaches core's repositories.

pnpm add @fortemi/graph

Graph Data Model

interface GraphNode { id: string }
interface GraphEdge { source: string; target: string; weight: number; kind?: string }
interface GraphCommunity { id: string; nodes: string[] }
interface CommunityGraph {
  nodes: GraphNode[]
  edges: GraphEdge[]
  communities: GraphCommunity[]
}

type GraphLayoutAlgorithm = 'force' | 'radial' | 'community' | 'manual'

interface PositionedGraphNode extends GraphNode {
  x: number
  y: number
  degree: number
  communityId?: string
}

interface PositionedGraph {
  nodes: PositionedGraphNode[]
  edges: GraphEdge[]
  nodeIndex: Map<string, PositionedGraphNode>
}

interface GraphBounds {
  minX: number; minY: number; maxX: number; maxY: number
  width: number; height: number; centerX: number; centerY: number
}

interface ViewportTransform { scale: number; offsetX: number; offsetY: number }

`@fortemi/graph` re-exports `CommunityGraph` and its member types for hosts that do not depend on `@fortemi/core`. Community detection (`detectCommunities`) lives in `@fortemi/core`, which remains the base layer; this package only projects graphs it is given.

Graph Helpers

// Layout — deterministic 2D positions + per-node degree/community
interface LayoutOptions {
  algorithm?: GraphLayoutAlgorithm
  width?: number
  height?: number
  seed?: number                     // deterministic PRNG seed (`force`)
  ticks?: number                    // settlement iterations (`force`)
  nodeRadius?: NodeRadiusResolver   // per-node render radius
  linkDistance?: number             // target edge length px (`force`)
  linkStrength?: number             // spring stiffness 0..1 (`force`)
  chargeStrength?: number           // repulsion magnitude (`force`)
  collisionPadding?: number         // extra collision spacing (`force`)
  communityStrength?: number        // pull toward community centroid (`force`)
  boundsPadding?: number            // min distance from canvas edges
  pinned?: PositionMap              // positions held fixed during settlement
  initialPositions?: PositionMap    // warm-start seed positions
}
function layoutCommunityGraph(
  graph: CommunityGraph,
  options?: LayoutOptions,
): PositionedGraph

// Filter — by community, edge kind, node allow-list, or predicate
interface GraphFilter {
  communityIds?: string[]
  edgeKinds?: string[]
  nodeIds?: string[]
  nodePredicate?: (node: GraphNode) => boolean
}
function filterCommunityGraph(graph: CommunityGraph | null | undefined, filter?: GraphFilter): CommunityGraph

// Sizing
function computeDegrees(graph: CommunityGraph): Map<string, number>
function nodeRadius(
  degree: number,
  options?: { base?: number; perDegree?: number; min?: number; max?: number },
): number

// Color — deterministic community → color (themeable palette)
const COMMUNITY_COLORS: readonly string[]
const UNASSIGNED_COMMUNITY_COLOR: string
function colorForCommunity(communityId: string | undefined, palette?: readonly string[], unassignedColor?: string): string

// Bounds / fit
function computeGraphBounds(nodes: ReadonlyArray<{ x: number; y: number }>): GraphBounds
function fitGraphToViewport(
  bounds: GraphBounds,
  viewport: { width: number; height: number },
  options?: { padding?: number; minScale?: number; maxScale?: number },
): ViewportTransform

// Selection / neighborhood
function buildAdjacency(graph: CommunityGraph): Map<string, Set<string>>
function neighborsOf(graph: CommunityGraph, nodeId: string): Set<string>
function expandNeighborhood(
  graph: CommunityGraph,
  seeds: Iterable<string>,
  options?: { depth?: number; includeSeeds?: boolean },
): Set<string>
function subgraphForNodes(graph: CommunityGraph, nodeIds: Iterable<string>): CommunityGraph
function neighborhoodSubgraph(graph: CommunityGraph, seeds: Iterable<string>, options?: { depth?: number; includeSeeds?: boolean }): CommunityGraph

// Static snapshots — reproducible JSON for JS-only hosts
interface GraphSnapshot {
  version: number
  graph: CommunityGraph
  layout?: { algorithm: GraphLayoutAlgorithm; width: number; height: number }
  generatedAt?: string
}
const GRAPH_SNAPSHOT_VERSION: number
function serializeGraphSnapshot(graph: CommunityGraph, options?: { layout?: GraphSnapshot['layout']; generatedAt?: string }): GraphSnapshot
function stringifyGraphSnapshot(graph: CommunityGraph, options?: { layout?: GraphSnapshot['layout']; generatedAt?: string }): string
function deserializeGraphSnapshot(input: GraphSnapshot | string): CommunityGraph

See `packages/graph/README.md` for a complete vanilla-JS host example that fetches a snapshot and renders it to SVG using these helpers.

Render Pipeline

Prepares a `CommunityGraph` for any renderer tier (SVG, Sigma 2D, 3D) as a `RenderGraph` — plain nodes/links with resolved colors, labels, and optionally baked positions.

// CommunityGraph → RenderGraph (colors + labels resolved, positions optional)
function mapCommunityGraph(graph: CommunityGraph, options?: MapCommunityGraphOptions): RenderGraph

// Layout + map in one step: positions baked in (x/y on every node)
function bakeRenderGraph(graph: CommunityGraph, options?: BakeRenderGraphOptions): RenderGraph

// Deterministic JSON (sorted nodes/links) for committing snapshots
function stringifyRenderGraph(graph: RenderGraph): string

// Load a snapshot from a URL, object, or factory; null on failure
function loadRenderSnapshot(
  source: string | RenderGraph | (() => Promise<RenderGraph | null> | RenderGraph | null),
  options?: { fetchImpl?: typeof fetch; requirePositions?: boolean },  // requirePositions default true
): Promise<RenderGraph | null>

// Guards and palette helpers
function isRenderGraph(value: unknown): value is RenderGraph
function hasBakedPositions(graph: RenderGraph): boolean
function communityRanks(graph: CommunityGraph): Map<string, number>   // largest community = rank 0
const GREYSCALE_COMMUNITY_RAMP: readonly string[]

Control Contract

Every renderer tier honors the shared `GraphControlContract` — `algorithm`, `filters`, `selectedNodeId` + `onSelectNode`, `onNavigate`, `labelFor`, `colors` — so switching tiers means passing the same options object.

interface GraphControlFilters {
  communityIds?: string[]
  edgeKinds?: string[]
  nodeIds?: string[]
  minDegree?: number
}

// The one filter function every tier runs — visibility is identical across tiers
function applyControlFilters(graph: CommunityGraph | null | undefined, filters?: GraphControlFilters): CommunityGraph

// Shared legend data: { communityId, color, count }[]
function communityLegend(graph: CommunityGraph | null | undefined, colors?: readonly string[]): GraphLegendEntry[]

SVG Renderer

function renderCommunityGraph(
  container: HTMLElement,
  graph: CommunityGraph,
  options?: GraphRenderOptions,   // extends GraphControlContract
): GraphRenderHandle

interface GraphRenderOptions extends GraphControlContract {
  layoutOptions?: Omit<LayoutOptions, 'algorithm' | 'width' | 'height'>
  background?: string       // default '#fafafa'
  interactive?: boolean     // default true (pan/zoom/click)
  minScale?: number
  maxScale?: number
}

interface GraphRenderHandle {
  update(next: GraphRenderUpdate): void   // patch graph/filters/algorithm/selection/labels
  focus(nodeId: string | null): void
  destroy(): void
  readonly element: SVGSVGElement
}

Dependency-free DOM/SVG renderer for vanilla-JS hosts — no React required.

GraphController

class GraphController {
  constructor(db: GraphControllerDb, options?: GraphControllerOptions)
  getState(): GraphControllerState
  subscribe(listener: GraphControllerListener): () => void
  start(): Promise<void>
  refresh(): Promise<void>
  setMode(mode: GraphSourceMode): void
  setEmbeddingSetSelector(selector: EmbeddingSetSelector): void
  setCommunitySource(sourceId: string | null): void
  setFilters(filters: CommunityFilterDefinition): void
  recompute(): Promise<void>
  previewDynamicCommunity(filters: CommunityFilterDefinition): Promise<void>
  saveCurrentCommunity(input: CommunityCreateInput): Promise<CommunitySourceDescriptor>
}

Graph-source state machine: selects the source mode, loads/derives the `CommunityGraph` through core's repositories, and publishes `GraphControllerState` to subscribers. This is the package's one dependency on `@fortemi/core`; the React `useGraphController` hook wraps it.


@fortemi/react

Dataset Workflow

Import the dataset workflow from the dedicated subpath:

import {
  DatasetConnectorForm,
  DatasetLineageView,
  DatasetWorkflowMachine,
  useDatasetWorkflow,
} from '@fortemi/react/dataset'

const workflow = useDatasetWorkflow(connectorSchema, datasetApi)

`DatasetWorkflowMachine` and `useDatasetWorkflow` coordinate schema-driven configuration, staged checks, bounded side-effect-free previews, immutable plan approval, cancellation/retry, status, redacted rejections, and bounded lineage traversal. All I/O is delegated to the supplied `DatasetWorkflowApi`; the React package does not implement a second ingest or lineage backend.

`createMcpDatasetWorkflowApi(options)` provides the Fortemi Server execution adapter. It composes an application-supplied connector API and authority request builder with an injected MCP `callTool` transport. The adapter accepts only the `fortemi-server-mcp` live-remote-persistence runtime and receipt-validation revision `1.0.1`, previews the exact request before execution, validates receipt structure/digests/request bindings locally, and invokes `verify` separately before promoting committed or degraded work to verified completion. A terminal acknowledgement without that receipt remains non-successful. Because `manage_dataset_execution` is not a lineage traversal authority, callers must inject `lineage.traverseLineage` explicitly.

The contract authority remains Fortemi `v2026.9.10` at `306bae55a41eff7e4f8b8349304765322865e6cc`. Fortemi `v2026.9.11` has release source `280e60e23a1f4bb52a2859ea211dee9e45f672be`; commit `b5f4dbebca289ebfee0dca88d2766700cd4c85df` introduced its producer-side declaration for signed consumer commit `6718e9f930193a12b8aed27b61ebf9c47871ac44`. That metadata does not replace the authority or qualify a live deployment.

function createMcpDatasetWorkflowApi(options: DatasetMcpWorkflowOptions): DatasetWorkflowApi
function validateDatasetRunReceiptV101(
  value: unknown,
  prepared?: DatasetPreparedReceiptBinding,
): { valid: boolean; errors: string[] }

The adapter accepts either a standard single-text MCP result envelope or an already decoded object. It does not resolve credential references, serialize connector secrets into plans, or claim live qualification from fixture success.

The subpath exports `DatasetConnectorForm`, `DatasetCheckView`, `DatasetPreviewView`, `DatasetPlanReview`, `DatasetRunView`, `DatasetStatusView`, `DatasetRejectionsView`, `DatasetLineageView`, and `DatasetWorkflowActions`. Credential references and password-shaped properties render as empty write-only inputs. Destructive reconciliation at or above the plan threshold requires the operator to enter the plan ID before approval.

`datasetStatusStoryFixtures` and `datasetTerminalRunStories` provide stable UI states for canonical, derived, cached, stale, degraded, unverifiable, offline-cold/warm, rejected, cancelled, and failed scenarios.


Provider

`FortemiProvider`

function FortemiProvider(props: FortemiProviderProps): JSX.Element

Context provider that initializes a `FortemiCore` instance and makes it available to the component tree. Must wrap all components that use Fortemi hooks.

<FortemiProvider persistence="opfs" archiveName="my-notes">
  <App />
</FortemiProvider>

`FortemiProviderProps`

interface FortemiProviderProps {
  persistence: PersistenceMode
  archiveName?: string
  executionMode?: 'main' | 'worker'
  createWorker?: () => Worker
  snapshotUrl?: string
  snapshotExpectations?: DbSnapshotExpectations
  children: React.ReactNode
}
PropertyTypeRequiredDescription
`persistence``PersistenceMode`YesStorage backend for the underlying PGlite instance
`archiveName``string`NoArchive name; uses a default when omitted
`executionMode``'main' \'worker'`NoRun PGlite on the main thread (default) or in a worker
`createWorker``() => Worker`NoWorker factory for `executionMode: 'worker'`
`snapshotUrl``string`NoRestore from a physical data-dir snapshot (#187) instead of migrating an empty DB — fetches `<url>` + `<url>.meta.json`, verifies the stamp, then loads pre-indexed (no migration/import/HNSW build). Main mode only.
`snapshotExpectations``DbSnapshotExpectations`NoOverride the snapshot version-compatibility expectations
`children``React.ReactNode`YesComponent subtree

`useFortemiContext()`

function useFortemiContext(): FortemiContextValue

Returns the `FortemiContextValue` from the nearest `FortemiProvider`. Throws if called outside a provider.


`FortemiContextValue`

interface FortemiContextValue {
  db: PGlite
  events: TypedEventBus
  archiveManager: ArchiveManager
  capabilityManager: CapabilityManager
  blobStore: BlobStore
}

The raw runtime objects exposed by the provider. Use the typed hooks below for most UI work; access `FortemiContextValue` directly when calling repositories or tool functions manually.


Hooks

All hooks re-render when relevant data changes via the event bus. Initial data is fetched on mount.


`useNotes(options?)`

function useNotes(options?: NoteListOptions): {
  notes: NoteSummary[]
  total: number
  hasNext: boolean
  loading: boolean
  error: Error | null
  refetch: () => void
}

Subscribe to a paginated, filtered list of notes. Re-fetches automatically on `note.created`, `note.updated`, `note.deleted`, and `note.restored` events.


`useNote(id)`

function useNote(id: string): {
  note: NoteFull | null
  loading: boolean
  error: Error | null
  refetch: () => void
}

Fetch and subscribe to a single note. Re-fetches when the note is updated or restored.


`useSearch()`

function useSearch(): {
  data: SearchResponse | null
  loading: boolean
  error: Error | null
  search: (query: string, options?: SearchOptions) => Promise<SearchResponse>
  clear: () => void
}

Automatically dispatches to the best available search mode. When semantic capability is ready, generates a query embedding and passes it to `SearchRepository.search()`, enabling hybrid search (text + vector). When semantic is not available, falls back to text-only search.


`useSearchHistory()`

function useSearchHistory(): {
  history: string[]
  addEntry: (query: string) => void
  removeEntry: (query: string) => void
  clearHistory: () => void
}

Persists search queries to `localStorage` (key: `fortemi:search-history`, max 50 entries). Deduplicates entries with most recent first. Survives archive switches.


`useSearchSuggestions(history?)`

function useSearchSuggestions(history?: string[]): {
  suggestions: Array<{ text: string; source: 'vocabulary' | 'history' }>
  loading: boolean
  getSuggestions: (prefix: string) => void
  clearSuggestions: () => void
  refreshVocabulary: () => Promise<void>
}

Loads vocabulary from `ts_stat` (top 500 words by document frequency) on mount. Merges with search history for prefix-matched suggestions. Pass the `history` array from `useSearchHistory` for history-augmented suggestions.


`useCreateNote()`

function useCreateNote(): {
  createNote: (input: NoteCreateInput) => Promise<NoteFull>
  loading: boolean
  error: Error | null
}

Returns a `createNote` function that constructs a `NotesRepository` from context and calls `NotesRepository.create()`. The repository emits `note.created` and queues post-creation jobs.


`useUpdateNote()`

function useUpdateNote(): {
  updateNote: (id: string, input: NoteUpdateInput) => Promise<NoteFull>
  loading: boolean
  error: Error | null
}

`useDeleteNote()`

function useDeleteNote(): {
  deleteNote: (id: string) => Promise<void>
  restoreNote: (id: string) => Promise<void>
  loading: boolean
  error: Error | null
}

Provides both soft-delete and restore in a single hook.


`useTags()`

function useTags(): {
  tags: string[]
  frequency: Array<{ tag: string; count: number }>
  loading: boolean
  error: Error | null
  suggest: (partial: string) => Promise<string[]>
}

Loads all tags and frequency counts on mount. `suggest` performs an on-demand prefix query.


`useCollections()`

function useCollections(): {
  collections: CollectionRow[]
  loading: boolean
  error: Error | null
  createCollection: (input: { name: string; description?: string }) => Promise<CollectionRow>
  deleteCollection: (id: string) => Promise<void>
  addNotes: (collectionId: string, noteIds: string[]) => Promise<void>
  removeNotes: (collectionId: string, noteIds: string[]) => Promise<void>
}

`useJobQueue(pollMs?)`

function useJobQueue(pollMs?: number): {
  jobs: Array<{ jobId: string; jobType: JobType; status: string; createdAt: string }>
  loading: boolean
}

Polls `getJobQueueStatus` at the specified interval (default: 2000 ms) and exposes the current queue state. Useful for displaying background processing indicators.

ParameterTypeDefaultDescription
`pollMs``number``2000`Polling interval in milliseconds

`useRelatedNotes(noteId, limit?)`

function useRelatedNotes(noteId: string, limit?: number): {
  links: RelatedNote[]
  loading: boolean
}

interface RelatedNote {
  noteId: string
  title: string | null
  confidence: number | null
  linkType: string
  direction: 'outbound' | 'inbound'
}

Returns semantically linked notes for the given note. Merges outbound and inbound links, deduplicates, and sorts by confidence descending. Auto-refreshes when a `linking` job completes for this note.

ParameterTypeDefaultDescription
`noteId``string`requiredNote to find related notes for
`limit``number``3`Maximum number of related notes to return

`useNoteConcepts(noteId)`

function useNoteConcepts(noteId: string): {
  concepts: NoteConcept[]
  loading: boolean
}

interface NoteConcept {
  conceptId: string
  prefLabel: string
  schemeName: string
  schemeId: string
}

Returns SKOS concepts assigned to a note via the `note_skos_tag` table. Joins through `skos_concept` and `skos_scheme` to provide full label and scheme context. Auto-refreshes when a `concept_tagging` job completes for this note.

ParameterTypeDefaultDescription
`noteId``string`requiredNote to retrieve concepts for

`useNoteProvenance(noteId)`

function useNoteProvenance(noteId: string): {
  events: ProvenanceEvent[]
  loading: boolean
}

interface ProvenanceEvent {
  timestamp: Date
  type: 'created' | 'job' | 'revision' | 'provenance'
  label: string
  detail?: string
  agent?: string
  activity?: string
  attributes?: Record<string, unknown> | null
}

Aggregates a chronological provenance timeline from existing data: note creation timestamp, stored note-scoped `provenance_edge` rows, completed job queue entries, and user/AI revisions. Stored W3C PROV-style rows expose their `activity`, `agent`, and `attributes` so UIs can render source lineage, derivation, confidence, and other metadata without raw SQL. Auto-refreshes when any job completes for this note.

Job results are summarized (e.g., `"3 links found"`, `"384-dim vector"`). Stored provenance attributes are passed through unchanged after JSON parsing.

ParameterTypeDefaultDescription
`noteId``string`requiredNote to retrieve provenance timeline for

`useExportShard()`

function useExportShard(): {
  exportShard: (options?: ExportOptions) => Promise<void>
  isExporting: boolean
  progress: ExportProgress | null
  error: Error | null
  report: ShardCapabilityReport | null
}

Exports current data through `exportShardWithReport` and triggers a browser download only after success. The default is `1.2.0/core-v1`: attachment references are included, attachment bytes and advanced components are excluded. `report` exposes the selected capability and every reported omission or normalization, including unsupported-profile failures. Render it alongside `error` in custom export UIs. Embedding, embedding-set, and materialized-selector options are rejected for `core-v1`; blob-sidecar and clustered-file requests are rejected by its producer. Explicit `full-v1` requires `schemaVersion: '2.0.0'` and its own supported-state requirements; the provider's BlobStore is supplied unless overridden.

The core `exportShard(db, options)` API still deliberately emits historical unprofiled React-local archives when no profile is supplied. Historical import behavior is unchanged. Those legacy envelopes are not renamed or advertised as a named server profile. The hook no longer selects that legacy producer by default. Cross-repository claims remain limited to exact receipt-bound profiles and participants; the suite audit remains `NO-GO`.


`useImportShard()`

function useImportShard(): {
  importShard: (file: File, strategy?: ConflictStrategy) => Promise<ImportResult>
  importFromUrl: (url: string, strategy?: ConflictStrategy, prefetchOptions?: PrefetchOptions) => Promise<ImportResult>
  isImporting: boolean
  progress: ImportProgress | null
  error: Error | null
  result: ImportResult | null
}

Imports a Knowledge Shard from a user-selected file or static URL. Warmed URLs from `useShardPrefetch` reuse prefetched bytes.


`useShardPrefetch()`

function useShardPrefetch(): {
  prefetch: (url: string, options?: PrefetchOptions) => Promise<PrefetchResult>
  isPrefetched: (url: string) => boolean
  warming: Record<string, boolean>
  error: Error | null
}

Warms static shard bytes in the background and optionally verifies SHA-256 before an import click.


`useGpuCapabilities()`

function useGpuCapabilities(): {
  caps: GpuCapabilities | null
  vramTier: VramTier | null
  isDetecting: boolean
  error: Error | null
}

Detects WebGPU capabilities and derives a VRAM tier for local inference planning.


`useInferenceCapabilities()`

function useInferenceCapabilities(): {
  capabilities: InferenceCapabilities | null
  loading: boolean
  error: Error | null
}

Detects browser and hardware inference capabilities, including WebGPU/WebNN/Chrome AI availability.


`useInferenceRouting(options?)`

function useInferenceRouting(options?: {
  tasks?: InferenceTask[]
}): {
  providers: InferenceProvider[]
  activeProvider: InferenceProvider | null
  routeValidation: ProviderRouteValidation[]
  routeIssues: ProviderRouteValidationIssue[]
  refresh: () => void
  setActiveProvider: (id: string) => void
  setRoute: (task: InferenceTask, policy: ProviderRoutePolicy) => void
  clearRoute: (task: InferenceTask) => void
  clearRoutes: () => void
  getRoute: (task: InferenceTask) => ProviderRoutePolicy | undefined
  previewRoute: (
    task: InferenceTask | undefined,
    capability?: keyof ProviderCapabilities,
    requestModel?: string,
  ) => ProviderRouteSelection
  probeRoute: (
    task: InferenceTask | undefined,
    capability?: keyof ProviderCapabilities,
    requestModel?: string,
  ) => Promise<ProviderRouteProbeResult>
}

React wrapper around the shared `ProviderRegistry`. It subscribes to provider and route configuration events so embedded host UIs can display provider lists, switch active providers, edit task routes, validate route configuration, and probe resolved routes without managing a separate routing store.


`useLocalDiscovery(options?)`

function useLocalDiscovery(options?: UseLocalDiscoveryOptions): {
  providers: DiscoveredProvider[]
  discovering: boolean
  error: Error | null
  refresh: () => void
}

Probes local inference servers on mount and on the configured interval.


`useEmbeddingPipeline(loader)`

function useEmbeddingPipeline(loader: EmbedFunctionLoader): {
  embedFunction: EmbedFunction | null
  status: 'idle' | 'loading' | 'ready' | 'error'
  progress: string
  error: Error | null
  load: () => void
  unload: () => void
}

Loads a host-provided embedding function on demand and registers it with `@fortemi/core`.


`useEmbeddingWorker(transport, options?)`

function useEmbeddingWorker(
  transport: EmbedTransportPort,
  options?: EmbedWorkerOptions
): {
  status: 'idle' | 'connected'
  connect: () => void
  disconnect: () => void
}

Wires a host-owned `Worker` or `MessagePort` into the core embed function so query and job embedding run off the main thread.


Graph and community hooks

`useEmbeddingSets()`

function useEmbeddingSets(): {
  embeddingSets: EmbeddingSetDescriptor[]
  loading: boolean
  error: Error | null
  refresh: () => Promise<void>
  create: (input: EmbeddingSetCreateInput) => Promise<EmbeddingSetRow>
  createVirtualDefinition: (input: VirtualEmbeddingSetDefinition) => Promise<EmbeddingSetRow>
}

Lists physical and virtual embedding-set descriptors and creates new physical sets or durable virtual definitions.

`useSimilarityGraph(embeddingSet, options?)`

function useSimilarityGraph(
  embeddingSet: string | EmbeddingSetSelector | null | undefined,
  options?: SimilarityGraphOptions & { autoRefresh?: boolean }
): {
  graph: CommunityGraph | null
  graphSource: SimilarityGraphResult['graphSource'] | null
  cache: SimilarityGraphResult['cache'] | null
  freshness: SimilarityGraphResult['freshness'] | null
  loading: boolean
  error: Error | null
  refresh: () => Promise<SimilarityGraphResult | null>
  recompute: () => Promise<SimilarityGraphResult | null>
  markStale: (reason: string) => Promise<void>
}

Loads cached precomputed similarity graphs when available, falls back to live computation, and exposes cache/freshness state for UI decisions.

`useCommunities()`

function useCommunities(): {
  sources: CommunitySourceDescriptor[]
  activeSourceId: string | null
  summaries: CommunitySummary[]
  assignments: Map<string, CommunityAssignmentView>
  loading: boolean
  error: Error | null
  preview: (filters: CommunityFilterDefinition) => Promise<CommunityAssignmentView[]>
  save: (input: CommunityCreateInput) => Promise<CommunitySourceDescriptor>
  rerun: (sourceId: string) => Promise<CommunityAssignmentView[]>
  setActiveSource: (sourceId: string | null) => void
  refresh: (sourceId?: string | null) => Promise<void>
}

Works with persisted community sources and unsaved dynamic previews for search-derived or manually authored groupings.

`useGraphController(options?)`

function useGraphController(options?: UseGraphControllerOptions): {
  mode: 'citations' | 'topics' | 'precomputed' | 'dynamic-search' | 'user-authored'
  graph: CommunityGraph | null
  graphSource?: SimilarityGraphResult['graphSource'] | { id: string; name: string }
  communitySource?: CommunitySourceDescriptor
  embeddingSetSelector?: EmbeddingSetSelector
  filters?: CommunityFilterDefinition
  layout: GraphLayoutState
  status: GraphControllerStatus
  transition?: GraphTransitionState
  setMode: (mode: GraphSourceMode) => void
  setEmbeddingSetSelector: (selector: EmbeddingSetSelector) => void
  setCommunitySource: (sourceId: string | null) => void
  setFilters: (filters: CommunityFilterDefinition) => void
  refresh: () => Promise<void>
  recompute: () => Promise<void>
  previewDynamicCommunity: (filters: CommunityFilterDefinition) => Promise<void>
  saveCurrentCommunity: (input: CommunityCreateInput) => Promise<CommunitySourceDescriptor>
}

Coordinates graph-source switching for citation, topic, precomputed, dynamic-search, and user-authored graph views without exposing raw SQL or graphology internals to React UI code.

`useCapabilitySetup(options)`

function useCapabilitySetup(options: UseCapabilitySetupOptions): {
  ready: boolean
  error: Error | null
}

Registers host-provided capability loaders and auto-enables previously enabled capabilities from local storage unless `autoEnable` is supplied.

`useAiwgIndex(initialIndex?)`

function useAiwgIndex(initialIndex?: AiwgFortemiIndexExport): {
  index: AiwgFortemiIndexExport | null
  chunkedManifest: AiwgFortemiChunkManifest | null
  counts: Record<string, number>
  data: AiwgIndexQueryResult | AiwgChunkedIndexQueryResult | null
  error: Error | null
  reviewDecisions: AiwgReviewDecision[]
  loadIndex: (value: unknown) => AiwgFortemiIndexExport
  loadChunkedIndex: (manifest: unknown, loader: AiwgChunkedIndexLoader, options?: AiwgChunkedIndexLoadOptions) => AiwgFortemiChunkManifest
  search: (query?: string, options?: AiwgIndexQueryOptions) => AiwgIndexQueryResult
  searchChunked: (query?: string, options?: AiwgChunkedIndexQueryOptions) => Promise<AiwgChunkedIndexQueryResult>
  getRecord: (id: string) => Promise<AiwgFortemiRecord>
  clearChunkCache: () => void
  setReviewDecision: (input: AiwgReviewInput) => AiwgReviewDecision
  clearReviewDecision: (itemId: string) => void
  exportReviewDecisions: () => AiwgReviewDecisionExport
  toCommunityGraph: (options?: AiwgIndexGraphOptions) => CommunityGraph
}

Loads whole or chunked AIWG Fortemi index exports, searches them, tracks review decisions, and projects loaded indexes into community graph data.

`useShard(source, options?)`

function useShard(source: ShardReaderSource, options?: OpenShardOptions): {
  reader: ShardReader | null
  manifest: ShardManifest | null
  loading: boolean
  error: Error | null
  listNotes: (options?: ShardListOptions) => Promise<{ items: ShardReaderNote[]; total: number }>
  getNote: (id: string) => Promise<ShardReaderNote | null>
  search: (query: string, options?: ShardSearchOptions) => Promise<ShardSearchResult>
  linksOf: (id: string) => Promise<ShardLink[]>
  conceptsOf: (id: string) => Promise<ShardSkosConcept[]>
  relationsOf: (conceptId: string) => Promise<ShardSkosRelation[]>
  provenanceOf: (id: string) => Promise<ShardProvenanceEdge[]>
  getNoteFull: (id: string) => Promise<ShardNoteFull | null>
  semantic: (query: string, k?: number) => Promise<Array<{ note: ShardReaderNote; score: number }>>
}

Opens a Knowledge Shard for in-place, read-only browsing and search without importing it into PGlite.

`useRemote(config)`

function useRemote(config: RemoteBackendConfig): {
  backend: RemoteDataBackend
  loading: boolean
  error: Error | null
  listNotes: (options?: BackendListOptions) => Promise<{ items: BackendNote[]; total: number }>
  getNote: (id: string) => Promise<BackendNote | null>
  search: (query: string, options?: RemoteSearchOptions) => Promise<RemoteSearchResult>
  getNoteFull: (id: string) => Promise<BackendNoteFull | null>
  linksOf: (id: string) => Promise<BackendLink[]>
  conceptsOf: (id: string) => Promise<BackendConcept[]>
  provenanceOf: (id: string) => Promise<BackendProvenanceEdge[]>
  provenanceGraphOf: (id: string) => Promise<RemoteProvenanceGraph>
  semantic: (query: string, k?: number) => Promise<BackendSearchHit[]>
  semanticWithReport: (query: string, k?: number) => Promise<RemoteSearchResult>
  manageNote: (input: unknown) => Promise<RemoteManageNoteResult>
}

Wraps `createRemoteBackend(config)` for React and exposes the Fortemi server-tier `DataBackend` surface with shared loading and error state.


Graph Views and Subpath Exports

Three renderer tiers over the same `@fortemi/graph` data, each on its own subpath so heavy dependencies never enter a consumer's bundle unless imported:

SubpathComponentExtra depsBest for
`@fortemi/react/graph``GraphView`none (PGlite-free; React + `@fortemi/graph` only)Static SVG rendering, docs-map/static tenants
`@fortemi/react/graph-2d``SigmaGraphView``sigma` + `graphology` + `graphology-layout-forceatlas2` (optional peers, lazy-loaded)Live force settling, camera focus, LOD labels
`@fortemi/react/graph-3d``ForceGraph3DView``react-force-graph-3d` + `three` (optional peers, lazy via `React.lazy`)Orbitable 3D force graph, 2D/3D toggle

`GraphView` is also re-exported from the package root; `SigmaGraphView` and `ForceGraph3DView` are subpath-only, so their renderers never enter the root module graph. Import `GraphView` from `@fortemi/react/graph` (rather than the root) when you also want to keep PGlite out of your bundle.

`GraphView`

interface GraphViewProps {
  graph: CommunityGraph | null
  layout?: Partial<GraphLayoutState>
  filters?: GraphViewFilters
  selectedNodeId?: string | null
  onSelectNode?: (nodeId: string) => void
  onNavigate?: (nodeId: string) => void
  labelFor?: (nodeId: string) => string
  draggableNodes?: boolean
  width?: number
  height?: number
  style?: CSSProperties
}

Static SVG renderer built on `layoutCommunityGraph`/`filterCommunityGraph`. Carries no runtime dependency on `@fortemi/core` (type-only `CommunityGraph` import, erased at build).

`SigmaGraphView`

interface SigmaGraphViewProps {
  graph: CommunityGraph | RenderGraph | null
  snapshot?: string | RenderGraph        // URL or prebaked RenderGraph; skips live layout
  filters?: GraphControlFilters
  labelFor?: (id: string) => string
  palette?: CommunityPalette
  onSelectNode?: (nodeId: string) => void
  onOpenNode?: (nodeId: string) => void
  settleMs?: number                      // ForceAtlas2 settle duration
  theme?: Partial<SigmaTheme>
  height?: number | string
  style?: CSSProperties
}

Interactive 2D explorer backed by Sigma + graphology ForceAtlas2, dynamically imported on mount. Renders an install hint if the optional peers are missing.

`ForceGraph3DView`

interface ForceGraph3DViewProps {
  graph: CommunityGraph | RenderGraph | null
  snapshot?: string | RenderGraph
  filters?: GraphControlFilters
  labelFor?: (id: string) => string
  palette?: CommunityPalette
  onSelectNode?: (nodeId: string) => void
  onOpenNode?: (nodeId: string) => void
  theme?: Partial<ForceGraph3DTheme>
  height?: number | string
  style?: CSSProperties
}

3D force-directed view backed by `react-force-graph-3d` (Three.js), loaded lazily so Three only ships when the view mounts. Accepts the same data and `GraphControlFilters` as the 2D tiers — 2D/3D parity by construction.