Skip to content

Metacore

Referencia de manifest.json

El manifest es el contrato único entre un addon y el kernel de metacore. Es consumido por Go (kernel/manifest) y mirroreado por el SDK TS vía tygo. Este documento refleja APIVersion = "2.0.0".

Cuando esta especificación evoluciona, APIVersion se bumpea y se documenta un path de migración. Los addons opt-in a una ventana de compatibilidad vía el field top-level kernel.

Tabla de contenidos

Fields top-level

FieldTipoRequeridoSección
key, name, version, kernelstringsí (key/name/version)Identidad
description, category, author, website, license, icon_*stringnoIdentidad
tenant_isolationenumno (default "shared")Aislamiento de tenants
model_definitionsarraynoModelos
navigationarraynoNavigation
actionsobjectnoActions
toolsarraynoTools
capabilitiesarrayrecomendadoCapabilities
hooks, lifecycle_hooksobjectnoHooks
settingsarraynoSettings
frontendobjectnoFrontend
backendobjectnoBackend
signatureobjectstampeado al publicarSignature
eventsstring[]noEvents
i18nobjectnoLocale → árbol de namespace, mergeado al i18next del host vía I18nProvider.

1. Identidad

json
{
  "key": "fiscal_mexico",
  "name": "Facturación Electrónica México",
  "version": "1.0.0",
  "kernel": ">=2.0.0 <3.0.0"
}
FieldTipoRequeridoNotas
keystringRegex ^[a-z][a-z0-9_]{1,63}$. Globalmente único. Define el schema Postgres addon_<key> y el namespace de routes /m/<key>.
namestringNombre para mostrar.
descriptionstringnoDescripción corta, mostrada en la card del marketplace.
versionstringSemver estricto.
categorystringnoUna de integration, utility, finance, crm, operations, ai.
kernelstringrecomendadoRango semver que el kernel del host debe satisfacer. Vacío = legacy.
author, website, licensestringnoMetadata de marketplace.
icon_type, icon_slug, icon_colorstringnoTriplete para renderizado más rico. icon_type: "lucide", "brand" (simple-icons), o "url".

2. Aislamiento de tenants

json
"tenant_isolation": "shared"
ValorComportamiento
"shared" (default)Schema único addon_<key>, columna organization_id + Postgres RLS.
"schema-per-tenant"Un schema por instalación (addon_<key>_<orgshort>), creado al instalar y dropeado al desinstalar. Usar para data regulada.
"database-per-tenant"Reservado para uso futuro.

Vacío se trata como shared por compatibilidad hacia atrás.

3. model_definitions[]

Cada entrada se materializa como CREATE TABLE addon_<key>.<table_name>.

json
"model_definitions": [{
  "table_name": "tickets",
  "model_key": "tickets",
  "label": "Tickets",
  "org_scoped": true,
  "soft_delete": true,
  "columns": [
    { "name": "title",  "type": "string", "size": 255, "required": true },
    { "name": "status", "type": "string", "size": 20, "default": "'open'", "index": true },
    { "name": "total",  "type": "decimal", "default": 0 },
    { "name": "opened_at", "type": "timestamp", "default": "now()" }
  ]
}]

Tipos de columna

TipoPostgresNotas
stringvarchar(<size>)size requerido, máximo 10485760.
texttextSin límite.
uuiduuid
intinteger
bigintbigint
decimalnumeric
boolboolean
timestamptimestamptzSiempre con timezone.
jsonbjsonb

Opciones de columna

FieldTipoSignificado
requiredboolConstraint NOT NULL.
indexboolCrea un índice btree.
uniqueboolConstraint UNIQUE.
defaultanyVer whitelist abajo.
refstringTarget de foreign key. "orders" o "addon_tickets.comments".

Whitelist de default

default va raw al DDL. Solo estos literales pasan validación:

