# Nisaba — Technical Specifications

Nisaba is the Ancient Text Analysis and Cross-Tradition Scholarly Research
domain of the Oshun monorepo. This specification documents what is actually
implemented under `libs/nisaba/` (22 publishable `@nisaba/*` packages) plus the
canonical Nisaba V1 API contracts in `libs/contracts/src/nisaba/index.ts` and
the Prisma persistence schema in `libs/nisaba/database/prisma/schema.prisma`.

There is no `apps/nisaba` or `services/nisaba` directory; Nisaba ships as
libraries plus generated API contracts.

> Three distinct entity layers exist and are documented separately below because
> they do **not** share field shapes:
>
> 1. **Canonical V1 contracts** (`libs/contracts/src/nisaba/`) — the schemas the
>    generated REST API (`@nisaba/api-client`) is built from.
> 2. **`@nisaba/schemas`** — Zod schemas used by domain logic and the
>    Prisma-backed persistence layer.
> 3. **`@nisaba/core` types** — the shared TypeScript vocabulary (enums and
>    domain interfaces) consumed across the engine packages.

A new engineer should read §1 first to understand the shared type vocabulary,
then §2 to see how it maps to the database, then §3 for the validation layer,
then §4 and §5 for the two REST surfaces.

---

## 1. Core Type Vocabulary (`@nisaba/core`)

Source: `libs/nisaba/core/src/types/`. These are the shared enums and interfaces
imported across the engine packages. They form the lingua franca of the domain —
when a morphology parser, a transliteration engine, and a collation algorithm
all need to refer to "Hebrew," they use the same `WritingSystem.HEBREW` constant
from this package rather than separate string literals.

### 1.1 `WritingSystem` enum

Defined in `core/src/types/writing-systems.ts`. Values are ISO 15924 (and
ISO-15924-derived) script codes, grouped below by geographic/cultural family.
Using ISO 15924 codes as enum values means they round-trip through external
standards (font selection, Unicode metadata, OpenType script tags) without a
translation step.

| Group       | Members (enum key = code)                                                                                                                                                                                                                                 |
| ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Cuneiform   | `SUMERIAN_CUNEIFORM='Xsux'`, `AKKADIAN_CUNEIFORM='Xsux_Akk'`, `ELAMITE_CUNEIFORM='Xsux_Elx'`, `HITTITE_CUNEIFORM='Xsux_Hit'`, `UGARITIC='Ugar'`, `OLD_PERSIAN_CUNEIFORM='Xpeo'`                                                                           |
| Egyptian    | `EGYPTIAN_HIEROGLYPHIC='Egyp'`, `HIERATIC='Egyp_Hier'`, `DEMOTIC='Egyd'`, `COPTIC='Copt'`                                                                                                                                                                 |
| Semitic     | `HEBREW='Hebr'`, `PALEO_HEBREW='Hebr_Pleo'`, `ARAMAIC='Armi'`, `SYRIAC='Syrc'`, `ARABIC='Arab'`, `SOUTH_ARABIAN='Sarb'`, `PHOENICIAN='Phnx'`, `MOABITE='Phnx_Moab'`, `NABATAEAN='Nbat'`, `SAMARITAN='Samr'`, `MANDAIC='Mand'`, `ETHIOPIC='Ethi'`          |
| Greek/Latin | `GREEK='Grek'`, `LINEAR_A='Lina'`, `LINEAR_B='Linb'`, `CYPRIOT='Cprt'`, `LATIN='Latn'`                                                                                                                                                                    |
| Indic       | `BRAHMI='Brah'`, `GRANTHA='Gran'`, `KHAROSHTHI='Khar'`, `DEVANAGARI='Deva'`, `BENGALI='Beng'`, `TAMIL='Taml'`, `TELUGU='Telu'`, `KANNADA='Knda'`, `MALAYALAM='Mlym'`, `SINHALA='Sinh'`, `TIBETAN='Tibt'`, `THAI='Thai'`, `KHMER='Khmr'`, `BURMESE='Mymr'` |
| Iranian     | `AVESTAN='Avst'`, `PAHLAVI='Phli'`, `SOGDIAN='Sogd'`                                                                                                                                                                                                      |
| East Asian  | `CHINESE='Hani'`, `JAPANESE_KANJI='Hani_Jpan'`, `KOREAN_HANJA='Hani_Kore'`                                                                                                                                                                                |
| Slavic      | `GLAGOLITIC='Glag'`, `OLD_CYRILLIC='Cyrs'`                                                                                                                                                                                                                |
| Other       | `ARMENIAN='Armn'`, `GEORGIAN='Geor'`, `OLD_TURKIC='Orkh'`, `RUNIC='Runr'`                                                                                                                                                                                 |

`ScriptDirection` encodes the physical direction text flows on the page:
`'ltr' | 'rtl' | 'ttb' | 'boustrophedon'`.

`WritingSystemMetadata` (interface) carries the full profile needed to render
and process a script: `code` (`WritingSystem`), `name`, `direction`
(`ScriptDirection`), `iso15924`, `iso639_3?` (`string[]`), `unicodeBlocks`
(`readonly string[]`), `hasCombiningCharacters` (`boolean`), `hasLigatures`
(`boolean`).

### 1.2 `Tradition` enum

Defined in `core/src/types/traditions.ts`. The `Tradition` enum names the
religious and philosophical traditions whose texts Nisaba manages. It is used as
a first-level grouping key in canonical references, corpus connectors, and the
comparative analysis package.

- Abrahamic: `CHRISTIANITY`, `JUDAISM`, `ISLAM`, `BAHAI`, `SAMARITANISM`,
  `MANDAEISM`
- Dharmic: `HINDUISM`, `BUDDHISM`, `JAINISM`, `SIKHISM`
- East Asian: `CONFUCIANISM`, `TAOISM`, `SHINTO`
- Iranian: `ZOROASTRIANISM`, `MANICHAEISM`
- Ancient Near Eastern: `SUMERIAN`, `BABYLONIAN`, `EGYPTIAN`, `CANAANITE`,
  `HITTITE`, `UGARITIC`
- Classical: `GREEK_PHILOSOPHY`, `ROMAN`, `NEOPLATONISM`, `STOICISM`,
  `GNOSTICISM`, `HERMETICISM`
- Other: `INDIGENOUS`, `NEW_RELIGIOUS_MOVEMENT`, `SECULAR_PHILOSOPHY`,
  `CROSS_TRADITION`

`TraditionMetadata` (interface) carries the displayable and computable profile
of each tradition: `tradition`, `displayName`, `primaryLanguages`,
`primaryScripts`, `approximateDateRange`
(`{ earliest: number; latest: number | null }`), `geographicOrigin`,
`parentTraditions` (`readonly Tradition[]`).

### 1.3 Morphology types

Defined in `core/src/types/morphology.ts`. These enums and the
`MorphologicalParse` interface form the universal output format for all of
Nisaba's language-specific morphological analyzers — so that downstream
consumers (the UI, the study plan engine, the annotation system) never need to
handle language-specific morphology formats.

The grammatical feature enums:

- `PartOfSpeech` —
  `noun, verb, adjective, adverb, pronoun, preposition, conjunction, particle, interjection, article, numeral, determiner, auxiliary, postposition, classifier, ideogram, unknown`
- `GrammaticalGender` —
  `masculine, feminine, neuter, common, animate, inanimate`
- `GrammaticalNumber` — `singular, dual, trial, plural, collective`
- `GrammaticalPerson` — `first, second, third`
- `GrammaticalCase` —
  `nominative, accusative, genitive, dative, ablative, vocative, locative, instrumental, ergative, absolutive, construct, oblique`
- `VerbTenseAspect` —
  `present, past, future, aorist, perfect, pluperfect, imperfect, future_perfect, preterite, stative, durative, punctual`
- `VerbMood` —
  `indicative, subjunctive, optative, imperative, jussive, cohortative, precative, prohibitive, infinitive, participle`
- `VerbVoice` — `active, passive, middle, medio-passive, causative, reflexive`
- `VerbStem` — Hebrew (`qal, niphal, piel, pual, hiphil, hophal, hithpael`),
  Akkadian (`G, D, S, N, Gt, Dt, St, Nt`), Arabic (`form_I` … `form_X`)

`MorphologicalParse` (interface) is the result type returned by every
morphological analyzer. It carries: `surfaceForm` (the original text token),
`lemma` (the dictionary form), `language` (ISO 639-3 code), `pos` (part of
speech); optional nominal features (`gender`, `number`, `case`, `state`),
optional verbal features (`person`, `tenseAspect`, `mood`, `voice`, `stem`),
optional `root`, affixes (`prefix`, `suffix`, `proclitic`, `enclitic`),
`confidence` (0–1 score), and `alternateParses?` for ambiguous forms.

