crear:tarea
- mutating
- requiresConfirmation
- scopes: tarea:crear
- handler: TaskService::create
Arma la tienda capacidad por capacidad. El campo acepta un módulo solo cuando todo lo que requiere ya está sembrado.
El tallo está listo para recibir módulos.
Campo listo
Selecciona una semilla o arrástrala al campo.
El juego representa módulos como nodos y sus capacidades como aristas. Cada módulo también se puede sembrar con teclado.
Lo que acabas de hacer
Validación de capacidades, ordenamiento topológico de Kahn y detección de ciclos. El runtime real lista los módulos del ciclo; la ruta A → B → A aquí es una explicación visual.
docs/GUION-WEBINAR-JUNIORS.md · getmilpa-resolver/src/Engine/GraphResolver.php:895-987 (computeLoadOrder, el Kahn real) · getmilpa-runtime/src/Kernel.php:156-177 (la compuerta y el boot en loadOrder).
CLI y MCP llegan a la misma acción por el mismo mecanismo de ejecución. El canal cambia el contexto y puede cambiar la policy; el tubo es compartido.
enviar_correo
Una definición declarada; dos callers con contextos explícitos.
Esperando caller
La misma acción puede entrar por CLI o por tools/call.
Mismo pipeline no significa mismo permiso
El runtime recibe un ToolContext. La configuración actual permite CLI por defecto y exige autenticación para MCP; ambas rutas sí convergen en ToolRegistry::call().
ToolRegistry.php:348-570 · JsonRpcService.php:282-300 · PolicyGate.php:34-56. Los botones simulan callers; no representan comandos incluidos hoy en el skeleton.
Una acción sensible queda pendiente hasta recibir un veredicto explícito. Aprobar, rechazar y exonerar producen datos distintos.
Dos contratos relacionados, no una sola llamada
VerificationResult modela PENDING/PASSED/FAILED/WAIVED. El anti autoaprobación vive en el flujo de gates; la exoneración con justificación vive en workflow. Esta pantalla los reúne para enseñar el sistema.
VerificationStatus.php:17-48 · HumanGate.php:101-165 · ProcessSubmitDecisionTool.php:63-99.
Un mapa de responsabilidades y dependencias declaradas. Elige un recorrido para ver qué paquetes participan sin convertir el ecosistema en una sola caja.
Las flechas son dependencias declaradas
No intentan inferir todas las llamadas en runtime. En MCP, HTTP/SSE/stdio pertenecen al host; JsonRpcService solo conoce arrays decodificados.
getmilpa-runtime/composer.json · getmilpa-orchestrator/composer.json · JsonRpcService.php:25-37 · ComponentDefinitionInterface.php:23-63 · milpa-design/package.json:11-42.
El recorrido de once pasos es una buena entrada. La implementación real agrega límites que importan para seguridad, costos, confirmación e intercepción.
Selecciona una ruta
La cobertura de auditoría cambia según el punto de retorno.
| Salida | Callback | Evidencia de auditoría |
|---|---|---|
| Tool inexistente | No | sin log explícito |
| Args inválidos | No | validation failure |
| Policy denied | No | auth failure |
| Plan / confirmación | No | sin log explícito |
| Cache short-circuit | No | tool.executed |
| Veto puro | No | hueco conocido |
| Callback / throw | Sí | executed / failed |
| Paso | Rol | Presencia | Fuente |
|---|---|---|---|
| Resolver | Guardia | Activo | búsqueda en el registry |
| Validar | Guardia | Activo | el tool declara inputSchema |
| Acotar | Transformación | Omitido | el tool no declara clamps |
| Autorizar | Guardia | Activo | canal 'web': require_auth=true, allow_all=false; tool sin scopes declarados; DB rules: skipped (no provider) |
| Rate limit | Guardia | Omitido | wiring del host: rateLimiter ausente; costo mutating?5:1 (mutating=false → costo 1) |
| Modo plan | Bifurcación | Condicional | dispara si ctx.mode es 'plan'; valor actual: execute |
| Confirmar | Bifurcación | Dormido | ni tool.requiresConfirmation ni la política del canal exigen confirmación para este tool |
| Emitir executing | Gancho | Omitido | wiring del host: dispatcher ausente (ancla + cache/veto) |
| Ejecutar | Ejecución | Activo | callback; se inyecta _ctx |
| Contener excepción | Límite | Activo | envuelve execute; \Throwable → INTERNAL_ERROR |
| Auditar | Resultado | Activo | audita: validate-fail, authz-fail, rate-limit, cache-hit, execute-éxito, execute-fallo; NO audita: resolve-miss, plan-mode, confirm, veto |
La garantía se lee por rama
“Todo se audita” no describe hoy todas las salidas tempranas. Este artifact hace visibles tanto las garantías implementadas como los huecos.
getmilpa-tool-runtime/src/ToolRegistry.php:348-570 · redacción en ToolAuditLogger.php:45-57,204-232.
No se guarda current_state. Se anexan hechos y un reducer puro reconstruye el estado al reproducirlos en orden.
el estado no existe como columna persistida — se re-deriva del log en cada corte
Minimalista a propósito
El file store actual reescanea JSONL para replay y secuencia; sirve para entender el patrón y persistencia ligera, no debe presentarse como event store concurrente de gran escala.
EventStoreInterface.php:7-42 · FileEventStore.php:25-83 · orchestrator/Reducer.php:19-68.
La intención visual no termina en un documento: tokens, contratos y pares de contraste pasan por generadores y gates repetibles.
tokens/milpa-tokens.json concentra primitivas y semántica.
Genera CSS, preset Tailwind y theme.contract.json.
Contraste, gobernanza, capas, drift y skins.
Carga seis bundles y puede sobrescribir tokens por contrato.
AA pasa
El gate mide el color efectivo, no el valor alpha aislado.
Garantía concreta, alcance concreto
verify-governance valida forma básica y tokens, no todo el JSON Schema. verify-theme fuerza directamente la opacidad de --bg; otras invariantes quedan documentadas.
scripts/build-tokens.mjs:189-265 · verify-governance.mjs:38-98 · verify-contrast.mjs:28-42 · verify-theme.mjs:107-149.
El generador devuelve archivos planeados en memoria. Eso permite inspeccionar el resultado y ejecutar un preflight de colisiones antes de escribir.
entity Product
fields: nombre:string:120,
precio:decimal:10,2final class Product implements EntityInterface
{ public string $nombre; }
{ public Decimal $precio; }Produce PlannedFile(path, contents).
Aborta antes de escribir si un target existe.
Crea directorios y escribe contenidos.
Entity cierra el loop contra reglas de runtime.
Preview posible, CLI pendiente
GenerationResult ya hace inspeccionable el plan, pero el skeleton actual no expone --dry-run: hace preflight, escribe y luego verifica entidades. WriteGuard evita overwrite; no promete atomicidad ni rollback.
getmilpa-devtools/src/Make/GenerationResult.php:8-29 · PlannedFile.php:8-28 · WriteGuard.php:12-41 · getmilpa-skeleton/src/Console/Application.php:238-334 (makeController/makeEntity).
Una operación se declara una vez. coa, MCP y HTTP son adaptadores del mismo handler — pero cambiar de puerta puede cambiar la política.
crear:tarea
Cobertura por superficie: MCP y HTTP aplican hoy los mismos scopes: HTTP corre RequireScopeMiddleware y después el mismo PolicyGate que usa MCP. coa no aplica scopes — es la superficie local/confiable, por diseño. Esto fue un hueco de cobertura real; se cerró cuando HttpProjector empezó a exigir scopes. La Radiografía del runtime muestra el pipeline completo ya gobernado. Fuente: Operation.php, HttpProjector.php.
coa crear:tarea --titulo=… --yes
Elige una puerta para proyectar.
tools/call · crear:tarea
Elige una puerta para proyectar.
POST /crear/tarea
Elige una puerta para proyectar.
| Superficie | Confirm | Scopes aplicados |
|---|---|---|
| coa | flag --yes | no (local/confiable) |
| MCP | gate heredado (tool-runtime) | sí (PolicyGate) |
| POST | token 428→201 | sí (RequireScopeMiddleware + PolicyGate) |
El handler nunca cambió. Cambiaste de puerta y el framework sintetizó la invocación — pero la puerta puede cambiar la política, y hoy no todas aplican los mismos scopes.
Modelo didáctico sobre implementación auditada. Este artifact sí afirma que HTTP aplica scopes (vía HttpProjector + PolicyGate) — no afirma que el token store in-memory sea de producción.
milpa/command: getmilpa-command/src/Operation.php, CommandProvider.php, SurfaceProjector.php.skeleton ≥0.7.0, ns App\Command, no parte del paquete): getmilpa-skeleton/src/Command/{CliProjector,McpProjector,HttpProjector}.php.milpa/auth ≥0.1, RequireScopeMiddleware.php + el mismo PolicyGate que usa MCP.ConfirmTokenStore.php (in-memory, single-process — no producción), SchemaCoercer.php.docs/GUION-WEBINAR-JUNIORS.md (Artifact 2).Cuando la lógica pura emite prosa en un idioma, localizarla te obliga a elegir dónde vive la traducción. Mapear en la frontera preserva la API — pero abre una clase de fuga: es hermética solo si cada punto de consumo pasa por el mapa.
Prefiere (a) cuando puedes refactorizar el núcleo: la garantía la da el compilador, no tu memoria. Usa (b) cuando la API tiene que quedarse quieta — y entonces trátala como lo que es: un contrato de cobertura total.
La frontera de abajo tiene varias salidas del núcleo. Cambia el idioma del demo a en y elige la salida que el mapa no cubre: el motor reporta mapped:false y el consumidor, sin traducción, deja pasar el código crudo — un literal en español dentro de la vista en inglés. Un solo punto olvidado basta.
Una frontera con ocho salidas del núcleo y un mapa que traduce al inglés. Falta una clave a propósito.
| Salida del núcleo | en · mapeado |
|---|---|
sin iniciar |
not started mapeado |
solicitado |
requested mapeado |
esperando verificación |
awaiting verification mapeado |
listo para ejecutar |
ready to execute mapeado |
detenido |
fuga |
ejecutando |
executing mapeado |
completado |
completed mapeado |
fallido |
failed mapeado |
faltan: detenido
Con la clave faltante, el acople falla: eso es exactamente lo que rompería CI antes de llegar a producción.
Agrega la clave que falta con Agregar al mapa. La salida que fugaba ahora resuelve a su traducción y la vista en inglés queda limpia. Reparaste el síntoma; lo que sigue evita que vuelva.
coupleCheck acopla los códigos que emite el núcleo con las claves que traduce el mapa. Reporta missing (un código sin traducción) y orphan (una clave muerta) y solo dice ok si la cobertura es total en ambos sentidos. Un estado nuevo del núcleo sin su clave rompe CI — no se cuela a producción por el ?? code de fallback.
Al bilingüizar esta galería, applyGateDecision localizó el resultado visible (#gate-result) pero el push al registro de auditoría siguió usando la prosa cruda del núcleo (verdict.reason). En /en/, el flujo de auto-aprobación mostraba una oración completa en español dentro del audit trail. El review lo cazó enumerando todas las salidas de las cuatro funciones y persiguiendo cada punto de consumo; el smoke del navegador no lo vio porque miró el resultado, no el registro. El fix empujó t.gateConstructionReason (localizado) en vez de verdict.reason.
Este demo es el patrón destilado: una frontera de juguete para que veas la fuga y la cierres. La implementación auditada vive en artifacts.js (los mapas PROJECTION_*_EN, el fix del audit trail) y en artifacts-core.js (el núcleo neutro) de esta misma galería.
Una frontera es cobertura total, no mayoría
Las defensas reales, en orden: prefiere códigos neutros en el núcleo cuando puedas (la fuga se vuelve imposible por construcción); si mapeas en la frontera, acopla los enums del núcleo a las claves del mapa con un test que rompa CI ante un estado sin traducir; y verifica la superficie completa, no solo el happy path que se ve. Es la gemela de i18n de La compuerta: lo que el sistema garantiza por construcción contra lo que promete por disciplina.
artifacts/artifacts.js (mapas PROJECTION_*_EN, fix del audit trail) · artifacts/artifacts-core.js (frontierProject, coupleCheck) · tests/i18n-contract.test.mjs (acople enum↔mapa) · docs/LESSON-CANDIDATES.md · commit 058fdf9.
El boot real no empieza ejecutando: empieza resolviendo. El kernel refleja los manifiestos, resuelve el grafo completo con milpa/resolver y solo entonces decide — un grafo bloqueado lanza ArchitectureBlockedException con el ResolutionReport a bordo; un grafo cerrado arranca en el orden que el propio reporte trae: loadOrder[].
Elige un escenario. Cada panel muestra el reporte REAL que el motor emitió para ese grafo, congelado de milpa/resolver 0.5.0.
Tres paquetes y todo lo requerido tiene proveedor: el grafo cierra y el estado es valid. El reporte trae loadOrder[] — la misma resolución que validó el grafo también lo ordenó, así que el orden de boot no puede divergir de lo validado.
milpa/config1.0.0milpa/correo0.3.0milpa/notificador1.2.0Nadie provee correo.transport. El resolver reporta el hueco como error aprendible y el estado bloquea: existe un orden parcial, pero la compuerta no se abre con el grafo abierto — el kernel lanza ArchitectureBlockedException antes de arrancar nada.
milpa/config1.0.0milpa/notificador1.2.0Compuerta cerrada: el kernel lanza ArchitectureBlockedException con este reporte a bordo — nada arranca, aunque exista un orden parcial.
milpa/riego y milpa/siembra se requieren en círculo: para ellos no existe orden de boot. El motor excluye a los miembros del ciclo de loadOrder[] — el independiente milpa/config conserva su lugar — y el estado bloquea el arranque completo.
milpa/config1.0.0milpa/riego y milpa/siembra quedan fuera de loadOrder[]: son los miembros del ciclo.
Compuerta cerrada: el kernel lanza ArchitectureBlockedException con este reporte a bordo — nada arranca, aunque exista un orden parcial.
El milpa.json declara una arquitectura y el #[PluginMetadata] del código carga otra. El reporte solo NO trae el drift: el motor nunca emite ese código. Lo detecta DriftDetector del lado del caller y coa:inspect architecture lo presenta junto al reporte — exactamente como aquí.
| campo | declarado · milpa.json | actual · #[PluginMetadata] |
|---|---|---|
provides | inventario.precios | |
version | 1.0.0 | 1.1.0 |
milpa/inventario1.1.0Salida real de GraphResolver::resolve() y DriftDetector::toLearnableErrors(); el snippet PHP exacto de captura vive como comentario junto a cada blob en la fuente de esta galería.
{
"status": "valid",
"errors": [],
"resolved": [
{
"kind": "capability",
"id": "config.provider",
"constraint": "*",
"level": "required",
"requiredBy": "hostProfile:tienda-demo@2026.07",
"providedBy": "milpa/config@1.0.0",
"via": "direct"
},
{
"kind": "capability",
"id": "correo.transport",
"constraint": "*",
"level": "required",
"requiredBy": "hostProfile:tienda-demo@2026.07",
"providedBy": "milpa/correo@0.3.0",
"via": "direct"
}
],
"loadOrder": [
{
"name": "milpa/config",
"version": "1.0.0"
},
{
"name": "milpa/correo",
"version": "0.3.0"
},
{
"name": "milpa/notificador",
"version": "1.2.0"
}
],
"missing": [],
"conflicts": [],
"warnings": [],
"legacy": [],
"migrationHints": [],
"learnLinks": [],
"metadata": {
"hostProfile": "tienda-demo@2026.07",
"hostMetadata": []
}
}La compuerta es el contrato del arranque
El kernel no colecciona checks sueltos: delega el veredicto completo a milpa/resolver y obedece el reporte. Si el grafo no cierra, el error aprendible te dice qué falta, por qué bloquea y dónde aprenderlo; si cierra, loadOrder[] ya es el orden de boot. La misma resolución que validó también ordenó — no hay dos fuentes que puedan divergir.
getmilpa-runtime/src/Kernel.php:156-177 (la compuerta y el boot en loadOrder) · getmilpa-resolver/src/Engine/GraphResolver.php · getmilpa-resolver/src/Ingest/DriftDetector.php · reportes congelados de milpa/resolver 0.5.0 (los snippets de captura están comentados junto a cada JSON).