FormaEjemplo
Numérico42, -3, 3.14
String entrecomillado"'open'", "'es-MX'" (sin ', ", ;, \ embebidos)
Llamada a builtin"now()", "gen_random_uuid()", "uuid_generate_v4()", "current_timestamp"
Boolean / nulltrue, false, "null"

Cualquier otra cosa (incluyendo SQL arbitrario) es rechazada por metacore validate.

Regex de keys

Cada identificador suministrado por el usuario (key, model_key, table_name, name de columna) debe matchear ^[a-z][a-z0-9_]{1,63}$. Esto bloquea tanto SQL injection como ambigüedades de quoting de Postgres.

4. navigation[]

json
"navigation": [{
  "title": "sidebar.tickets",
  "icon": "Ticket",
  "target": "sidebar.operations",
  "items": [{
    "title": "sidebar.tickets.board",
    "url": "/m/tickets",
    "icon": "Kanban",
    "model": "tickets"
  }]
}]
  • target (opcional): id de un grupo existente del sidebar core. Cuando matchea, los items se mergean adentro; si no, se crea un grupo nuevo.
  • model: cuando está presente, el host sabe que la route es CRUD dinámico sobre esa tabla. No se requiere código de frontend.

5. actions{} (disparadas por UI)

json
"actions": {
  "tickets": [{
    "key": "resolve",
    "label": "Resolve",
    "confirm": true,
    "requiresState": ["open", "in_progress"],
    "fields": [
      { "name": "note", "type": "text", "required": true }
    ]
  }]
}

Ejecutado como POST /api/models/tickets/:id/actions/resolve. El host despacha a un webhook declarado en hooks, un export WASM, o un ActionInterceptor compilado. modal: "custom_slug" abre un modal de frontend custom.

6. tools[] (disparadas por LLM)

Contraparte semántica de las acciones. Hosts conversacionales las registran en su registry de agent-tool al instalar.

json
"tools": [{
  "id": "create_order",
  "name": "Crear pedido",
  "description": "Crea un pedido cuando el cliente expresa intención de comprar. NO llamar para cotizaciones.",
  "category": "action",
  "endpoint": "/webhooks/create_order",
  "method": "POST",
  "input_schema": [
    { "name": "product_sku", "type": "string", "required": true,
      "extraction_hint": "Código tipo SKU-123 o nombre del producto",
      "normalize": "uppercase" },
    { "name": "quantity", "type": "number", "default_value": "1",
      "extraction_hint": "Si el cliente dice 'una' o 'un par' inferir 1 o 2" }
  ],
  "trigger_keywords": ["pedido", "comprar", "quiero"],
  "trigger_intents": ["order.create"],
  "timeout": 15
}]
FieldPropósito
descriptionPrompt que el LLM ve. Sé específico; incluí casos negativos.
trigger_keywords / trigger_intentsHints para la capa de routing.
input_schema[i].extraction_hintInstrucción en lenguaje natural para el LLM al extraer ese field.
input_schema[i].normalizeTransform post-extracción: uppercase, lowercase, trim, phone_e164.
input_schema[i].validationRegex que el valor debe matchear después de la normalización.
cache_ttlSegundos; distinto de cero marca el tool como tipo idempotent-GET.

7. capabilities[]

Permisos sandboxed que el addon solicita. Enforceados en runtime por kernel/security/context.go. Ver capabilities.md para el catálogo completo de kinds y reglas de validación.

json
"capabilities": [
  { "kind": "db:read",    "target": "users", "reason": "Display author names" },
  { "kind": "http:fetch", "target": "api.factura.com", "reason": "Timbrar CFDI" },
  { "kind": "event:emit", "target": "fiscal.stamped" }
]

El schema propio del addon (addon_<key>.*) es siempre accesible — nunca lo declares.

8. hooks{} y lifecycle_hooks{}

json
"hooks": {
  "tickets::resolve": "/webhooks/resolve_ticket"
}
  • hooks: "<model>::<action>" → <path o URL del webhook>. El host hace POST de un envelope firmado HMAC (ver addon-publishing.md).
  • lifecycle_hooks: triggers CRUD por modelo:
    json
    "lifecycle_hooks": {
      "tickets": [
        { "event": "after_create",
          "target": { "type": "webhook", "url": "/webhooks/ticket_created" },
          "async": true }
      ]
    }
    Tipos de target: webhook, wasm_call, agent_task.

9. settings[]

Valores configurables por instalación. Almacenados por el host en metacore_installations.settings.

json
"settings": [
  { "key": "slack_webhook", "label": "Slack webhook", "type": "text", "secret": true },
  { "key": "default_locale", "label": "Locale",
    "type": "select",
    "default_value": "es-MX",
    "options": [
      { "value": "es-MX", "label": "Español (México)" },
      { "value": "en-US", "label": "English (US)" }
    ] }
]

secret: true asegura que el valor nunca sale del servidor en GETs y se almacena en el secrets manager cuando el host lo soporta.

10. frontend{}

json
"frontend": {
  "entry": "https://cdn.example.com/addons/tickets@1.0.0/remoteEntry.js",
  "format": "federation",
  "expose": "./plugin",
  "container": "metacore_tickets",
  "integrity": "sha384-..."
}
FieldSignificado
entryURL o path relativo de remoteEntry.js.
format"federation" (recomendado) o "script" (window global legacy).
exposeNombre de módulo de federation para importar (ej. ./plugin).
containerNombre del container global. Debe matchear la opción name de @originjs/vite-plugin-federation. Default: metacore_<key>.
integrityHash SRI opcional.

11. backend{}

json
"backend": {
  "runtime": "wasm",
  "entry": "backend/backend.wasm",
  "exports": ["resolve_ticket", "ping"],
  "memory_limit_mb": 64,
  "timeout_ms": 10000
}
RuntimeComportamiento
"webhook" (default)Los hooks despachan como HTTP firmado HMAC saliente.
"wasm"Sandboxed in-process por wasm-abi.md.
"binary"Reservado.

12. signature{}

Stampeado por el marketplace al publicar. Contiene developer_id, algorithm (ed25519), digest (sha256 del bundle), valor de firma y checksums por archivo. Los addons nunca escriben este bloque; lo produce metacore sign y lo verifica el host al instalar.

13. events[]

Lista de nombres de topic que el addon va a publicar. Hosts con un event bus registran el schema; los suscriptores declaran capabilities: [{kind: "event:subscribe"}].

json
"events": ["ticket.created", "ticket.resolved"]

Ver también

  • dynamic-ui.md — cómo el SDK convierte la metadata derivada de este manifest en una UI CRUD funcional.
  • addon-cookbook.md — recetas para foreign keys, soft delete, acciones custom, eventos y más.
  • capabilities.md — catálogo completo de valores kind y patterns de target.
  • quickstart.md — walkthrough hands-on end-to-end.

Metacore es open-source. Apache-2.0.