Flujos completos y buenas prácticas

Por: Artiko
buzznostrworkflowsgitagentesbuenas-practicas

Flujos completos y buenas prácticas

Los trece capítulos anteriores fueron por piezas. Este los junta. El README.md de Buzz abre con tres historias cortas —memoria de incidentes, la rama como sala, y un release que se escribe solo— y aquí las convertimos en tres recorridos ejecutables, con los comandos que existen hoy y las advertencias sobre lo que aún no está cableado.

El entorno mínimo es el mismo para los tres:

export BUZZ_RELAY_URL="https://relay.example.com"   # default: http://localhost:3000
export BUZZ_PRIVATE_KEY="nsec1..."                  # tu clave privada = tu identidad

No hay buzz login, buzz init ni buzz whoami. La clave privada es la identidad: sin ella todo comando contra el relay falla con {"error":"auth_error", ...} y código de salida 3. Repaso en buzz-cli: el workspace desde la terminal.


Recorrido A: investigar un incidente

Son las 2 de la mañana. Alguien reporta connection reset by peer en producción y la pregunta de siempre es “¿esto ya nos pasó?”. El trabajo es pasar de una frase suelta a una respuesta con evidencia, y dejar el resultado donde el próximo turno lo encuentre.

A.1 Acotar dónde buscar

channels search casa por subcadena del nombre, sin distinguir mayúsculas —--exact lo vuelve coincidencia exacta— y trae hasta 1000 eventos de metadatos de canal por defecto, así que conviene bajar --limit. --include-archived suma las salas cerradas, que es donde viven los incidentes viejos:

buzz channels search --query "incident" --limit 20 --format compact
buzz channels search --query "outage" --include-archived --limit 50

A.2 Buscar el texto y a quien lo escribió

messages search hace búsqueda full-text sobre el índice del relay. El --query es opcional si entregas --author, lo que permite las dos preguntas útiles: “quién habló de esto” y “qué escribió esta persona”:

buzz messages search --query "connection reset by peer" --since 1754179200 --limit 50
buzz messages search --author npub1exampleexampleexample --since 1735689600 --limit 100

--author acepta hex, npub o un nombre. Limitación estructural: los DMs no se indexan en búsqueda —los kinds de gift wrap escriben search_tsv nulo—, así que si la conversación ocurrió en privado, la búsqueda no la va a encontrar.

A.3 Reconstruir el hilo

Con el event_id en la mano, trae el hilo completo y limita la profundidad para no arrastrar una discusión de cien respuestas:

buzz messages thread --channel <uuid-del-canal> --event <event-id> --depth-limit 3

A.4 Meter a un agente en la investigación

Un agente que sea miembro del canal se dispara por @mención, y desde el CLI eso se hace con --mention (repetible), que acepta hex o npub:

buzz messages send --channel <uuid-del-canal> --mention <hex-del-agente> --content \
"Se repite 'connection reset by peer' en el gateway. Hilos previos: <event-id-1>, <event-id-2>.
Necesito causa raíz de cada uno, si el fix sigue vigente y qué commit lo introdujo."

Dos detalles que cambian el resultado. El harness ACP encola las menciones por canal y mantiene como máximo un prompt en vuelo por canal: cinco mensajes seguidos se agrupan en un solo prompt, así que escribe una petición completa. Y el agente solo responde si su puerta de autores lo permite: el default de buzz-acp es owner-only, y eso se configura desde el escritorio del dueño, no desde tu terminal (capítulo 9).

Mientras esperas, tu bandeja avisa si te mencionaron o si algo quedó pendiente. Los cuatro tipos válidos son mentions, needs_action, activity y agent_activity:

buzz feed get --types mentions,needs_action --limit 20 --format compact

A.5 Dejar el resultado donde se encuentre

Un incidente resuelto y no escrito se vuelve a investigar en tres meses. Guarda la conclusión como nota NIP-23, un upsert idempotente por (tu clave, --name):

buzz notes set --name incidente-gateway-reset --title "Gateway: connection reset by peer" \
  --tag postmortem --tag gateway --content - < postmortem.md
buzz notes ls --tag postmortem --limit 20

Ojo: --tag reemplaza los tags existentes, no los fusiona.

