Oshun Platform · Features

Taxonomy, Localization, and Versioning

A focused page within the Oshun Platform Features documentation. The full map and every sibling page live in the Features hub.

7sections12 minread3tables

On this page

These three Oshun Studio disciplines — concept-graph curation, the translation workspace, and per-artifact version control — are the connective tissue that keeps published content coherent, multilingual, and reversible as it ages. They serve curators, translators, reviewers, and editors who work behind the customer experience, and they sit inside the §16 Studio surface (a route tree under apps/oshun/web/src/app/studio/, not a separate app). All three are implemented as real, domain-specific logic — not CRUD stubs — in the source library @oshun/studio-authoring (v0.1.0, pure ESM, main/types pointing at ./src/index.ts), which re-exports nine subdomain modules including taxonomy-curation, localization-workflow, and versioning. This page is the feature-side companion to the architecture catalog; the hub for the set is ../features.md.

What ships, honestly#

The contract and logic layer is real and tested. Taxonomy curation, localization, and versioning are pure functions over typed records — cycle detection runs a real three-color DFS, stale-translation detection compares a stored sourceHash against the current source, the version chain validator emits five distinct issue kinds, and a translation-memory lookup computes real Jaccard similarity. The web workspace at apps/oshun/web/src/components/studio/StudioAuthoringWorkspace.tsx genuinely imports from @oshun/studio-authoring, so this is consumed code rather than a shelf library.

What is honestly less than the prose implies: these modules are pure functions whose timestamps are inputs, not a clock, and there is no database persistence inside the library itself — durability lives at the application and infrastructure layer (Postgres for state, MinIO/S3 for binary assets). The ontology-impact "preview" computes affected-artifact counts from operator-supplied maps; it does not crawl a live index. Where something is spec-only or relies on an external substrate, this page says so. Honest "planned/gated" beats fake "shipped."


Taxonomy, Ontology, and Concept Graph Curation#

Implemented in libs/oshun/studio-authoring/src/taxonomy-curation/ (§16.5), this is the operator-and-curator surface for shaping the shared vocabulary that every domain reads from: tags, themes, lineages, moods, modalities, topics, claim clusters, prerequisite chains, and the concept graph itself.

Object kinds and operations#

The library names the curatable object kinds and the operations as canonical constants in curation.ts:

Constant Members
TAXONOMY_OBJECT_KINDS (10) tag, theme, lineage, mood, modality, topic, claim-cluster, prerequisite-chain, concept-node, concept-edge
TAXONOMY_CURATION_OPERATIONS (5) add, merge, split, deprecate, reparent

A curator's intent is captured as a TaxonomyChangeRequest carrying the operation, targetKind, the targetIds it acts on, an optional newParentId (for reparenting), newNodeIds (for splits), evidenceReferences, a contestStatus of uncontested | contested | resolved, and proposer provenance. This is the structural answer to the source's promise of "add, merge, split, deprecate, and re-parent" with "contested-edge review."

Typed, evidence-bearing edges#

The concept graph uses typed edges, not bare links. CONCEPT_EDGE_TYPES enumerates seven relationships — broader-than, narrower-than, prerequisite-for, contradicts, supports, lineage-derived-from, and locale-variant-of — and a ConceptEdgeRecord carries a provenanceActorId, evidenceReferences, a contested flag, and a resolvedByCreatorId. The function validateConceptEdgePolicies is where "evidence requirements per edge type" becomes enforceable: four edge types (prerequisite-for, contradicts, supports, lineage-derived-from) are held in an EVIDENCE_REQUIRED_EDGE_TYPES set, and any such edge with no evidenceReferences yields a missing-evidence issue. The validator also flags invalid-endpoint (an edge pointing at a non-existent node), missing-provenance (no actor recorded), and unresolved-contest (a contested edge with no resolver) — directly realizing "edge typing, edge-provenance attribution, evidence requirements per edge type, contested-edge review, and conflict resolution."

Ontology change preview, staged rollout, and rollback#

Before a change lands, buildTaxonomyChangePreview answers the source's "downstream impact" promise: given maps of artifactsByTag, recommendationSlotsByTag, and searchResultsByTag, it returns a TaxonomyChangePreview with the de-duplicated affectedArtifactIds, the count of affectedRecommendationSlots, and the count of affectedSearchResults — so a curator can see which artifacts shift, how recommendations re-rank, and how search results change before committing.

