アーカイブ仕様 は何をしますか?
完了した仕様書をアーカイブするには、解決されたアーカイブルートの下に <slug>.md を書き込み、<解決済みスペックルート>/<slug>/ を削除します。これらはレコードの source_revision でGitに残ります。ソースは設定されたSpecルートであり、宛先はその解決されたアーカイブルートです。組み込みの docs/specs ルートの場合は docs/history/specs/、外部またはデフォルト以外のルートの場合は <spec-root>/_archived/ となり、roundfix archive の宛先と一致します。通常のアーカイブとは、実装され、検証され、自己完結している ことを意味します。すべてのタスクが完了し、タスクグラフがQAゲートを作成した場合はQAが合格し、インデックス化されたすべての参照がSpecに所有されている必要があります。QA Archive Override(QAアーカイブオーバーライド)は、検証を主張せずに明示的な例外を記録します。いずれかの処分の後、ls <spec-root>/ を実行すると、ライブの作業と履歴が分離され、アーカイブは何が構築され、その理由を記録したものとしてgrep可能な状態が維持されます。
トリガーは仕様の完了であり、公開ではありません。QAゲートが合格した後のRoundfix Spec Runの終了時、またはユーザーが要求したときに、これを自動的に実行します。マージとリリースは別個のユーザー主導のステップです。アーカイブコミットは単にブランチに沿って移動し、フィーチャー自身のPR内で出荷されます。
アーカイブリリンク
アーカイブレコードを書き込む前に、アーカイブはアーカイブリリンクを実行します。ライブドキュメントが参照する、所有者Specからの各引用された非コアファイルを自動的に docs/references/<basename> に昇格させ、各ライブドキュメントからの引用を昇格されたコピーに書き換えます。Specフォルダまたはコアアーティファクトへの引用は、アーカイブレコードを指すように書き換えられます。レコードは、リライトされたファイルが存在する場合、relinked の下にリストします。ソースは1つの所有者を保持するため、セカンダリSpecはコピーを必要としません(ADR-0260、ADR-0083)。
Specフォルダは削除によって廃止されます。コピーされた成果物と各昇格されたコピーにおいて、削除されたフォルダ内の宛先は、昇格されたコピーを指定するか、Gitロケータ `<path>@<commit>` になります。これは決してレコード自体を指しません。コミットはパスを保持し、デフォルトブランチまたはタグから到達可能です。そのようなコミットが存在しない場合、ロケータはパスとレコードのプルリクエストを指定します。その他の宛先は、リポジトリターゲットを保持します(ADR-0278)。
昇格先がすでに存在する場合、2つの昇格がベース名を共有する場合、または書き換え対象のライブドキュメントに HEAD でコミットされていない変更がある場合、アーカイブは書き込みを拒否します。ライブドキュメントではないファイル内の引用、または書き換え後も残っている引用は、ファイル、行、ターゲットを指定する警告として出力されます。アーカイブはそれらのファイルを変更しません。
スカッシュマージ
スカッシュマージは source_revision をプルリクエストのヘッドリファレンスにのみ保持します。レコードはそのプルリクエストを pull_request に指定します。
宣言されたQA辞退
qa: declined(空でない qa_reason 付き)を宣言するタスクグラフは、すべてのタスクが完了すると、QAレポートなし、オーバーライドなしでアーカイブし、そのアーカイブレコードは disposition no-qa を持ちます。そのようなSpecに対して --qa-override は拒否されます。その他のすべてのアーカイブ事前条件は引き続き適用されます。
QA決着
同じ結果が作成された qa タスクを決着させ、アーカイブが残すものを決定します。
| 結果 | 決着 | アーカイブ |
|---|---|---|
pass | QAタスクを completed として決着させ、レポートに許可されていないブロック行がない場合、Specをアーカイブ可能にします。 | アーカイブレコード。QAレポートと評決を指定します。Spec、そのQAレポート、証拠は、レコードの source_revision でGitに残ります。 |
条件を満たす宣言された partial | 失敗、スキップ、または検索ブロックされた行がなく、宣言ブロックされたすべての行が一致する ## Unreachable Acceptance 宣言でカバーされ、環境ブロックされたすべての行がPR前のプルリクエスト行である場合(blocked (環境: オープンプルリクエストなし) として記録され、プルリクエスト行が出典に指定されている)、または実行サンドボックスが到達できなかった外部証拠行である場合(blocked (環境: ネットワーク拒否: <ホスト>) として記録され、外部証拠行が出典に指定されている場合)、QAタスクを completed として決着させます。どちらの行も Unreachable Acceptance 宣言を必要とせず、そのような行のみが未達であるpartialは条件を満たします。 | アーカイブレコード。QAレポートと評決を指定し、宣言の satisfied-by レコードを unproven として保持します。Spec、そのQAレポート、証拠は、レコードの source_revision でGitに残ります。 |
environment-blocked | 行をブロックされたままにします。同等の証拠が環境ポリシーを満たす場合、レポートは引き続き pass として決着できます。 | それ自体では何もしません。条件を満たすレポートはSpecをアーカイブできます。 |
failed | QAタスクを未解決のままにし、許可されたオーバーライドが適用されない限りアーカイブを拒否します。 | 何もしません。 |
missing | QAタスクを未解決のままにし、許可されたオーバーライドが適用されない限りアーカイブを拒否します。 | 何もしません。 |
override | QAタスクのステータスまたはレポートの評決を変更しません。失敗または欠落したQAにもかかわらず、明示的に許可されたものとしてアーカイブを決着させます。 | qa_override、qa_override_approval、qa_override_reason、qa_override_qa_outcome、qa_override_qa_task_status(QAタスクが不完全な場合)、および qa_override_revision を含むアーカイブレコード。QAタスクとレポートは、レコードの source_revision でGitに変更されずに残ります。 |
Specの許可によりオフラインに保持される行
上記の条件を満たす宣言された partial は、Spec自身の許可がそのルックアップを禁止しているためにゲートがルックアップしなかった外部証拠行も受け入れます。これは blocked (environment: <件名> Spec authorizationにより禁止) として記録され、外部証拠行が出典に指定されます。件名は、許可が禁止するものをその言葉で指定します。ネットワークルックアップの場合は network、別のリポジトリの場合は another repository です。1〜80文字で、括弧を含みません。ネットワーク拒否行と同様に、条件を満たすpartialを決定することはなく、Unreachable Acceptance宣言を必要とせず、行は最大で1つの免除種類にカウントされ、決着、roundfix qa-report accept、および roundfix archive は同じポリシーを通じてそれを読み取ります(ADR-0276、ADR-0335)。
Specのキャンセル
納品されないSpecは、roundfix cancel <slug> --reason <text> でキャンセルされます。決して手動で削除してはいけません。このコマンドは、disposition cancelled のアーカイブレコードを書き込み、採用された各バックログエントリを status: open で docs/backlog/ に戻し、採用された各ファインディングを status: pending で docs/findings/ に戻し、その後Specフォルダを削除します。削除されたフォルダのバイトは source_revision でGitに残ります。キャンセルされたレコードは、前提条件を満たすことも、別のSpecを置き換えることもなく、Delivery Retry(配信再試行)によって拒否されます。
無視されたファイルと削除の失敗
アーカイブコマンドとキャンセルコマンドは、Specフォルダ下のファイルがGitによって無視されている場合、実行を拒否します。各コマンドは、無視されているすべてのファイルをリストし、何も書き込みません。オペレーターは各ファイルを削除するか、git add -f でコミットしてから再試行する必要があります。
Specフォルダの最終的な削除が失敗した場合、両方のコマンドはすべての書き込みを保持し、終了コード 1 で終了し、削除を完了するコマンドを出力します: chmod -R u+w '<folder>' && rm -rf '<folder>'。
事前条件 — 確認し、信頼しないでください
何かに触れる前に、新鮮なコマンドの証拠を使用して3つすべてを確認してください:
-
必須タスクが完了していること。 通常のアーカイブの場合、
_tasks.mdにリストされている各task_NN.mdを読み取ります。すべてのstatusがcompletedである必要があります。QA Archive Overrideの場合、QA以外のすべてのタスクがcompletedである必要があります。QAタスクはpendingまたはfailedのままでも構いません。QAタスクもcompletedの場合、最新のレポートが不適格であるか、オーバーライドが不要であり、拒否される必要があります。コマンド:
grep -n '^status:' <_tasks.md にリストされている各タスクファイル>を実行し、その出力を保持します。status: completed以外の値は、通常のアーカイブをブロックします。オーバーライド中は、別のステータスを持つQA以外のタスクのみがアーカイブをブロックします。そのタスクファイルを指定します。 -
QAに合格していること。
qa/内の最新のレポートは、リポジトリのQA検証器に合格する必要があります。構造化された検証の代わりに行grepを使用しないでください。Roundfixでは、アーカイブコマンドと同じinternal/spec.QAVerdictコントラクトを使用します。qa-report-YYYY-MM-DD[-NN].mdファイル名コントラクトで最新のレポートを選択し、そのYAMLフロントマターを解析し、サポートされているverdictを要求し、ブロック行フィールドが存在する場合は非負整数であることを要求し、rows_blocked_findingがゼロ以外の場合はverdict: passを拒否します。検証器のレポートパスと結果を証拠として保持します。qa/ディレクトリがない、最新のレポートが不正である、または合格しない評決がある場合は、通常のアーカイブをブロックします。roundfix archive <slug> --qa-override --approval <source> --reason <text>を通じて実行される明示的なQA Archive Overrideがある場合にのみ続行します。アーカイブレコードを編集してオーバーライドを記録しないでください。条件を満たす最新のレポートは、失敗または保留中のQAタスクを完了済みにしません。オーバーライドは、レポートが条件を満たし、すべてのタスクが完了している場合にのみ拒否されます。 -
Specが自己完結していること。
docs/specs/<slug>/references/_index.mdが存在するか、シンボリックリンクである場合にこの事前条件を適用します。シンボリックリンク(壊れたものを含む)は、レガシーSpecではなく、無効なインデックス状態です。そのパスが存在せず、シンボリックリンクでもないSpecのみが、このコントラクトより前のものであり、履歴アーティファクトを改造せずに合格します。インデックス化されたSpecの場合、すべてのインデックス化されたpathが_index.mdに対して相対的に存在し、更新されたことのないすべてのsourceパスが存在してはならず、Spec内のMarkdownリンク先がdocs/_inbox/またはdocs/findings/を指してはなりません。コマンド: まず、このリンク先チェックを実行します。その構文は、インラインまたは参照スタイルのMarkdownリンクに意図的に一致し、単にどちらかのツリーに言及する散文には一致しません:
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 "自己完結に失敗しました: 採用ステップ8で各リストされたリンクを書き換えてください" exit 1 fi test "$link_status" -eq 1 || exit "$link_status"次に、すべてのデータ行を解析して検証します。インデックスは現在のSpecに属します。
ownerはその4桁のプレフィックスと等しく、typeはinbox、finding、またはbacklogでなければならず、各sourceとpathは1回だけ出現する必要があります。pathは_index.mdに対する1つのベース名でなければなりません。絶対パス、.、..、パス区切り文字、およびシンボリックリンクは、トラバーサルまたはreferences/外部へのリンクを許可する代わりに拒否します。リポジトリルートから以下を実行し、正規化された行と診断を証拠として保持します: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\/[^\/]+\.md$/) || (type == "finding" && source !~ /^docs\/findings\/[^\/]+\.md$/) || (type == "backlog" && source !~ /^docs\/backlog\/[^\/]+\.md$/)) { 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"
…
[翻訳のため省略]