Compuertas de Paridad (Parity Gates)
Construye la línea base una vez desde capture/, luego verifica cada compilación contra ella hasta que todas las compuertas pasen. Las compuertas solo leen baseline/, nunca el sitio en vivo, por lo que una compilación aún puede verificarse después de que el antiguo host haya desaparecido.
La regla de oro
La compilación por defecto es igual al sitio en vivo, y una compuerta nunca se suaviza. Ninguna bandera convierte un fallo en una advertencia. Cuatro reglas que se derivan de ella:
- Nunca edites
baseline/a mano. La única excepción:keepenbaseline/scripts.json, que es cómo se elimina un vendor (ponlo enfalsey registra la decisión). - Una diferencia es una entrada, no una edición. Inevitables:
exceptions.json. Una mejora:proposals.json, activa solo conPROPOSALS=on. Ambos están enreferences/exceptions.md. - Un salto no es un pase. Una compuerta que no pudo verificar algo dice
N omitidos (razón)en su línea de resumen. Reporta cada omisión. - Reporta una compuerta incorrecta como un error del kit; nunca cambies la compuerta en el sitio, ni la línea base.
Ejecutando los scripts
Un sitio creado por convert-site tiene los scripts en scripts/; ejecútalos desde la raíz del sitio como los scripts npm a continuación, opciones después de --. Fuera de un sitio andamiado, ejecuta la forma node que se muestra bajo cada paso desde la raíz del sitio, donde <skill-dir> es la ruta absoluta de la carpeta de este skill en una copia del kit donde se haya ejecutado npm install. Cada script imprime --help; ejecútalo en lugar de leer el código fuente. Salida 0 pase, 1 compuerta fallida o desviación, 2 uso o entrada faltante.
1. Línea base (una vez)
npm run baseline
node <skill-dir>/scripts/build-baseline.mjs --capture capture --out baseline # alternativa
Necesita capture/inventory.json, media.json (el media.mjs de la captura) y templates.json (templates.mjs), y sale con 2 nombrando el paso a ejecutar cuando falta uno. Escribe baseline/{routes,seo,redirects,media,forms,scripts,meta,counts}.json y baseline/text/, y redirects.csv en la raíz del sitio. Un shortlink sin destino se omite y se nombra en stderr. Cada salto intermedio de una cadena capturada obtiene su propia fila directamente al destino final (/old-services → /old-services/ → /services/ también da /old-services/,/services/, nota salto de /old-services), a menos que ese salto sea en sí mismo una ruta construida, por lo que un enlace a cualquier URL de la cadena llega en un salto.
Confirma baseline/ y redirects.csv juntos. --check recuenta baseline/ contra counts.json (salida 1 en caso de desviación). La forma de cada archivo está en references/contracts.md.
2. Redirecciones
redirects.csv (desde,hacia,estado,fuente,nota) es la única fuente de las redirecciones del sitio. fuente es crawl, sitemap, shortlink o seed de la captura; una fila que agregues es decision, su nota nombrando el id de la decisión. Nunca edites a mano la salida compilada; compila:
npm run redirects:build
node <skill-dir>/scripts/build-redirects.mjs --csv redirects.csv --host vercel --out vercel.json --trailing-slash always # alternativa
--host cloudflare --out public/_redirects para Pages (Astro lo copia en dist/). Vercel: la clave redirects es reemplazada, todas las demás claves se mantienen, y trailingSlash se elimina y se escribe como reglas de barra 308 propias del compilador después de las del sitio, por lo que una fuente sin barra sigue siendo un salto. Solo una ruta que termina en una extensión de archivo estática cuenta como archivo para esas reglas, en cualquier caso de letra: los tipos web, de documento, multimedia y archivo que contiene una carpeta de subida (docx, xlsx, csv, mov, wav, heic, gz, ...), y cada extensión encontrada en dist/ (si no, public/), así que vuelve a ejecutarlo después de la compilación; /upgrade-guide/3.5/ es una página y mantiene su barra. Un archivo construido que las reglas redirigirían falla la compuerta de redirecciones como archivo construido redirigido.
Cloudflare: una fila de consulta (/?p=7) no puede ser emparejada por _redirects y va a cloudflare-query-redirects.csv, para ser creada como un Single Redirect (un elemento "necesita tu acción"). Una fila de consulta cuyo destino es su propia ruta (/?p=2 a /) es una no operación y se omite en ambos hosts.
Una fila capturada (cualquier fuente excepto decision) es una URL en vivo y no contiene parámetros: para Vercel cada : y * en ella se escapa (/foo:bar/ se escribe /foo\:bar/, y también /trackshttps://www.example.com/x/, un href roto en vivo que la captura siguió); // se mantiene. Solo una fila decision usa parámetros :name y splats *, e incluso allí un : antes de un dígito (un puerto) se escapa. Vercel rechaza un : desnudo, y también lo hace la simulación de las compuertas. Una fuente sin forma válida (sin / inicial, espacio en blanco, un carácter de control; en Cloudflare también un :<name> o * capturado, que _redirects no puede escapar) se descarta, se nombra en stderr y se lista como { source, reason } en redirects-skipped.json. Ese archivo se sitúa junto a redirects.csv (la raíz del sitio), nunca bajo public/, por lo que no se despliega. La compuerta de redirecciones y verify-live cuentan una fila listada como omitida, nunca pasada. Cuando la ruta simplemente no debe tener redirección, registra una excepción de "sin redirección" en su lugar (sección 4): { "kind": "redirect", "path": "<source>", "reason": "...", "action": "none", "approvedBy": "...", "date": "..." }.
3. Verificar
npm run build
npm run verify -- --endpoint https://forms.example.com/f/abc --thank-you /thank-you/
node <skill-dir>/scripts/verify/run-all.mjs --endpoint https://forms.example.com/f/abc --thank-you /thank-you/ # alternativa
run-all ejecuta las diez compuertas en orden alfabético, da a cada una solo las opciones que acepta y se detiene en el primer fallo. Opciones compartidas: --paths (lista separada por comas, * coincide con cualquier cosa), --json, --origin (enlaces, seo, sitemaps: el host para el que es la compilación), --allow-extra (rutas), --endpoint, --honeypot, --thank-you (formularios; --endpoint es obligatorio cuando la línea base tiene un formulario POST), --host, --config (redirecciones, enlaces), --csv (redirecciones), --media-plan (multimedia). Arregla el primer fallo, reconstruye, vuelve a ejecutar. Una compuerta sola: node <skill-dir>/scripts/verify/<compuerta>.mjs.
| Compuerta | Pasa cuando | Una omisión significa |
|---|---|---|
forms | Cada formulario en vivo está en su página con los mismos nombres de campo en orden y método; la acción de un formulario POST es --endpoint y lleva --honeypot; un formulario GET (un cuadro de búsqueda) mantiene la ruta de acción en vivo y no necesita ninguno ("N formularios GET/búsqueda comparados"); un formulario POST cuya acción en vivo está en otro dominio registrable (un host de lista de correo; www, http, subdominios y manejadores de WordPress como admin-ajax.php o ?wpcf7 cuentan como el sitio) se mantiene tal cual, se compara como un formulario GET por su URL de acción completa, y no necesita --endpoint ("N formularios mantenidos tal cual (publicaciones en )") | Sin --thank-you: la página de agradecimiento de los formularios POST no fue verificada |
headers | La configuración del host envía nosniff, Referrer-Policy, X-Frame-Options o CSP frame-ancestors en cada ruta, y, cuando no es indexable, X-Robots-Tag: noindex en cada ruta o (Vercel) el meta robots noindex en cada página construida; una regla de host de vista previa (https://*.pages.dev/*) cuenta solo para el host de vista previa; con PUBLIC_INDEXABLE=true noindex en cada ruta está prohibido | |
links | Cada href y src interno resuelve a una página o archivo construido, una fuente de redirección o multimedia de línea base, o aterriza en una página construida en un salto a través de las reglas compiladas (vercel.json o dist/_redirects: las reglas de barra y cada fila de redirects.csv), como lo redirigió en vivo ("N enlaces resueltos a través de redirecciones"); dos saltos o un destino faltante fallan; sin configuración de host, /about donde la compilación sirve /about/ falla | |
media | Cada archivo multimedia de línea base es servido por la compilación con los mismos bytes (--paths elige un archivo por su ruta o por una página que lo referencia): en dist/ en modo plan repo; bajo el prefix del plan, en el manifiesto de la tienda (store) o todavía en vivo (live-temporary); fuera del prefijo, en dist/ en todos los modos. Con PUBLIC_INDEXABLE=true, live-temporary falla cada archivo bajo el prefijo (una compilación de lanzamiento no puede servir multimedia desde el sitio en vivo; un plan sin prefijo cubre cada archivo) y aún verifica los archivos fuera de él en dist/ | Modo plan live-temporary en una compilación de staging: los archivos bajo el prefijo se sirven desde el sitio en vivo, no se verifican |
redirects | Cada redirección de línea base y fila csv responde en un salto con el mismo estado, y el destino responde 200 en dist/ (o es externo, o está bajo un prefijo en baseline/meta.json excluded: entonces solo se verifica el primer salto); ninguna fuente es también un archivo construido; ninguna página o archivo construido es redirigido desde su propia URL (página construida redirigida por la regla <regla>; ninguna excepción la cubre, arregla la regla); una ruta con una excepción de "sin redirección" (kind: "redirect", action: "none") no es redirigida en absoluto | Una fila de consulta sin operación, cualquier fila de consulta en Cloudflare, o una fuente sin forma válida que redirects-skipped.json lista con una razón (no compilada) |
routes | Cada página, entrada, archivo y feed a 200 está construido, las rutas noindexable aún dicen noindex, no se construye nada extra (los scripts propios de la captura en baseline/js.json y hojas de estilo en baseline/css.json en sus rutas en vivo, los nombres de sitemap en vivo en baseline/sitemaps.json, y el conjunto de iconos de lanzamiento no son extras); una ruta capturada en cualquier estado que no sea 200, 3xx, 404 o 410 falla como no capturada: <estado> a menos que una excepción routes la omita o nombre su reemplazo construido | Una excepción routes con to: "skip": esa ruta nunca fue capturada |
scripts | Cada vendor mantenido está exactamente en sus páginas en vivo con los mismos ids; uno eliminado en ninguna; ningún vendor nuevo | |
seo | Título, descripción, canónico, robots, lang, h1, h2, og y tipos @type de JSON-LD iguales por página; URLs del sitio en el origen del sitio. A menos que PUBLIC_INDEXABLE=true, robots debe llevar el noindex, nofollow de staging y el resto debe ser igual al de las dos en vivo pasadas; una compilación de lanzamiento compara robots exactamente | |
sitemaps | Cada sitemap superior en dist/ (el sitemap-index.xml de Astro, o un nombre en vivo en baseline/sitemaps.json como wp-sitemap.xml o sitemap_index.xml) lista exactamente las páginas 200 indexables en el origen (los escapes de porcentaje comparan sin distinción de mayúsculas: %c3 es %C3); cada línea Sitemap: en robots.txt está en el origen y nombra un archivo en dist/, o es una línea que el robots.txt en vivo (capture/robots.txt) llevaba textualmente; cada feed está construido con tantos elementos | |
text | El texto visible de cada página es igual a baseline/text/ (diff de palabras, 5 palabras de contexto) |
Una captura hecha con --exclude registra sus prefijos de ruta en baseline/meta.json excluded: la compuerta links no verifica un enlace bajo uno (como un enlace a otro host, anotado "N enlaces bajo rutas excluidas ... no verificados"), y la compuerta sitemaps acepta una línea Sitemap: de robots.txt bajo uno cuando el robots.txt en vivo lo llevaba textualmente (el sitemap de un subsitio multisitio en el mismo host).
4. Diferencias y decisiones
Cuando la compilación no puede o no debe coincidir con el sitio en vivo, lee references/exceptions.md. En resumen: una entrada es { path, field, from, to, reason, approvedBy, date }; from: "" registra un valor que la compilación agrega, to: "" uno que elimina; una excepción que ya no coincide, o que nombra una ruta que la compuerta no verifica, falla la compuerta. Cada rareza en el sitio en vivo (un H1 faltante, una etiqueta muerta, un enlace roto) se reproduce por defecto y obtiene una fila de decisión (id, tema, elección, defecto, razón, costo si es incorrecto) que lo dice y cuál sería la solución.
Un registro tiene un kind en lugar de un field: { "kind": "redirect", "path": "<source>", "reason": "...", "action": "none", "approvedBy": "...", "date": "..." } significa "sin redirección para esta ruta, a propósito" (una redirección en vivo registrada para un href roto que nada debería redirigir). Cada clave es obligatoria: elimina un comportamiento en vivo, por lo que está autorizada como cualquier excepción. La compuerta de redirecciones solicita la ruta y pasa solo cuando las reglas no la redirigen; en una ruta que la compuerta no verifica, falla como ruta no verificada; verify-live la omite con la razón. Todas las demás compuertas la ignoran.
5. Compilaciones de staging, propuestas y lanzamiento
PUBLIC_INDEXABLE y PROPOSALS cambian lo que Astro construye, así que ponlas en la compilación, no solo en la ejecución de la compuerta; una ejecución de compuerta en una compilación hecha sin ellas verifica el sitio equivocado. Pasa --origin solo cuando la compilación misma fue hecha para ese host; el site de una compilación andamiada es el origen en vivo, por lo que sus canónicos y sitemap permanecen en él.
npm run build && npm run verify -- --endpoint ... --thank-you ... # staging
PROPOSALS=on npm run build && npm run verify:proposals -- --endpoint ... --thank-you ... # propuestas
PUBLIC_INDEXABLE=true npm run build && npm run verify:release -- --endpoint ... --thank-you ... # lanzamiento
Después de un despliegue de staging, verifícalo sobre HTTP contra la línea base. Un despliegue de lanzamiento se verifica de la misma manera sin --expect-noindex:
npm run verify:live -- --base https://my-site.vercel.app --expect-noindex
node <skill-dir>/scripts/verify-live.mjs --base https://my-site.vercel.app --expect-noindex # alternativa
--bypass "$VERCEL_AUTOMATION_BYPASS_SECRET" se aplica solo cuando el staging fue deliberadamente protegido (el valor de derivación del entorno, nunca pegado en el chat); un alias de staging público no necesita ninguno.
Verifica cada ruta de línea base a 200 (o una --sample), cada redirección en un salto con el mismo tipo de estado, noindex en páginas si y solo si --expect-noindex, el sitemap, y 20 archivos multimedia por bytes (o --media-manifest, cuyo url puede ser la URL absoluta de la tienda o una ruta del sitio verificada en <base><path>). Una redirección listada en redirects-skipped.json o cubierta por una excepción de "sin redirección" (--skipped, --exceptions; ambos se leen desde la raíz del sitio por defecto) se omite y se nombra con su razón. Cada solicitud expira después de 30 s, un fallo de esa verificación en lugar de un bloqueo. Salida 3 significa que fue bloqueado: espera y vuelve a ejecutar. Salida 3 con deployment protection: significa que Vercel respondió con su redirección de inicio de sesión (302 a vercel.com/sso-api): usa el alias de producción del proyecto (público por defecto) en lugar de la URL de despliegue en bruto; solo cuando el staging fue deliberadamente protegido se necesita el bypass. El comando verify-live no construye nada, solo verifica en vivo.