Rollout is a gated state machine. ONTOLOGY_ROLLOUT_STAGES = preview → staged → rolled-out → rolled-back, and tryAdvanceOntologyRollout enforces the legal adjacency (preview→staged, staged→{rolled-out, rolled-back}, rolled-out→rolled-back, rolled-back→∅). It refuses with a typed reason — illegal-transition, commit-timestamp-already-set, or rollback-timestamp-already-set — rather than silently no-op'ing, and stamps committedAtUnixSeconds / rolledBackAtUnixSeconds on the appropriate transition. This is the "staged rollout, and rollback" guarantee made mechanical.

Cross-domain mapping#

A CrossDomainMapping binds one conceptId to its local identity across the domains nisaba | veritas | nyx | metis | tara | arete, so the source's example — a Nisaba concept ↔ a Veritas topic ↔ a Nyx phenomenon ↔ a Metis learning objective — is a real shape, not just a sentence. See Search, Discovery, Recommendations, and Knowledge Graph for how these mappings feed retrieval.

Stewardship, freshness, drift, and audit#

stewardship.ts makes ontology maintenance a first-class concern.

  • Stewards. TAXONOMY_STEWARD_ROLES = lead-curator, domain-curator, lineage-steward, linguistics-reviewer. Each object carries a TaxonomyStewardship with a leadStewardId and delegateStewardIds.
  • Freshness. gradeTaxonomyFreshness grades each object fresh | stale | overdue from time-since-last-review against operator-supplied staleAfterSeconds / overdueAfterSeconds thresholds.
  • Drift. detectTaxonomyDrift watches artifact mass attached to a node over a sliding window and emits a DriftSignal with a deltaRatio and a shrinking | growing | stable direction — the documented intuition is that a node that used to anchor many artifacts but has lost mass "usually deserves attention" because drift can indicate ontology rot.
  • Backlog priority. prioritizeCurationBacklog re-scores the backlog by bumping priority for stale (+1), overdue (+3), large drift (|delta|>0.5, +2), and contested objects (+4), then sorts descending.
  • Audit history. AuditTrailEntry records each add | merge | split | deprecate | reparent | rolled-out | rolled-back | contested | review-signoff with actor, justification, and evidence; auditTrailForObject filters the immutable, append-only trail.

Integrity tests#

checkTaxonomyIntegrity returns a TaxonomyIntegrityVerdict with orphanCount (nodes with zero edge incidence), cycleDetected (a real DFS over prerequisite edges using VISITING/VISITED colors), invalidEndpointEdgeCount, missingProvenanceEdgeCount, and contestedCount. This is the engine behind the source's "cycle detection, orphan detection, prerequisite consistency, edge-provenance completeness" tests — and it is genuine graph logic, not a truthiness check.


Localization and Translation Workspace#

Implemented in libs/oshun/studio-authoring/src/localization-workflow/ (§16.6), this is the translator's surface for segment-level translation, glossary control, per-locale QA, and stale-translation detection. The launch locale set it works against is fixed in libs/oshun/i18n/src/index.ts: OSHUN_LAUNCH_LOCALES = en-US, es-US, fr-FR, de-DE, ar, he, ja-JP, pt-BR (eight), with OSHUN_DEFAULT_LAUNCH_LOCALE = en-US. Two of those — ar and he — are right-to-left, and the i18n library records that in RTL_LOCALES with a localeDirection() helper, which is why the QA layer treats RTL as a real concern rather than a checkbox.

Segment model, translation memory, and glossary#

The core records in translation.ts:

  • TranslationSegmentsegmentId, sourceLocale, targetLocale, sourceText, targetText, a sourceHash (the linchpin of stale detection), translatedAtUnixSeconds, and reviewedByCreatorId.
  • TranslationMemoryEntry — a source/target text pair with a score. fuzzyMatchTranslationMemory finds the best candidate above minScore using a real token Jaccard similarity, realizing the source's "translation memory, fuzzy match."
  • GlossaryEntryterm, locale pair, preferredTranslation, a doNotTranslate flag, and a lineageScope so terminology can be scoped to a specific tradition. lookupGlossary resolves a term for a given locale pair.

