Sync parity docs and strengthen replay gate coverage
Some checks failed
Release Gate / gate (push) Has been cancelled

This commit is contained in:
2026-04-03 19:34:14 +09:00
parent 5de5c74040
commit 3b03b18f83
6 changed files with 122 additions and 11 deletions

View File

@@ -35,5 +35,17 @@
## 6. 최신 검증 스냅샷 (2026-04-03) ## 6. 최신 검증 스냅샷 (2026-04-03)
- `dotnet test --filter "Suite=ParityBenchmark"`: 7/7 통과. - `dotnet test --filter "Suite=ParityBenchmark"`: 7/7 통과.
- `dotnet test --filter "Suite=ReplayStability"`: 8/8 통과. - `dotnet test --filter "Suite=ReplayStability"`: 12/12 통과.
- `dotnet test`: 355/355 통과. - `dotnet test`: 361/361 통과.
## 7. 권한 Hook 계약 (P2 마감 기준)
- lifecycle hook 키:
- `__permission_request__` (pre)
- `__permission_granted__` (post)
- `__permission_denied__` (post)
- payload 기준 필드: `runId`, `tool`, `target`, `permission`, `granted`, `reason`.
- 우선순위:
1. Hook `updatedPermissions`가 현재 run의 `AgentContext.ToolPermissions`를 즉시 갱신.
2. 갱신 후 `context.CheckToolPermissionAsync()`로 최종 판정.
3. hook 실패/예외는 non-blocking(권한 흐름 지속).
4. `additionalContext`는 가능한 경로에서 메시지 컨텍스트로 반영.

View File