sequenceDiagram
    participant U as Tu terminal
    participant R as Relay
    participant A as Agente del canal
    participant C as Canal de incidentes

    U->>R: buzz channels search --include-archived
    U->>R: buzz messages search --query connection-reset --since
    R-->>U: eventos coincidentes
    U->>R: buzz messages thread --event --depth-limit 3
    R-->>U: hilo reconstruido
    U->>C: buzz messages send --mention agente
    C->>A: mención encolada por canal
    A->>C: respuesta con causa raíz y evidencia
    U->>R: buzz feed get --types mentions,needs_action
    U->>R: buzz notes set --name incidente-gateway-reset
    R-->>U: nota publicada, buscable por todo el equipo

Recorrido B: de la rama al merge, dentro de una sala

El segundo recorrido lleva una feature desde una rama nueva hasta el merge, con la conversación, los parches y la decisión en el mismo lugar. Aclaración honesta: que crear una rama cree automáticamente un canal, y que ese canal se archive solo al fusionar, aparece únicamente en los documentos de visión; hoy tú creas el canal y tú lo archivas.

B.1 Abrir la sala de la rama

buzz channels create --name "feat-oauth-refresh" --type stream --visibility open \
  --description "Refresco de tokens OAuth en el gateway"

buzz channels topic --channel <uuid> --topic "Rama feat/oauth-refresh -> main"
buzz channels purpose --channel <uuid> --purpose "Parches, revisión y decisión de merge"

buzz channels add-member --channel <uuid> --pubkey <hex-del-revisor> --role member
buzz channels add-member --channel <uuid> --pubkey <hex-del-agente> --role bot

Si la rama es de vida corta, --ttl <segundos> la vuelve efímera: el relay archiva el canal tras ese tiempo sin mensajes nuevos —en el escritorio ese selector aparece como Ongoing y Temporary. Y ojo con el rol bot: no está en la jerarquía de permisos, su nivel es 0 y requiere concesiones explícitas.

B.2 Anunciar y enlazar el repositorio

El anuncio NIP-34 hace visible el repo, y el tag buzz-channel es la ACL de git: mientras el repo no esté enlazado a un canal, el relay responde 404 a todo clone, fetch y push.

buzz repos create --id api --name "api" --description "Gateway HTTP" \
  --clone "$BUZZ_RELAY_URL/git/<tu-pubkey-hex>/api" --channel <uuid>

# Si lo creaste sin --channel, enlázalo después. Sin esto, git da 404.
buzz repos bind --id api --channel <uuid>

El segmento de dueño en la URL de clon es tu pubkey en hex, no un nombre de usuario.

Protege la rama principal antes de que alguien la pise. protect set reemplaza todas las reglas del patrón exacto —cualquier restricción omitida queda eliminada—, así que escribe siempre la regla completa. Si protect list devuelve un validation_error, esa regla está malformada en el evento almacenado: quítala y vuelve a crearla.

buzz repos protect set --id api --ref refs/heads/main \
  --push admin --no-force-push --no-delete --require-patch
buzz repos protect list --id api

B.3 Empujar sin poner la clave en la línea de comandos

El transporte es git Smart HTTP estándar. La autenticación la resuelve el helper de credenciales, que firma un evento NIP-98 por cada request. Se configura una vez y después git es git:

git config --global credential.helper nostr
git config --global credential.useHttpPath true

mkdir -p ~/.nostr
echo "nsec1..." > ~/.nostr/key && chmod 600 ~/.nostr/key
git config --global nostr.keyfile ~/.nostr/key

git remote add origin "$BUZZ_RELAY_URL/git/<tu-pubkey-hex>/api"
git push -u origin feat/oauth-refresh

Requiere git 2.46 o superior —el helper depende de la capacidad authtype del protocolo de credenciales— y un reloj sincronizado: un desfase mayor a 60 segundos hace que el relay rechace la firma. En CI usa NOSTR_PRIVATE_KEY, que tiene precedencia y no toca el disco.

B.4 Conversar el diff y registrar el parche

Son dos cosas distintas. Para conversar un cambio dentro del canal, send-diff publica un mensaje de tipo diff con metadatos, que el escritorio renderiza con visor de diff:

git diff main...feat/oauth-refresh > /tmp/oauth.diff

buzz messages send-diff --channel <uuid> \
  --diff - \
  --repo "$BUZZ_RELAY_URL/git/<tu-pubkey-hex>/api" \
  --commit <sha> --parent-commit <sha-base> --lang rust \
  --source-branch feat/oauth-refresh --target-branch main \
  --description "Refresco de token antes del vencimiento, con jitter" < /tmp/oauth.diff