The glossary editor (glossary-editor.ts) applies add | update | remove operations through applyGlossaryEdit, returning a typed verdict that refuses duplicates (reason: 'duplicate') and missing entries (reason: 'not-found') rather than corrupting the list. enforceGlossaryInSegment then audits a translated segment: if the source contains a glossary term, it flags do-not-translate-violated when a DNT term was translated away, or preferred-term-missing when the preferred translation is absent — the mechanical form of "glossary enforcement."

Locale-specific QA#

Two layers of QA run over segments. evaluateLocaleQA covers the LOCALE_QA_CHECKLIST_AREASrtl-layout, text-expansion, date-format, time-format, number-format, currency-format, honorifics, contemplative-sensitivity, accessibility — and, for example, warns when a target string expands past a maxExpansionRatio (text-expansion is a genuine RTL/CJK layout risk) and emits an RTL render-parity note for ar-*/he-* targets.

locale-formats.ts adds format-leak detectors that catch source-locale conventions bleeding into a translation:

Detector Catches
detectSourceDateLeak "April 5, 2026" or M/D/Y left in a non-English target
detectSourceTimeLeak am/pm clock surviving into a locale that uses 24h
detectNumberFormatMismatch , group separator where the locale expects . or a space (per NUMBER_FORMAT_BY_LANG)
detectCurrencyFormatMismatch $/USD formatting left in a non-en-US segment
detectHonorificGap a source honorific with no -さん/-様 (ja), -님/-씨 (ko), or equivalent in honorific-gated languages (ja, ko, hi, ar)
detectContemplativeSensitivity sensitive religious/contemplative terms (guru, rinpoche, imam, rabbi, lama, sensei, …) flagged for lineage-steward confirmation

The contemplative-sensitivity scanner exists because Oshun's content spans prayer-style and lineage-rooted practice; it routes a finding back to a lineage steward rather than auto-deciding. See Tara — Rituals and Contemplative Practice for the lineage model these terms touch.

Queues, stale detection, and re-translate triggers#

prioritizeLocaleQueue orders LocaleQueueItems by priority, then by deadlineUnixSeconds, each item carrying its locale, artifactId, segmentIds, assignedTranslatorId, and assignedReviewerId — the source's "localization queues per locale with priority, deadline, reviewer assignment."

Stale detection is the workspace's most load-bearing piece. detectStaleTranslations compares each segment's stored sourceHash against the currentSourceHashes map and returns stale: true with reason source-text-changed-since-translation when they diverge. buildReTranslateTriggers emits a ReTranslateTrigger (with priorSourceHash and newSourceHash) for every changed segment, and surfaceCustomerStalenessBanners produces the customer-facing message — "This translation is being updated to reflect a recent revision of the source text." — fulfilling the source's promise that "stale-translation indicators surfaced in the customer experience."

Launch readiness scorecards#

buildLocaleLaunchScorecard produces a per-locale, per-domain LocaleLaunchScorecard: translated vs. total segment counts, stale-segment count, blocking-QA-finding count, and a readinessRatio in [0,1] computed as (translated − stale) / total − 0.1 × blockingFindings, clamped. This is the quantitative "locale launch readiness scorecards per domain and per surface" the source describes, and it feeds the broader launch gates discussed in Content, Localization, Documentation, Launch, and Exit Criteria.


Versioning, Diff, and Rollback#

Implemented in libs/oshun/studio-authoring/src/versioning/ (§16.7), this is the PR-style change-control layer for shared content — version history, visual diff, branch/propose/merge, and rollback with cascade detection.

Version records and chain integrity#

Every revision is an ArtifactVersionRecord (versioning.ts):

ts
interface ArtifactVersionRecord {
  artifactId: string;
  version: number;
  authorId: string;
  authoredAtUnixSeconds: number;
  changeSummary: string;
  approvalState: 'pending' | 'approved' | 'rejected' | 'merged';
  reviewPackageId: string | null;
  priorVersion: number | null;
  contentFingerprint: string;
}

appendArtifactVersion links each record to its predecessor by setting priorVersion. validateVersionChain is the integrity check the source calls "version chain integrity," and it emits five distinct VersionChainIssue kinds:

Issue kind Meaning
artifact-mismatch a record belongs to a different artifact than the chain head
non-monotonic-version a version number that does not strictly increase
prior-version-mismatch priorVersion does not point at the actual predecessor
missing-review-package an approved/merged record with no reviewPackageId
empty-change-summary a blank changeSummary

That an approved or merged version must carry a review-package link is the audit-linkage guarantee made enforceable, not aspirational.

Visual diff#