@@ -40,7 +40,7 @@
## 6. 2026-04-03 점검 스냅샷 ## 6. 2026-04-03 점검 스냅샷
- 기준 시점: 2026-04-03. - 기준 시점: 2026-04-03.
- 계획 대비 현재 수준: 약 92~95%. - 계획 대비 현재 수준: 약 92~95%.
- 테스트 상태: `dotnet test` 355/355 통과. - 테스트 상태: `dotnet test` 361/361 통과.
- P1 Hook 계약: 구현 완료 수준. - P1 Hook 계약: 구현 완료 수준.
- P2 세션/이벤트 내구성: 구현 완료 수준(복원/재생 경계 케이스 테스트 반영). - P2 세션/이벤트 내구성: 구현 완료 수준(복원/재생 경계 케이스 테스트 반영).
- P3 실패 복구 표준화: 구현 완료 수준(unknown-tool/권한/정체/fork 강제 흐름 반영). - P3 실패 복구 표준화: 구현 완료 수준(unknown-tool/권한/정체/fork 강제 흐름 반영).
@@ -55,7 +55,7 @@
- 레거시 도구명 `process_run` 참조: 0건 (`process`로 정규화). - 레거시 도구명 `process_run` 참조: 0건 (`process`로 정규화).
- 레거시 도구명 `grep_tool` 참조: 0건 (`grep`로 정규화). - 레거시 도구명 `grep_tool` 참조: 0건 (`grep`로 정규화).
- 내부 모드 차단 정책: `http_tool` 전면 차단, `open_external`의 외부 URL 차단. - 내부 모드 차단 정책: `http_tool` 전면 차단, `open_external`의 외부 URL 차단.
- 테스트 상태: `dotnet test` 355/355 통과. - 테스트 상태: `dotnet test` 361/361 통과.
## 8. claw-code 소스 직접 비교 결과 (2026-04-03) ## 8. claw-code 소스 직접 비교 결과 (2026-04-03)
- 비교 기준 소스: `claw-code/.../src/tools.ts`, `src/Tool.ts`, `src/skills/loadSkillsDir.ts`, `src/skills/bundled/*.ts`. - 비교 기준 소스: `claw-code/.../src/tools.ts`, `src/Tool.ts`, `src/skills/loadSkillsDir.ts`, `src/skills/bundled/*.ts`.
@@ -104,13 +104,14 @@
### 벤치마크 배포 체크리스트 연결 ### 벤치마크 배포 체크리스트 연결
1. `dotnet build` 경고 0/오류 0. 1. `dotnet build` 경고 0/오류 0.
2. `dotnet test` 전체 통과 (`355/355` 기준, 증가 시 최신 값으로 동기화). 2. `dotnet test` 전체 통과 (`361/361` 기준, 증가 시 최신 값으로 동기화).
3. 위 7개 시나리오의 회귀 테스트가 모두 통과. 3. 위 7개 시나리오의 회귀 테스트가 모두 통과.
4. 패리티 수치/상태를 `NEXT_ROADMAP.md`와 동일 문구로 동기화. 4. 패리티 수치/상태를 `NEXT_ROADMAP.md`와 동일 문구로 동기화.
5. 릴리즈 전 게이트 스크립트 실행: `powershell -ExecutionPolicy Bypass -File .\scripts\release-gate.ps1` 5. 릴리즈 전 게이트 스크립트 실행: `powershell -ExecutionPolicy Bypass -File .\scripts\release-gate.ps1`
### 실행 증적 (2026-04-03) ### 실행 증적 (2026-04-03)
- `dotnet test --filter "Suite=ParityBenchmark"`: 7/7 통과. - `dotnet test --filter "Suite=ParityBenchmark"`: 7/7 통과.
- `powershell -ExecutionPolicy Bypass -File .\scripts\release-gate.ps1`: build/replay/full gate 통과.
## 13. 세션 Replay 안정성 기준 (고정) ## 13. 세션 Replay 안정성 기준 (고정)
@@ -123,10 +124,26 @@
| run 종료 시 dangling 정리 | `TaskRunServiceTests.RestoreRecentFromExecutionEvents_CompleteClearsDanglingRunScopedActiveTasks` | Complete 이후 run 스코프 active task 잔존 0건 | | run 종료 시 dangling 정리 | `TaskRunServiceTests.RestoreRecentFromExecutionEvents_CompleteClearsDanglingRunScopedActiveTasks` | Complete 이후 run 스코프 active task 잔존 0건 |
| 현재 run 복원 우선순위 | `AppStateServiceTests.RestoreCurrentAgentRun_PrefersRunningExecutionEventOverHistory` | 실행 중 이벤트가 history보다 우선되어 현재 run 복원 | | 현재 run 복원 우선순위 | `AppStateServiceTests.RestoreCurrentAgentRun_PrefersRunningExecutionEventOverHistory` | 실행 중 이벤트가 history보다 우선되어 현재 run 복원 |
| recent timeline 재구성 | `AppStateServiceTests.RestoreRecentTasks_RebuildsRecentTaskTimelineFromExecutionEvents` | 도구/권한/에이전트 최근 이력 순서 복원 | | recent timeline 재구성 | `AppStateServiceTests.RestoreRecentTasks_RebuildsRecentTaskTimelineFromExecutionEvents` | 도구/권한/에이전트 최근 이력 순서 복원 |
| 권한 거부 후 active 권한 상태 정리 | `AppStateServiceTests.RestoreRecentTasks_PermissionDeniedLeavesNoActivePermissionAfterResume` | PermissionDenied 이후 run 재개 시 active permission 잔존 0건 |
| Hook 타임라인 역순 병합 정합성 | `AppStateServiceTests.ApplyAgentEvent_ReplaysHookTimelineInReverseChronologicalOrder` | Hook 이벤트가 역순 타임라인에서도 시간/의미 순서 보존 |
| 완료 이벤트 우선 정리(병렬 도구) | `TaskRunServiceTests.RestoreRecentFromExecutionEvents_CompleteClearsParallelToolCallsForSameRun` | Complete 도착 시 동일 run의 병렬 도구 active task 즉시 정리 |
### 운영 규칙 ### 운영 규칙
1. 위 시나리오는 `Suite=ReplayStability` 테스트 태그로 관리. 1. 위 시나리오는 `Suite=ReplayStability` 테스트 태그로 관리.
2. 릴리즈 전 `Suite=ReplayStability` 전건 통과를 replay 불일치 0건의 최소 조건으로 사용. 2. 릴리즈 전 `Suite=ReplayStability` 전건 통과를 replay 불일치 0건의 최소 조건으로 사용.
### 실행 증적 (2026-04-03) ### 실행 증적 (2026-04-03)
- `dotnet test --filter "Suite=ReplayStability"`: 8/8 통과. - `dotnet test --filter "Suite=ReplayStability"`: 12/12 통과.
- `powershell -ExecutionPolicy Bypass -File .\scripts\release-gate.ps1`: `ReplayStability` 포함 게이트 통과.
## 14. 권한 Hook 계약 (P2 마감 기준)
- lifecycle hook 키:
- `__permission_request__` (pre)
- `__permission_granted__` (post)
- `__permission_denied__` (post)
- payload 기준 필드: `runId`, `tool`, `target`, `permission`, `granted`, `reason`.
- 우선순위:
1. Hook `updatedPermissions`가 현재 run의 `AgentContext.ToolPermissions`를 즉시 갱신.
2. 갱신 후 `context.CheckToolPermissionAsync()`로 최종 판정.
3. hook 실패/예외는 non-blocking(권한 흐름 지속).
4. `additionalContext`는 가능한 경로에서 메시지 컨텍스트로 반영.

