Archive Spec
Archiver une spécification terminée en écrivant <slug>.md dans la racine d'archive résolue et en supprimant <racine-spec-résolue>/<slug>/, qui reste dans Git à la source_revision de l'enregistrement. La source est la racine Spec configurée ; la destination est sa racine d'archive résolue — docs/history/specs/ pour la racine intégrée docs/specs, ou <racine-spec>/_archived/ pour une racine externe ou non par défaut, correspondant à la destination de roundfix archive. Un archivage normal signifie implémenté, vérifié et autonome — chaque tâche accomplie, QA passée lorsque le graphe de tâches rédige un portail QA, et chaque référence indexée appartenant à la spécification. Un remplacement QA Archive enregistre l'exception explicite sans revendiquer la vérification. Après l'une ou l'autre disposition, un ls <racine-spec>/ sépare le travail actif de l'historique, et l'archive reste consultable comme enregistrement de ce qui a été construit et pourquoi.
Le déclencheur est l'achèvement de la spécification, pas la publication : exécutez-la automatiquement à la fin d'un Roundfix Spec Run une fois que le portail QA est passé, ou à la demande de l'utilisateur. La fusion et la publication sont des étapes distinctes, pilotées par l'utilisateur — le commit d'archive voyage simplement avec la branche et est livré dans la propre PR de la fonctionnalité.
Reliure d'archive
Avant d'écrire l'enregistrement d'archive, l'archive effectue une reliure d'archive. Elle promeut automatiquement chaque fichier non essentiel cité qu'un document en direct référence depuis la spécification propriétaire vers docs/references/<basename>, puis réécrit les citations de chaque document en direct vers la copie promue ; les citations du dossier Spec ou d'un artefact essentiel pointent vers l'enregistrement d'archive. L'enregistrement liste les fichiers réécrits sous relinked lorsqu'ils existent. La source conserve un propriétaire, donc une spécification secondaire n'a pas besoin de copie (ADR-0260, ADR-0083).
Le dossier Spec est retiré par suppression. Dans le résultat copié et chaque copie promue, une destination dans le dossier supprimé nomme une copie promue ou devient un localisateur Git, `<path>@<commit>` ; il ne pointe jamais vers l'enregistrement lui-même. Le commit contient le chemin et est accessible depuis la branche par défaut ou un tag. Lorsqu'aucun commit de ce type n'existe, le localisateur nomme le chemin et la pull request de l'enregistrement. Les autres destinations conservent leur cible de dépôt (ADR-0278).
L'archive refuse d'écrire lorsqu'une destination de promotion existe déjà, lorsque deux promotions partagent un basename, ou lorsqu'un document en direct à réécrire a des modifications non commitées à HEAD. Les citations dans des fichiers qui ne sont pas des documents en direct, ou les citations qui subsistent après la réécriture, sont des avertissements nommant le fichier, la ligne et la cible ; l'archive laisse ces fichiers inchangés.
Fusions squash
Une fusion squash conserve source_revision uniquement dans la ref de tête de la pull request ; l'enregistrement nomme cette pull request dans pull_request.
Déclin QA déclaré
Un graphe de tâches qui déclare qa: declined avec un qa_reason non vide archive une fois que chaque tâche est terminée, sans rapport QA et sans remplacement, et son enregistrement d'archive a la disposition no-qa.
--qa-override est refusé pour une telle spécification. Toute autre précondition d'archive s'applique toujours.
Règlement QA
Le même résultat règle la tâche qa rédigée et détermine ce que l'archive laisse :
| Résultat | Règle | Archive |
|---|---|---|
pass | Règle la tâche QA comme terminée et rend la spécification éligible à l'archivage lorsque le rapport n'a pas de lignes bloquées interdites. | L'enregistrement d'archive, qui nomme le rapport QA et le verdict ; la spécification, son rapport QA et ses preuves restent dans Git à la source_revision de l'enregistrement. |
partial déclaré qualifiant | Règle la tâche QA comme terminée lorsqu'aucune ligne n'a échoué, n'a été ignorée ou n'est bloquée par une constatation, chaque ligne déclarée bloquée est couverte par une déclaration ## Acceptation inaccessible correspondante, et chaque ligne bloquée par l'environnement est la ligne de pull request pré-PR, enregistrée comme bloquée (environnement : aucune pull request ouverte) avec la ligne de pull request nommée dans sa provenance, ou une ligne de preuve externe que le bac à sable d'exécution n'a pas pu atteindre, enregistrée comme bloquée (environnement : réseau refusé : <hôte>) avec la ligne de preuve externe nommée dans sa provenance. Aucune de ces lignes n'a besoin d'une déclaration d'Acceptation inaccessible, et un partial dont les seules lignes non satisfaites sont de telles lignes est qualifiant. | L'enregistrement d'archive, qui nomme le rapport QA et le verdict et porte l'enregistrement satisfait-par des déclarations comme non-prouvé ; la spécification, son rapport QA et ses preuves restent dans Git à la source_revision de l'enregistrement. |
environment-blocked | Laisse la ligne bloquée ; le rapport peut toujours régler comme pass lorsque des preuves équivalentes satisfont la politique d'environnement. | Rien par lui-même ; un rapport qualifiant peut archiver la spécification. |
failed | Laisse la tâche QA non résolue et refuse l'archivage sauf si un remplacement autorisé s'applique. | Rien. |
missing | Laisse la tâche QA non résolue et refuse l'archivage sauf si un remplacement autorisé s'applique. | Rien. |
override | Ne change pas le statut de la tâche QA ou le verdict du rapport ; règle l'archivage comme explicitement autorisé malgré une QA échouée ou manquante. | L'enregistrement d'archive avec qa_override, qa_override_approval, qa_override_reason, qa_override_qa_outcome, qa_override_qa_task_status lorsque la tâche QA est incomplète, et qa_override_revision ; la tâche QA et les rapports restent inchangés dans Git à la source_revision de l'enregistrement. |
Lignes que l'autorisation de la spécification maintient hors ligne
Le partial déclaré qualifiant ci-dessus accepte également une ligne de preuve externe que le portail n'a pas consultée parce que l'autorisation propre de la spécification interdit cette consultation, enregistrée comme bloquée (environnement : <sujet> interdit par l'autorisation de la spécification) avec la ligne de preuve externe nommée dans sa provenance. Le sujet nomme ce que l'autorisation interdit, dans ses mots : réseau pour une consultation réseau, ou un autre dépôt ; il fait 1 à 80 caractères et ne contient pas de parenthèses. Comme la ligne refusée par réseau, elle ne décide jamais d'un partial qualifiant et n'a pas besoin de déclaration d'Acceptation inaccessible, une ligne compte pour au plus un type exempté, et le règlement, roundfix qa-report accept et roundfix archive le lisent par la même politique (ADR-0276, ADR-0335).
Annulation d'une spécification
Une spécification qui ne sera pas livrée est annulée avec roundfix cancel <slug> --reason <texte>, jamais supprimée manuellement. La commande écrit un enregistrement d'archive avec la disposition cancelled, retourne chaque entrée de backlog adoptée à docs/backlog/ avec status: open et chaque constat adopté à docs/findings/ avec status: pending, puis supprime le dossier Spec tandis que ses octets restent dans Git à source_revision. Un enregistrement annulé ne répond jamais à un prérequis, ne remplace jamais une autre spécification et est refusé par la livraison différée.
Fichiers ignorés et suppression échouée
La commande Archiver et la commande Annuler refusent tant que Git ignore un fichier dans le dossier Spec. Chaque commande liste chaque fichier ignoré et n'écrit rien. L'opérateur doit supprimer chaque fichier ou le commiter avec git add -f, puis réessayer.
Si la suppression finale du dossier Spec échoue, les deux commandes conservent chaque écriture, se terminent avec 1 et affichent la commande qui termine la suppression :
chmod -R u+w '<dossier>' && rm -rf '<dossier>'.
Préconditions — vérifier, ne pas faire confiance
Vérifiez les trois avec des preuves fraîches de commandes avant de toucher quoi que ce soit :
-
Tâches requises terminées. Pour un archivage normal, lisez chaque
task_NN.mdlisté dans_tasks.md; chaquestatusdoit êtrecompleted. Pour un remplacement d'archive QA, chaque tâche non-QA doit êtreterminée; la tâche QA peut resterpendingoufailed. Si la tâche QA est égalementterminée, le rapport le plus récent doit rester inéligible ou le remplacement est inutile et refusé.Commande : exécutez
grep -n '^status:' <chaque-fichier-tâche-listé-dans-_tasks.md>et conservez sa sortie. Toute valeur autre questatus: completedbloque un archivage normal. Pendant un remplacement, seule une tâche non-QA avec un autre statut bloque l'archivage ; nommez ce fichier de tâche. -
QA passée. Le rapport le plus récent dans
qa/doit passer le vérificateur QA du dépôt. Ne substituez pas une grep de ligne à une validation structurée. Dans Roundfix, utilisez le même contratinternal/spec.QAVerdictque la commande d'archive : sélectionnez le rapport le plus récent par le contrat de nom de fichierqa-report-YYYY-MM-DD[-NN].md, analysez son frontmatter YAML, exigez unverdictpris en charge, exigez que les deux champs de lignes bloquées soient des entiers non négatifs lorsqu'ils sont présents, et rejetezverdict : passlorsquerows_blocked_findingest non nul. Conservez le chemin et le résultat du rapport du vérificateur comme preuve. Un répertoireqa/manquant, un rapport le plus récent mal formé ou un verdict non passant bloque l'archivage normal. Procédez uniquement sous un remplacement explicite d'archive QA effectué viaroundfix archive <slug> --qa-override --approval <source> --reason <texte>. N'enregistrez jamais le remplacement en éditant l'enregistrement d'archive. Un rapport le plus récent qualifiant ne rend pas une tâche QA échouée ou en attente terminée ; le remplacement est refusé uniquement lorsque le rapport est qualifiant et que chaque tâche est terminée. -
La spécification est autonome. Appliquez cette précondition lorsque
docs/specs/<slug>/references/_index.mdexiste ou est un lien symbolique ; un lien symbolique, y compris un lien brisé, est un état d'index invalide plutôt qu'une spécification héritée. Seule une spécification où ce chemin n'existe ni n'est un lien symbolique est antérieure à ce contrat et passe sans rétrofitter les artefacts historiques. Pour une spécification indexée, chaquepathindexé doit exister relatif à_index.md, chaque cheminsourcejamais mis à jour doit être absent, et aucune destination de lien Markdown à l'intérieur de la spécification ne peut pointer versdocs/_inbox/oudocs/findings/.Commandes : exécutez d'abord cette vérification de destination de lien ; sa syntaxe correspond délibérément aux liens Markdown en ligne ou par référence, pas à une prose qui nomme simplement l'un ou l'autre répertoire :
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 "auto-contenance échouée : réécrivez chaque lien listé à l'étape d'adoption 8" exit 1 fi test "$link_status" -eq 1 || exit "$link_status"Ensuite, analysez et validez chaque ligne de données. L'index appartient à la spécification actuelle :
ownerdoit être égal à son préfixe à quatre chiffres,typedoit êtreinbox,findingoubacklog, et chaquesourceetpathne doit apparaître qu'une seule fois. Unpathdoit être un basename relatif à_index.md; rejetez les chemins absolus,.,.., les séparateurs de chemin et les liens symboliques au lieu d'autoriser la traversée ou un lien en dehors dereferences/. Exécutez les commandes suivantes depuis la racine du dépôt et conservez les lignes normalisées ainsi que tout diagnostic comme preuve :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 'auto-contenance échouée : references/ ne doit pas être un lien symbolique\n' >&2 exit 1 fi if test -L "$index" || test ! -f "$index"; then printf 'auto-contenance échouée : references/_index.md doit être un fichier régulier, non un lien symbolique\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 "auto-contenance échouée : " 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 doit utiliser l'en-tête fixe source | type | owner | adopted date | path") } else { header = 1 } next } !divider { if (NF != 7 || !separator($2) || !separator($3) || !separator($4) || !separator($5) || !separator($6)) { reject("_index.md a un séparateur de tableau invalide") } 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("ligne d'index invalide à la ligne " NR ": " $0) next } if (type != "inbox" && type != "finding" && type != "backlog") { reject("type doit être `inbox`, `finding` ou `backlog` à la ligne " NR ": " type) } if (owner != expected_owner) { reject("owner doit être " expected_owner " à la ligne " NR ": " owner) } if (seen_source[source]++) { reject("source en double à la ligne " NR ": " source) } if (seen_path[path]++) { reject("path en double à la ligne " NR ": " path) } if (adopted !~ /^[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]$/) { reject("la date adoptée doit être AAAA-MM-JJ à la ligne " NR ": " adopted) } if ((type == "inbox" && source !~ /^docs\/_inbox\/[^\/]+\.md$/) || (type == "finding" && source !~ /^docs\/findings\/[^\/]+\.md$/) || (type == "backlog" && source !~ /^docs\/backlog\/[^\/]+\.md$/)) { reject("la source ne correspond pas au type à la ligne " NR ": " source) } print source "|" type "|" owner "|" adopted "|" path } END { if (!header || !divider) { reject("_index.md n'a pas son en-tête de tableau fixe") } if (invalid) { exit 1 } } ' "$index" > "$parsed_index" || exit $? while IFS='|' read -r source type owner adopted path; do case "$path" in ""|.|..|/*|*/*|*\\*) printf 'auto-contenance échouée : path doit être un basename relatif à `_index.md` : %s\n' "$path" >&2 exit 1 ;; esac source_basename=${source##*/} if test "$path" != "$source_basename" ... # Suite du contenu tronqué done