buildVersionDiff produces a VersionDiff whose entries are typed by what changed, matching the source's "visual diff for prose, structured blocks, citations, source bindings, assets, metadata, and translations": VersionDiffEntry.kind is one of prose | structured-block | citation | source-binding | asset | metadata | translation. It diffs two field maps, skips unchanged paths, and labels each change by the supplied kindByPath (defaulting to metadata).

Branch, propose-change, request-review, and merge#

branching.ts models the change request as its own state machine. CHANGE_REQUEST_STATES = draft → review-requested → changes-requested → approved → merged | abandoned, with a strict ALLOWED_CR_TRANSITIONS adjacency. transitionChangeRequest refuses bad moves with typed reasons:

  • illegal-transition — not an allowed edge;
  • missing-reviewer-assignment — moving to review-requested with no reviewers;
  • open-signoff-items — approving while required checklist items remain (the count is supplied by the §16.8 collaboration layer's evaluateReviewSignoff);
  • conflicts-unresolved — merging while hasUnresolvedConflicts is set.

A ChangeRequest also names its mergeStrategybranch-takes-precedence | trunk-takes-precedence | three-way-merge — and mergeChangeRequestVersion mints the trunk version that the merge produces (stamped approvalState: 'merged' with the change-request id as its reviewPackageId). projectTrunk reconstructs the trunk as of a given version, which is what a "request-changes / merge analogous to PR review" workflow needs.

Rollback with cascade detection#

Rollback is deliberately hard to do by accident. buildRollbackCascade throws unless every precondition holds: a non-empty artifactId, a positive integer toVersion, a non-empty userVisibleNote, and — critically — that the operator-supplied confirmedDownstreamArtifactIds exactly match the computed downstreamArtifactIds (same set, same size). Only then does it return a RollbackCascade with operatorConfirmedCascadeScope: true. This is the source's "operator-confirmed cascade scope" over "linked artifacts, citations, derived courses, derived study plans": you cannot roll back without acknowledging the exact blast radius. rollbackWithChangeRequest wraps the same guarantee inside a change-request so the rollback is itself auditable, requiring both an initiatedByCreatorId and a changeRequestId.

Public-facing change notes#

buildPublicChangeNote emits a PublicChangeNote with a summary, a reason, and a visibility of public | subscriber | tenant — the "updated on … because …" transparency note the source requires where policy demands it, with linkage back to the relevant correction or evidence revision.


How these connect to the rest of V1#

  • The editorial lifecycle (§16.3, editorial-lifecycle/) is the upstream state machine these three disciplines support; its 12-state model (idea → … → published → updated → deprecated → sunset → archived → takedown) and the nextOccurrences recurrence engine — which literally encodes daily Veritas briefings, daily Tara passages, weekly Arete reflections, nightly Nyx highlights as cadence logic — are detailed in Editorial Calendar and Asset & Media Library. A small caveat worth recording: the EDITORIAL_LIFECYCLE_STATES array has no retracted and no rejected member (the canonical terminal/feedback names are takedown and changes-requested); an older mermaid diagram in architecture/oshun-studio.md uses the non-existent retracted/rejected names, so trust the code names quoted here.
  • The §32 agentic content pipeline is where many artifacts originate before they enter taxonomy/localization/versioning. The deployable @oshun/content-service (apps/oshun/content-service, project @oshun/content-service-app) boots createContentHttpServer over an Iris-routed writer and a three-member judge panel, and fails loud without an ANTHROPIC_API_KEY (NotConfiguredError). Its router (http-router.ts) exposes POST /v1/content/briefs, GET /v1/content/runs, GET /v1/content/runs/:id, POST /v1/content/runs/:id/replay, and the operator views GET /v1/operator/runs[/:id]. The generated, gated artifact is what a curator tags, a translator localizes, and the versioning layer tracks — see Isis Generation Control and Sophia Grounding for the generate→gate handoff.

Honest scope notes#

  • The three modules are pure functions over typed records; persistence (Postgres) and asset storage (MinIO/S3) are real but live at the application and infrastructure layer, not inside @oshun/studio-authoring.
  • buildTaxonomyChangePreview computes impact from operator-supplied maps; it does not query a live recommendation or search index.
  • Real-time co-editing and the customer-facing rendering of stale banners depend on running substrates documented elsewhere; the library supplies the logic and the contracts, and reports absence honestly rather than faking success.