View File

@@ -36,7 +36,7 @@
## 7. 2026-04-03 실행 증적 동기화 (M4 포함) ## 7. 2026-04-03 실행 증적 동기화 (M4 포함)
- 기준 시점: 2026-04-03. - 기준 시점: 2026-04-03.
- 테스트: `dotnet test` 355/355 통과. - 테스트: `dotnet test` 361/361 통과.
- M1 증적: Hook 계약 필드(`updatedInput`, `updatedPermissions`, `additionalContext`) 반영 경로 구현 완료. - M1 증적: Hook 계약 필드(`updatedInput`, `updatedPermissions`, `additionalContext`) 반영 경로 구현 완료.
- M2 증적: run 복원/이력 재구성(`RestoreRecentFromExecutionEvents`, `RestoreCurrentAgentRun`, plan 이력 조회) 구현 및 테스트 존재. - M2 증적: run 복원/이력 재구성(`RestoreRecentFromExecutionEvents`, `RestoreCurrentAgentRun`, plan 이력 조회) 구현 및 테스트 존재.
- M3 증적: unknown-tool 복구 루프/결정 이벤트 처리 경로 구현 및 테스트 존재. - M3 증적: unknown-tool 복구 루프/결정 이벤트 처리 경로 구현 및 테스트 존재.
@@ -56,5 +56,17 @@
- 기준 문서: `docs/CLAW_CODE_PARITY_PLAN.md` 13절. - 기준 문서: `docs/CLAW_CODE_PARITY_PLAN.md` 13절.
- 테스트 태그: `Suite=ReplayStability`. - 테스트 태그: `Suite=ReplayStability`.
- 운영 기준: 릴리즈 전 `ReplayStability` 시나리오 전건 통과 시 replay 불일치 0건으로 판정. - 운영 기준: 릴리즈 전 `ReplayStability` 시나리오 전건 통과 시 replay 불일치 0건으로 판정.
- 최신 실행 증적(2026-04-03): `ParityBenchmark 7/7`, `ReplayStability 8/8`, 전체 `355/355`. - 최신 실행 증적(2026-04-03): `ParityBenchmark 7/7`, `ReplayStability 12/12`, 전체 `361/361`.
- 실행 자동화: `scripts/release-gate.ps1`로 빌드/벤치마크/리플레이/전체 테스트를 일괄 점검. - 실행 자동화: `scripts/release-gate.ps1`로 빌드/벤치마크/리플레이/전체 테스트를 일괄 점검.
## 11. 권한 Hook 계약 고정 (M1 완료 기준)
- lifecycle hook 키:
- `__permission_request__` (pre)
- `__permission_granted__` (post)
- `__permission_denied__` (post)
- payload 기준 필드: `runId`, `tool`, `target`, `permission`, `granted`, `reason`.
- 실행 우선순위:
1. Hook의 `updatedPermissions`를 현재 run 권한 컨텍스트에 즉시 반영.
2. 반영 후 `CheckToolPermissionAsync()`로 최종 권한 판정 수행.
3. hook 예외/실패는 non-blocking으로 처리하고 권한 흐름은 지속.
4. `additionalContext`는 가능한 경로에서 실행 메시지 컨텍스트에 병합.

View File

