# La redacción de la tarjeta de aprobación debe preservar la decisión

Una tarjeta de aprobación tiene un único objetivo: ayudar a una persona a decidir si una acción concreta puede salir de su equipo. Si oculta suficientes detalles para proteger una credencial, pero también oculta lo que hará la solicitud, ha fracasado en ese objetivo.

El error habitual consiste en tratar la redacción como un reemplazo de cadenas. Los ingenieros ocultan `Authorization`, dejan vacío el cuerpo JSON y consideran que el resultado es seguro. El operador ve entonces `POST https://api.example.com/...` y un botón de aprobación. Eso no es un consentimiento informado. Es un ritual vacío que acostumbra a las personas a hacer clic justo en el momento en que el control humano debía importar.

Una buena tarjeta conserva el significado de la acción y elimina el material que permitiría a quien la lee, la captura, registra o mira por encima del hombro reutilizar un secreto. Para lograrlo hace falta una representación consciente de los campos. Las cabeceras, las cadenas de consulta, los cuerpos y los identificadores de destino requieren tratamientos distintos.

## Una tarjeta de aprobación debe explicar la acción

En pocos segundos, el operador debería poder responder cuatro preguntas: quién lo solicita, adónde se dirige la solicitud, qué hará y qué objeto o alcance afectará. Si falta una respuesta, la tarjeta está incompleta, aunque todos los secretos estén perfectamente ocultos.

Empieza con una línea de acción que combine el método del protocolo con un verbo comprensible:

```text
POST  api.billing.example  /v1/invoices/inv_7KD2/refund
Action: issue a refund
```

El método importa porque `GET`, `POST`, `PATCH` y `DELETE` generan expectativas distintas. El verbo comprensible importa porque el método por sí solo no indica si `POST /v1/invoices/inv_7KD2/refund` crea un borrador, envía un pago o activa un reembolso. No pidas a la persona que deduzca la semántica de la aplicación a partir del nombre de una ruta cuando quien llama ya conoce la operación prevista.

Después, muestra el destino como una autoridad real, no como un apodo de cuenta. «Producción de facturación» puede ser un contexto útil, pero no puede sustituir a `api.billing.example`. Una solicitud mal dirigida puede usar una etiqueta conocida. El host es el límite que indica qué servicio recibirá los datos y la credencial.

RFC 3986 separa una URI en componentes que incluyen autoridad, ruta, consulta y fragmento. Esta división resulta útil para representar aprobaciones porque cada componente aporta una señal de decisión distinta. No la reduzcas a una única cadena de URL atractiva esperando que el enmascaramiento posterior conserve la información adecuada.

La tarjeta también debe indicar si la acción crea, modifica, elimina, publica, transfiere o simplemente lee. «Modificar el registro del cliente» es menos claro que «cambiar el destino de los pagos del cliente». Si tu constructor de solicitudes no puede proporcionar esa frase, corrige el constructor. Un generador de tarjetas no puede reconstruir de forma fiable la intención empresarial a partir de un JSON arbitrario.

## Representa un manifiesto de solicitud, no una solicitud embellecida

La unidad de trabajo segura es un manifiesto de solicitud tipado. Registra lo que el agente pretende hacer antes de inyectar credenciales y antes de crear cualquier vista de aprobación.

Un manifiesto mínimo podría tener este aspecto:

```json
{
  "channel": "http",
  "method": "POST",
  "destination": {
    "scheme": "https",
    "host": "api.billing.example",
    "port": 443,
    "path_template": "/v1/invoices/{invoice}/refund"
  },
  "action": "issue refund",
  "targets": [
    {"role": "invoice", "display": "inv_7KD2", "sensitivity": "internal"}
  ],
  "query": [],
  "headers": [],
  "body": {
    "media_type": "application/json",
    "fields": []
  },
  "effect": "financial"
}
```

No es una representación HTTP de transporte. Es el objeto que debe consumir el generador de la tarjeta de aprobación. La diferencia importa. Una solicitud de transporte incluye credenciales inyectadas, valores codificados y detalles del transporte. Un manifiesto contiene etiquetas semánticas como `action`, `target role` y `effect`, que una solicitud sin procesar no tiene.

Clasifica cada valor que pueda mostrarse según lo que el operador necesita decidir, no según el lugar donde apareció. Un bearer token en una cabecera es un secreto. Una URL de webhook firmada en una cadena de consulta también lo es. Una dirección de correo dentro de un cuerpo JSON puede ser un dato personal. El nombre de un repositorio puede ser un identificador de destino cuya visibilidad resulta necesaria para decidir con seguridad.