### 1.4 Lexicon types

Defined in `core/src/types/lexicon.ts`. These types describe structured lexical
entries returned by the unified `LexiconService`. The `LexiconEntry` shape is
language-neutral so the same downstream display code works for Hebrew, Greek,
Sanskrit, and Akkadian entries.

Key types: `LexicalSense`, `LexicalExample`, `LexicalCrossRef` (`relationType`:
`synonym | antonym | cognate | derived | related | see_also`), and
`LexiconEntry` carrying: `id`, `lemma`, `language`, `writingSystem`, optional
`transliteration`/`pronunciation`, `partOfSpeech`, `root?`, `pattern?`,
`gender?`, `senses`, `etymology?`, `cognates?`, `crossRefs?`,
frequency/attestation fields, and external lexicon IDs (`strongsNumber`,
`bdbEntry`, `halalEntry`, `lsjEntry`), plus `sources` and `lastUpdated`.

### 1.5 Reference types

Defined in `core/src/types/reference.ts`. These types describe the canonical
reference system used by `@nisaba/canon` to give every passage in every
tradition a stable, dereferenceable identifier.

- `ReferenceSystemType` enumerates the citation schemes in use across
  traditions. Values:
  `chapter_verse, stephanus, bekker, diels_kranz, pitaka_nikaya, adhyaya_shloka, sutra_verse, page_line, folio_line, tablet_line, column_line, section_paragraph, custom`
- `CanonicalReferenceSystem` (interface) describes a single reference system:
  `id`, `name`, `tradition`, `type`, `canon?`, `uriPrefix`, `referencePattern?`,
  `supportsRanges`, `notes?`
- `CanonicalRef` (interface) is a parsed reference to a specific passage: `raw`
  (the original string), `tradition`, `canon`, `book`, `system`, optional
  structural components (`chapter`, `verse`, `verseEnd`, `section`, `page`,
  `line`, `lineEnd`, `fragment`), and the computed `uri`
- `CanonicalRefRange` groups a start and end `CanonicalRef` with a
  human-readable `displayString`

### 1.6 Transliteration types

Defined in `core/src/types/transliteration.ts`. The `TransliterationScheme` enum
names every romanization and transcription scheme that Nisaba supports, grouped
by language family. Using a typed enum prevents the subtle bugs that arise from
inconsistent scheme naming (e.g., "IAST" vs. "iast" vs. "iso-233").

`TransliterationScheme` values by family:

- Hebrew: `SBL_HEBREW`, `ACADEMIC_HEBREW`, `SIMPLIFIED_HEBREW`
- Arabic: `DIN_31635`, `BUCKWALTER`, `ALA_LC_ARABIC`
- Greek: `SBL_GREEK`, `BETA_CODE`, `SIMPLIFIED_GREEK`
- Akkadian/Sumerian: `ATF`, `CDLI`
- Sanskrit/Pali: `IAST`, `ITRANS`, `SLP1`, `HARVARD_KYOTO`, `VELTHUIS`,
  `ISO_15919`
- Syriac: `SYRIAC_ROMANIZATION`
- Egyptian: `MANUEL_DE_CODAGE`, `JSesh`
- Tibetan: `WYLIE`, `EXTENDED_WYLIE`
- Coptic: `COPTIC_ROMANIZATION`
- General: `IPA`, `ASCII`, `UNICODE`

`TransliterationConfig` (interface) specifies the parameters for a
transliteration operation: `scheme`, `preserveDiacritics`,
`preserveWordBoundaries`, `includeVowels`, `normalizeWhitespace` (all boolean).

### 1.7 Manuscript, apparatus, and annotation core types

These types in `@nisaba/core` describe the physical and scholarly properties of
manuscripts and the data structures used in textual criticism. They are used by
both the `@nisaba/criticism` engine and the persistence layer.

`core/src/types/manuscript.ts` defines:

- `ManuscriptMaterialType` — the physical medium of a manuscript. Values:
  `PAPYRUS, PARCHMENT, VELLUM, CLAY_TABLET, PAPER, STONE, METAL, WOOD, BAMBOO, PALM_LEAF, SILK, BONE, CERAMIC, WAX_TABLET, OTHER`
- `VariantClassificationType` — the scholarly classification of a textual
  variant. All 22 values:
  `ORTHOGRAPHIC, PHONETIC, MORPHOLOGICAL, LEXICAL, SYNTACTIC, TRANSPOSITION, ADDITION, OMISSION, SUBSTITUTION, CONFLATION, HARMONIZATION, HOMOEOTELEUTON, HOMOEOARCTON, DITTOGRAPHY, HAPLOGRAPHY, METATHESIS, CORRECTION, MARGINAL_NOTE, GLOSS, INTERPOLATION, LACUNA, OTHER`

`core/src/types/apparatus.ts` defines the data structures for critical apparatus
entries — the footnotes in a critical edition that document textual variants:

- `ApparatusFormatType` / `ApparatusFormat` — the apparatus presentation style.
  Values: `POSITIVE, NEGATIVE, FULL, SELECTIVE, LEIDEN_PLUS`
- `VariantClassification` — same 22 values as `VariantClassificationType`
- `ApparatusReading` (interface) — one manuscript witness's reading of a variant
  unit: `text`, `witnesses` (`readonly string[]`), `isPreferred`,
  `classification?`, `note?`
- `ApparatusEntry` (interface) — a complete variant unit entry with its
  `reference`, `lemma`, all `readings` (`readonly ApparatusReading[]`), and
  `significance` score (0–10)

`core/src/types/annotations.ts` defines the W3C-aligned annotation type system:

- `AnnotationTypeValue` — the semantic role of an annotation. Values:
  `COMMENT, HIGHLIGHT, BOOKMARK, TAG, CLASSIFICATION, LINKING, DESCRIBING, IDENTIFYING, MODERATING, QUESTIONING, REPLYING, TRANSLATION, TRANSCRIPTION`
- `AnnotationMotivation` — the W3C Web Annotation vocabulary for why an
  annotation exists:
  `assessing, bookmarking, classifying, commenting, describing, editing, highlighting, identifying, linking, moderating, questioning, replying, tagging`
- W3C selector types for targeting text: `TextQuoteSelector`,
  `TextPositionSelector`, `FragmentSelector`, `XPathSelector`; union type
  `AnnotationSelector`

### 1.8 Comparative types

Defined in `core/src/types/comparative.ts`. These types support the scholarly
rigor of the cross-tradition comparison system — preventing superficial
syncretism by requiring every claim about similarity or influence to declare its
type, evidence grade, and level of scholarly consensus.

- `SimilarityTaxonomy` — the kind of relationship being claimed between concepts
  across traditions. Values:
  `THEMATIC, LEXICAL, STRUCTURAL, GENEALOGICAL, TYPOLOGICAL, MORPHOLOGICAL, PHONETIC, SEMANTIC, SYNTACTIC, NARRATIVE, DOCTRINAL, RITUAL, ICONOGRAPHIC`
- `EvidenceGradeType` — how strongly the evidence supports a claim. Values:
  `CERTAIN, HIGHLY_PROBABLE, PROBABLE, POSSIBLE, UNCERTAIN, SPECULATIVE, DISPUTED`
- `ConsensusLevelType` — how widely the scholarly community agrees with a claim.
  Values: `UNIVERSAL, STRONG, MAJORITY, DIVIDED, MINORITY, INDIVIDUAL, UNKNOWN`
- `ComparativeEvidence` (interface) — a single piece of evidence supporting a
  comparative claim: `type`
  (`textual | archaeological | linguistic | historical | iconographic | structural`),
  `source`, `reference`, `description`, `grade`, `date?`
- `InfluenceDirection` — the direction of a textual influence relationship:
  `forward, reverse, bidirectional, uncertain`

---

## 2. Persistence Schema (`@nisaba/database`)

Source: `libs/nisaba/database/prisma/schema.prisma`. Nisaba uses PostgreSQL via
Prisma. The datasource URL is `env("NISABA_DATABASE_URL")`. Preview features
`fullTextSearch` and `fullTextIndex` are enabled to support the cross-corpus
lemma-aware search.

The generated Prisma client is not imported directly by consumers — instead it
is re-exported through the three lifecycle functions in
`database/src/client.ts`: `getNisabaClient()`, `createNisabaClient()`, and
`disconnectNisabaClient()`. This pattern allows connection pooling and teardown
to be managed centrally.

### 2.1 Database enums

The Prisma schema defines its own database-level enums (separate from the
TypeScript enums in `@nisaba/core`) because the persistence layer uses
string-serialized enum values. The major enums and their value counts:

