Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions DEVELOPER_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -3036,9 +3036,11 @@ Local suggestion retention is bounded by `CDIDX_SUGGESTION_MAX_AGE_DAYS` and `CD

### Local lifecycle fields

`SuggestionStore.TryLinkIssue` associates an operator-supplied existing issue under the same lock and atomic writer, preserving submission history and recording independent manual association evidence. See [issue-link identity, offline behavior, lifecycle and export contracts](docs/suggestion-issue-linking.md#english).

Local suggestion records use the `status` lifecycle field instead of a binary submitted flag. New records start as `draft`; successful GitHub submission moves them to `submitted_pending_triage` and stamps `upstream_url`, `upstream_issue_number`, and `last_synced_at` when known. Every GitHub submission attempt also stamps `last_submit_attempt`, increments `submit_attempt_count`, and records `last_submit_error` on failure; success clears the last error. GitHub rate-limit responses also stamp `next_retry_at`, and duplicate unsubmitted suggestions are not retried until that timestamp has passed. Older records containing `submitted_to_github` / `github_issue_url` are normalized on read to the new lifecycle fields.

`SuggestionStore.TryTransitionStatus` is the atomic manual-transition boundary used by `suggestions update <id> --status <state>`. `submitted_pending_triage` is automatic-only. `open_in_upstream` and `resolved_in_upstream` require existing upstream evidence; `draft` requires the absence of upstream evidence; and `wont_fix`, `duplicate`, or `superseded` are local maintainer dispositions. Local dispositions suppress automatic duplicate resubmission without setting `AlreadySubmitted` or an upstream-submission response flag. Same-state transitions and transitions during an active submission reservation fail closed. The store rechecks the expected revision under its file lock, stamps the latest `previous_status`, UTC `status_changed_at`, bounded/redacted `status_changed_by`, and optional bounded/redacted `status_change_reason`, updates `resolved_at` for `resolved_in_upstream`, and recomputes `revision_hash`. Full audit values are redacted before a surrogate-safe final cap so a credential crossing the cap boundary cannot evade redaction. Content edits and lifecycle transitions are separate CLI operations so one audit event has one unambiguous meaning.
`SuggestionStore.TryTransitionStatus` is the atomic manual-transition boundary used by `suggestions update <id> --status <state>`. `submitted_pending_triage` is set by GitHub submission or explicit issue linking, and is not a manual status-transition target. `open_in_upstream` and `resolved_in_upstream` require existing upstream evidence; `draft` requires the absence of upstream evidence; and `wont_fix`, `duplicate`, or `superseded` are local maintainer dispositions. Local dispositions suppress automatic duplicate resubmission without setting `AlreadySubmitted` or an upstream-submission response flag. Same-state transitions and transitions during an active submission reservation fail closed. The store rechecks the expected revision under its file lock, stamps the latest `previous_status`, UTC `status_changed_at`, bounded/redacted `status_changed_by`, and optional bounded/redacted `status_change_reason`, updates `resolved_at` for `resolved_in_upstream`, and recomputes `revision_hash`. Full audit values are redacted before a surrogate-safe final cap so a credential crossing the cap boundary cannot evade redaction. Content edits and lifecycle transitions are separate CLI operations so one audit event has one unambiguous meaning.

`suggestions list|export --query <text>` matches the NFKC-normalized query as an ordinal, case-insensitive substring against the redacted stable ID, sampled title, description, context, evidence paths, category, and language. Applying `SuggestionStore.RedactSensitiveText` before matching is a confidentiality contract: a caller cannot use zero-result/count differences to probe a value removed by redaction. Status, time, category, language, and agent filters run first; the text query follows; records are then ordered by descending `CreatedAt` and ordinal stable ID before offset/limit pagination.

Expand Down Expand Up @@ -7537,9 +7539,11 @@ suggestion sidecar は `DataDirectorySecurity.ResolveSensitiveSidecarDirectoryFo

### ローカルライフサイクルフィールド