Usa un vocabulario pequeño y coherente:

- `public`: seguro para mostrarlo tal cual.
- `internal`: muéstralo cuando identifique el objeto afectado, pero evita copiarlo en registros generales.
- `personal`: muestra solo la forma mínima útil, normalmente una etiqueta más un valor parcial.
- `secret`: nunca muestres el valor en la tarjeta, los registros, el portapapeles ni el texto de error.
- `opaque`: muestra un alias aprobado o una referencia estable no secreta solo cuando ayude a distinguir el destino.

No des a los llamadores una salida sin restricciones como `safe_to_display: true`. Alguien la usará para facilitar una sesión de depuración y después la dejará en una ruta que maneja credenciales de producción. Exige una clasificación concreta en el límite donde el agente construye la acción.

## Las cabeceras necesitan nombres, propósito y casi nunca valores

Los nombres de las cabeceras suelen decir al operador mucho más que sus valores. Los valores suelen contener exactamente aquello que no debe aparecer en una superficie dirigida a personas.

Muestra el nombre de cada cabecera relevante para la seguridad y una breve etiqueta de propósito. Por ejemplo:

```text
Headers
Authorization: bearer credential from vault
Idempotency-Key: generated request identifier
X-Request-Reason: "refund requested by finance"
Content-Type: application/json
```

La primera línea indica al operador que la solicitud se autenticará, y la fuente de la credencial le dice si el proceso usa un secreto almacenado previsto. Mostrar `Bearer eyJ...` no aporta nada a la aprobación. Crea una vía de exposición y anima a comparar fragmentos de token sin significado.

La especificación de tokens bearer de OAuth define la cabecera de solicitud Authorization como el método de transmisión preferido, mientras que sus recomendaciones de seguridad tratan los bearer tokens como credenciales que necesitan protección durante el transporte y el almacenamiento. Ese es también el modelo mental adecuado para la interfaz de aprobación: el usuario necesita saber que se aplicará una credencial bearer, no inspeccionarla.

Aplica estas reglas a las cabeceras:

1. Muestra valores de protocolo seguros como `Content-Type`, `Accept` e `If-Match` cuando afecten al comportamiento.
2. Muestra los nombres, pero no los valores, de `Authorization`, `Proxy-Authorization`, `Cookie`, `Set-Cookie`, las cabeceras de firma, las cabeceras de claves API y las cabeceras personalizadas clasificadas como secretas.
3. Muestra un valor limitado y escapado para el contexto empresarial declarado como no secreto, como `X-Request-Reason`, solo si es corto y no puede contener material personal o secreto.
4. Indica que una cabecera está ausente cuando su ausencia cambie la decisión. La falta de `If-Match` puede importar en una operación de sobrescritura.
5. Nunca representes todas las cabeceras por defecto. Las bibliotecas de solicitudes añaden ruido, y el ruido oculta la cabecera que cambia la acción.

Un diseño defectuoso habitual oculta un secreto mostrando sus cuatro primeros y últimos caracteres: `sk_live_...9a31`. El patrón parece prudente, pero es inseguro para valores cortos, valores estructurados, claves de prueba y valores que ya se hayan filtrado en otro lugar. También hace pensar que hay que reconocer fragmentos de secretos. Sustituye el valor por una descripción del tipo, como `credencial API almacenada` o `firma de solicitud`.

Las cabeceras también pueden ocultar identificadores de destino. Una cabecera de enrutamiento de tenant, una cabecera de suplantación o `X-Account-ID` pueden cambiar quién recibe el efecto. No la ocultes solo porque sea una cabecera. Muestra su función y una etiqueta de destino segura: `X-Account-ID: account «Northwind production»`. Si no puedes asociar un identificador opaco con una etiqueta segura, indica que se usará un identificador de cuenta opaco y exige una aprobación más deliberada para operaciones sensibles.

## Las cadenas de consulta merecen más sospecha de la que reciben

Las cadenas de consulta son visibles en las URL, se copian en terminales, se insertan en informes de errores y con frecuencia las registran infraestructuras que nunca ven el cuerpo de la solicitud. Precisamente por su comodidad, las tarjetas de aprobación deben tratarlas con cuidado.

RFC 9110 advierte que la información de una URI puede divulgarse mediante referencias, registros y otros canales, y recomienda a los remitentes evitar información sensible en las URI de destino HTTP. No es una preocupación abstracta de los estándares. Una tarjeta que muestra una cadena de consulta completa puede convertirse en otro canal de divulgación para un valor que ni siquiera debería haber estado en la URI.