`ManuscriptMaterial` (15 values), `ManuscriptScriptType` (16 values:
`UNCIAL, MINUSCULE, MAJUSCULE, CURSIVE, SEMI_CURSIVE, HIERATIC, DEMOTIC, CUNEIFORM_WEDGE, SQUARE_HEBREW, PROTO_HEBREW, KUFIC, NASKH, NASTALIQ, BRAHMI, DEVANAGARI, OTHER`),
`VariantClassification` (22 values), `VariantCause` (10 values:
`SCRIBAL_ERROR, DELIBERATE_CHANGE, THEOLOGICAL_MOTIVATION, HARMONIZATION, LITURGICAL_ADAPTATION, STYLISTIC_IMPROVEMENT, CLARIFICATION, CENSORSHIP, DAMAGE, UNCERTAIN`),
`ApparatusFormat` (5 values), `AnnotationType` (13 values), `AnnotationStatus`
(6 values: `ACTIVE, ARCHIVED, DELETED, UNDER_REVIEW, APPROVED, REJECTED`),
`SimilarityType` (13 values), `EvidenceGrade` (7 values), `ConsensusLevel` (7
values), `ProjectStatus` (6 values:
`PLANNING, ACTIVE, ON_HOLD, COMPLETED, ARCHIVED, CANCELLED`), `ProjectType` (9
values:
`TEXT_EDITION, COLLATION, TRANSLATION, COMMENTARY, COMPARATIVE_STUDY, PALEOGRAPHIC_ANALYSIS, CORPUS_BUILDING, LEXICOGRAPHY, OTHER`),
`EditionStatus` (7 values:
`DRAFT, IN_PROGRESS, REVIEW, PUBLISHED, REVISED, SUPERSEDED, WITHDRAWN`).

### 2.2 Models (tables)

The 16 Prisma models map to PostgreSQL tables. The table below summarizes the
purpose and key fields of each model. All models carry `createdAt`/`updatedAt`
timestamps; most carry an `ownerId` (VarChar 50) that is indexed for per-user
queries.