@@ -24,11 +24,11 @@ Write-Host "Workspace: $PSScriptRoot\.."
Push-Location (Join-Path $PSScriptRoot "..") Push-Location (Join-Path $PSScriptRoot "..")
try { try {
Invoke-Step -Name "Build (warnings/errors gate)" -Action { dotnet build } Invoke-Step -Name "Build (warnings/errors gate)" -Action { dotnet build }
Invoke-Step -Name "Parity benchmark suite" -Action { dotnet test --filter "Suite=ParityBenchmark" } Invoke-Step -Name "Parity benchmark suite" -Action { dotnet test --no-build --filter "Suite=ParityBenchmark" }
Invoke-Step -Name "Replay stability suite" -Action { dotnet test --filter "Suite=ReplayStability" } Invoke-Step -Name "Replay stability suite" -Action { dotnet test --no-build --filter "Suite=ReplayStability" }
if (-not $SkipFullTest) { if (-not $SkipFullTest) {
Invoke-Step -Name "Full test suite" -Action { dotnet test } Invoke-Step -Name "Full test suite" -Action { dotnet test --no-build }
} else { } else {
Write-Host "" Write-Host ""
Write-Host "==> Full test suite skipped by -SkipFullTest" Write-Host "==> Full test suite skipped by -SkipFullTest"

View File

@@ -486,6 +486,57 @@ public class AppStateServiceTests
state.RecentTasks[2].Kind.Should().Be("tool"); state.RecentTasks[2].Kind.Should().Be("tool");
} }
[Fact]
[Trait("Suite", "ReplayStability")]
public void RestoreRecentTasks_PermissionDeniedLeavesNoActivePermissionAfterResume()
{
var state = new AppStateService();
var now = DateTime.Now;
state.RestoreRecentTasks(
[
new ChatExecutionEvent { Timestamp = now.AddMinutes(-3), RunId = "run-11", Type = "PermissionRequest", ToolName = "file_write", Summary = "권한 확인", Iteration = 1 },
new ChatExecutionEvent { Timestamp = now.AddMinutes(-2), RunId = "run-11", Type = "PermissionDenied", ToolName = "file_write", Summary = "권한 거부", Success = false, Iteration = 2 },
new ChatExecutionEvent { Timestamp = now.AddMinutes(-1), RunId = "run-11", Type = "Thinking", Summary = "다음 단계 재계획", Iteration = 3 },
]);
state.ActiveTasks.Should().NotContain(t => t.Kind == "permission");
state.ActiveTasks.Should().Contain(t => t.Kind == "agent" && t.Status == "running");
state.RecentTasks.Should().Contain(t => t.Kind == "permission" && t.Status == "failed");
}
[Fact]
[Trait("Suite", "ReplayStability")]
public void ApplyAgentEvent_ReplaysHookTimelineInReverseChronologicalOrder()
{
var state = new AppStateService();
var now = DateTime.Now;
state.ApplyAgentEvent(new AgentEvent
{
RunId = "run-hook-replay",
Type = AgentEventType.HookResult,
ToolName = "__permission_request__",
Summary = "[Hook:a] first",
Timestamp = now.AddSeconds(-2),
Success = true,
});
state.ApplyAgentEvent(new AgentEvent
{
RunId = "run-hook-replay",
Type = AgentEventType.HookResult,
ToolName = "__permission_denied__",
Summary = "[Hook:b] second",
Timestamp = now,
Success = false,
});
var hooks = state.GetRecentHookEvents(2);
hooks.Should().HaveCount(2);
hooks[0].ToolName.Should().Be("__permission_denied__");
hooks[1].ToolName.Should().Be("__permission_request__");
}
[Fact] [Fact]
public void ApplyAgentEvent_TracksPermissionLifecycleInTaskStore() public void ApplyAgentEvent_TracksPermissionLifecycleInTaskStore()
{ {

View File

@@ -257,6 +257,25 @@ public class TaskRunServiceTests
service.RecentTasks.Should().Contain(t => t.Kind == "agent" && t.Status == "completed"); service.RecentTasks.Should().Contain(t => t.Kind == "agent" && t.Status == "completed");
} }
[Fact]
[Trait("Suite", "ReplayStability")]
public void RestoreRecentFromExecutionEvents_CompleteClearsParallelToolCallsForSameRun()
{
var service = new TaskRunService();
var now = DateTime.Now;
service.RestoreRecentFromExecutionEvents(
[
new Models.ChatExecutionEvent { Timestamp = now.AddSeconds(-4), RunId = "run-parallel", Type = "ToolCall", ToolName = "file_read", Summary = "read", Iteration = 1 },
new Models.ChatExecutionEvent { Timestamp = now.AddSeconds(-3), RunId = "run-parallel", Type = "ToolCall", ToolName = "grep", Summary = "grep", Iteration = 1 },
new Models.ChatExecutionEvent { Timestamp = now.AddSeconds(-2), RunId = "run-parallel", Type = "ToolResult", ToolName = "file_read", Summary = "done", Success = true, Iteration = 2 },
new Models.ChatExecutionEvent { Timestamp = now.AddSeconds(-1), RunId = "run-parallel", Type = "Complete", Summary = "complete", Success = true, Iteration = 3 },
]);
service.ActiveTasks.Should().BeEmpty();
service.RecentTasks.Should().Contain(t => t.Kind == "agent" && t.Status == "completed");
}
[Fact] [Fact]
public void RestoreRecentFromExecutionEvents_RunLevelErrorClearsDanglingRunScopedActiveTasks() public void RestoreRecentFromExecutionEvents_RunLevelErrorClearsDanglingRunScopedActiveTasks()
{ {