No decidas que todos los valores de consulta son seguros porque la solicitud es un `GET`. Usa nombres, tipos declarados y el contexto de la operación.

```text
GET  api.crm.example  /v2/contacts
Query
status = "active"
owner = "sales-west"
include = "notes"
access_token = [secret, hidden]
search = [private text, hidden]
```

`status` e `include` suelen ser información útil para decidir. `search` puede contener nombres, direcciones de correo, términos médicos o cualquier cosa que un agente haya extraído de archivos locales. `access_token` es claramente secreto, pero el diseño no puede depender de nombres evidentes. Algunas API usan `sig`, `token`, `key`, `code`, `state`, `assertion` o un parámetro específico del proveedor sin ninguna advertencia en su nombre.

Trata los valores de consulta como secretos por defecto, salvo que el manifiesto los haya clasificado explícitamente. Es deliberadamente más estricto que muchos exploradores de API. La interfaz de aprobación no es una consola de depuración. Quien la lee necesita información suficiente para autorizar la solicitud, no reconstruirla byte por byte.

Conserva los parámetros duplicados y su orden cuando afecten a la semántica. Un generador que convierta una cadena de consulta en un diccionario puede perder en silencio `tag=urgent&tag=finance`, transformar valores repetidos o hacer invisible un error de firma. Muestra una lista de entradas en lugar de un mapa:

```text
Query
label = "finance"
label = "urgent"
expand = "line_items"
```

Si un campo de consulta redactado cambia el enrutamiento o la autorización de la solicitud, dilo. `signature = [signed request value, hidden]` ofrece al operador una señal mejor que una fila vacía. Si la consulta incluye un enlace compartido opaco, no expongas el token. Muestra la etiqueta del recurso si la conoces, como `informe compartido: previsión del segundo trimestre`, y, si no, muestra `token de recurso compartido presente`.

## Los cuerpos deben conservar su forma después de la redacción

Un cuerpo que se convierte en `[redacted]` apenas informa al operador. Un cuerpo que muestra todos los campos literalmente acabará filtrando algo que nunca debería haber llegado a la superficie de aprobación. La respuesta correcta es una redacción estructural.

Representa el cuerpo como un árbol tipado. Conserva las claves de objetos, el número de elementos de los arrays, los tipos de datos, los valores de enumeración seguros y las etiquetas de destino seleccionadas. Sustituye las hojas inseguras por un indicador explicativo.

```json
{
  "invoice": "inv_7KD2",
  "amount": {"currency": "USD", "minor_units": 12500},
  "reason": "duplicate charge",
  "customer_note": "[private text, 84 characters]",
  "payment_method": {
    "id": "[opaque payment method]",
    "token": "[secret, hidden]"
  }
}
```

Esta representación permite al operador ver que la acción reembolsa 125,00 USD por el motivo indicado y que llevará consigo una nota privada que saldrá del equipo. Es suficiente para decidir si la solicitud se parece a la tarea prevista. La tarjeta no divulga la nota ni el token.

Conserva los números cuando los números constituyan el efecto. Ocultar importes de pagos, cantidades de puestos, periodos de retención, límites de velocidad, niveles de permisos y cantidades de eliminaciones hace que la aprobación pierda sentido. Trata estos valores como parámetros de la acción, no como datos incidentales. Un cuerpo `DELETE` que contenga `{"purge": true}` debe mostrar `purge: true`; de lo contrario, la tarjeta oculta la parte irreversible.

El texto necesita una regla aparte. El texto libre puede contener código fuente, datos de clientes, secretos pegados o instrucciones que cambien la acción. Mostrar una vista previa arbitraria resulta tentador porque ayuda a detectar errores absurdos. También convierte la ventana de aprobación en una superficie de exfiltración de datos. Para texto libre no clasificado, muestra el nombre del campo, el número de caracteres y la función del destino. Muestra un fragmento limitado solo cuando el llamador marque el campo como público o interno y el generador escape los caracteres de control.

Los arrays necesitan cantidades y resúmenes. Esto es malo:

```text
recipients: [redacted]
```

Esto es mejor:

```text
recipients: 37 email addresses [personal values hidden]
```

En una operación destructiva, la cantidad cambia la decisión. Para un cambio de acceso, muestra la función y la cantidad: `add 4 members to role: billing-admin`. Si los miembros son identificadores internos que el aprobador necesita distinguir, muestra nombres visibles o alias aprobados, no identificadores sin procesar.

Nunca deduzcas la sensibilidad solo a partir del nombre de un campo. `password`, `token` y `secret` merecen una lista de denegación estricta, pero `content`, `message`, `value`, `data` y `metadata` pueden contener el mismo material. La clasificación debe venir de un esquema, un constructor de acciones o una anotación explícita del campo. Un filtro basado en nombres es la última línea de defensa, no el diseño principal.