| Model                  | Table                    | Key fields                                                                                                                                                                                                                                                       |
| ---------------------- | ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Manuscript`           | `manuscripts`            | `id` (cuid, VarChar 25), `siglum`, `name`, physical description, provenance, dating, scholarly assessment, digital images, `ownerId`                                                                                                                             |
| `CollationProject`     | `collation_projects`     | `id`, `name`, `witnesses` (`String[]`), `config` (Json), `status`, `progress`, `totalVariantUnits`, `significantVariants`                                                                                                                                        |
| `VariantUnit`          | `variant_units`          | `id`, `collationProjectId`, `reference`, `startOffset`/`endOffset`, `baseReading`, `lemma?`, `readings` (Json), `classification?`, `cause?`, `significance`                                                                                                      |
| `CriticalEdition`      | `critical_editions`      | `id`, `title`, `version`, `collationProjectId?`, `text`, `apparatus` (Json), `apparatusFormat`, `status`, `editor?`, `editors` (Json)                                                                                                                            |
| `Annotation`           | `annotations`            | `id`, `targetUri`, `targetType`, `targetSelector?` (Json), `type`, `body`, `bodyFormat`, `motivation?`, `status`, `layerId?`, `manuscriptId?`, `tags`                                                                                                            |
| `AnnotationLayer`      | `annotation_layers`      | `id`, `name`, `description?`, `color?`, `scope?`, `permissions` (Json), `ownerId`                                                                                                                                                                                |
| `ConceptMapping`       | `concept_mappings`       | `id`, source concept (tradition/concept/language/reference), target concept, `similarityType`, `similarityScore`, `evidenceGrade`, `consensusLevel`, `evidence` (Json)                                                                                           |
| `MotifAttestation`     | `motif_attestations`     | `id`, `motifId`, `motifName`, `motifCategory?`, `tradition`, `textReference`, `passage?`, `function?`, `evidenceGrade`                                                                                                                                           |
| `InfluenceEdge`        | `influence_edges`        | `id`, source (`sourceId`/`sourceType`/`sourceTradition`), target, `influenceType`, `direction`, `strength`, `evidenceGrade`, `consensusLevel`, temporal context                                                                                                  |
| `CanonicalReference`   | `canonical_references`   | `id`, `tradition`, `canon`, `collection?`, `bookId`, `bookName`, `chapterCount?`, `verseCount?`, `structure?` (Json), `canonicalOrder`, `versificationScheme?`/`versificationMap?`                                                                               |
| `Passage`              | `passages`               | `id` (uuid), `slug` (unique), `title`, `summary`, `primaryDomain`, `domains` (`String[]`), `origin`, `status`, `visibility`, `reference`/`language`/`text`/`provenance` (Json), `wordCount`, `translations`/`parallelPassages`/`commentary`/`annotations` (Json) |
| `PaleographicAnalysis` | `paleographic_analyses`  | `id`, `manuscriptId`, `scriptClassification?`, `dateEstimate?`/`dateEarliest?`/`dateLatest?`/`dateConfidence?`, `region?`, `features`/`letterForms`/`abbreviations`/`ligatures` (Json), `handCount`                                                              |
| `ScribalHand`          | `scribal_hands`          | `id`, `manuscriptId`, `analysisId?`, `handLabel`, `folioRange?`/`startFolio?`/`endFolio?`, `scriptStyle?`, `characteristics`/`letterForms` (Json), `scribeName?`                                                                                                 |
| `ResearchProject`      | `research_projects`      | `id`, `name`, `type` (`ProjectType`), `status` (`ProjectStatus`), `config`/`members`/`milestones` (Json), `traditions`/`languages` (`String[]`), `progress`, `ownerId`, `organizationId?`                                                                        |
| `PersonalLibraryItem`  | `personal_library_items` | `id`, `itemType`, `itemId`, `itemTitle`, `collection?`, `tags`, `readingStatus?`/`readingProgress?`, `notes?`, `highlights` (Json), `citation?`                                                                                                                  |
| `Bookmark`             | `bookmarks`              | `id`, `targetUri`, `targetTitle`, `targetType`, `position?` (Json), `label?`/`notes?`/`color?`, `folder?`, `tags`, `sortOrder`                                                                                                                                   |

### 2.3 Persistence invariants

The schema enforces data quality through unique constraints, cascade rules, and
soft-delete patterns. The key invariants:

- `Manuscript`: `@@unique([ownerId, siglum])` — no two manuscripts belonging to
  the same owner may share a siglum. Relations: `variantUnits`,
  `paleographicAnalyses`, `scribalHands`, `annotations`. Soft delete via
  `deletedAt`.
- `VariantUnit` → `CollationProject` (`onDelete: Cascade`); optional
  `manuscript` relation.
- `CriticalEdition` → optional `CollationProject` relation.
- `Annotation` → optional `AnnotationLayer` and `Manuscript` relations; soft
  delete via `deletedAt`.
- `AnnotationLayer`: `@@unique([ownerId, name])`.
- `ConceptMapping`:
  `@@unique([ownerId, sourceTradition, sourceConcept, targetTradition, targetConcept])`
  — prevents duplicate concept equivalence claims by the same scholar.
- `InfluenceEdge`: `@@unique([sourceId, targetId, influenceType, ownerId])`.
- `CanonicalReference`: `@@unique([tradition, canon, bookId])` — each book in a
  canon has exactly one canonical reference record.
- `PaleographicAnalysis` → `Manuscript` (`onDelete: Cascade`); has
  `scribalHands`.
- `ScribalHand` → `Manuscript` (`onDelete: Cascade`); optional `analysis`
  relation.
- `PersonalLibraryItem`: `@@unique([ownerId, itemType, itemId])`.

The generated Prisma client exposes delegates for: `manuscript`,
`collationProject`, `variantUnit`, `criticalEdition`, `annotation`,
`annotationLayer`, `conceptMapping`, `motifAttestation`, `influenceEdge`,
`canonicalReference`, `passage`, `paleographicAnalysis`, `scribalHand`,
`researchProject`, `personalLibraryItem`, `bookmark`.

---

## 3. Validation Schemas (`@nisaba/schemas`)

Source: `libs/nisaba/schemas/src/`. These are Zod schemas (and inferred types)
for the domain entities that cross trust boundaries — API request bodies, corpus
ingestion payloads, and data from external scholarly platforms. The schemas
serve as both a runtime firewall against invalid input and a source of
TypeScript types, so the same Zod definition drives both validation and the type
system.

Each schema module exports a `Create*` variant (`.omit({ id: true })`) and most
export an `Update*` variant (`.partial().omit({ id: true, ownerId: true })`).
The full field-level details of each module follow.

### 3.1 `manuscript.ts`

Schemas for manuscript records and queries. The `ManuscriptSchema` captures both
the physical description of the artifact and its scholarly metadata.

- Enum schemas: `ManuscriptMaterialSchema`, `ManuscriptScriptTypeSchema`,
  `EvidenceGradeSchema`.
- `ManuscriptDimensionsSchema` —
  `{ height, width (positive), unit ('mm' | 'cm' | 'in') }`, strict.
- `WitnessClassificationSchema` — `textualFamily?`, `textType?`, `reliability?`
  (0–1), `completeness?` (0–1), `notes?`.
- `ManuscriptSchema` — `id?`, `siglum`, `name`, `alternateNames`,
  `catalogNumber?`, `material?`, `scriptType?`, `dimensions?`, `folioCount?`,
  `columnCount?`, `linesPerColumn?`, `contents?`, `languages`, `writingSystem?`,
  provenance fields, dating fields, scholarly assessment, `digitalImages`
  (`{ url, label?, type? }[]`), `iiifManifestUrl?` (URL), `transcriptionUrl?`,
  `externalIds`, `bibliography`, `notes?`, `metadata`, `ownerId`, `projectId?`.
- `ManuscriptQuerySchema` — filter fields plus `limit` (1–100, default 20),
  `offset`, `orderBy` (`siglum | name | dateEarliest | dateLatest | createdAt`),
  `orderDir` (`asc | desc`).

### 3.2 `variant.ts`

Schemas for collation projects and textual variants — the input and output of
the collation engine.

- `VariantClassificationSchema` (22 values), `VariantCauseSchema` (10 values),
  `ProjectStatusSchema` (6 values).
- `VariantReadingSchema` — `text`, `witnesses` (`string[]`), `isPreferred`,
  `classification?`, `cause?`, `note?`.
- `VariantUnitSchema` — `id?`, `collationProjectId`, `reference`,
  `startOffset`/`endOffset` (int ≥ 0), `baseReading`, `lemma?`, `readings`,
  `classification?`, `cause?`, `significance` (0–10), `preferredReading?`,
  `rationale?`, `evidenceGrade?`, `manuscriptId?`, `notes?`, `metadata`.
  Refinement: `endOffset >= startOffset`.
- `CollationConfigSchema` — `algorithm`
  (`needleman-wunsch | smith-waterman | gotoh | medite | collatex`),
  `matchScore`, `mismatchPenalty`, `gapPenalty`, `normalizeUnicode`,
  `caseSensitive`, `ignoreAccents`, `ignorePunctuation`, `tokenizer`
  (`word | character | morpheme`).
- `CollationResultSchema` — `projectId`, `totalVariantUnits`,
  `significantVariants`, `agreementPercentage` (0–100), `witnesses`,
  `executionTimeMs?`.
- `CollationProjectSchema` — `id?`, `name`, `description?`, `baseText?`,
  `witnesses`, `collationMethod?`, `config?`, `status` (`ProjectStatus`, default
  `PLANNING`), `ownerId`.

### 3.3 `apparatus.ts`

Schemas for critical apparatus entries and edition configuration. The
`ApparatusConfigSchema` controls how apparatus entries are presented, while
`CriticalEditionSchema` describes a complete critical edition record.

- `ApparatusFormatSchema` (5 values), `EditionStatusSchema` (7 values).
- `ApparatusReadingSchema` — `text`, `witnesses`, `isPreferred`,
  `classification?`, `note?`.
- `ApparatusEntrySchema` — `reference`, `lemma`, `readings` (min 1),
  `significance` (0–10), `note?`.
- `ApparatusConfigSchema` — `format` (default `POSITIVE`),
  `includeOrthographic`, `minimumSignificance` (0–10),
  `showWitnessDescriptions`, `groupByFamily`, `language`.
- `CriticalEditionSchema` — `id?`, `title`, `description?`, `version`,
  `collationProjectId?`, `text`, `apparatus`, `apparatusFormat`, `status`
  (`EditionStatus`, default `DRAFT`), `editor?`, `editors`
  (`{ name, role? }[]`), `methodology?`, `bibliography`, `metadata`, `ownerId`.

### 3.4 `annotation.ts`

Schemas for standoff annotations and annotation layers, aligned with the W3C Web
Annotation Data Model. The selector schemas enforce the W3C geometry constraints
(e.g., `end >= start` for position selectors).

- `AnnotationTypeSchema` (13 values), `AnnotationStatusSchema` (6 values),
  `AnnotationMotivationSchema` (13 W3C values).
- W3C selector schemas: `TextQuoteSelectorSchema`, `TextPositionSelectorSchema`
  (refinement: `end >= start`), `FragmentSelectorSchema`, `XPathSelectorSchema`;
  discriminated union `AnnotationSelectorSchema`.
- `AnnotationSchema` — `id?`, `targetUri`, `targetType`, `targetSelector?`,
  `type`, `body`, `bodyFormat` (default `text/plain`), `language?`,
  `motivation?`, `status` (default `ACTIVE`), `layerId?`, `manuscriptId?`,
  `tags`, `metadata`, `ownerId`, `projectId?`.
- `AnnotationLayerSchema` — `id?`, `name`, `description?`, `color?`, `scope?`,
  `permissions`, `metadata`, `ownerId`.
- `AnnotationExportSchema` — `format` (`w3c | json | csv | tei`, default `w3c`),
  `layerIds?`, `types?`, `includeDeleted`.

### 3.5 `reference.ts`

Schemas for canonical reference records and queries. These ensure that reference
data entering the system is structurally valid — for example, that a
versification mapping has consistent source and target scheme identifiers.

- `ReferenceSystemTypeSchema` (13 values).
- `CanonicalReferenceSchema` — `id?`, `tradition`, `canon`, `collection?`,
  `bookId`, `bookName`, `bookAbbreviation?`, `alternateNames`, `chapterCount?`,
  `verseCount?`, `structure?`, `canonicalOrder`, `originalLanguage?`,
  `languages`, `versificationScheme?`, `versificationMap?`, `externalIds`,
  `metadata`.
- `VersificationMappingSchema` — `sourceScheme`, `targetScheme`, `bookId`,
  `mappings`
  (`{ sourceChapter, sourceVerse, targetChapter, targetVerse, note? }[]`).
- `ReferenceRangeSchema` — `startTradition`/`startCanon`/`startBook` plus
  optional chapter/verse, optional end fields.
- `ReferenceQuerySchema` — `tradition?`, `canon?`, `bookId?`, `search?`, `limit`
  (1–500, default 50), `offset`.

### 3.6 `tradition.ts`

Schemas for cross-tradition comparative data — concept mappings, motif
attestations, and influence edges. The evidence grading and consensus level
fields are enforced at the schema level, requiring every comparative claim to
declare its epistemic status.

- `SimilarityTypeSchema` (13 values), `ConsensusLevelSchema` (7 values).
- `ConceptMappingSchema` — source/target concept fields, `similarityType`,
  `similarityScore` (0–1), `evidenceGrade` (default `POSSIBLE`),
  `consensusLevel` (default `UNKNOWN`), `evidence` (typed array),
  `bibliography`, `notes?`, `metadata`, `ownerId`.
- `MotifSchema` — `id?`, `motifId`, `motifName`, `motifCategory?`, `tradition`,
  `textReference`, `passage?`, `language?`, `function?`, `context?`, `dating?`,
  `evidenceGrade` (default `PROBABLE`), `bibliography`, `metadata`, `ownerId`.
- `InfluenceEdgeSchema` — `id?`, `sourceId`/`sourceType`/`sourceTradition`,
  `targetId`/`targetType`/`targetTradition`, `influenceType`, `direction`
  (`forward | reverse | bidirectional | uncertain`, default `forward`),
  `strength` (0–1), `evidenceGrade`, `consensusLevel`, `dateEarliest?`,
  `dateLatest?`, `period?`, `evidence`, `bibliography`, `notes?`, `metadata`,
  `ownerId`.

### 3.7 `corpus.ts`

Schemas for corpus metadata and paleographic analysis records. The
`DateEstimationSchema` is notable because it requires the dating method to be
declared explicitly — distinguishing paleographic estimates from radiocarbon
dates from colophon evidence — which matters for downstream confidence
calculation.

- `CorpusMetadataSchema` — `id?`, `name`, `description?`, `tradition?`,
  `language`, `writingSystem?`, `dateRange?` (`{ earliest, latest }`),
  `textCount`, `tokenCount`, `genre?`, `source?`, `license?`, `version`,
  `format` (`plain | tei | conll | json | xml`), `encoding` (default `utf-8`),
  `metadata`, `ownerId`.
- `PaleographicFeatureSchema` — `featureName`, `featureCategory`
  (`letter_form, stroke_pattern, ductus, angle, proportion, spacing, decoration, ligature, abbreviation, ruling, other`),
  `value`, `description?`, `confidence` (0–1), `reference?`.
- `DateEstimationSchema` — `dateEarliest`/`dateLatest`, `confidence`
  (`EvidenceGrade`), `method`
  (`paleographic, radiocarbon, archaeological, historical, stylistic, colophon, other`),
  `comparanda`, `rationale?`. Refinement: `dateLatest >= dateEarliest`.
- `ScribalHandSchema` — `id?`, `manuscriptId`, `analysisId?`, `handLabel`,
  `handDescription?`, folio range fields, `scriptStyle?`, `characteristics`,
  `letterForms`, `dateEstimate?`, `dateConfidence?`, `scribeName?`,
  `scribeIdentification?`, `notes?`, `metadata`.
- `PaleographicAnalysisSchema` — `id?`, `manuscriptId`, `scriptClassification?`,
  dating fields, `region?`, `features`, `letterForms`, `abbreviations`,
  `ligatures`, `handCount`, `rulingPattern?`, `inkDescription?`, `decorations`,
  `methodology?`, `comparanda`, `bibliography`, `notes?`, `metadata`, `ownerId`.

### 3.8 `geotemporal.ts`

Schemas for geographic and temporal data about manuscripts and traditions. The
`FuzzyDateSchema` is the domain's standard representation for ancient dates
where the exact year is unknown — using earliest/latest integer bounds and a 0–1
confidence score rather than a single uncertain date.

- `FuzzyDateSchema` — `earliest`/`latest` (int), `confidence` (0–1), `label?`,
  `calendar` (`gregorian, julian, hebrew, islamic, buddhist, other`, default
  `gregorian`). Refinement: `latest >= earliest`.
- `ProvenanceEventSchema` — `date?` (`FuzzyDate`), `location?`, `event`
  (`created, copied, acquired, sold, donated, stolen, discovered, restored, transferred, other`),
  `description?`, `actor?`, `source?`, `confidence` (`EvidenceGrade`, default
  `PROBABLE`).
- `ProvenanceChainSchema` — `manuscriptId`, `events`, `notes?`, `bibliography`,
  `metadata`.
- `HistoricalLocationSchema` — `id?`, `name`, `modernName?`, `alternateNames`,
  `latitude?` (−90…90), `longitude?` (−180…180), `region?`, `country?`,
  `dateRange?`, `description?`, `type?`
  (`city, temple, monastery, scriptorium, library, archaeological_site, region, province, kingdom, other`),
  `tradition?`, `externalIds`, `metadata`.
- `TimelineEventSchema` — `id?`, `title`, `description?`, `date` (`FuzzyDate`),
  `location?`, `tradition?`, `category?`
  (`composition, translation, discovery, publication, council, schism, persecution, founding, destruction, reform, contact, other`),
  `significance` (0–10), `relatedTexts`, `relatedPersons`, `bibliography`,
  `metadata`.
- `GeospatialQuerySchema` — `latitude`/`longitude`, `radiusKm` (positive,
  default 50), `dateRange?`, `tradition?`, `type?`, `limit` (1–100, default 20).

---

## 4. Canonical V1 API (`@nisaba/api-client` + `libs/contracts/src/nisaba`)

The V1 REST API is the stable public surface through which other Oshun services
and external consumers interact with Nisaba data. It is generated from Zod
contracts rather than hand-written, which means the OpenAPI document, the
TypeScript types, and the validation logic are all derived from a single source
of truth.

The generation chain is: Zod contracts in `libs/contracts/src/nisaba/index.ts` →
OpenAPI 3.1 document at `libs/openapi/src/specs/nisaba/nisaba-v1-contracts.yaml`
→ generated typed client at `libs/nisaba/api-client/src/client.ts`
(`createNisabaApiClient`). The OpenAPI types live in
`api-client/src/generated/openapi.ts`. All generation is driven by
`libs/openapi/scripts/generate-oshun-v1-api-clients.ts`.

### 4.1 Endpoints

The base path is `/api/v1/nisaba`. All endpoints require Bearer JWT
authentication (`bearerAuth`). Every resource exposes the same five-operation
surface:

| Method | Path                                  | Operation (resource = `<R>`) | Purpose                    |
| ------ | ------------------------------------- | ---------------------------- | -------------------------- |
| GET    | `/api/v1/nisaba/<R>`                  | `listNisaba<R>`              | Paginated list (cursor)    |
| POST   | `/api/v1/nisaba/<R>`                  | `createNisaba<R>`            | Create record              |
| GET    | `/api/v1/nisaba/<R>/{sourceRecordId}` | `getNisaba<R>`               | Fetch one record           |
| PUT    | `/api/v1/nisaba/<R>/{sourceRecordId}` | `upsertNisaba<R>`            | Upsert by source record id |
| DELETE | `/api/v1/nisaba/<R>/{sourceRecordId}` | `tombstoneNisaba<R>`         | Soft-delete (tombstone)    |

The 13 resources covered by this surface: `passages`, `manuscripts`, `editions`,
`translations`, `lexicon-entries`, `morphology-entries`, `annotations`,
`concept-graph-nodes`, `concept-graph-edges`, `notebooks`, `study-plans`,
`citations`, `scholar-profiles`.

List operations accept `LimitParam`, `CursorParam`, and
`IncludeTombstonesParam`. Standard error responses: `400` BadRequest, `401`
Unauthorized, `403` Forbidden, `409` Conflict, `500` InternalError. Tombstone
responses use `TombstoneResponse`.

### 4.2 V1 contract entities

Source: `libs/contracts/src/nisaba/index.ts`. All entities use UUID `id` and ISO
timestamps (`createdAt`/`updatedAt`). The entity shapes here are the V1 public
contract — they differ from the `@nisaba/schemas` shapes in §3 because the
public API surface is designed for interoperability, not internal domain
richness.

#### Shared value objects and enums

These small types are used across multiple V1 entities:

- `NisabaScript` —
  `latin, greek, hebrew, arabic, devanagari, pali-sinhala, coptic, cuneiform, tibetan, chinese, japanese, syriac, ethiopic, other`
- `TextDirection` — `ltr, rtl, vertical, boustrophedon`
- `TextFormat` — `plain-text, markdown, tei-xml, html`
- `PassageStatus` — `draft, review, published, archived`
- `ReviewState` — `unreviewed, in-review, approved, rejected`
- `PartOfSpeech` —
  `noun, verb, adjective, adverb, pronoun, particle, preposition, conjunction, interjection, numeral, proper-noun, unknown`
- `MorphologyFeature` —
  `case, number, gender, person, tense, aspect, mood, voice, state, degree, stem, root, prefix, suffix`
- `CitationStyle` — `chicago, mla, apa, sbl, cts, custom`
- `RightsStatus` — `public-domain, open-license, licensed, restricted, unknown`
- `ScholarCredentialKind` —
  `academic-degree, faculty-appointment, editorial-role, translator-credit, institutional-affiliation, community-expert`
- `CanonicalReference` — `scheme`
  (`cts | osis | sefaria | library-of-congress | custom`), `reference`,
  `normalizedReference`, `urn` (nullable), `workId` (slug), `workTitle`,
  `divisionPath` (≤ 12 entries)
- `TextRangeSelector` — `segmentId` (nullable UUID), `startOffset`, `endOffset`,
  `quote` (nullable). Refinement: `endOffset > startOffset`

#### `Passage`

A Passage is the fundamental text unit in the V1 API — a contiguous piece of
text with a canonical reference, broken into `PassageSegment` records that
enable fine-grained annotation and morphology tagging.

Fields: `id`, `canonicalReference`, `title`, `tradition`, `language`, `script`,
`direction`, `text`, `normalizedText`, `textFormat`, `segments` (1–2000
`PassageSegment` records), `sourceLineage` (`manuscriptIds`, `editionId`
nullable, `translationIds`, `citationIds`), `conceptNodeIds`, `lexiconEntryIds`,
`morphologyEntryIds`, `annotationIds`, `status`, timestamps. `PassageSegment` —
`segmentId`, `label`, `order`, `text`, `startOffset`, `endOffset`.

#### `Manuscript`

A Manuscript is a physical or digital artifact witness to a text tradition, with
full digitization and rights metadata.

Fields: `id`, `siglum`, `title`, `tradition`, `language`, `script`, `repository`
(`name`, `city`, `country`, `shelfmark`, `catalogUrl`), `dateRange`
(`notBefore`, `notAfter`, `displayLabel`), `material`
(`papyrus | parchment | paper | stone | clay | metal | digital`), `extent`
(`folioCount`, `lineCount`, `completeness`:
`complete | fragmentary | excerpt | unknown`), `digitization` (`status`:
`not-digitized | digitized | iiif | transcribed`, `imageManifestUrl`,
`transcriptionId`, `ocrQualityBand`: `human | high | mixed | low | none`),
`rights` (`status`, `licenseLabel`, `attribution`), `relatedPassageIds`,
timestamps.

#### `Edition`

An Edition groups manuscripts under a scholarly editor's apparatus and
publication record.

Fields: `id`, `workId`, `title`, `editionType`
(`critical | diplomatic | reader | digital | facsimile`), `language`, `script`,
`manuscriptIds` (1–300), `editorScholarProfileIds` (1–40), `apparatus` (≤ 1000
`ApparatusEntry`), `publication` (`status`, `publisher`, `publicationDate`,
`doi`, `isbn`), `review` (`state`, `reviewerScholarProfileId`, `reviewedAt`),
timestamps. `ApparatusEntry` — `entryId`, `passageSegmentId`, `lemma`,
`reading`, `witnessManuscriptIds` (1–100), `note`.

#### `Translation`

A Translation links a source-language passage to a target-language rendering
with provenance and rights metadata.

Fields: `id`, `passageId`, `editionId` (nullable), `sourceLanguage`,
`targetLanguage`, `locale`, `translatorScholarProfileIds` (1–20), `title`,
`text`, `textFormat`, `translationMode`
(`literal | formal-equivalence | dynamic | commentarial | adaptive`), `rights`,
`review`, timestamps.

#### `LexiconEntry`

A LexiconEntry is a structured dictionary entry for one lemma, with full sense
decomposition, etymology, and inflection data.

Fields: `id`, `lemma`, `normalizedLemma`, `language`, `script`,
`transliteration` (nullable), `partOfSpeech`, `roots`, `glosses` (1–50),
`senses` (1–100 `LexiconSense`: `senseId`, `order`, `gloss`, `semanticDomain`,
`passageIds`), `etymology` (nullable), `inflectedForms`, `sourceCitationIds`
(1–100), `relatedEntryIds`, `reviewState`, timestamps.

#### `MorphologyEntry`

A MorphologyEntry records the morphological parse of a single token within a
passage segment, with provenance indicating whether the parse was human-reviewed
or machine-generated.

Fields: `id`, `passageId`, `segmentId`, `tokenIndex`, `surface`,
`normalizedSurface`, `lemmaEntryId` (nullable), `partOfSpeech`, `features`
(`{ feature, value }[]`), `parsing` (`method`:
`human | rule-engine | model-assisted | imported`, `score`,
`reviewerScholarProfileId`, `reviewedAt`), `sourceCitationIds` (1–20),
timestamps.

#### `Annotation`

An Annotation is a scholarly note, highlight, variant reading, or commentary
targeted at a passage, segment, manuscript, or edition.

Fields: `id`, `target` (`kind`:
`passage | segment | translation | manuscript | edition | token`, `objectId`,
`selector` nullable), `kind`
(`highlight | note | question | variant-note | commentary | cross-reference | morphology | lexicon`),
`body` (nullable), `authorUserId`, `visibility` (`private | shared | public`),
`tags`, `linkedNotebookIds`, `citationIds`, `status`
(`active | resolved | archived`), timestamps.

#### `ConceptGraphNode`

A ConceptGraphNode represents a concept, deity, place, person, or thematic unit
in the cross-tradition knowledge graph.

Fields: `id`, `slug`, `label`, `kind`
(`term | deity | place | person | textual-theme | ritual-practice | cosmology | historical-event | school | manuscript-family`),
`summary`, `traditions` (1–40), `languageCoverage` (1–40), `aliases`,
`passageIds`, `lexiconEntryIds`, `externalRefs`
(`{ authority, identifier, url }[]`), `reviewState`, timestamps.

#### `ConceptGraphEdge`

A ConceptGraphEdge connects two concept graph nodes with a typed, evidenced
relationship. High-confidence edges must cite supporting passages or citations —
this is enforced by the `superRefine` invariant in §4.3.

Fields: `id`, `sourceNodeId`, `targetNodeId`, `relation`
(`broader-than | narrower-than | related-to | influences | contrasts-with | translation-equivalent | ritualizes | comments-on | shares-source-lineage`),
`direction` (`directed | undirected`), `evidencePassageIds`, `citationIds`,
`confidenceBand` (`high | medium | low | contested`), `rationale`, timestamps.

#### `Notebook`

A Notebook is a scholar's personal or collaborative research workspace,
collecting passages, annotations, and concepts with optional collaborators.

Fields: `id`, `ownerUserId`, `title`, `summary` (nullable), `kind`
(`research | translation | philology | teaching | reflection | collection`),
`visibility`, `passageIds`, `annotationIds`, `conceptNodeIds`, `citationIds`,
`collaborators` (`{ userId, role: owner|editor|commenter|viewer, addedAt }[]`),
`items` (`NotebookItem`: `itemId`, `kind`, `targetId`, `title`, `body`,
`order`), `tags`, timestamps.

#### `StudyPlan`

A StudyPlan is a structured learning path through Nisaba's texts, with typed
steps that map to specific scholarly activities.

Fields: `id`, `userId`, `title`, `objective`, `level`
(`introductory | intermediate | advanced | scholar`), `status`
(`draft | active | paused | completed | archived`), `notebookId` (nullable),
`passageIds`, `conceptNodeIds`, `steps` (1–300 `StudyPlanStep`: `stepId`,
`order`, `kind`, `targetId`, `title`, `dueAt`, `completedAt`), `startedAt`,
`completedAt`, timestamps. `StudyPlanStep.kind` values:
`read-passage, compare-translation, view-manuscript, lexicon-review, morphology-review, concept-map, annotation, reflection, assessment`.

#### `Citation`

A Citation records the scholarly provenance of a claim, linking a subject object
to a source with full bibliographic detail.

Fields: `id`, `subject` (`kind`, `objectId`, `selector`), `source` (`kind`:
`manuscript | edition | translation | article | book | lexicon | database`,
`title`, `authors`, `year`, `url`, `doi`, `shelfmark`), `locator` (`page`,
`folio`, `line`, `section`), `style` (`CitationStyle`), `formatted`,
`rightsStatus`, `verification` (`state`, `verifiedAt`, `scholarProfileId`),
timestamps.

#### `ScholarProfile`

A ScholarProfile establishes a scholar's credentials, areas of expertise, and
review authority — determining what they can approve (editions, translations,
citations) and at what risk level.

Fields: `id`, `userId`, `displayName`, `publicBio` (nullable), `credentials`
(`{ credentialId, kind, institution, field, verified, verifiedAt }[]`),
`areasOfExpertise` (1–50), `languages` (1–40), `attributionPreference`
(`full-name | initials | institutional | anonymous-review`),
`disclosureControls` (`showCredentials`, `showInstitution`,
`showReviewHistory`), `reviewAuthority` (`canApproveEditions`,
`canApproveTranslations`, `canVerifyCitations`, `maxReviewRisk`:
`low | medium | high`), timestamps.

### 4.3 V1 contract invariants

The Zod contracts in `libs/contracts/src/nisaba/index.ts` use `superRefine` to
enforce cross-field and cross-entity consistency rules. These invariants go
beyond field-level validation to express scholarly and operational requirements.

- **Manuscript** — digitized/iiif manuscripts require `imageManifestUrl`;
  non-public-domain manuscripts require `rights.licenseLabel`;
  `dateRange.notAfter` cannot precede `notBefore`; `relatedPassageIds` unique.
- **Edition** — `apparatus` witness ids must be declared in `manuscriptIds`;
  published editions require `publicationDate`; approved editions require
  reviewer identity + `reviewedAt`; id arrays unique.
- **Translation** — `sourceLanguage` ≠ `targetLanguage`; approved translations
  require reviewer identity + `reviewedAt`.
- **Passage** — segments strictly ordered and non-overlapping; published
  passages require ≥ 1 source citation; all id arrays unique.
- **LexiconEntry** — entries cannot relate to themselves; `roots`/`glosses`/id
  arrays unique.
- **MorphologyEntry** — `human` parsing method requires
  `reviewerScholarProfileId`; `features` keys unique.
- **Annotation** — non-`highlight` annotations require `body`; `segment` and
  `token` targets require a selector.
- **ConceptGraphNode/Edge** — edges cannot self-loop; `high`-confidence edges
  require evidence passages or citations.
- **Notebook** — `shared` notebooks require ≥ 2 collaborators; id arrays unique.
- **StudyPlan** — steps strictly ordered; `active` plans require `startedAt`;
  `completed` plans require `completedAt`.
- **Citation** — approved citations require `verifiedAt` + `scholarProfileId`.
- **ScholarProfile** — verified credentials require `verifiedAt`; citation
  verification authority requires ≥ 1 verified credential.

Exported helper predicates: `isPublishedPassage`, `conceptEdgeRequiresEvidence`,
`scholarCanVerifyCitations`.

---

## 5. Scholarly SDK (`@nisaba/client`)

`libs/nisaba/client/src/api-client.ts` is a separate, zero-dependency TypeScript
SDK (`NisabaClient`, built via `NisabaClientBuilder`) that targets a broader
scholarly API surface than the V1 contract API above. It is exported from
`@nisaba/client` as the `apiClient` namespace, alongside a `reactHooks`
namespace (`react-hooks.ts`).

The SDK exists because the V1 contract API is intentionally minimal and stable,
while the scholarly API needs richer operations like stemma queries, full
collation management, and AI assistant invocations that are not yet part of the
versioned public contract.

### 5.1 SDK resource operations

All paths are relative to the configured `baseUrl`. The operations are organized
by the scholarly domain they serve:

| Area          | Methods                                                                                                                                                                                                  | Paths                                                                                                                 |
| ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |
| Manuscripts   | `getManuscript`, `listManuscripts`, `createManuscript`, `updateManuscript` (PATCH), `deleteManuscript`, `getManuscriptWitnesses`, `addWitness`, `getManuscriptFolios`, `searchManuscripts`               | `/manuscripts`, `/manuscripts/{id}`, `/manuscripts/{id}/witnesses`, `/manuscripts/{id}/folios`, `/manuscripts/search` |
| Collation     | `getCollation`, `listCollations`, `createCollation`, `runCollation`, `getCollationResult`, `getVariantUnits`, `classifyVariant`, `getApparatus`, `getStemma`                                             | `/collations`, `/collations/{id}`, `/variants/{id}`                                                                   |
| Annotations   | `getAnnotation`, `listAnnotations`, `createAnnotation`, `updateAnnotation`, `deleteAnnotation`, `getAnnotationsForDocument`, `getAnnotationsForPassage`, `batchCreateAnnotations`, `getAnnotationLayers` | `/annotations`, `/annotations/{id}`, `/annotations/batch`, `/documents/{id}`                                          |
| Editions      | `getEdition`, `listEditions`, `createEdition`, `updateEdition`, `publishEdition`, `getEditionApparatus`, `getEditionTranslation`, `exportEdition`                                                        | `/editions`, `/editions/{id}`                                                                                         |
| References    | `resolveReference`, `parseReference`, `getPassage`, `listCanonicalWorks`, `getWorkStructure`, `getParallelPassages`                                                                                      | `/references/resolve`, `/references/parse`, `/passages`, `/passages/parallels`, `/works`, `/works/{id}`               |
| Corpus search | `searchCorpus`, `searchWithFacets`, `getSuggestions`                                                                                                                                                     | `/search`, `/search/faceted`, `/search/suggestions`                                                                   |
| Comparative   | `getConceptMap`, `listConceptMaps`, `createConceptMap`, `getConceptParallelPassages`, `getInfluenceNetwork`, `getMotifAttestations`                                                                      | `/concept-maps`, `/concept-maps/{id}`, `/concept-maps/influence-network`, `/concepts/{id}`, `/motifs/{id}`            |
| AI assistant  | `invokeTranslationAssistant`, `invokeCommentaryGenerator`, `invokeVariantEvaluator`, `invokeResearchAssistant`                                                                                           | `/ai/translate`, `/ai/commentary`, `/ai/evaluate-variant`, `/ai/research`                                             |
| Batch / WS    | `batchRequests`, `connectWebSocket`                                                                                                                                                                      | —                                                                                                                     |

Generic HTTP verbs are also public: `get`, `post`, `put`, `patch`, `delete`.

### 5.2 SDK behavior

The SDK is designed to be resilient and cache-aware, which matters for scholarly
use cases where large reference datasets (lexicon entries, canonical reference
tables) are accessed repeatedly across a session.

Error handling uses a typed error class hierarchy:

- `NisabaErrorCode` enum values:
  `BAD_REQUEST, UNAUTHORIZED, FORBIDDEN, NOT_FOUND, CONFLICT, VALIDATION_ERROR, RATE_LIMITED, INTERNAL_ERROR, TIMEOUT, NETWORK_ERROR, PARSE_ERROR, ABORTED, SERVICE_UNAVAILABLE, UNKNOWN`.
  Errors surface as `NisabaApiError` with `code`, `status`, `details?`,
  `validationErrors?`, `retryAfterMs?`.

Operational defaults and retry policy:

- Defaults: 30 s timeout, 3 retries, 500 ms base backoff, 5-minute cache TTL.
- Retryable HTTP methods: `GET, HEAD, OPTIONS, PUT, DELETE`.
- Retryable status codes: `408, 429, 500, 502, 503, 504`.

Infrastructure features:

- In-memory `ResponseCache` with configurable TTL and prefix invalidation.
- Offset (`PaginationParams`) and cursor (`CursorPaginationParams`) pagination.
- Request/response interceptors for authentication, logging, and transformation.
- `NisabaClientEvents` event emitter for monitoring.
- `connectWebSocket(channel, config?)` for real-time collaboration channels.

The SDK type surface includes: `Witness`, `Folio`, `Collation`,
`CollationResult`, `Apparatus`, `Stemma`/`StemmaNode`/`StemmaEdge`,
`VariantUnit`, `Reading`, `ConceptMap`, `InfluenceNetwork`, `MotifAttestation`,
AI request/response interfaces (`CommentaryGeneratorParams`,
`VariantEvaluatorResponse`, etc.), and `WebSocketMessage<T>`.

> The SDK's collation/stemma/AI endpoints describe the scholarly API the
> `@nisaba/client` package targets. The currently generated and served REST
> contract is the 13-resource V1 surface in §4; the two surfaces are not yet
> unified.

---

## 6. Domain Error Model (`@nisaba/core`)

Source: `libs/nisaba/core/src/errors.ts`. Nisaba's errors are structured rather
than generic strings, which allows API middleware to map them to appropriate
HTTP status codes and allows client code to handle specific error conditions.
The error classes extend `@oshun/errors` base classes, so Oshun-wide error
handling middleware recognizes them.

The `NISABA_ERROR_CODES` registry groups error codes by domain area (all
prefixed `NISABA_`):

- Manuscript (`MANUSCRIPT_NOT_FOUND`, `MANUSCRIPT_ALREADY_EXISTS`,
  `MANUSCRIPT_INVALID_SIGLUM`)
- Collation (`COLLATION_NOT_FOUND`, `COLLATION_INVALID_CONFIG`,
  `COLLATION_ALIGNMENT_FAILED`, `COLLATION_WITNESS_MISSING`)
- Variant (`VARIANT_NOT_FOUND`, `VARIANT_INVALID_CLASSIFICATION`,
  `VARIANT_OVERLAP_DETECTED`)
- Edition (`EDITION_NOT_FOUND`, `EDITION_ALREADY_PUBLISHED`,
  `EDITION_INVALID_APPARATUS`)
- Annotation (`ANNOTATION_NOT_FOUND`, `ANNOTATION_INVALID_TARGET`,
  `ANNOTATION_INVALID_SELECTOR`, `ANNOTATION_LAYER_NOT_FOUND`)
- Reference (`REFERENCE_NOT_FOUND`, `REFERENCE_INVALID_FORMAT`,
  `REFERENCE_AMBIGUOUS`, `REFERENCE_OUT_OF_RANGE`,
  `REFERENCE_RESOLUTION_FAILED`)
- Language (`LANGUAGE_NOT_SUPPORTED`, `LANGUAGE_PARSE_FAILED`,
  `MORPHOLOGY_PARSE_FAILED`, `TRANSLITERATION_FAILED`, `LEXICON_LOOKUP_FAILED`,
  `NORMALIZATION_FAILED`, `ALIGNMENT_FAILED`)
- Comparative (`MAPPING_NOT_FOUND`, `CONCEPT_MAPPING_FAILED`,
  `MAPPING_CYCLE_DETECTED`, `INFLUENCE_EDGE_INVALID`)
- Paleography (`ANALYSIS_NOT_FOUND`, `ANALYSIS_INVALID_FEATURES`,
  `PALEOGRAPHIC_ANALYSIS_FAILED`, `DATE_ESTIMATION_FAILED`, `HAND_NOT_FOUND`)
- Corpus (`CORPUS_NOT_FOUND`, `CORPUS_IMPORT_FAILED`, `CORPUS_EXPORT_FAILED`,
  `CORPUS_CONNECTION_FAILED`, `TEI_PARSE_FAILED`)
- External API (`EXTERNAL_API_ERROR`, `EXTERNAL_API_RATE_LIMITED`,
  `EXTERNAL_API_TIMEOUT`, `IIIF_FAILED`)
- Critical edition (`APPARATUS_GENERATION_FAILED`)
- Unicode/encoding (`UNICODE_INVALID_SEQUENCE`, `ENCODING_CONVERSION_FAILED`,
  `OFFSET_OUT_OF_BOUNDS`)
- Workspace (`PROJECT_NOT_FOUND`, `BOOKMARK_NOT_FOUND`,
  `LIBRARY_ITEM_NOT_FOUND`)

The concrete error classes (each extending an `@oshun/errors` base):
`ManuscriptNotFoundError`, `ManuscriptAlreadyExistsError`,
`CollationNotFoundError`, `CollationAlignmentError`, `WitnessMissingError`,
`VariantNotFoundError`, `VariantOverlapError`, `EditionNotFoundError`,
`EditionAlreadyPublishedError`, `AnnotationNotFoundError`,
`AnnotationInvalidTargetError`, `AnnotationLayerNotFoundError`,
`ReferenceNotFoundError`, `ReferenceFormatError`, `ReferenceAmbiguousError`,
`ReferenceResolutionError`, `LanguageNotSupportedError`, `MorphologyParseError`,
`TransliterationError`, `LexiconLookupError`, `AlignmentError`,
`NormalizationError`, `CollationError`, `ApparatusGenerationError`,
`PaleographicAnalysisError`, `DateEstimationError`, `ConceptMappingError`,
`CorpusConnectionError`, `TEIParseError`, `IIIFError`, `ExternalApiError`,
`ExternalApiRateLimitError`, `ExternalApiTimeoutError`,
`UnicodeInvalidSequenceError`, `OffsetOutOfBoundsError`.

`AncientTextErrorContext` carries optional diagnostic fields that can be
attached to any error: `language`, `script`, `sourceText`, `position`,
`reference`, `corpus`, `manuscriptId`, `projectId`, `apiName`, `operation`. Type
guards: `isNisabaError`, `isRetryableNisabaError`.

---

## 7. Canonical URI Scheme

Nisaba's canonical URI scheme gives every passage in every tradition a single,
stable, dereferenceable identifier that works across all corpus connectors,
canon systems, and the cross-domain bridge.

`@nisaba/core` (`core/src/utils/references.ts`) builds and parses `nisaba://`
URIs. The format is:
`nisaba://tradition/canon/book[/chapter[/verse[-verseEnd]]]`, where each segment
is URI-encoded. Example: `nisaba://Greek%20Philosophy/Classical/republic/514a`.