Tope duro: el contenido de un evento no puede superar 65 536 bytes y un diff, 61 440. Si tu cambio no cabe, ese ya es el aviso de que hay que partirlo.

Para registrar el cambio como entidad git portable, el evento NIP-34 kind 1617:

git format-patch main --stdout > /tmp/oauth.patch

buzz patches send --repo-owner <tu-pubkey-hex> --repo-id api --patch-file /tmp/oauth.patch \
  --root --commit <sha> --parent-commit <sha-base> --to <hex-del-revisor>
buzz patches list --repo-owner <tu-pubkey-hex> --repo-id api --limit 20

--root marca este parche como raíz de la serie. Las revisiones posteriores van con --reply-to <event-id-raiz> y, si son una nueva versión completa, con --root-revision.

B.5 Revisar: hilos y reacciones

La revisión ocurre en el hilo del mensaje de diff, no en un panel aparte. Y la señal ligera de “esto está bien por mí” es una reacción, que no es decoración: sobre reacciones se disparan workflows.

buzz messages send --channel <uuid> --reply-to <event-id-del-diff> \
  --content "El jitter debería salir de una constante, no de un literal."

buzz messages thread --channel <uuid> --event <event-id-del-diff>
buzz reactions add --event <event-id-del-diff> --emoji "👍"

Si además usas clientes Nostr de terceros: el relay deriva el canal de una reacción desde el #e del evento objetivo, así que una suscripción global solo por kind no recibe esas reacciones; hay que suscribirse con el filtro #h del canal (capítulo 13).

B.6 Cerrar el ciclo

Cuando el merge ocurre en git, el estado del parche se publica como evento —kinds 1630 a 1633— para que el registro coincida con la realidad:

buzz patches status --root <event-id-del-parche> --status merged --merge-commit <sha-del-merge> \
  --repo-owner <tu-pubkey-hex> --repo-id api --content "Fusionado en main tras revisión."

buzz channels archive --channel <uuid>

Los estados de parches y PRs son open, merged, closed, draft. Los de issues son distintos: open, resolved, closed, draft.

sequenceDiagram
    participant D as Tu máquina
    participant R as Relay y git
    participant S as Canal de la rama
    participant V as Revisor

    D->>R: buzz channels create feat-oauth-refresh
    D->>R: buzz repos create --clone ... --channel
    Note over R: sin buzz-channel el git responde 404
    D->>R: git push -u origin feat/oauth-refresh
    D->>S: buzz messages send-diff --source-branch --target-branch
    D->>R: buzz patches send --root --patch-file
    V->>S: buzz messages send --reply-to con observaciones
    V->>S: buzz reactions add --emoji 👍
    D->>R: buzz patches status --status merged --merge-commit
    D->>R: buzz channels archive
    Note over S: la sala queda como registro de por qué existe ese código

Recorrido C: un release con workflow y aprobación por reacción

El tercer recorrido automatiza la publicación de un release manteniendo a una persona en el camino crítico.

Primero, dos correcciones de expectativa que evitan perder una tarde:

  • No existe un trigger on: tag. Los cinco triggers reales son message_posted, reaction_added, diff_posted, schedule y webhook. Un tag de git llega al workflow porque tu CI hace un POST al webhook.
  • request_approval no se reanuda todavía. El motor emite el token pero no lo persiste ni continúa: las corridas que llegan a la puerta se marcan como fallidas. Por eso la aprobación humana de este recorrido se hace con una reacción.

C.1 Workflow 1: publicar el borrador cuando el tag se construye

name: "Borrador de notas de release"
description: "Publica el borrador cuando CI termina de construir un tag"
enabled: true
trigger:
  on: webhook
steps:
  - id: anuncio
    name: "Publicar borrador"
    timeout_secs: 60
    action: send_message
    text: |
      Tag {{trigger.tag}} construido por {{trigger.actor | npub}}.
      Artefacto: {{trigger.artifact_url}}
      Revisen el borrador y reaccionen con 🚀 sobre este mensaje para publicar.

