Modelo de datos
Los tipos del modelo viven en packages/ontology-core (fuente única de verdad,
TypeScript estricto). Esta página los resume. Todo cuelga de
/organizations/{orgId}/… salvo /users (global).
Campos comunes: AuditFields (createdAt, updatedAt, createdBy),
TenantFields (organizationId), SoftDeleteFields (deleted, deletedAt,
deletedBy). Los logs inmutables (ActionExecution, IngestionRun) no llevan
SoftDeleteFields.
OntologyType
/organizations/{orgId}/ontologyTypes/{typeId}
{
id: string // doc id, inmutable
displayName: string
description: string
icon: string // nombre Lucide
category: 'core' | 'derived' | 'use_case'
properties: PropertyDefinition[]
links: LinkDefinition[]
version: number // optimistic locking
// + AuditFields, TenantFields, SoftDeleteFields
}
type PropertyDefinition =
| { name; displayName; required; indexed; type: 'string'|'number'|'boolean'|'date'|'datetime' }
| { …; type: 'enum'; enumValues: string[] }
| { …; type: 'reference'; referenceType: string }
interface LinkDefinition {
name; displayName; targetType: string
cardinality: 'one_to_one' | 'one_to_many' | 'many_to_many'
inverse?: string
}MentatObject
/organizations/{orgId}/objects/{objectId} — colección plana discriminada por typeId.
{
id: string // UUID
typeId: string
properties: Record<string, PrimitiveValue>
links: Record<string, string | string[]>
metadata: {
createdAt; updatedAt; createdBy; version: number
dataSourceId?: string // si vino de una ingesta
externalKey?: string // "{dataSourceId}:{clave}" para upsert
}
// + deleted? (soft delete)
}ActionType
/organizations/{orgId}/actionTypes/{id}
{
id; displayName; description
affectedTypes: string[]
parameters: ActionParameter[] // type: string|text|number|boolean|enum|reference
rules: ActionRule[] // modify | create | delete
validations: ActionValidation[] // permission | stateCheck
sideEffects: ActionSideEffect[] // notification | webhook
permissions: { roles: UserRole[] }
version: number
}
type ChangeSpec = { from: 'literal'; value } | { from: 'parameter'; parameter: string }
type TargetSelector = { from: 'parameter'; parameter: string } // MVP: solo por parámetro reference
interface StateCheckConfig { target: TargetSelector; property: string;
operator: 'eq'|'neq'|'in'|'notIn'|'gt'|'gte'|'lt'|'lte'|'exists'|'notExists'; value? }ActionExecution (inmutable)
/organizations/{orgId}/actionExecutions/{id}
{
id; actionTypeId; executedBy; executedAt
parameters: Record<string, PrimitiveValue>
affectedObjects: string[]
status: 'success' | 'failed' | 'partial'
error?: string
sideEffectsExecuted: { type; status; details? }[]
durationMs: number
}DataSource
/organizations/{orgId}/dataSources/{id}
{
id; name; description?
source: { type: 'csv_upload'|'rest_api'|'webhook'|'manual'; config: … }
mappings: { sourceField; targetType; targetProperty; transformation?; isIdentity? }[]
schedule?: string // cron, solo rest_api
lastRun?; lastRunStatus?
version: number
}Secretos fuera del doc: /dataSourceSecrets/{id} (API keys; deny-all) y el
tokenHash del webhook.
IngestionRun (inmutable)
/organizations/{orgId}/ingestionRuns/{id}
{
id; dataSourceId; triggeredBy: userId|'scheduler'|'webhook'
startedAt; finishedAt; durationMs
status: 'success'|'partial'|'failed'
counts: { read; created; updated; failed }
errors: { row; message }[] // acotado
error?: string
}SavedView
/organizations/{orgId}/savedViews/{id} — privada por owner.
{
id; owner: userId; name; typeId
filters: { property; operator: FilterOperator; value? }[]
sort?: { property; direction: 'asc'|'desc' }
columns?: string[]
// + AuditFields, TenantFields, SoftDeleteFields (sin version)
}AuditEvent (inmutable)
/organizations/{orgId}/auditEvents/{id} — línea de tiempo unificada.
{
id; organizationId
domain: 'ontology' | 'action' | 'user' | 'datasource'
op: string // ej. "action.execution.complete"
actor: userId; occurredAt
target: { kind; id; context? }
versions?: { prev?; next }
diff?; metadata?
}MentatUser
/users/{userId} (global).
{
id; email; displayName
organizationMemberships: { organizationId; role: UserRole; joinedAt }[]
whitelistStatus: 'invited' | 'active' | 'rejected'
metadata: { createdAt; lastLoginAt }
}UserRole = 'admin' | 'editor' | 'operator' | 'viewer'. La membership efectiva
para autorización se lee de /organizations/{orgId}/members/{uid}.