`@nisaba/canon` (`canon/src/canonical-uri.ts`) documents the canonical form as
`nisaba://tradition/work/reference`, with representative examples:
`nisaba://bible/protestant/Gen.1.1` and `nisaba://hindu/gita/2.47`.

`parseNisabaUri` / `parseUri` decode a URI back to its components for display
and database lookup.

---

## 8. Cross-Domain Contract (`@nisaba/cross-domain`)

`libs/nisaba/cross-domain/src/nisaba-cross-domain.ts` defines the typed
interface through which other Oshun domains consume Nisaba research outputs. The
boundary is important: Nisaba owns the scholarly text layer; Tara, Arete,
Veritas, and Nyx each own their own UX and data model but read-link to Nisaba
entities rather than duplicating them.

The cross-domain types:

- `NisabaCrossDomainCapability` — names the six capabilities Nisaba exposes to
  other domains:
  `tara-passage-companions, arete-study-plans, veritas-source-depth, nyx-cosmology-overlays, shared-concept-graph, domain-to-domain-recommendations`
- `NisabaBridgeDomain` — the domains Nisaba bridges to:
  `tara, arete, veritas, nyx, iris, nisaba`
- Signal interfaces describe the data payloads that cross each boundary:
  `NisabaPassageSignal`, `TaraPracticeSignal`, `VeritasSourceDepthSignal`,
  `NyxCosmologySignal`, `NisabaConceptGraphSignal`