`SuggestionStore.TryLinkIssue` は同じロックと原子的な書き込み処理で、操作者が指定した既存 Issue を関連付けます。投稿履歴を維持し、独立した手動関連付けの証跡を記録します。[識別情報・オフライン動作・状態・エクスポートの契約](docs/suggestion-issue-linking.md#日本語)を参照してください。

ローカルの提案レコードは、送信済みかどうかの二値フラグではなく `status` ライフサイクルフィールドを使う。新規レコードは `draft` で始まり、GitHub への送信が成功すると `submitted_pending_triage` へ移行し、判明している範囲で `upstream_url`、`upstream_issue_number`、`last_synced_at` を記録する。GitHub 送信を試みるたびに `last_submit_attempt` を stamp し、`submit_attempt_count` を増やし、失敗時は `last_submit_error` を記録します。成功時は最後の error を clear します。GitHub の rate-limit 応答では `next_retry_at` も記録し、未送信の重複提案はその時刻を過ぎるまで再送しない。`submitted_to_github` / `github_issue_url` を含む古いレコードは、読み取り時に新しいライフサイクルフィールドへ正規化される。

`SuggestionStore.TryTransitionStatus` は `suggestions update <id> --status <state>` が使う原子的な手動遷移境界です。`submitted_pending_triage` は自動設定専用です。`open_in_upstream` と `resolved_in_upstream` には既存の upstream 根拠が必要で、`draft` には upstream 根拠がないことが必要です。`wont_fix`、`duplicate`、`superseded` はメンテナーによるローカルの判断です。ローカルの判断は重複提案の自動再送を抑止しますが、`AlreadySubmitted` や upstream 送信済み response flag は設定しません。同じ状態への遷移、および送信 reservation が active な間の遷移は fail closed になります。store は file lock 内で expected revision を再確認し、最新の `previous_status`、UTC の `status_changed_at`、上限・redaction 付きの `status_changed_by`、任意の上限・redaction 付き `status_change_reason` を stamp し、`resolved_in_upstream` では `resolved_at` を更新して、`revision_hash` を再計算します。監査値全体を redaction してから surrogate-safe な最終上限を適用するため、上限境界をまたぐ credential も redaction を回避できません。1件の監査 event の意味を曖昧にしないため、content 編集と lifecycle 遷移は別々の CLI 操作です。
`SuggestionStore.TryTransitionStatus` は `suggestions update <id> --status <state>` が使う原子的な手動遷移境界です。`submitted_pending_triage` は GitHub 投稿または明示的な Issue 関連付けで設定され、手動の状態遷移先には指定できません。`open_in_upstream` と `resolved_in_upstream` には既存の upstream 根拠が必要で、`draft` には upstream 根拠がないことが必要です。`wont_fix`、`duplicate`、`superseded` はメンテナーによるローカルの判断です。ローカルの判断は重複提案の自動再送を抑止しますが、`AlreadySubmitted` や upstream 送信済み response flag は設定しません。同じ状態への遷移、および送信 reservation が active な間の遷移は fail closed になります。store は file lock 内で expected revision を再確認し、最新の `previous_status`、UTC の `status_changed_at`、上限・redaction 付きの `status_changed_by`、任意の上限・redaction 付き `status_change_reason` を stamp し、`resolved_in_upstream` では `resolved_at` を更新して、`revision_hash` を再計算します。監査値全体を redaction してから surrogate-safe な最終上限を適用するため、上限境界をまたぐ credential も redaction を回避できません。1件の監査 event の意味を曖昧にしないため、content 編集と lifecycle 遷移は別々の CLI 操作です。

`suggestions list|export --query <text>` は、NFKC 正規化した query を、redaction 済みの stable ID、sampled title、description、context、evidence path、category、language に対して ordinal・大文字小文字を区別しない部分一致で照合します。照合前に `SuggestionStore.RedactSensitiveText` を適用することは confidentiality contract です。caller は 0 件結果や count の差を使って redaction により除去された値を探索できません。status、時刻、category、language、agent の filter を最初に適用し、次に text query、続いて `CreatedAt` 降順と ordinal stable ID の順で並べてから offset/limit pagination を行います。

Expand Down
4 changes: 4 additions & 0 deletions TESTING_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -1130,6 +1130,8 @@ Candidate-ordered parallel-index recovery tests must prove that the fatal result

## Conventions

Issue #5350 uses `SuggestionStoreTests.Association.cs` and `SuggestionLinkCommandTests` for same-issue consolidation, immutable manual provenance, idempotency, identity/revision conflicts, offline operation, legacy records, the 8 MiB store boundary, no resubmission callbacks, and list/show/export parity. The instance-scoped pre-publication failure seam verifies previous-store preservation and temporary-file cleanup. Run these with suggestion, CLI schema/help and GitHub/MCP submission tests on net8/net9.

- Keep test names descriptive. The current suite mostly uses `Method_Scenario_ExpectedBehavior`.
- Keep tests deterministic. Do not depend on machine-global git config, locale-specific output, or ambient files.
- Prefer `ManualTimeProvider` for fake clocks and `TestDeterminism.CreateRandom` for randomized fixture input so repeated test runs replay the same timeline and data. Use `TestDeterminism.WaitUntilAsync` or the synchronous `WaitUntil` for bounded polling/eventual assertions instead of local `Task.Delay` loops or fixed sleeps. Use `AssertConditionRemainsTrue` for short absence/stability observations, and `TestDeterminism.RunConcurrentlyAsync` when a test needs workers to start from the same gate.
Expand Down Expand Up @@ -2504,6 +2506,8 @@ dotnet test --filter "FullyQualifiedName~GitHelperTests"

## 規約

Issue #5350 は `SuggestionStoreTests.Association.cs` と `SuggestionLinkCommandTests` で、同一 Issue への統合、手動登録の証跡保持、冪等性、識別情報・revision の競合、オフライン動作、旧レコード、ストアの8 MiB境界、再投稿コールバックの抑止、一覧・詳細・エクスポートの一致を検証します。インスタンス単位の公開直前失敗フックで、以前のストアの保持と一時ファイルの片付けを確認します。提案・CLI スキーマ/ヘルプ・GitHub/MCP 投稿の既存テストとともに net8/net9 で実行してください。

- テスト名は説明的にする。現在のスイートは `Method_Scenario_ExpectedBehavior` 形式が中心です。
- テストは決定的に保つ。マシン全体の git 設定、ロケール依存出力、外部の残存ファイルに依存しないこと。
- 本番コードが `TimeProvider` を受け取れる場合は `ManualTimeProvider` を使い、fixture data の時刻は wall clock ではなく明示的に進めてください。ランダム入力は `TestDeterminism.CreateRandom` を使い、同じ timeline とデータを再実行できるようにします。境界付きポーリング / 最終的な条件成立のアサーションには、ローカルの `Task.Delay` loop や固定 sleep ではなく `TestDeterminism.WaitUntilAsync` または同期版の `WaitUntil` を使い、短い不在・安定性の観測には `AssertConditionRemainsTrue` を使ってください。ワーカーを同じ gate から開始したい場合は `TestDeterminism.RunConcurrentlyAsync` を使ってください。
Expand Down
Loading
Loading