Archivar Especificación
Archivar una especificación completada escribiendo <slug>.md bajo la raíz de archivo resuelta y eliminando <raíz-especificación-resuelta>/<slug>/, que permanece en Git en la source_revision del registro. La fuente es la Raíz de Especificación configurada; el destino es su raíz de archivo resuelta — docs/history/specs/ para la raíz integrada docs/specs, o <spec-root>/_archived/ para una raíz externa o no predeterminada, coincidiendo con el destino de roundfix archive. Un archivo normal significa implementado, verificado y autocontenido — cada tarea realizada, QA pasado cuando el Grafo de Tareas define una puerta de QA, y cada referencia indexada propiedad de la Especificación. Una Anulación de Archivo de QA registra la excepción explícita sin reclamar verificación. Después de cualquier disposición, un ls <spec-root>/ separa el trabajo vivo del historial, y el archivo permanece buscable como el registro de lo que se construyó y por qué.
El desencadenante es la finalización de la especificación, no la publicación: ejecutar esto automáticamente al final de una Ejecución de Especificación Roundfix una vez que la puerta de QA pase, o cuando el usuario lo solicite. La fusión y el lanzamiento son pasos separados, impulsados por el usuario; el commit de archivo simplemente viaja con la rama y se envía dentro del PR de la propia característica.
Reenlace de Archivo
Antes de escribir el Registro de Archivo, el archivo realiza un Reenlace de Archivo. Promueve automáticamente cada archivo no central citado al que un Documento Vivo hace referencia desde la Especificación propietaria a docs/references/<basename>, luego reescribe las citas de cada Documento Vivo a la copia promovida; las citas de la carpeta Especificación o un artefacto central apuntan al Registro de Archivo. El registro enumera los archivos reescritos bajo relinked cuando existen. La fuente mantiene un propietario, por lo que una Especificación secundaria no necesita copia (ADR-0260, ADR-0083).
La carpeta Especificación se retira mediante eliminación. En el resultado copiado y cada copia promovida, un destino dentro de la carpeta eliminada nombra una copia promovida o se convierte en un Localizador Git, `<path>@<commit>`; nunca apunta al registro en sí. El commit contiene la ruta y es accesible desde la rama predeterminada o una etiqueta. Cuando no existe tal commit, el localizador nombra la ruta y la solicitud de extracción del registro. Otros destinos conservan su objetivo de repositorio (ADR-0278).
El archivo se niega a escribir cuando un destino de promoción ya existe, cuando dos promociones comparten un nombre base, o cuando un Documento Vivo que se va a reescribir tiene cambios no confirmados en HEAD. Las citas en archivos que no son Documentos Vivos, o las citas que permanecen después de la reescritura, son advertencias que nombran el archivo, la línea y el objetivo; el archivo deja esos archivos sin cambios.
Fusiones squash
Una fusión squash mantiene source_revision solo en la ref head de la solicitud de extracción; el registro nombra esa solicitud de extracción en pull_request.
Declinación de QA declarada
Un Grafo de Tareas que declara qa: declined con un qa_reason no vacío archiva una vez que cada Tarea está completada, sin un Informe de QA y sin una anulación, y su Registro de Archivo tiene la disposición no-qa.
--qa-override es rechazado para tal Especificación. Cada otra condición previa de archivo aún se aplica.
Liquidación de QA
El mismo resultado liquida la tarea qa creada y determina lo que el archivo deja:
| Resultado | Liquida | Archiva |
|---|---|---|
pass | Liquida la Tarea de QA como completed y hace que la Especificación sea elegible para archivo cuando el informe no tiene filas bloqueadas no permitidas. | El Registro de Archivo, que nombra el Informe de QA y el veredicto; la Especificación, su informe de QA y la evidencia permanecen en Git en la source_revision del registro. |
partial declarado calificado | Liquida la Tarea de QA como completed cuando ninguna fila falló, se omitió o está bloqueada por hallazgo, cada fila bloqueada declarada está cubierta por una declaración ## Aceptación Inalcanzable coincidente, y cada fila bloqueada por entorno es la fila de Solicitud de Extracción previa al PR, registrada como blocked (environment: no open Pull Request) con la fila de Solicitud de Extracción nombrada en su procedencia, o una fila de evidencia externa que el sandbox de la Ejecución no pudo alcanzar, registrada como blocked (environment: network denied: <host>) con la fila de evidencia externa nombrada en su procedencia. Ninguna fila necesita una declaración de Aceptación Inalcanzable, y un partial cuyas únicas filas no cumplidas son tales filas califica. | El Registro de Archivo, que nombra el Informe de QA y el veredicto y lleva el registro satisfied-by de las declaraciones como unproven; la Especificación, su informe de QA y la evidencia permanecen en Git en la source_revision del registro. |
environment-blocked | Deja la fila bloqueada; el informe aún puede liquidarse como pass cuando la evidencia equivalente satisface la política de entorno. | Nada por sí mismo; un informe calificado puede archivar la Especificación. |
failed | Deja la Tarea de QA sin resolver y rechaza el archivo a menos que se aplique una anulación autorizada. | Nada. |
missing | Deja la Tarea de QA sin resolver y rechaza el archivo a menos que se aplique una anulación autorizada. | Nada. |
override | No cambia el estado de la Tarea de QA ni el veredicto del informe; liquida el archivo como explícitamente autorizado a pesar de QA fallido o faltante. | El Registro de Archivo con qa_override, qa_override_approval, qa_override_reason, qa_override_qa_outcome, qa_override_qa_task_status cuando la Tarea de QA está incompleta, y qa_override_revision; la Tarea de QA y los Informes permanecen sin cambios en Git en la source_revision del registro. |
Filas que la autorización de la Especificación mantiene fuera de línea
El partial declarado calificado anterior también acepta una fila de evidencia externa que la puerta no buscó porque la autorización de la propia Especificación prohíbe esa búsqueda, registrada como blocked (environment: <subject> prohibited by Spec authorization) con la fila de evidencia externa nombrada en su procedencia. El sujeto nombra lo que la autorización prohíbe, en sus palabras: network para una búsqueda de red, o another repository; tiene de 1 a 80 caracteres y no contiene paréntesis. Al igual que la fila denegada por red, nunca decide un partial calificado y no necesita una declaración de Aceptación Inalcanzable, una fila cuenta como máximo para un tipo exento, y la liquidación, roundfix qa-report accept y roundfix archive lo leen a través de la misma política (ADR-0276, ADR-0335).
Cancelar una Especificación
Una Especificación que no será entregada se cancela con roundfix cancel <slug> --reason <text>, nunca se elimina a mano. El comando escribe un Registro de Archivo con disposición cancelled, devuelve cada Elemento del Backlog adoptado a docs/backlog/ con status: open y cada Hallazgo adoptado a docs/findings/ con status: pending, luego elimina la carpeta Especificación mientras sus bytes permanecen en Git en source_revision. Un registro cancelado nunca cumple un requisito previo, nunca reemplaza a otra Especificación y es rechazado por Reintento de Entrega.
Archivos ignorados y una eliminación fallida
El Comando de Archivo y el Comando de Cancelación se niegan mientras Git ignore cualquier archivo dentro de la carpeta Especificación. Cada comando enumera cada archivo ignorado y no escribe nada. El operador debe eliminar cada archivo o confirmarlo con git add -f, luego reintentar.
Si la eliminación final de la carpeta Especificación falla, ambos comandos mantienen cada escritura, salen con 1, e imprimen el comando que finaliza la eliminación:
chmod -R u+w '<folder>' && rm -rf '<folder>'.
Condiciones previas — verificar, no confiar
Verificar las tres con evidencia de comando fresca antes de tocar algo:
-
Tareas requeridas completadas. Para un archivo normal, leer cada
task_NN.mdlistado en_tasks.md; cadastatusdebe sercompleted. Para una Anulación de Archivo de QA, cada Tarea no-QA debe estarcompleted; la Tarea de QA puede permanecerpendingofailed. Si la Tarea de QA también estácompleted, el informe más reciente debe permanecer no elegible o la anulación es innecesaria y rechazada.Comando: ejecutar
grep -n '^status:' <cada-archivo-de-tarea-listado-en-_tasks.md>y conservar su salida. Cualquier valor que no seastatus: completedbloquea un archivo normal. Durante una anulación, solo una Tarea no-QA con otro estado bloquea el archivo; nombrar ese archivo de Tarea. -
QA aprobado. El informe más reciente en
qa/debe pasar el verificador de QA del repositorio. No sustituir un grep de línea por una validación estructurada. En Roundfix, usar el mismo contratointernal/spec.QAVerdictque el Comando de Archivo: seleccionar el informe más reciente por el contrato de nombre de archivoqa-report-YYYY-MM-DD[-NN].md, analizar su preámbulo YAML, requerir unverdictcompatible, requerir que ambos campos de fila bloqueada sean enteros no negativos cuando estén presentes, y rechazarverdict: passcuandorows_blocked_findingsea distinto de cero. Conservar la ruta del informe del verificador y el resultado como evidencia. Un directorioqa/faltante, informe más reciente mal formado o veredicto que no seapassbloquea el archivo normal. Proceder solo bajo una Anulación de Archivo de QA explícita realizada a través deroundfix archive <slug> --qa-override --approval <source> --reason <text>. Nunca registrar la anulación editando el Registro de Archivo. Un informe más reciente calificado no completa una Tarea de QA fallida o pendiente; la anulación se rechaza solo cuando el informe califica y cada Tarea está completada. -
La Especificación es autocontenida. Aplicar esta condición previa cuando
docs/specs/<slug>/references/_index.mdexiste o es un enlace simbólico; un enlace simbólico, incluido uno roto, es un estado de índice inválido en lugar de una Especificación heredada. Solo una Especificación donde esa ruta no existe ni es un enlace simbólico es anterior a este contrato y pasa sin adaptar artefactos históricos. Para una Especificación indexada, cadapathindexado debe existir relativo a_index.md, cada rutasourcenunca actualizada debe estar ausente, y ningún destino de enlace Markdown dentro de la Especificación puede apuntar adocs/_inbox/odocs/findings/.Comandos: primero ejecutar esta verificación de destino de enlace; su sintaxis coincide deliberadamente con enlaces Markdown en línea o de estilo de referencia, no prosa que simplemente nombre cualquiera de los árboles:
spec_dir=docs/specs/<slug> link_hits=$(grep --include='*.md' -RInE '(\]\([^)]*(docs/)?(_inbox|findings)/[^)]*\)|^[[:space:]]*\[[^]]+\]:[[:space:]]*<?[^[:space:]>]*(docs/)?(_inbox|findings)/)' "$spec_dir") link_status=$? if test "$link_status" -eq 0; then printf '%s\n' "$link_hits" echo "self-containment failed: rewrite each listed link at adoption step 8" exit 1 fi test "$link_status" -eq 1 || exit "$link_status"Luego analizar y validar cada fila de datos. El índice pertenece a la Especificación actual:
ownerdebe ser igual a su prefijo de cuatro dígitos,typedebe serinbox,findingobacklog, y cadasourceypathdeben aparecer solo una vez. Unpathdebe ser un nombre base relativo a_index.md; rechazar rutas absolutas,.,.., separadores de ruta y enlaces simbólicos en lugar de permitir el cruce o un enlace fuera dereferences/. Ejecutar lo siguiente desde la raíz del repositorio y conservar las filas normalizadas más cualquier diagnóstico como evidencia:slug=<slug> index="docs/specs/$slug/references/_index.md" expected_owner=${slug%%-*} parsed_index=$(mktemp) || exit 1 trap 'rm -f "$parsed_index"' EXIT HUP INT TERM if test -L "$(dirname "$index")"; then printf 'self-containment failed: references/ must not be a symbolic link\n' >&2 exit 1 fi if test -L "$index" || test ! -f "$index"; then printf 'self-containment failed: references/_index.md must be a regular, non-symbolic-link file\n' >&2 exit 1 fi awk -F '|' -v expected_owner="$expected_owner" ' function trim(value) { gsub(/^[[:space:]]+|[[:space:]]+$/, "", value) return value } function reject(message) { print "self-containment failed: " message > "/dev/stderr" invalid = 1 } function separator(value) { value = trim(value) return value ~ /^:?-{3,}:?$/ } /^[[:space:]]*$/ || /^[[:space:]]*#/ { next } !header { if (NF != 7 || trim($2) != "source" || trim($3) != "type" || trim($4) != "owner" || trim($5) != "adopted date" || trim($6) != "path") { reject("_index.md must use the fixed source | type | owner | adopted date | path header") } else { header = 1 } next } !divider { if (NF != 7 || !separator($2) || !separator($3) || !separator($4) || !separator($5) || !separator($6)) { reject("_index.md has an invalid table separator") } else { divider = 1 } next } { source = trim($2) type = trim($3) owner = trim($4) adopted = trim($5) path = trim($6) if (NF != 7 || source == "" || type == "" || owner == "" || adopted == "" || path == "") { reject("invalid index row at line " NR ": " $0) next } if (type != "inbox" && type != "finding" && type != "backlog") { reject("type must be `inbox`, `finding`, or `backlog` at line " NR ": " type) } if (owner != expected_owner) { reject("owner must be " expected_owner " at line " NR ": " owner) } if (seen_source[source]++) { reject("duplicate source at line " NR ": " source) } if (seen_path[path]++) { reject("duplicate path at line " NR ": " path) } if (adopted !~ /^[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]$/) { reject("adopted date must be YYYY-MM-DD at line " NR ": " adopted) } if ((type == "inbox" && source !~ /^docs\/_inbox\/[^\/]+$/) || (type == "finding" && source !~ /^docs\/findings\/[^\/]+$/) || (type == "backlog" && source !~ /^docs\/backlog\/[^\/]+$/)) { reject("source does not match type at line " NR ": " source) } print source "|" type "|" owner "|" adopted "|" path } END { if (!header || !divider) { reject("_index.md is missing its fixed table header") } if (invalid) { exit 1 } } ' "$index" > "$parsed_index" || exit $? while IFS='|' read -r source type owner adopted path; do case "$path" in ""|.|..|/*|*/*|*\\*) printf 'self-containment failed: path must be one basename relative to `_index.md`: %s\n' "$path" >&2 exit 1 ;; esac source_basename=${source##*/} if test "$path" != "$source_basename"
…
[truncado para traducción]