Los id de paso admiten solo alfanuméricos ASCII y guion bajo, de 1 a 64: mi-paso es inválido porque el id se convierte en la variable steps_mi_paso_output_campo y el motor de expresiones leería el guion como una resta. El YAML se pasa literal por stdin —no hay flag de ruta— y si al disparar obtienes 400 workflow not found, puede ser solo que el relay aún no indexó la definición: la indexación es asíncrona.

buzz workflows create --channel <uuid-canal-releases> --yaml - < release-draft.yaml
buzz workflows list --channel <uuid-canal-releases>

C.2 Disparar desde CI

El endpoint es POST /hooks/{workflow_id} y el secreto es obligatorio: sin secreto configurado el relay responde 401 pidiendo que vuelvas a guardar el workflow para generarlo. La cabecera es preferible al parámetro de query, porque los proxies no la registran:

curl -X POST "$BUZZ_RELAY_URL/hooks/<workflow-id>" \
  -H "x-webhook-secret: $BUZZ_WEBHOOK_SECRET" \
  -H "content-type: application/json" \
  -d '{"tag":"v1.4.0","actor":"<pubkey-hex>","artifact_url":"https://artifacts.example.com/v1.4.0"}'

Cada clave de nivel superior del cuerpo JSON queda disponible como {{trigger.tag}} en plantillas y como trigger_tag en expresiones. Las claves estándar se registran después, así que no puedes falsear trigger_author desde el webhook.

C.3 Workflow 2: la reacción como puerta humana

name: "Publicar release"
description: "Publica cuando alguien reacciona con cohete al borrador"
trigger:
  on: reaction_added
  emoji: "🚀"
steps:
  - id: confirmar
    action: send_message
    text: "Publicación autorizada por {{trigger.author | npub}}."
  - id: publicar
    action: call_webhook
    url: "https://ci.example.com/api/release/publish"
    method: POST
    headers:
      content-type: "application/json"
    body: '{"approved_by":"{{trigger.author}}","message_id":"{{trigger.message_id}}"}'

Cuatro restricciones reales de este workflow:

  1. call_webhook exige autoridad elevada. Solo un owner o admin del canal puede guardarlo, y el rol se revalida justo antes de cada corrida.
  2. Hay protección anti-SSRF. El motor resuelve el DNS, rechaza IPs privadas o reservadas, fija la IP en el cliente HTTP, deshabilita proxies y redirecciones, aplica un timeout de 10 segundos y corta la respuesta en 1 MiB. No puedes apuntar a localhost ni a un servicio interno.
  3. Los filtros de plantilla son tres: truncate(N), npub y el alias heredado truncate_pubkey. No hay upper, lower, default ni date.
  4. buzz workflows runs devuelve hoy []. El relay guarda las corridas en su propia tabla, no como eventos Nostr, así que el historial se consulta desde el escritorio.

Si prefieres disparar a mano —buzz workflows trigger --workflow <id> --inputs '{"tag":"v1.4.0"}'— recuerda que --inputs debe ser un objeto JSON; un array o un escalar se rechazan.

sequenceDiagram
    participant CI as Pipeline de CI
    participant R as Relay
    participant W as Motor de workflows
    participant C as Canal de releases
    participant H as Persona que aprueba

    CI->>R: POST /hooks/workflow-id con x-webhook-secret
    R->>R: verifica secreto y autoridad vigente del dueño
    R->>W: crea la corrida con los campos del cuerpo
    W->>C: send_message con el borrador de notas
    H->>C: lee el borrador y reacciona con 🚀
    C->>W: trigger reaction_added con emoji exacto
    W->>C: send_message confirmando quién autorizó
    W->>CI: call_webhook hacia el endpoint de publicación
    Note over W: sin IPs privadas, sin redirecciones, 10 s de timeout

Buenas prácticas

Higiene de canales

  • Un canal, un propósito escribible. buzz channels topic y buzz channels purpose sirven para que quien llega en frío entienda la sala sin leer tres semanas de historial. Ambos los edita cualquier miembro; el nombre y la descripción, solo owner o admin.
  • Cierra lo que terminó. buzz channels archive no borra: deja el registro buscable y saca la sala del camino. --ttl archiva solo y buzz channels unarchive revierte.
  • La membresía es la única puerta. No hay ACLs paralelas ni banderas de permiso. Y cuidado con la intuición al revés: un canal open sigue siendo legible y escribible por no miembros en tiempo de ejecución, aunque los eventos de descubrimiento siempre lleven el tag closed. Ese tag refleja el modelo de membresía, no el control de acceso.
  • Lo sensible va en canal privado, no en DM. Los DMs no se indexan en búsqueda: protegen la privacidad y destruyen la memoria del equipo.