- Builder functions construct the typed link objects for each direction:
  `buildNisabaCrossDomainBridge`, `buildTaraPassageCompanionLinks`,
  `buildAreteStudyPlanLinks`, `buildVeritasSourceDepthLinks`,
  `buildNyxCosmologyOverlayLinks`, `buildSharedConceptGraphLinks`,
  `buildDomainRecommendationLinks`
- `verifyNisabaCrossDomainCoverage` checks that all required capabilities are
  covered; `REQUIRED_NISABA_CROSS_DOMAIN_CAPABILITIES` enumerates the full
  validated set

---

## 9. Mobile Surface (`@nisaba/mobile`)

`libs/nisaba/mobile/src/mobile-study.ts` defines the mobile-specific study
surface for Nisaba. It specifies which features are available on mobile, the
routes those features live at, and the data shapes used by mobile clients.

- `NisabaMobileFeature` — the 12 features available on mobile:
  `domain-home, reading-queue, daily-passage, compact-highlight-flow, annotation-flow, quick-compare, glossary-lookup, morphology-lookup, saved-manuscripts, saved-editions, study-reminders, assistant-assisted-explanation`
- `NisabaMobileRoute` — the 7 mobile routes: `/domains/nisaba`,
  `/mobile/nisaba/queue`, `/mobile/nisaba/daily`,
  `/mobile/nisaba/read/:passageId`, `/mobile/nisaba/compare/:passageId`,
  `/mobile/nisaba/saved`, `/mobile/nisaba/study`