## Los identificadores de destino deben ser legibles, no quedar completamente expuestos

El destino es el objeto que da consecuencias a la solicitud. Puede estar en un segmento de ruta, una cabecera, un parámetro de consulta, un campo JSON o un argumento de un comando SSH. La tarjeta debe hacer visible el destino aunque no pueda mostrar de forma segura el identificador sin procesar.

Separa la referencia de máquina del destino de su forma visible para las personas:

```json
{
  "role": "repository",
  "raw_reference": "repo_01HZX8M9...",
  "display": "payments-service",
  "scope": "production",
  "sensitivity": "internal"
}
```

La referencia sin procesar puede ser necesaria para ejecutar la acción, pero la forma visible es la que pertenece a la tarjeta. Si la acción cambia permisos, usa una frase que nombre la relación: `Conceder permiso de despliegue en payments-service production a la cuenta de automatización de versiones.` La tarjeta no debe obligar al aprobador a memorizar identificadores opacos.

A veces el valor sin procesar es el único identificador disponible. No lo soluciones mostrando todo. Elige una referencia estable y no reversible, como un alias local o una referencia breve de aprobación generada a partir del valor protegido. No llames hash a un identificador truncado, salvo que sea un resumen criptográfico real y entiendas las consecuencias de colisión y correlación. En muchos casos, `registro de cliente [referencia opaca 4F8C]` es más honesto que fingir que el operador puede verificar `cus_Qa8J7kW2m9` de un vistazo.

No ocultes demasiado los identificadores que determinan el radio de impacto. Una solicitud para `DELETE /projects/{project}/members` cuyo proyecto está oculto es una tarjeta peligrosa, aunque todos los identificadores personales de los miembros estén enmascarados. Muestra el nombre visible del proyecto, el entorno y la cantidad de miembros afectados. Mantén fuera de la vista los valores individuales sensibles.

Aquí hay una distinción fundamental: ocultar un secreto protege la confidencialidad; ocultar un destino debilita la autorización. Los equipos suelen agrupar ambas cosas bajo «redacción». Son tareas diferentes y una tarjeta necesita reglas distintas para cada una.

## El alcance de aprobación debe coincidir con la información de la tarjeta

Una tarjeta completa no autoriza más de lo que describe. Si una persona aprobó una solicitud para leer un repositorio, esa aprobación no puede cubrir en silencio una solicitud posterior para cambiar su configuración solo porque ambas procedan del mismo proceso de agente.

La aprobación por sesión y la aprobación por llamada responden a preguntas distintas. La aprobación por sesión responde si este proceso firmado puede actuar a través de la puerta de enlace durante esta ejecución. La aprobación por llamada responde si esta acción saliente concreta, con este destino y efecto, puede producirse. Fusionarlas en un único permiso enorme hace que la primera tarjeta soporte una carga imposible.

Usa una regla de escalamiento basada en las consecuencias. Una lectura desde un servicio conocido puede encajar en una autorización de sesión. Una llamada que use una credencial especialmente protegida, cambie accesos, envíe un mensaje, cree un compromiso financiero o elimine datos necesita una tarjeta vinculada al manifiesto concreto de la solicitud.

El resultado de la aprobación debe vincularse a un resumen canónico de la acción, no al texto visible de la tarjeta. El resumen debe incluir el método, el destino normalizado, las referencias de destino, los parámetros no secretos clasificados y una representación de los campos protegidos. También debe incluir metadatos suficientes para detectar si la solicitud cambió después de mostrarse. No vincules la decisión solo a un resumen pensado para capturas de pantalla.

Por ejemplo, estas dos llamadas requieren aprobaciones distintas, aunque un generador descuidado podría hacer que parezcan iguales:

```text
POST /v1/roles/grant
body: role = "viewer", subject = "build-bot"

POST /v1/roles/grant
body: role = "owner", subject = "build-bot"
```

La función no es un detalle que deba ocultarse en una vista JSON contraída. Es la acción. Si un ingeniero dice que la tarjeta está demasiado cargada, elimina primero los datos decorativos del protocolo. No elimines el campo que determina si el agente puede hacerse con el control de una cuenta.

Sallyport mantiene separadas la autorización de sesión y las claves por llamada por este motivo. Una decisión de sesión puede identificar y admitir un proceso de agente nuevo, mientras que una credencial marcada para aprobación en cada uso sigue solicitando confirmación antes de cada acción individual.