Hilo o canal nuevo

La regla sale de cómo funciona el timeline: las respuestas nunca entran al timeline del canal, viven en el panel de hilo. Un hilo mantiene el canal legible; un canal nuevo, el trabajo separable.

flowchart TD
    A["Tengo algo que decir"] --> B{"¿Responde a un mensaje concreto?"}
    B -- Sí --> C{"¿Va a durar más de un día o suma más de 3 personas nuevas?"}
    B -- No --> D{"¿Es un tema con entregable propio?"}
    C -- No --> E["Hilo con --reply-to"]
    C -- Sí --> F["Canal nuevo, y enlaza el mensaje de origen"]
    D -- Sí --> F
    D -- No --> G["Mensaje normal en el canal"]
    F --> H["Pon topic y purpose el primer día"]
    E --> I["Usa --broadcast solo si toda la sala debe ver la conclusión"]

Una advertencia sobre --broadcast, porque su texto de ayuda engaña: dice «Also publish to the Nostr network», pero lo que hace en realidad es añadir el tag ["broadcast","1"] al evento, y ese tag es lo que el relay guarda en los metadatos de hilo para que la respuesta aparezca también en el timeline principal del canal, además de en el panel de hilo. No manda nada a relays externos. Úsalo para la conclusión de un hilo largo, no por costumbre.

Escribir para que un agente pueda ayudarte después

Un agente lee el mismo historial que tú, por el mismo índice.

  • Pega el error literal, no una paráfrasis: la búsqueda es full-text y el string exacto es lo que se encuentra.
  • Menciona identificadores estables: event_id, SHA de commit, UUID de canal, id de repo. Un agente tira de ellos con buzz messages thread, buzz patches get o buzz social event.
  • Escribe la conclusión, no solo el proceso. “Era el pool de conexiones, timeout de 5 s, corregido en abc1234” vale más que veinte mensajes de depuración.
  • Una petición completa por turno, porque el harness agrupa las menciones encoladas por canal en un solo prompt. Y consolida en notas y canvases: buzz notes set es idempotente por (tu clave, --name) y buzz canvas set --channel mantiene un documento vivo por canal.

Custodia de claves

  • No hay recuperación de cuenta. El par de claves es la identidad: no hay tokens, ni sesiones, ni “olvidé mi contraseña”. Perder la clave es perder la identidad, no el acceso a ella.
  • Respalda desde el escritorio. Buzz Desktop ofrece copia cifrada de la clave con frase de paso generada y derivada con scrypt, más una fila dedicada de respaldo en Ajustes. Hazlo el primer día, no el del incidente: Tu identidad: claves, perfil y dispositivos.
  • Nunca escribas la nsec en la línea de comandos de git. Para eso está git-credential-nostr con archivo en modo 0600, o NOSTR_PRIVATE_KEY en CI. En el CLI, mismo criterio: --private-key y --auth-tag existen, pero sus valores se ocultan en la ayuda justamente porque no deberían quedar en tu historial de shell.
  • Un par de claves por agente. Compartir la tuya vuelve cada acción del agente indistinguible de la tuya en el registro de auditoría.
  • Sincroniza el reloj: NIP-98 firma sobre URL, método y marca de tiempo, y un desfase mayor a 60 segundos rompe git y las operaciones REST.
  • Rota con intención. buzz agents archive <pubkey> --reason rotated --replaced-by <nueva-pubkey> deja constancia del reemplazo. El padrón de miembros del relay es tarea de operador: curso de administrador.

Qué NO delegar a un agente

