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 aTaxonomyStewardshipwith aleadStewardIdanddelegateStewardIds. - Freshness.
gradeTaxonomyFreshnessgrades each objectfresh | stale | overduefrom time-since-last-review against operator-suppliedstaleAfterSeconds/overdueAfterSecondsthresholds. - Drift.
detectTaxonomyDriftwatches artifact mass attached to a node over a sliding window and emits aDriftSignalwith adeltaRatioand ashrinking | growing | stabledirection — 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.
prioritizeCurationBacklogre-scores the backlog by bumping priority forstale(+1),overdue(+3), large drift (|delta|>0.5, +2), and contested objects (+4), then sorts descending. - Audit history.
AuditTrailEntryrecords eachadd | merge | split | deprecate | reparent | rolled-out | rolled-back | contested | review-signoffwith actor, justification, and evidence;auditTrailForObjectfilters 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:
TranslationSegment—segmentId,sourceLocale,targetLocale,sourceText,targetText, asourceHash(the linchpin of stale detection),translatedAtUnixSeconds, andreviewedByCreatorId.TranslationMemoryEntry— a source/target text pair with ascore.fuzzyMatchTranslationMemoryfinds the best candidate aboveminScoreusing a real token Jaccard similarity, realizing the source's "translation memory, fuzzy match."GlossaryEntry—term, locale pair,preferredTranslation, adoNotTranslateflag, and alineageScopeso terminology can be scoped to a specific tradition.lookupGlossaryresolves 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_AREAS — rtl-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):
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 toreview-requestedwith no reviewers;open-signoff-items— approving while required checklist items remain (the count is supplied by the §16.8 collaboration layer'sevaluateReviewSignoff);conflicts-unresolved— merging whilehasUnresolvedConflictsis set.
A ChangeRequest also names its mergeStrategy —
branch-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 thenextOccurrencesrecurrence 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: theEDITORIAL_LIFECYCLE_STATESarray has noretractedand norejectedmember (the canonical terminal/feedback names aretakedownandchanges-requested); an older mermaid diagram inarchitecture/oshun-studio.mduses the non-existentretracted/rejectednames, 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) bootscreateContentHttpServerover an Iris-routed writer and a three-member judge panel, and fails loud without anANTHROPIC_API_KEY(NotConfiguredError). Its router (http-router.ts) exposesPOST /v1/content/briefs,GET /v1/content/runs,GET /v1/content/runs/:id,POST /v1/content/runs/:id/replay, and the operator viewsGET /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. buildTaxonomyChangePreviewcomputes 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.
Related#
- Collaboration, Review, and Templates — the §16.8/§16.9 sibling that owns review signoff and reusable templates
- Editorial Calendar and Asset & Media Library — the editorial lifecycle and asset metadata these disciplines support
- Creator Roles and the Authoring Workspace — the roles and the workspace that consume this library
- Search, Discovery, Recommendations, and Knowledge Graph — what the curated concept graph feeds
- Content, Localization, Documentation, Launch, and Exit Criteria — the launch-readiness gates the locale scorecards inform
- Isis Generation Control and Sophia Grounding — the §32 generate→gate pipeline upstream of authoring
- Product Surfaces and the hub ../features.md