- `NisabaMobileArtifactKind` — `manuscript, edition`
- Data interfaces: `NisabaMobilePassage`, `NisabaMobileGlossaryEntry`,
  `NisabaMobileMorphologyEntry`, `NisabaMobileSavedArtifact`,
  `NisabaMobileStudyReminderRule`

---

## 10. Study Plans (`@nisaba/study-plans`)

`libs/nisaba/study-plans/src/study-plans.ts` defines the data model for
structured learning paths through Nisaba's texts — used both by the Nisaba
workspace and by the Arete cross-domain bridge.

- `NisabaStudyPlanCadence` — `daily, weekly, intensive, self_paced`
- `NisabaStudyPlanStatus` — `draft, active, paused, completed, archived`
- `NisabaStudyPlanDifficulty` — `introductory, developing, advanced, expert`
- `NisabaCompletionForecast` — `on_track, at_risk, blocked, completed`
- Additional enums: `NisabaStudyPlanObjectiveKind`, `NisabaStudyResourceKind`,
  `NisabaStudyStepKind`
- Interface: `NisabaStudyPlanObjective`

---

## 11. Encoding & Interoperability Standards

Nisaba implements a specific set of international encoding and interchange
standards. Compliance with these standards is what makes Nisaba data
interoperable with external digital humanities infrastructure — Perseus, CDLI,
CBETA, and other platforms all expect these formats.