El README lo dice sin adornos: “Not an AI replacement plan. Buzz works best when humans stay in the loop and agents stay in the room.”

  • La decisión de merge en ramas protegidas. El agente prepara el parche, corre las pruebas y redacta el resumen; la reacción que autoriza es de una persona y queda firmada con su clave.
  • La moderación. Los reportes son señales, no disparadores: el relay nunca actúa solo sobre ellos. Y tampoco la custodia de claves ajenas: entregar una clave privada a un binario de despliegue es una decisión de confianza explícita, no un detalle de configuración.
  • Abrir el agente a cualquiera sin pensarlo. La opción “Anyone” comparte el acceso del host con alguien distinto del dueño, y por eso la interfaz muestra una advertencia persistente. El default owner-only lo es por una razón.
  • Cualquier cosa que dependa de la columna 💭 del README, que pide literalmente no planificar tu programa de cumplimiento sobre lo que aún no tiene código.
  • La supervisión de un agente remoto. Tras un despliegue por proveedor el escritorio no retiene canal de gestión: sin consulta de estado, sin exec, sin logs, sin kill garantizado, y con una presencia que puede estar equivocada hasta 180 segundos.

Hacia dónde seguir

Este curso cubrió el workspace desde la silla de quien colabora. Los caminos naturales desde aquí:

  • Los documentos de visión. VISION.md es la versión larga de qué quiere ser Buzz; VISION_PROJECTS.md describe el forge nativo en Nostr; VISION_AGENT.md y VISION_ACTIVITY.md explican el runtime de agentes y el feed de actividad; VISION_REMOTE_AGENTS.md, VISION_MODERATION.md, VISION_MESH.md y VISION_SOVEREIGN.md completan el cuadro. Léelos sabiendo que son visión: la tabla “Works today · Being wired up · Strong opinions” del README.md es la que manda sobre qué funciona hoy.
  • Los NIPs propios, en docs/nips/. Las especificaciones que usaste sin verlas: NIP-OA para la atestación de dueño que deja a un agente heredar acceso, NIP-AE para la memoria cifrada de agente, NIP-AP para personas, NIP-MP para proyectos multi-repo, NIP-IA para el ciclo de vida de identidades, NIP-ER para recordatorios.
  • CONTRIBUTING.md. Si vas a mandar código: cada commit se firma con git commit -s —el check de DCO bloquea el PR sin la firma—, el título del PR sigue Conventional Commits porque el merge es squash y ese título termina como asunto del commit en main, y para algo que no sea un arreglo pequeño conviene abrir un issue antes. Los PRs asistidos por IA son bienvenidos, pero el código final es tuyo y tienes que haberlo revisado.
  • El curso de administrador. Todo lo que aquí terminó en “eso lo hace el operador” —desplegar el relay, el allowlist, el padrón NIP-43, la cola de moderación, las personas integradas— vive ahí.

Y el consejo menos técnico del curso: el valor de Buzz no está en estas features por separado, sino en que la conversación, el parche, la decisión y la automatización terminan en el mismo registro firmado y buscable. Cada vez que sacas una de las cuatro fuera del canal, lo pagas en la siguiente investigación.


Resumen

  • Un incidente se investiga en cuatro movimientos: channels search para acotar, messages search para encontrar, messages thread para reconstruir y --mention para meter a un agente; el cierre es una nota con notes set. Los DMs no se indexan: lo que deba recordarse va en un canal.
  • La rama como sala se arma a mano hoy: crear canal, repos create con --channel —sin ese enlace git responde 404—, protect set para la rama principal, send-diff para conversar, patches send para registrar, reacciones para aprobar, patches status --status merged y channels archive.
  • Git es Smart HTTP estándar con git-credential-nostr: git 2.46+, archivo de clave en 0600, NOSTR_PRIVATE_KEY en CI, y nunca la nsec en la línea de comandos.
  • No existe trigger on: tag: un tag llega por on: webhook con secreto obligatorio en la cabecera x-webhook-secret, y la aprobación humana se hace con on: reaction_added porque request_approval todavía no reanuda la corrida y workflows runs devuelve lista vacía.
  • call_webhook exige rol owner o admin revalidado por corrida, y rechaza IPs privadas, proxies y redirecciones.
  • Hilo para lo que responde a un mensaje y muere en un día; canal nuevo para lo que tiene entregable propio; --broadcast solo cuando la conclusión le importa a toda la sala.
  • Escribe para el índice: error literal, identificadores estables, conclusión explícita, una petición completa por turno.
  • La clave privada es la identidad y no hay recuperación: respaldo cifrado, un par de claves por agente, reloj sincronizado. No se delegan la decisión de merge, la moderación, la custodia de claves ajenas ni nada que dependa de features que aún no tienen código.

Con esto cierras el curso de usuario de Buzz. Si además te toca sostener el servidor, sigue con el curso de administrador.