## Un fallo de redacción suele empezar antes de la representación

Imagina que un agente debe enviar un contrato para firmarlo. Construye esta solicitud:

```http
POST /v1/envelopes?template=msa&signature=QmFzZTY0U2lnbmVkVmFsdWU HTTP/1.1
Host: api.signing.example
Authorization: Bearer eyJhbGciOi...
Content-Type: application/json

{
  "recipients": [
    {"name": "Maya Chen", "email": "maya@example.com"}
  ],
  "subject": "MSA for Northwind",
  "message": "Please sign the attached agreement.",
  "document": "JVBERi0xLjQK..."
}
```

La implementación superficial da formato a la solicitud sin procesar, sustituye el valor de Authorization y trunca las líneas largas. La tarjeta expone ahora la firma en la cadena de consulta, el correo del destinatario y quizá el principio de un documento codificado como texto. Truncar no es redactar. Solo hace que la filtración sea menos predecible.

Un manifiesto correcto separa primero las piezas:

```text
POST api.signing.example /v1/envelopes
Action: send contract for signature
Target: template "msa"
Recipients: 1 email address [personal value hidden]
Subject: "MSA for Northwind"
Message: public text, 39 characters
Document: 1 PDF attachment [content hidden]
Credential: bearer credential from vault
Request signature: present, hidden
```

La tarjeta permite al operador detectar un host equivocado, una plantilla incorrecta, una cantidad inesperada de destinatarios o un envío accidental. No expone la credencial, la firma, la dirección de correo ni los bytes del documento.

La versión peligrosa no falló porque su patrón de enmascaramiento no detectara `signature`. Falló porque el sistema trató una solicitud HTTP como texto listo para mostrar. El generador recibió un bloque con secretos, sin tipos de campo, funciones de destino ni conocimiento sobre qué valores expresaban el significado de la operación.

## Construye un generador que cierre por defecto

El generador debe aceptar únicamente entradas estructuradas, aplicar reglas de presentación incluidas en una lista permitida y negarse a representar una acción que contenga campos salientes sin clasificar. Parece estricto porque lo es. Un campo sin clasificar es una decisión que alguien aplazó, y el momento de aprobar no es el momento de adivinar.

Un contrato práctico de representación tiene tres etapas:

1. Normaliza la acción prevista en un manifiesto antes de inyectar credenciales y codificar el transporte.
2. Valida que cada campo tenga un tipo, una etiqueta de sensibilidad y una regla de representación. Rechaza las cabeceras, los valores de consulta y las hojas del cuerpo desconocidos, salvo que el llamador los dirija explícitamente a una representación oculta segura.
3. Representa una disposición fija de tarjeta que reserve espacio destacado para el destino, la acción, los objetivos, el efecto y los avisos sobre campos protegidos.

No permitas HTML, caracteres de control de terminal, Markdown ni controles de dirección Unicode arbitrarios en los valores visibles. Escápalos antes de maquetar. Un valor malicioso no debe poder convertir `recipient: alice@example.com` en una línea engañosa, crear botones falsos ni reordenar visualmente un identificador de destino.

Establece también límites de presentación. Una cadena pública puede tener 50.000 caracteres y hacer inutilizable la tarjeta. Limita el texto visible según el tipo de campo, indica que se ha acortado y conserva una vista de detalle controlada solo para el contenido que el manifiesto haya declarado seguro. Nunca conviertas «mostrar la solicitud completa» en una salida universal.

Prueba el generador con ejemplos hostiles, no solo con llamadas API normales. Incluye un bearer token en todas las ubicaciones posibles, nombres de consulta duplicados, JSON con arrays anidados, un segmento de ruta con delimitadores codificados mediante porcentajes, un secreto vacío, un secreto corto, un campo de texto muy largo y un valor que contenga saltos de línea. Prueba también solicitudes cuyo significado dañino sea un booleano, una cantidad, una función o un host de destino.

Por último, registra lo que cubrió la aprobación sin copiar secretos en texto plano al rastro de evidencias. Los registros de actividad y sesión de Sallyport proceden de un único registro de auditoría cifrado y encadenado mediante hashes, y su comando de verificación sin conexión puede comprobar la cadena sin una clave de la bóveda. Esa es la separación que debes buscar: la auditabilidad debe demostrar lo ocurrido sin convertirse en una segunda bóveda llena de credenciales reutilizables.

La tarjeta debe hacer que una acción incorrecta parezca incorrecta. Si una persona puede aprobar una solicitud que contiene credenciales sin ver su destino, efecto y objetivo, el sistema ha ocultado precisamente los hechos que necesitaba para protegerse.