The standards implemented in `@nisaba/standards` (`text-encoding-standards.ts`,
`reference-linked-data.ts`) and `@nisaba/corpora`:

| Standard           | Usage                                                                     |
| ------------------ | ------------------------------------------------------------------------- |
| ISO 15924          | Script codes backing `WritingSystem` enum values                          |
| Unicode NFC        | Text normalization (`core/src/utils/unicode.ts`)                          |
| TEI P5 / EpiDoc    | Critical edition and corpus interchange (`corpora/src/tei-xml-parser.ts`) |
| W3C WADM           | Annotation data model (`@nisaba/annotations`)                             |
| ATF                | Cuneiform interchange (`corpora/src/atf-parser.ts`)                       |
| CTS/CITE           | Canonical reference interoperability (`canon/src/cts-cite.ts`)            |
| Leiden Conventions | Epigraphic markup (`criticism/src/leiden-conventions.ts`)                 |
| IIIF               | Manuscript image manifests (`criticism/src/iiif-linking.ts`)              |

Annotation export formats: `w3c`, `json`, `csv`, `tei`
(`AnnotationExportSchema`). Citation styles in the V1 contract: `chicago`,
`mla`, `apa`, `sbl`, `cts`, `custom`.

---

## 12. Package Inventory

The full set of 22 publishable `@nisaba/*` packages under `libs/nisaba/` (all at
version `0.1.0`):

`annotations`, `api-client`, `assistant`, `canon`, `client`, `comparative`,
`core`, `corpora`, `criticism`, `cross-domain`, `database`, `editions`,
`geotemporal`, `languages`, `mobile`, `paleography`, `philology`, `schemas`,
`standards`, `study-plans`, `translations`, `workspace`.

See `architecture.md` for the dependency layering and per-package internals.
