From 3667a69adcb89f1845281902ffd43d26b1c4592a Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 19 Oct 2026 03:11:47 +0000 Subject: [PATCH 1/2] docs: report the Gateway recovery qualification (P1R-C06) The runs on the candidate 1c0eadd qualified the independent Runner on macOS for one exact combination: `uds` with `--runner-lifecycle independent` and a Runner the operator starts, pipe and PTY executions, participations `off` and `required`, and both in one gateway. The five check suites, the freeze, the rehearsal and the full `recovery` run passed on the qualification host, and CS-RG's `candidate`, `matrix`, `faults` and `authority` passed again on the same candidate. Linux `off` stays CI functional verification only, and Linux `required` stays unsupported. The new report, in English and Korean, records the combination, the runs, the functional suites' check counts, the six intervals of the restart measurement, the completion evidence, the regressions, the three harness corrections that each made a new candidate, and the SHA-256 of every evidence directory's manifest, the earlier candidates' included. The protocol, the runner-isolation and operations guides and the DevGuard roadmap now state the result and link to the report, and the documentation registry lists it. Documentation only: the code tree, and with it the candidate, is unchanged. Co-Authored-By: Claude Opus 5.5 (1M context) --- docs/devguard-integration.md | 4 +- docs/ko/devguard-integration.md | 4 +- docs/ko/operations.md | 2 +- docs/ko/recovery-qualification-report.md | 253 +++++++++++++++++++++++ docs/ko/recovery-qualification.md | 2 +- docs/ko/runner-isolation.md | 2 +- docs/operations.md | 2 +- docs/recovery-qualification-report.md | 253 +++++++++++++++++++++++ docs/recovery-qualification.md | 2 +- docs/runner-isolation.md | 2 +- docs/translations.json | 61 +++++- 11 files changed, 569 insertions(+), 18 deletions(-) create mode 100644 docs/ko/recovery-qualification-report.md create mode 100644 docs/recovery-qualification-report.md diff --git a/docs/devguard-integration.md b/docs/devguard-integration.md index a5ca463..994808d 100644 --- a/docs/devguard-integration.md +++ b/docs/devguard-integration.md @@ -8,7 +8,7 @@ [DevGuard](https://github.com/novelKR/DevGuard) is an independent resource authority for development workloads, licensed under Apache-2.0 like CodeSpace. Its approved integration path adds a shared admission and accounting layer while CodeSpace keeps process ownership, PTY, input/output, permissions, approval holds and workspace coordination. -**Current status:** DevGuard has completed DG-1 in its independent repository ([`395315d`](https://github.com/novelKR/DevGuard/tree/395315d34b5d458ea1774446727f0cb14bd8a120), [milestone ledger](https://github.com/novelKR/DevGuard/blob/395315d34b5d458ea1774446727f0cb14bd8a120/milestones.json)). On top of the DG-0 contracts it provides native macOS host evidence, an authenticated daemon installed as the current user's LaunchAgent, the fenced launch helper and reconciliation, the `devguard` command-line owner with Cargo adapters, parent leases for candidate tests, and upgrade and repair. Its macOS SLO is qualified for release `0.1.0-6e7e065-94d85365` (a release ID, not a Git tag), built from the client pin's commit and measured on 2026-10-13 on the measured host under the policy that includes CodeSpace's `codespace` consumer ([DevGuard #34](https://github.com/novelKR/DevGuard/pull/34)); Linux enforcement is not qualified. CodeSpace consumption (CS-RG) is qualified on macOS for one exact combination. CSRG-U1 added an opt-in, status-only connection: a gateway built with the `devguard` feature and started with `--devguard status` reports DevGuard's status in `workspace_info`, through DevGuard's client pinned at [`6e7e065`](upstream-lock.md#devguard-client-pin). With CSRG-U2, the process that owns CodeSpace's executions registers itself with DevGuard (`--devguard register`), and a workspace's `resources` setting can require resource participation. With CSRG-U3, that owner takes a process slot and has DevGuard admit each such execution before anything is created. With CSRG-U4 and a configured launch helper (`--devguard-launch-helper`), it then launches the execution once through DevGuard's launch helper, which DevGuard authorizes after binding and policing its scope; without a helper it cancels the unstarted attempt and refuses the execution. Nothing runs without DevGuard. With CSRG-U5, each process has one reaper; DevGuard observes a governed execution's scope before its root is reaped, on a pipe through CodeSpace's own reaper and on a PTY through the pinned Codex PTY's waiter with the [recorded patch](upstream-lock.md#codex-downstream-patch); killing a governed process also terminates its scope; and an approval whose governed execution provably never started can be resumed again. CSRG-U6 qualified one exact combination under the [CS-RG qualification protocol](cs-rg-qualification.md), in the runner modes `in-process` and `uds` with `--runtime-bin`, each with pipe and PTY executions, as the [qualification report](cs-rg-qualification-report.md) records ([qualification and adoption](#devguard-qualification)); Linux `required` stays unsupported until DG-LINUX. The qualified release remains a candidate for execution, not a selected pin; the client pin is a source pin of the client crates. P1-RECOVERY started on the owner's direction of 2026-10-14 and is in progress, not qualified ([Gateway recovery scope](#resource-gateway-recovery)). +**Current status:** DevGuard has completed DG-1 in its independent repository ([`395315d`](https://github.com/novelKR/DevGuard/tree/395315d34b5d458ea1774446727f0cb14bd8a120), [milestone ledger](https://github.com/novelKR/DevGuard/blob/395315d34b5d458ea1774446727f0cb14bd8a120/milestones.json)). On top of the DG-0 contracts it provides native macOS host evidence, an authenticated daemon installed as the current user's LaunchAgent, the fenced launch helper and reconciliation, the `devguard` command-line owner with Cargo adapters, parent leases for candidate tests, and upgrade and repair. Its macOS SLO is qualified for release `0.1.0-6e7e065-94d85365` (a release ID, not a Git tag), built from the client pin's commit and measured on 2026-10-13 on the measured host under the policy that includes CodeSpace's `codespace` consumer ([DevGuard #34](https://github.com/novelKR/DevGuard/pull/34)); Linux enforcement is not qualified. CodeSpace consumption (CS-RG) is qualified on macOS for one exact combination. CSRG-U1 added an opt-in, status-only connection: a gateway built with the `devguard` feature and started with `--devguard status` reports DevGuard's status in `workspace_info`, through DevGuard's client pinned at [`6e7e065`](upstream-lock.md#devguard-client-pin). With CSRG-U2, the process that owns CodeSpace's executions registers itself with DevGuard (`--devguard register`), and a workspace's `resources` setting can require resource participation. With CSRG-U3, that owner takes a process slot and has DevGuard admit each such execution before anything is created. With CSRG-U4 and a configured launch helper (`--devguard-launch-helper`), it then launches the execution once through DevGuard's launch helper, which DevGuard authorizes after binding and policing its scope; without a helper it cancels the unstarted attempt and refuses the execution. Nothing runs without DevGuard. With CSRG-U5, each process has one reaper; DevGuard observes a governed execution's scope before its root is reaped, on a pipe through CodeSpace's own reaper and on a PTY through the pinned Codex PTY's waiter with the [recorded patch](upstream-lock.md#codex-downstream-patch); killing a governed process also terminates its scope; and an approval whose governed execution provably never started can be resumed again. CSRG-U6 qualified one exact combination under the [CS-RG qualification protocol](cs-rg-qualification.md), in the runner modes `in-process` and `uds` with `--runtime-bin`, each with pipe and PTY executions, as the [qualification report](cs-rg-qualification-report.md) records ([qualification and adoption](#devguard-qualification)); Linux `required` stays unsupported until DG-LINUX. The qualified release remains a candidate for execution, not a selected pin; the client pin is a source pin of the client crates. P1-RECOVERY started on the owner's direction of 2026-10-14, and its independent Runner is qualified on macOS for one exact combination, as the [recovery qualification report](recovery-qualification-report.md) records ([Gateway recovery scope](#resource-gateway-recovery)). DevGuard [design revision 1](https://github.com/novelKR/DevGuard/blob/d4981b4c241cff42687f5c2c681b583c7847776e/docs/design-revision-1.md), merged in [DevGuard PR #8](https://github.com/novelKR/DevGuard/pull/8) as `d4981b4c241cff42687f5c2c681b583c7847776e`, revised the CS-RG execution layer and work order. It changes plans and design decisions that bind later implementation. It does not change current CodeSpace behavior or any implementation or qualification status. @@ -338,7 +338,7 @@ Default participation remains `off`; `required` is an explicit operator choice i ## Initial Gateway recovery scope -**Status, P1-RECOVERY (owner directive 2026-10-14):** P1-RECOVERY is implemented one pull request per unit, as re-baselined in DevGuard's [P1-RECOVERY plan](https://github.com/novelKR/DevGuard/blob/165cb84a991fbc941b822681b47ef7dd07363878/docs/planning/milestones/P1-RECOVERY.md#re-baseline-2026-10-14). Its first unit, P1R-C01, adds the [independent Runner](runner-isolation.md#independent-runner), an opt-in mode: an operator or a harness starts the Runner, and its processes, output and timeouts outlive each gateway, which detaches from it or stops it as its exit intent says. The second unit, P1R-C02, adds [process records](runner-isolation.md#process-records): the gateway records each execution in its operations database before it sends the execution to the Runner and follows it to its end, as the database's only writer. The third unit, P1R-C03, adds the [Claim](runner-isolation.md#claim): one connection at a time controls the Runner, under a controller epoch that each request changing a process or the workspace carries, and a gateway that lost its connection claims the Runner again instead of being restarted. While no gateway controls the Runner, any process of the same user that can reach its socket can claim it, an exposure the [Runner's documentation](runner-isolation.md#claim-exposure) states. The fourth unit, P1R-C04, adds [reconciliation](runner-isolation.md#reconciliation): after each Claim, the gateway compares its records, what the Runner holds and remembers, and for a `required` execution its DevGuard attempt, which the Runner looks up, and refuses new executions, patches and approval resumes with `RECOVERY_PENDING` only in the workspaces where an earlier controller left work, until it has reconciled them. A process found running holds its workspace again, an approval left resuming is consumed with the process the Runner created for it or granted again when the Runner proves that it created none, and a workspace whose accounts do not agree, or whose record names another Runner, stays refused for an operator. The fifth unit, P1R-C05, adds [Runner loss](runner-isolation.md#runner-loss): when the Runner is killed, crashes or stops, or the host restarts, the gateway answers `process_status` and `read_process` for its executions from their records, with a `lost_reason`, and each execution that had not ended becomes `unknown` and holds its workspace until an operator finds its processes and releases its record with `codespace-mcp records` ([Operations](operations.md#runner-loss)). The operator releases a `required` execution's record only once DevGuard no longer charges its attempt, which only DevGuard's administrator quiescence shows, since DevGuard answers a lookup only to the attempt's own instance; until then DevGuard keeps the lost Runner's instance, so a new Runner cannot register as the `codespace` consumer's one instance. The sixth and last unit, P1R-C06, adds the [qualification protocol](recovery-qualification.md) with its harness suites, and the mode is supported only on the combinations that its run qualifies; none is qualified yet. [Operations](operations.md#independent-runner-adoption) describes how to adopt the mode and roll it back. In this mode the Runner is DevGuard's execution owner and reads the consumer secret from its own credential file, as DevGuard's [ADR-001](https://github.com/novelKR/DevGuard/blob/165cb84a991fbc941b822681b47ef7dd07363878/docs/planning/decisions.md#adr-001--one-registration-by-the-execution-owner) now provides; its gateway admits `required` workspaces only when the Runner states that it registers and launches through a launch helper. The rest of this section is the scope as planned. +**Status, P1-RECOVERY (owner directive 2026-10-14):** P1-RECOVERY is implemented one pull request per unit, as re-baselined in DevGuard's [P1-RECOVERY plan](https://github.com/novelKR/DevGuard/blob/165cb84a991fbc941b822681b47ef7dd07363878/docs/planning/milestones/P1-RECOVERY.md#re-baseline-2026-10-14). Its first unit, P1R-C01, adds the [independent Runner](runner-isolation.md#independent-runner), an opt-in mode: an operator or a harness starts the Runner, and its processes, output and timeouts outlive each gateway, which detaches from it or stops it as its exit intent says. The second unit, P1R-C02, adds [process records](runner-isolation.md#process-records): the gateway records each execution in its operations database before it sends the execution to the Runner and follows it to its end, as the database's only writer. The third unit, P1R-C03, adds the [Claim](runner-isolation.md#claim): one connection at a time controls the Runner, under a controller epoch that each request changing a process or the workspace carries, and a gateway that lost its connection claims the Runner again instead of being restarted. While no gateway controls the Runner, any process of the same user that can reach its socket can claim it, an exposure the [Runner's documentation](runner-isolation.md#claim-exposure) states. The fourth unit, P1R-C04, adds [reconciliation](runner-isolation.md#reconciliation): after each Claim, the gateway compares its records, what the Runner holds and remembers, and for a `required` execution its DevGuard attempt, which the Runner looks up, and refuses new executions, patches and approval resumes with `RECOVERY_PENDING` only in the workspaces where an earlier controller left work, until it has reconciled them. A process found running holds its workspace again, an approval left resuming is consumed with the process the Runner created for it or granted again when the Runner proves that it created none, and a workspace whose accounts do not agree, or whose record names another Runner, stays refused for an operator. The fifth unit, P1R-C05, adds [Runner loss](runner-isolation.md#runner-loss): when the Runner is killed, crashes or stops, or the host restarts, the gateway answers `process_status` and `read_process` for its executions from their records, with a `lost_reason`, and each execution that had not ended becomes `unknown` and holds its workspace until an operator finds its processes and releases its record with `codespace-mcp records` ([Operations](operations.md#runner-loss)). The operator releases a `required` execution's record only once DevGuard no longer charges its attempt, which only DevGuard's administrator quiescence shows, since DevGuard answers a lookup only to the attempt's own instance; until then DevGuard keeps the lost Runner's instance, so a new Runner cannot register as the `codespace` consumer's one instance. The sixth and last unit, P1R-C06, adds the [qualification protocol](recovery-qualification.md) with its harness suites, and its runs qualified the mode on macOS for one exact combination, as the [qualification report](recovery-qualification-report.md) records; the mode is supported only on that combination. [Operations](operations.md#independent-runner-adoption) describes how to adopt the mode and roll it back. In this mode the Runner is DevGuard's execution owner and reads the consumer secret from its own credential file, as DevGuard's [ADR-001](https://github.com/novelKR/DevGuard/blob/165cb84a991fbc941b822681b47ef7dd07363878/docs/planning/decisions.md#adr-001--one-registration-by-the-execution-owner) now provides; its gateway admits `required` workspaces only when the Runner states that it registers and launches through a launch helper. The rest of this section is the scope as planned. P1-RECOVERY introduces an operator-selected independent Runner mode and explicit capability. Existing modes keep their current shutdown/disconnect contracts. InProcess shares the Gateway PID and is excluded from live Gateway restart recovery. diff --git a/docs/ko/devguard-integration.md b/docs/ko/devguard-integration.md index 9e99201..a4832c7 100644 --- a/docs/ko/devguard-integration.md +++ b/docs/ko/devguard-integration.md @@ -8,7 +8,7 @@ [DevGuard](https://github.com/novelKR/DevGuard)는 개발 작업의 자원을 중앙에서 관리하는 독립 시스템이며 CodeSpace와 동일한 Apache-2.0 라이선스를 적용한다. 승인된 결합 경로에 따라 실행 허용과 자원 회계를 공통 계층에 맡기고, CodeSpace는 프로세스 소유권, PTY, 입출력, 권한, 승인 hold와 workspace 조정을 계속 담당한다. -**현재 상태:** DevGuard는 독립 저장소에서 DG-1을 완료했다([`395315d`](https://github.com/novelKR/DevGuard/tree/395315d34b5d458ea1774446727f0cb14bd8a120), [마일스톤 원장](https://github.com/novelKR/DevGuard/blob/395315d34b5d458ea1774446727f0cb14bd8a120/milestones.json)). DG-0 계약 위에 macOS 실제 호스트 증거, 현재 사용자의 LaunchAgent로 설치하는 인증 daemon, fenced launch helper와 대조, Cargo adapter를 갖춘 `devguard` 명령행 owner, 후보 시험용 parent lease, upgrade와 repair를 제공한다. macOS SLO qualification은 release `0.1.0-6e7e065-94d85365`(Git tag가 아닌 release ID)에 대해 마쳤다. 이 release는 client pin의 commit에서 빌드했고, 2026-10-13에 측정한 호스트에서 CodeSpace의 `codespace` 소비자를 포함한 정책으로 측정했다([DevGuard #34](https://github.com/novelKR/DevGuard/pull/34)). Linux 강제 보호는 qualification하지 않았다. CodeSpace 소비(CS-RG)는 정확히 한 조합에 대해 macOS에서 qualification을 마쳤다. CSRG-U1은 opt-in 상태 전용 연결을 추가했다. `devguard` feature로 빌드하고 `--devguard status`로 시작한 gateway는 [`6e7e065`](upstream-lock.md#devguard-client-pin)에 고정한 DevGuard client로 DevGuard의 상태를 `workspace_info`에 보고한다. CSRG-U2부터는 CodeSpace의 실행을 소유한 프로세스가 스스로를 DevGuard에 등록하며(`--devguard register`), workspace의 `resources` 설정으로 자원 참여를 필수로 지정할 수 있다. CSRG-U3부터 그 소유자는 이런 실행마다 무엇을 만들기 전에 프로세스 slot을 잡고 DevGuard의 실행 허용을 받는다. CSRG-U4부터는 launch helper를 설정했으면(`--devguard-launch-helper`) 그 실행을 DevGuard의 launch helper로 한 번 실행한다. DevGuard는 helper의 scope를 묶고 정책을 적용한 뒤에 실행을 승인한다. helper가 없으면 시작하지 않은 attempt를 취소하고 실행을 거부한다. DevGuard 없이 실행하는 것은 없다. CSRG-U5부터 프로세스마다 reaper는 하나다. DevGuard는 관리 대상 실행의 scope를 root가 reap되기 전에 관찰한다. pipe에서는 CodeSpace 자신의 reaper가, PTY에서는 [기록된 패치](upstream-lock.md#codex-downstream-patch)를 적용한 고정 Codex PTY의 waiter가 그 기회를 준다. 관리 대상 프로세스를 죽이면 그 scope도 종료하며, 시작하지 않았음이 증명된 관리 대상 실행의 승인은 다시 재개할 수 있다. CSRG-U6은 [CS-RG qualification 절차](cs-rg-qualification.md)에 따라 정확히 한 조합을 qualification했다. runner 모드는 `in-process`와 `--runtime-bin`을 쓴 `uds`이고, 각각 pipe와 PTY 실행을 포함한다. 그 결과는 [qualification 보고서](cs-rg-qualification-report.md)에 있다([qualification과 도입](#devguard-qualification)). Linux `required`는 DG-LINUX 전까지 지원하지 않는다. qualification을 마친 release는 실행에 쓸 후보로 남으며 선택된 pin이 아니다. client pin은 client crate의 소스 pin이다. P1-RECOVERY는 소유자 지시로 2026-10-14에 시작해 진행 중이며 qualification하지 않았다([Gateway 복구 범위](#resource-gateway-recovery)). +**현재 상태:** DevGuard는 독립 저장소에서 DG-1을 완료했다([`395315d`](https://github.com/novelKR/DevGuard/tree/395315d34b5d458ea1774446727f0cb14bd8a120), [마일스톤 원장](https://github.com/novelKR/DevGuard/blob/395315d34b5d458ea1774446727f0cb14bd8a120/milestones.json)). DG-0 계약 위에 macOS 실제 호스트 증거, 현재 사용자의 LaunchAgent로 설치하는 인증 daemon, fenced launch helper와 대조, Cargo adapter를 갖춘 `devguard` 명령행 owner, 후보 시험용 parent lease, upgrade와 repair를 제공한다. macOS SLO qualification은 release `0.1.0-6e7e065-94d85365`(Git tag가 아닌 release ID)에 대해 마쳤다. 이 release는 client pin의 commit에서 빌드했고, 2026-10-13에 측정한 호스트에서 CodeSpace의 `codespace` 소비자를 포함한 정책으로 측정했다([DevGuard #34](https://github.com/novelKR/DevGuard/pull/34)). Linux 강제 보호는 qualification하지 않았다. CodeSpace 소비(CS-RG)는 정확히 한 조합에 대해 macOS에서 qualification을 마쳤다. CSRG-U1은 opt-in 상태 전용 연결을 추가했다. `devguard` feature로 빌드하고 `--devguard status`로 시작한 gateway는 [`6e7e065`](upstream-lock.md#devguard-client-pin)에 고정한 DevGuard client로 DevGuard의 상태를 `workspace_info`에 보고한다. CSRG-U2부터는 CodeSpace의 실행을 소유한 프로세스가 스스로를 DevGuard에 등록하며(`--devguard register`), workspace의 `resources` 설정으로 자원 참여를 필수로 지정할 수 있다. CSRG-U3부터 그 소유자는 이런 실행마다 무엇을 만들기 전에 프로세스 slot을 잡고 DevGuard의 실행 허용을 받는다. CSRG-U4부터는 launch helper를 설정했으면(`--devguard-launch-helper`) 그 실행을 DevGuard의 launch helper로 한 번 실행한다. DevGuard는 helper의 scope를 묶고 정책을 적용한 뒤에 실행을 승인한다. helper가 없으면 시작하지 않은 attempt를 취소하고 실행을 거부한다. DevGuard 없이 실행하는 것은 없다. CSRG-U5부터 프로세스마다 reaper는 하나다. DevGuard는 관리 대상 실행의 scope를 root가 reap되기 전에 관찰한다. pipe에서는 CodeSpace 자신의 reaper가, PTY에서는 [기록된 패치](upstream-lock.md#codex-downstream-patch)를 적용한 고정 Codex PTY의 waiter가 그 기회를 준다. 관리 대상 프로세스를 죽이면 그 scope도 종료하며, 시작하지 않았음이 증명된 관리 대상 실행의 승인은 다시 재개할 수 있다. CSRG-U6은 [CS-RG qualification 절차](cs-rg-qualification.md)에 따라 정확히 한 조합을 qualification했다. runner 모드는 `in-process`와 `--runtime-bin`을 쓴 `uds`이고, 각각 pipe와 PTY 실행을 포함한다. 그 결과는 [qualification 보고서](cs-rg-qualification-report.md)에 있다([qualification과 도입](#devguard-qualification)). Linux `required`는 DG-LINUX 전까지 지원하지 않는다. qualification을 마친 release는 실행에 쓸 후보로 남으며 선택된 pin이 아니다. client pin은 client crate의 소스 pin이다. P1-RECOVERY는 소유자 지시로 2026-10-14에 시작했고, 그 독립 Runner는 macOS에서 정확한 조합 하나로 qualification을 마쳤다. 그 조합은 [복구 qualification 보고서](recovery-qualification-report.md)에 있다([Gateway 복구 범위](#resource-gateway-recovery)). DevGuard [설계 개정 1](https://github.com/novelKR/DevGuard/blob/d4981b4c241cff42687f5c2c681b583c7847776e/docs/ko/design-revision-1.md)은 [DevGuard PR #8](https://github.com/novelKR/DevGuard/pull/8)의 병합 `d4981b4c241cff42687f5c2c681b583c7847776e`로 반영되었으며 CS-RG의 실행 계층과 작업 순서를 개정했다. 이 개정은 계획과 이후 구현을 구속하는 설계 결정을 바꾼다. 현재 CodeSpace 동작이나 구현·qualification 상태는 바꾸지 않는다. @@ -338,7 +338,7 @@ contract/client pin, daemon/helper artifact와 CodeSpace Runner wire는 각각 ## 최초 Gateway 복구 범위 -**상태, P1-RECOVERY(소유자 지시 2026-10-14):** P1-RECOVERY는 DevGuard의 [P1-RECOVERY 계획](https://github.com/novelKR/DevGuard/blob/165cb84a991fbc941b822681b47ef7dd07363878/docs/ko/planning/milestones/P1-RECOVERY.md#재기준-2026-10-14)에서 다시 잡은 기준대로 단위마다 PR 하나로 구현한다. 첫 단위 P1R-C01은 opt-in 모드인 [독립 Runner](runner-isolation.md#independent-runner)를 더한다. 운영자나 하네스가 Runner를 시작하고, Runner의 프로세스, 출력, timeout은 모든 gateway보다 오래 산다. gateway는 종료 의도대로 Runner에서 detach하거나 Runner를 중지한다. 두 번째 단위 P1R-C02는 [실행 기록](runner-isolation.md#process-records)을 더한다. gateway는 작업 데이터베이스에 쓰는 유일한 gateway로서 각 실행을 Runner에 보내기 전에 기록하고 끝날 때까지 따라간다. 세 번째 단위 P1R-C03은 [Claim](runner-isolation.md#claim)을 더한다. 한 번에 한 연결만 controller epoch 아래에서 Runner를 제어하고, 프로세스나 workspace를 바꾸는 요청은 모두 그 epoch를 함께 보낸다. 연결을 잃은 gateway는 재시작하지 않고 Runner를 다시 Claim한다. Runner를 제어하는 gateway가 없는 동안에는 Runner의 소켓에 닿을 수 있는 같은 사용자의 프로세스라면 무엇이든 Runner를 Claim할 수 있으며, 이 노출은 [Runner 문서](runner-isolation.md#claim-exposure)에 적었다. 네 번째 단위 P1R-C04는 [대조](runner-isolation.md#reconciliation)를 더한다. gateway는 Claim할 때마다 자기 기록, Runner가 가진 것과 기억하는 것, `required` 실행이면 Runner가 조회한 DevGuard attempt를 맞춰 보고, 앞선 controller가 작업을 남긴 workspace에서만 대조를 마칠 때까지 새 실행, 패치, 승인 재개를 `RECOVERY_PENDING`으로 거부한다. 실행 중으로 확인한 프로세스는 다시 workspace를 점유하고, resuming으로 남은 승인은 Runner가 그 승인을 위해 만든 프로세스로 consumed가 되거나, Runner가 아무것도 만들지 않았음이 증명되면 다시 granted가 된다. 세 가지가 맞지 않거나 기록이 다른 Runner를 가리키는 workspace는 운영자가 판단할 때까지 계속 거부된다. 다섯 번째 단위 P1R-C05는 [Runner 손실](runner-isolation.md#runner-loss) 처리를 더한다. Runner가 kill되거나, 크래시하거나, 멈추거나, 호스트가 재시작하면 gateway는 그 실행들에 대한 `process_status`와 `read_process`에 기록으로 `lost_reason`과 함께 답하고, 끝나지 않은 실행은 `unknown`이 되어 운영자가 그 프로세스를 찾고 `codespace-mcp records`로 기록을 풀 때까지 workspace를 점유한다([운영](operations.md#runner-loss)). `required` 실행의 기록은 DevGuard가 그 attempt를 더는 과금하지 않을 때에만 운영자가 푼다. DevGuard는 attempt를 소유한 인스턴스의 조회에만 답하므로, 이는 DevGuard 관리자의 quiescence로만 확인할 수 있다. 그때까지 DevGuard는 잃은 Runner의 인스턴스를 유지하므로, 새 Runner는 `codespace` 소비자의 유일한 인스턴스로 등록할 수 없다. 여섯 번째이자 마지막 단위 P1R-C06은 [qualification 절차](recovery-qualification.md)와 그 하네스 suite를 더하며, 이 모드는 그 실행이 통과시킨 조합에서만 지원한다. 아직 통과한 조합은 없다. 이 모드를 도입하고 되돌리는 방법은 [운영](operations.md#independent-runner-adoption)에 있다. 이 모드에서는 Runner가 DevGuard의 실행 소유자이며, DevGuard [ADR-001](https://github.com/novelKR/DevGuard/blob/165cb84a991fbc941b822681b47ef7dd07363878/docs/ko/planning/decisions.md#adr-001--실행-소유자인-runner의-단일-등록)의 보완대로 소비자 secret을 자기 자격 파일에서 직접 읽는다. 그 gateway는 Runner가 등록하고 launch helper로 실행한다고 알릴 때만 `required` workspace를 받아들인다. 이 절의 나머지는 계획한 범위다. +**상태, P1-RECOVERY(소유자 지시 2026-10-14):** P1-RECOVERY는 DevGuard의 [P1-RECOVERY 계획](https://github.com/novelKR/DevGuard/blob/165cb84a991fbc941b822681b47ef7dd07363878/docs/ko/planning/milestones/P1-RECOVERY.md#재기준-2026-10-14)에서 다시 잡은 기준대로 단위마다 PR 하나로 구현한다. 첫 단위 P1R-C01은 opt-in 모드인 [독립 Runner](runner-isolation.md#independent-runner)를 더한다. 운영자나 하네스가 Runner를 시작하고, Runner의 프로세스, 출력, timeout은 모든 gateway보다 오래 산다. gateway는 종료 의도대로 Runner에서 detach하거나 Runner를 중지한다. 두 번째 단위 P1R-C02는 [실행 기록](runner-isolation.md#process-records)을 더한다. gateway는 작업 데이터베이스에 쓰는 유일한 gateway로서 각 실행을 Runner에 보내기 전에 기록하고 끝날 때까지 따라간다. 세 번째 단위 P1R-C03은 [Claim](runner-isolation.md#claim)을 더한다. 한 번에 한 연결만 controller epoch 아래에서 Runner를 제어하고, 프로세스나 workspace를 바꾸는 요청은 모두 그 epoch를 함께 보낸다. 연결을 잃은 gateway는 재시작하지 않고 Runner를 다시 Claim한다. Runner를 제어하는 gateway가 없는 동안에는 Runner의 소켓에 닿을 수 있는 같은 사용자의 프로세스라면 무엇이든 Runner를 Claim할 수 있으며, 이 노출은 [Runner 문서](runner-isolation.md#claim-exposure)에 적었다. 네 번째 단위 P1R-C04는 [대조](runner-isolation.md#reconciliation)를 더한다. gateway는 Claim할 때마다 자기 기록, Runner가 가진 것과 기억하는 것, `required` 실행이면 Runner가 조회한 DevGuard attempt를 맞춰 보고, 앞선 controller가 작업을 남긴 workspace에서만 대조를 마칠 때까지 새 실행, 패치, 승인 재개를 `RECOVERY_PENDING`으로 거부한다. 실행 중으로 확인한 프로세스는 다시 workspace를 점유하고, resuming으로 남은 승인은 Runner가 그 승인을 위해 만든 프로세스로 consumed가 되거나, Runner가 아무것도 만들지 않았음이 증명되면 다시 granted가 된다. 세 가지가 맞지 않거나 기록이 다른 Runner를 가리키는 workspace는 운영자가 판단할 때까지 계속 거부된다. 다섯 번째 단위 P1R-C05는 [Runner 손실](runner-isolation.md#runner-loss) 처리를 더한다. Runner가 kill되거나, 크래시하거나, 멈추거나, 호스트가 재시작하면 gateway는 그 실행들에 대한 `process_status`와 `read_process`에 기록으로 `lost_reason`과 함께 답하고, 끝나지 않은 실행은 `unknown`이 되어 운영자가 그 프로세스를 찾고 `codespace-mcp records`로 기록을 풀 때까지 workspace를 점유한다([운영](operations.md#runner-loss)). `required` 실행의 기록은 DevGuard가 그 attempt를 더는 과금하지 않을 때에만 운영자가 푼다. DevGuard는 attempt를 소유한 인스턴스의 조회에만 답하므로, 이는 DevGuard 관리자의 quiescence로만 확인할 수 있다. 그때까지 DevGuard는 잃은 Runner의 인스턴스를 유지하므로, 새 Runner는 `codespace` 소비자의 유일한 인스턴스로 등록할 수 없다. 여섯 번째이자 마지막 단위 P1R-C06은 [qualification 절차](recovery-qualification.md)와 그 하네스 suite를 더하며, 그 실행으로 이 모드는 macOS에서 정확한 조합 하나로 qualification을 마쳤다. 그 조합은 [qualification 보고서](recovery-qualification-report.md)에 있고, 이 모드는 그 조합에서만 지원한다. 이 모드를 도입하고 되돌리는 방법은 [운영](operations.md#independent-runner-adoption)에 있다. 이 모드에서는 Runner가 DevGuard의 실행 소유자이며, DevGuard [ADR-001](https://github.com/novelKR/DevGuard/blob/165cb84a991fbc941b822681b47ef7dd07363878/docs/ko/planning/decisions.md#adr-001--실행-소유자인-runner의-단일-등록)의 보완대로 소비자 secret을 자기 자격 파일에서 직접 읽는다. 그 gateway는 Runner가 등록하고 launch helper로 실행한다고 알릴 때만 `required` workspace를 받아들인다. 이 절의 나머지는 계획한 범위다. P1-RECOVERY는 운영자가 선택하는 독립 Runner 모드와 명시적인 capability를 도입한다. 기존 모드의 종료·연결 소실 계약은 유지한다. InProcess는 Gateway와 PID를 공유하므로 Gateway 재시작 중 live 복구 대상에서 제외한다. diff --git a/docs/ko/operations.md b/docs/ko/operations.md index a623640..21154fb 100644 --- a/docs/ko/operations.md +++ b/docs/ko/operations.md @@ -145,7 +145,7 @@ worker는 같은 호스트에서 실행하는 별도 프로세스이며 컨테 ## 독립 Runner 도입과 되돌리기 -[독립 Runner](runner-isolation.md#independent-runner)는 [qualification](recovery-qualification.md)이 통과시킨 조합에서만 지원하며, 아직 통과한 조합은 없습니다. 이 모드는 opt-in이고 기본값은 계속 `in-process`입니다. 통과한 조합에서만, 그것도 qualification을 받은 CodeSpace 빌드, DevGuard release, 정책 그대로 도입하세요. 그 밖의 것은 모두 새 후보입니다. +[독립 Runner](runner-isolation.md#independent-runner)는 [qualification](recovery-qualification.md)이 통과시킨 조합에서만 지원합니다. 그 조합은 macOS에서 [qualification 보고서](recovery-qualification-report.md#recovery-report-combination)가 기록한 하나뿐입니다. 이 모드는 opt-in이고 기본값은 계속 `in-process`입니다. 통과한 조합에서만, 그것도 qualification을 받은 CodeSpace 빌드, DevGuard release, 정책 그대로 도입하세요. 그 밖의 것은 모두 새 후보입니다. 1. **준비합니다.** 모든 workspace 밖에 상태 디렉터리를 두고, 실행 기록이 생기기 전의 CodeSpace 버전이 열지 않을 작업 데이터베이스([스키마 버전](#operations-database))와 Runner용 mode 0700 소켓 디렉터리를 준비합니다. DevGuard를 쓰면 Runner가 실행 소유자로서 `codespace` 소비자의 인스턴스 하나로 등록하므로, 그 소비자로 등록하는 다른 게이트웨이가 있으면 먼저 멈춥니다. 2. **Runner를 시작합니다.** qualification이 시작하듯 Runner를 자기 세션에서 시작해, 터미널이 닫히거나 어떤 게이트웨이가 끝나도 Runner에 닿지 않게 합니다. Linux에서는 `setsid`가 이렇게 하며, 아래 Python 줄은 macOS와 Linux 모두에서 같은 일을 합니다. DevGuard를 쓰면 [상태 전용 연결](devguard-integration.md#devguard-status-connection)의 소비자 설정과 release의 `devguard-launch`를 줍니다. DevGuard 없이 쓰면 `--devguard-*` 설정을 빼며, 이때 `required` workspace는 거부됩니다. diff --git a/docs/ko/recovery-qualification-report.md b/docs/ko/recovery-qualification-report.md new file mode 100644 index 0000000..a401164 --- /dev/null +++ b/docs/ko/recovery-qualification-report.md @@ -0,0 +1,253 @@ + + +# Gateway 복구 qualification 보고서 + +[English](../recovery-qualification-report.md) | [한국어](recovery-qualification-report.md) + +이 문서는 P1R-C06 단위가 [Gateway 복구 qualification 절차](recovery-qualification.md)에 따라 Gateway 복구(P1-RECOVERY)를 qualification한 결과를 보고합니다. 실행이 무엇을 보여야 하는지는 절차가 정하고, 이 문서는 고정한 후보에서 한 실행이 실제로 무엇을 보였는지와 그 증거가 어디 있는지를 기록합니다. 횟수, 시간, 백분위수는 한 호스트에서 이 실행들이 관찰한 값이며, 실패 확률이 아닙니다. + +**결과:** [독립 Runner](runner-isolation.md#independent-runner)는 아래 [조합](#recovery-report-combination)으로 macOS에서 qualification을 마쳤습니다. 대상은 `--runner-lifecycle independent`를 쓰고 운영자가 Runner를 시작하는 `uds`이며, pipe와 PTY 실행, 참여 `off`와 `required`, 그리고 한 gateway 안의 두 참여입니다. 아래 [주장](#recovery-report-claims)대로 Linux `off`는 CI 기능 확인일 뿐이고, Linux `required`는 DG-LINUX 전까지 지원하지 않으며, Runner 자신이 재시작한 뒤의 프로세스 복원은 지원하지 않습니다. 같은 후보에서 CS-RG가 qualification한 모드는 CS-RG의 스위트를 다시 통과했습니다. 독립 모드는 계속 opt-in이며, 기본값으로 바꾸는 것은 별도 결정입니다. 운영자가 이 모드를 도입하고 되돌리는 방법은 [도입과 되돌리기](operations.md#independent-runner-adoption)에 있습니다. + + + +## 주장하는 것 + +수준은 [CS-RG 보고서](cs-rg-qualification-report.md#report-claims)의 것입니다. **구현**은 CodeSpace main 브랜치에 병합되었다는 뜻입니다. **기능 검증**은 fake, 스크립트로 흉내 낸 worker, DevGuard fixture authority를 쓴 시험으로 CodeSpace의 시험이나 CI에서 보였다는 뜻입니다. **플랫폼 검증**은 절차의 스위트가 macOS에서 DevGuard 자신의 구성 요소를 상대로 보였다는 뜻입니다. **qualification**은 정확한 조합이 그 주장에 절차가 요구하는 모든 스위트를 통과하고, 봉인한 증거를 확인하고 검토했다는 뜻입니다. + +| 모드나 동작 | 수준 | 근거 | +| --- | --- | --- | +| macOS, `--runner-lifecycle independent`를 쓴 `uds`, pipe와 PTY, `required` | qualification | `required`로 실행한 [기능 스위트](#recovery-report-functional), [재시작 측정](#recovery-report-measurement), [회귀](#recovery-report-regressions) | +| macOS, 같음, `off` | qualification | `off`로 실행한 기능 스위트, 측정의 관리하지 않는 workspace | +| macOS, 한 gateway 안의 `off`와 `required` workspace | qualification | 모든 gateway가 관리 대상 프로세스와 관리하지 않는 프로세스를 함께 다루는 측정 | +| Linux, 같음, `off` | 기능 검증: CI 기능 시험일 뿐이며 Linux qualification이 아님(결정 D7) | 후보 commit의 Linux CI에서 실행한 CodeSpace의 독립 Runner 시험. 후보의 하네스와 코드로 Linux에서 하네스의 `off` 시나리오 34개도 통과함([#115](https://github.com/novelKR/CodeSpace/pull/115)) | +| Linux, 같음, `required` | DG-LINUX 전까지 지원하지 않음 | Linux의 모든 `required` 실행처럼 아무것도 실행하기 전에 거부. DevGuard는 Linux에 실제 호스트 증거가 없어 거기서는 실행 소유자가 등록하지 못하며, CS-RG의 [Linux CI](cs-rg-qualification-report.md#report-regressions)가 이를 보여 줌 | +| Runner 자신이 재시작한 뒤 프로세스 복원 | 지원하지 않으며 P1-RECOVERY 밖. 거부는 플랫폼 검증 | observation `runner-replaced` | +| 호스트 재시작 | 기능 검증. 실제 재시작은 qualification하지 않음 | CodeSpace 시험이 기록에 다른 boot ID를 주면 `host_rebooted`로 보고됨 | +| gateway가 Runner를 제어하지 않는 동안 같은 사용자의 다른 프로세스가 Runner를 Claim함 | 설계상 가능(결정 D6). 기록되고 fence된다는 것은 플랫폼 검증 | fence `stale-epoch` | +| gateway가 읽지 않는 동안 Runner의 256 KiB ring을 넘는 출력 | 설계상 잃음. 잃었다고 밝힌다는 것은 플랫폼 검증 | observation `ring-overflow`, 측정의 한꺼번에 쓰기 | +| Runner를 잃은 뒤의 PTY 프로세스 | 그 루트를 기록하지 않으므로 운영자가 기록을 해제하기 전에 끝냄. 플랫폼 검증 | PTY에서 실행한 observation `runner-loss` | +| runner wire에서 준비나 launch 메시지 재전송 | 시험하지 않음. 알려진 공백 | epoch fence가 이전 controller의 요청을 거부함(fence 스위트) | +| DG-1 warm 부하에서의 복구 | 측정하지 않음 | 측정은 cold 부하를 씀 | +| CS-RG가 qualification한 모드 | 이 후보에서 CS-RG의 스위트를 다시 통과함. CS-RG 자신의 qualification 조합은 그대로 [`ca306ee`](cs-rg-qualification-report.md#report-combination) | [회귀](#recovery-report-regressions) | + + + +## qualification한 조합 + +`candidate` 스위트가 2026-10-18 16:31 UTC에 기록했습니다. 그 전에 같은 commit과 바이너리로 검사 스위트 다섯 개가 통과했습니다. 예행과 측정은 이 기록이 검사를 실행한 조합과 일치할 때만 시작했고, code tree hash도 commit에서 따로 다시 계산했습니다. + +| 구성 요소 | 값 | +| --- | --- | +| CodeSpace | commit `1c0eaddd81fdd17d35da673b0c8fbe750fe4ac60`([#115](https://github.com/novelKR/CodeSpace/pull/115)의 병합), 커밋하지 않은 변경 없음. code tree SHA-256 `cb18534dedbd001a1de2f42c930ee303d41791dee279a36a7e5fe1b7de808d38`. 이 보고서를 더하는 commit은 문서만 바꾸므로 이 code tree를 유지함 | +| DevGuard client | `novelKR/DevGuard`의 `6e7e065a0dd5b3876dc7f4546ab76069489937d1`에 있는 `devguard-client` 0.1.0과 `devguard-contract` 0.1.0([client pin](upstream-lock.md#devguard-client-pin)). CS-RG와 같음 | +| Codex | gitlink `6b9826e3aa83b1a5947db50f4332cb9c65f1b340`(`rust-v0.154.0`)에 [기록된 패치](upstream-lock.md#codex-downstream-patch)를 적용, 패치한 tree `1999937194e3a68d982be85679557f0e50fe5408`. `codex_patch.py check` 통과 | +| wire와 프로토콜 | CodeSpace runner wire 11, DevGuard 프로토콜 1, 하네스가 쓰는 MCP `2025-11-25` | +| 바이너리 | `codespace-mcp` 0.6.0(`-p codespace-server --features devguard`, release) `18e6f6da6becd3be35f4a3591c7ec047d999eade561d346b2ea6ea4bb6065027`, 40,183,376 bytes. `uds` worker이자 독립 Runner인 `codespace-codex-runtime`(`--features devguard`, release) `fa3959b430fa93f7a874970dd0e4c77b274f97720d9849ba7bc6e548eeb80237`, 27,718,336 bytes. fixture 시나리오에는 둘 다 client pin으로 빌드한 `fixture_authority` `ea234acf2a3ddf645d4e5a3ad8ab163cfa81df003d323070ac754c035b3ff124`와 `devguard-launch` `85c2165e95e19ca52e8a6c213b3efa4f48184de335a828b5409b737dce0e5bf0`, 부하에는 `devguard-qualify` `694c838621b14762dc2ad4af5139a48041764fe44fd62bed005a501d6eb68cc1`. 이 셋은 CS-RG의 것임 | +| DevGuard release | `6e7e065`에서 빌드한 `0.1.0-6e7e065-94d85365`. `MANIFEST.json` SHA-256 `4535b52cc6466e892ebee2a2ad52f396e6b7520d7560e7e25c20ffa3647ed0df`. 설치된 `devguardd`, `devguard`, `devguard-launch`가 이와 일치하고, 서비스는 정상이며 CS-RG 때처럼 프로세스 ID 23023으로 이 release를 실행함 | +| 정책 | CS-RG 이후 바뀌지 않음. 구성 fingerprint `8cbb019bbb58e92e2a0d65f797cd5eb5f1696ce9e254bfdb1af97395382efc6f`. `devguard doctor --require admission,registration,macos-cooperative` 충족. capability `durable_admission`, `fenced_launch`, `per_resource_evidence`, `static_control_reservations`, `macos_cooperative`. `codespace` 소비자는 역할 `control_service`, generation `codespace-g1`, 인스턴스 하나, 제어 예약 150 mCPU, 536,870,912 bytes, 40 tasks. `host.toml`의 SHA-256은 봉인한 기록에 있음 | +| DG-1 qualification | 이 release에 대한 DevGuard의 기록, SHA-256 `fd4fed318e0fbc640d9e6174a8dd731db80f9607d2235bdfa773c4101f6b17a4`. 같은 `host.toml`, fingerprint, capability에서 cold와 warm 부하가 qualification을 통과함 | +| 도구 | `rustc 1.95.0 (59807616e 2026-04-14)`, `cargo 1.95.0 (f2d3ce0bd 2026-03-21)`, Python 3.9.6, Xcode 27.0(27A266a) | +| 호스트 | 논리 CPU 8개와 16 GiB를 갖춘 Mac mini(Macmini9,1, Apple M1), AC 전원, macOS 27.0(26A428). 소비자 예약을 뺀 작업 용량 5,350 mCPU, 12,079,595,520 bytes, 104 tasks | + + + +## 실행 + +qualification 호스트의 모든 실행은 최소 환경에서 후보의 바이너리를 썼고, `PATH` 맨 앞에 Rust 1.95.0을 두었으며, 자원을 차지한 것이 없고 활성 `codespace` 인스턴스가 없고 메모리 pressure가 정상임을 확인한 뒤 시작했습니다. 실행은 모두 봉인했고, `verify`가 원시 파일에서 판정을 다시 계산해 바뀌지 않았음을 확인했습니다. 검사 스위트, 고정, 예행, 측정은 소유자의 터미널에서 검토한 명령 하나로 실행했고, 각 단계는 앞 단계가 깨끗할 때만 시작했으며, 터미널의 열린 파일 한도 256 아래에서 돌았습니다. CS-RG 스위트는 그 뒤 바이너리를 빌드한 세션에서 CS-RG 때처럼 실행했습니다. + +| 실행 | 시각(UTC) | 결과 | +| --- | --- | --- | +| 검사 스위트 다섯 개: `recovery-lifecycle`, `recovery-store`, `recovery-fence`, `recovery-reconcile`, `recovery-observation` | 2026-10-18 16:26–16:31 | 통과: 시나리오 55개, 검사 650개, 실패 없음. 공백 두 개는 이유와 함께 실행하지 않음으로 기록 | +| `candidate` | 2026-10-18 16:31 | 통과 | +| `recovery --rehearsal` | 2026-10-18 16:31–16:42 | 예행이 절차보다 짧다는 사유 하나로만 판정 불가(설계대로). 기능 시나리오와 두 구간은 통과 | +| `recovery` | 2026-10-18 16:42–18:52 | qualification 통과: 기능 시나리오와 세 반복의 여섯 구간이 모두 통과 | +| CS-RG의 `candidate`, `matrix`, `faults`, `authority` | 2026-10-19 02:22–02:31 | 통과([회귀](#recovery-report-regressions)) | +| P1R-C06의 모든 디렉터리에 대한 `verify` | 2026-10-19 02:31 | 이전 후보의 것을 포함해 35개 모두 확인됨 | +| 후보 commit의 CI | 2026-10-18 09:18–09:31, 21:51–22:02 | 통과: 병합 뒤 push 실행과 예약된 전체 실행의 모든 작업 | + + + +## 기능 스위트 + +기능 시나리오는 후보에서 세 번 실행했습니다. 고정 전 검사 스위트 다섯 개에서, 그리고 `recovery` 예행과 `recovery` 실행의 첫 부분으로입니다. 매번 macOS에서 실행하는 시나리오 55개(`off` 34개, `required` 21개)가 같은 검사 650개로 모두 통과해 실패한 검사가 없었고, 절차가 [공백](recovery-qualification.md#recovery-gaps)으로 적은 두 시나리오, 곧 호스트 재시작과 Runner 재시작 뒤 프로세스 복원은 이유와 함께 실행하지 않음으로 기록했습니다. + +| 스위트 | `off`로 | `required`로 | 검사 (`off` / `required`) | +| --- | --- | --- | ---: | +| `recovery-lifecycle` | exit-kinds와 original-timeout(각각 pipe와 PTY), runner-sigterm, invalid-settings | exit-kinds와 original-timeout(각각 pipe와 PTY), runner-sigterm | 98 / 95 | +| `recovery-store` | records(pipe와 PTY), writer-lock, schema, records-command | records(pipe와 PTY) | 49 / 28 | +| `recovery-fence` | foreign-connection(pipe와 PTY), stale-epoch, stopped-gateway, concurrent-claims, other-database, concurrent-gateways | foreign-connection(pipe와 PTY) | 58 / 30 | +| `recovery-reconcile` | running, terminal, kill-during-exec, kill-during-terminate(각각 pipe와 PTY), approval, missing-record | running, terminal, kill-during-exec, kill-during-terminate(각각 pipe와 PTY), approval, authority-outage | 89 / 110 | +| `recovery-observation` | ring-overflow와 runner-loss(각각 pipe와 PTY), retention, runner-replaced | runner-loss(pipe와 PTY) | 55 / 38 | + +각 시나리오가 무엇을 보여 주는지는 절차의 [기능 스위트](recovery-qualification.md#recovery-functional)에 있습니다. `required` 시나리오는 모두 인스턴스 하나짜리 `codespace` 소비자를 따로 둔 새 fixture authority를 상대로 실행했습니다. [#114](https://github.com/novelKR/CodeSpace/pull/114)가 더한 대기도 기록했습니다. `required`의 `kill-during-terminate`에서는 `recovery-reconcile` 검사, 예행, 실행 각각의 round 16개 가운데 6개가 다음 gateway에 새 작업을 보낼 때 거부를 한 번 만났습니다. 그 gateway가 workspace를 대조하는 중이었거나 앞 프로세스를 위해 아직 workspace를 잡고 있었기 때문이며, 각 round는 두 번째 시도에서 첫 시도 뒤 85–102 ms 만에 시작했습니다. `off`의 round는 거부를 만나지 않았습니다. + + + +## gateway 재시작 측정 + +`recovery`는 2026-10-18 16:42 UTC부터 18:52 UTC까지 전체 절차를 실행했습니다. 바로 앞의 예행(16:31–16:42)은 예행이 절차보다 짧다는 사유 하나로만 판정 불가였습니다. 기능 시나리오(16:42–16:46) 뒤에 측정은 10분 유휴 구간과 30분 이상의 부하 구간을 관리 대상으로 세 번 반복했고, 설치된 release를 상대로 DG-1의 cold 부하를 걸었습니다. 구간은 예정된 끝 뒤에 마지막 주기를, 부하는 진행 중인 실행을 마치므로 유휴 구간은 606–607초, 부하 구간은 1,837–1,850초였습니다. 여섯 구간과 세 반복이 모두 통과해 조합이 qualification을 통과했고, 실행 전체도 통과했습니다. 모든 구간은 자원을 차지한 것도 활성 `codespace` 인스턴스도 없는 상태에서 시작했고, 서비스는 모든 반복의 앞뒤에서 프로세스 ID 23023으로 그 release를 실행했습니다. + +유휴 구간마다 gateway를 26번, 부하 구간마다 76번이나 77번 재시작했습니다. 계획은 25초마다 한 번이고, 절차는 그 80%를 요구합니다. gateway는 입력 닫기, SIGTERM, SIGUSR1, SIGKILL로 번갈아 끝냈고 모두 요청한 대로 끝났으며, 다음 gateway는 매번 다음 epoch로 Runner를 Claim하고 오래 실행되는 프로세스가 모두 `running`임을 확인했습니다. 요청을 받은 초는 gateway가 MCP `initialize`에 답한 때부터 요청을 받은 시간이며, 표본은 이 시간을 따릅니다. 나머지는 대부분 gateway가 없는 동안 deadline이 오게 하려고 두는 4초입니다. 시간은 밀리초입니다. + +| 반복 | 구간 | 초 | 요청을 받은 초 | 재시작 (계획) | 입력 닫기, SIGTERM, SIGUSR1, SIGKILL | 시작부터 요청을 받기까지 p50 / 최대 | 판정 | +| ---: | --- | ---: | ---: | ---: | ---: | ---: | --- | +| 1 | 유휴 | 607 | 558 | 26 (24) | 6, 7, 7, 6 | 28.9 / 131.8 | 통과 | +| 1 | 부하 | 1,850 | 1,654 | 76 (72) | 19, 19, 19, 19 | 14.5 / 4,062.6 | 통과 | +| 2 | 유휴 | 607 | 558 | 26 (24) | 6, 7, 7, 6 | 31.2 / 111.4 | 통과 | +| 2 | 부하 | 1,842 | 1,656 | 77 (72) | 19, 20, 19, 19 | 15.2 / 4,117.7 | 통과 | +| 3 | 유휴 | 606 | 557 | 26 (24) | 6, 7, 7, 6 | 29.3 / 88.8 | 통과 | +| 3 | 부하 | 1,837 | 1,658 | 77 (72) | 19, 20, 19, 19 | 15.1 / 1,659.5 | 통과 | + +유휴 구간에서는 새 gateway가 시작한 뒤 132 ms 안에 요청을 받았습니다. 부하 아래에서도 중앙값은 15 ms였지만, 가장 느린 시작은 처음 두 반복에서 4.1초, 셋째 반복에서 1.7초 걸렸습니다. 절차에 이 시간의 목표는 없으므로 판정하지 않고 보고만 합니다. + +| 반복 | 구간 | 다른 프로세스의 요청 (경쟁 gateway) | timeout 대상 (gateway가 없을 때 온 deadline) | deadline 뒤 가장 늦은 끝 | 승인 (SIGKILL 때 재개 진행 중) | 가장 긴 재개 | 태그를 붙인 명령: 시작, 거부 | +| ---: | --- | ---: | ---: | ---: | ---: | ---: | ---: | +| 1 | 유휴 | 405 (27) | 25 (12) | 103.5 | 26 (6) | 269.8 | 56, 20 | +| 1 | 부하 | 1,155 (77) | 75 (35) | 104.1 | 76 (19) | 273.5 | 161, 58 | +| 2 | 유휴 | 405 (27) | 25 (12) | 101.6 | 26 (6) | 279.6 | 56, 18 | +| 2 | 부하 | 1,170 (78) | 76 (38) | 113.0 | 77 (19) | 2,014.1 | 164, 57 | +| 3 | 유휴 | 405 (27) | 25 (12) | 119.4 | 26 (6) | 261.3 | 55, 18 | +| 3 | 부하 | 1,170 (78) | 76 (37) | 103.7 | 77 (19) | 331.1 | 162, 62 | + +재시작할 때마다 다른 프로세스의 Claim은 거부되었고, 이전 epoch, 현재 epoch, epoch 없이 보낸 요청 15개도 각각 `STALE_CONTROLLER`로 거부되어 아무것도 바꾸지 않았습니다. 경쟁 gateway는 모두 writer를 기다린 뒤 Runner에 닿지 않고 포기했습니다. timeout 대상은 모두 `timeout`으로 보고되었고, deadline이 gateway가 있을 때 왔든 없을 때 왔든 원래 deadline 뒤 120 ms 안에 끝났습니다. 절차가 허용하는 것은 2초입니다. 먼저 끝나거나 deadline이 바뀐 대상은 없었습니다. 승인은 모두 다음 gateway가 재개했고, 두 번째 재개는 같은 프로세스를 돌려줬으며, 그 명령은 한 번 실행되었습니다. SIGKILL로 gateway가 끝날 때 재개가 진행 중이던 75개도 마찬가지입니다. 재개는 workspace의 대조를 기다린 시간을 포함해 첫 시도부터 답까지 최대 331 ms 걸렸고, 둘째 반복의 부하 구간에서만 가장 긴 것이 2,014 ms였습니다. 두 번 실행된 태그를 붙인 명령은 없었고, 거부된 명령이 실행된 적도 없습니다. + +제어 경로의 목표는 gateway가 요청을 받은 시간에 대해 `process_status` p99 500 ms 이하와 종료 확인 응답 p99 1초 이하입니다. 다른 시간은 정해진 목표가 없어 보고만 합니다. 종료처럼 표본이 100개보다 적으면 nearest-rank p99는 가장 큰 값입니다. 일정은 gateway가 요청을 받은 시간을 따르므로 표본 수는 일정보다 조금 많거나 적을 수 있습니다. + +| 반복 | 구간 | status p99 / 최대 | status 표본 / 일정 | 종료 확인 p99 | 종료 / 일정 | scope 소멸 p99 | 해제 p99 | I/O p99 | 거부 p99 | +| ---: | --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | +| 1 | 유휴 | 3.283 / 7.786 | 1,120 / 1,116 | 0.540 | 27 / 27 | 79.1 | 188.6 | 22.942 | 49.405 | +| 1 | 부하 | 1.882 / 41.516 | 3,297 / 3,306 | 0.487 | 82 / 82 | 92.6 | 195.3 | 2.390 | 48.449 | +| 2 | 유휴 | 3.137 / 9.269 | 1,116 / 1,114 | 0.739 | 27 / 27 | 77.5 | 185.7 | 6.239 | 46.620 | +| 2 | 부하 | 1.475 / 누락 | 3,312 / 3,312 | 0.508 | 84 / 82 | 66.7 | 182.2 | 17.081 | 47.541 | +| 3 | 유휴 | 2.651 / 8.858 | 1,116 / 1,114 | 0.543 | 26 / 27 | 65.6 | 161.2 | 5.523 | 41.619 | +| 3 | 부하 | 1.497 / 15.581 | 3,316 / 3,314 | 0.486 | 82 / 82 | 76.8 | 184.3 | 2.640 | 53.347 | + +둘째 반복의 부하 구간에서는 각 status 대상의 일정을 하나씩 놓쳤습니다. 앞 표본이 아직 실행 중일 때 그 시각이 지나갔으므로, DevGuard 절차가 세는 대로 누락된 표본으로 셉니다. 누락된 표본은 모든 응답보다 높은 순위이므로 그 구간의 최대는 누락으로 표시되며, p99는 1.475 ms였습니다. 오류이거나 이미 실행 중이 아닌 대상에 대한 status 표본은 없었습니다. 확인된 종료는 모두 대상을 끝냈고 해제되었으며, 각 status 대상의 마지막 종료도 효과가 있었습니다. I/O 줄은 모두 돌아왔고 가장 느린 것은 506 ms였습니다. 거부는 모두 `RESOURCE_UNAVAILABLE`이었고, 거부되어야 할 요청이 허용된 적은 없습니다. 종료 대상 두 개와 거부 표본 두 개는 막 시작한 gateway에게서 `RECOVERY_PENDING`을 한 번 받고, 절차가 허용하는 대로 다시 보내졌습니다. 첫째 반복의 유휴 구간과 셋째 반복의 부하 구간에서 각각 하나씩입니다. MCP 연결이 끊긴 표본은 없었습니다. + +두 출력 흐름은 모든 gateway를 거쳐 읽었습니다. PTY의 번호 붙은 줄은 건너뛰거나 되풀이한 것 없이 모두 왔습니다. pipe의 40,000줄 한꺼번에 쓰기는 매번 gateway가 읽지 않는 동안 일어났고, 그때마다 Runner는 256 KiB ring을 넘는 22,524줄에 공백(`ring_overflow`)을 알렸으며, 나머지 17,476줄은 모두 왔습니다. 알린 공백 밖에서 잃거나 되풀이한 줄은 없었습니다. + +| 반복 | 구간 | PTY에서 읽은 줄 | pipe에서 읽은 줄 | Runner가 알린 공백 | 알린 공백의 줄 | +| ---: | --- | ---: | ---: | ---: | ---: | +| 1 | 유휴 | 2,825 | 52,428 | 3 | 67,572 | +| 1 | 부하 | 8,795 | 157,284 | 9 | 202,716 | +| 2 | 유휴 | 2,825 | 52,428 | 3 | 67,572 | +| 2 | 부하 | 8,756 | 174,760 | 10 | 225,240 | +| 3 | 유휴 | 2,822 | 52,428 | 3 | 67,572 | +| 3 | 부하 | 8,765 | 174,760 | 10 | 225,240 | + +**Launch 기록.** 실행의 gateway가 기록하거나 표본이 가리킨 attempt 967개가 모두 DevGuard journal에 있었습니다. 두 번 launch된 attempt, 다시 허용된 불확실한 attempt, 자원을 차지한 채 남은 것은 없었습니다. journal은 유휴 구간마다 4개, 부하 구간마다 13개나 14개의 launch를 gateway 로그보다 많이 기록했습니다. gateway가 기록하기 전에 죽으면, Runner가 그 gateway를 위해 한 launch는 journal에는 있고 어느 gateway 로그에도 없습니다. 이 수는 보고만 하고 중복으로 세지 않으며, 태그를 붙인 명령이 실행될 때마다 한 줄씩 쓰는 marker 파일이 두 번 실행된 명령이 없음을 보여 줍니다. + +**부하.** DG-1의 소비자 여섯은 부하 구간 세 개에서 583번(구간마다 193–196번) 실행했고 모두 성공했습니다. 0이 아닌 종료, 시작 실패, 불확실한 실행은 없었고, 허용된 부하가 각 부하 구간의 99.99%를 덮었습니다. 모든 소비자가 모든 구간에서 실행했습니다. `cargo`는 19–21번, `cpu`와 `memory`는 각각 15번, `io`는 58번, `output`은 58–59번, `slow-input`은 28번입니다. + +**호스트와 사용량.** 모든 구간의 호스트 표본이 메모리 pressure 수준 1(정상)이었고, swap은 최대 1,378 MiB, load average는 둘째 반복의 부하 구간에서 최대 7.92였습니다. 이 모드에서 모든 실행을 소유하고 `codespace` 소비자의 인스턴스로 등록하는 Runner는 모든 구간에서 소비자의 예약 150 mCPU, 512 MiB, 40 tasks 안에 있었습니다. 최대값은 RSS 15.25 MiB와 thread 15개였고, CPU 평균은 최대 2.9 mCPU였습니다. 각 gateway는 최대 21.3 MiB와 thread 12개, CPU 평균 최대 4.9 mCPU였습니다. 이 비교는 판정하지 않고 보고만 합니다. + +**증거 writer.** 하네스의 원시 증거 writer는 유휴 구간에서 최대 33 ms, 부하 구간에서 최대 6.7초 늦었습니다. 그동안 DG-1의 cold 빌드가 증거 디스크를 함께 썼습니다. [#115](https://github.com/novelKR/CodeSpace/pull/115) 뒤로 각 gateway의 로그는 writer가 저장하기 전에 분석하며, 모든 종료 표본을 DevGuard가 해제할 때까지 지켜봤습니다. + +**예행과 그 뒤.** 예행의 두 구간(유휴 127초와 부하 216초, 재시작 5번과 7번)도 같은 0의 기록으로 통과했고, status p99는 최대 6.329 ms, 종료 확인 응답 p99는 최대 0.916 ms였습니다. 실행이 끝났을 때 마지막 구간의 Runner는 drain된 상태였고, 자원을 차지한 것도 lease도 없었으며, DevGuard journal의 `codespace` 인스턴스는 모두 retire되었고, 서비스는 프로세스 ID 23023으로 그 release를 계속 실행했습니다. 예행과 실행 동안 서비스는 재시작하지 않았습니다. + + + +## 완료 증거 + +절차의 [완료 증거](recovery-qualification.md#recovery-completion)와, 후보에서 한 실행이 보여 준 것입니다. + +| P1-RECOVERY가 보여야 하는 것 | 보여 주는 곳 | 결과 | +| --- | --- | --- | +| 재시작 뒤 같은 실행에 다시 닿음 | 측정의 재시작 308번. 매번 다음 gateway가 오래 실행되는 프로세스가 모두 `running`임을 확인하고 두 출력 흐름을 계속 읽음. 생명주기 `exit-kinds`, fence `stale-epoch` | 보임: 잃은 오래 실행되는 프로세스가 없고, Runner가 알린 공백 밖에서 잃거나 되풀이한 줄이 없음 | +| 두 번 실행된 실행 없음 | 측정의 승인 308개, 시작한 태그를 붙인 명령 654개와 거부된 233개, gateway 로그와 대조한 DevGuard journal. 대조 `approval`, `kill-during-exec`, `kill-during-terminate` | 보임: 명령마다 한 번 실행되었고, 거부된 명령은 실행되지 않았으며, 두 번 launch된 attempt가 없음 | +| stale mutation 없음 | 주기마다 다른 프로세스의 요청 15개(모두 4,710개)와 경쟁 gateway(모두 314개). fence 스위트 | 보임: 요청은 모두 stale로 거부되어 아무것도 바꾸지 않았고, Runner에 닿은 경쟁 gateway가 없음 | +| timeout 연장 없음 | 측정의 timeout 대상 302개(그중 146개는 gateway가 없는 동안 deadline이 옴). 생명주기 `original-timeout` | 보임: 연장되거나 먼저 끝나거나 잘못 보고된 대상이 없고, 가장 늦은 것도 deadline 뒤 119 ms에 끝남 | +| 명확한 공백 | 절차의 [공백](recovery-qualification.md#recovery-gaps) | `recovery-observation` 검사, 예행, 실행에 이유와 함께 실행하지 않음으로 기록 | +| 기존 모드가 계속 통과 | [회귀](#recovery-report-regressions) | 보임: 후보에서 CS-RG 스위트 네 개, 그 commit의 CI | +| 지원 모드 | 위의 [주장](#recovery-report-claims) | macOS `off`와 `required`는 qualification 통과, Linux `off`는 CI 기능 확인일 뿐, Linux `required`는 지원하지 않음 | + + + +## 회귀 + +실행이 끝났을 때 마지막 Runner는 drain된 상태였고 DevGuard에 활성 `codespace` 인스턴스도 차지한 자원도 없었으므로, CS-RG 스위트가 자기 gateway를 등록할 수 있었습니다. CS-RG 스위트는 2026-10-19 02:22 UTC부터 02:31 UTC까지 qualification 호스트에서 같은 후보로, 바이너리를 빌드한 세션에서 그 세션의 열린 파일 한도 1,048,576 아래에서 실행했습니다. 그러므로 이 실행은 터미널의 한도 256에 대해서는 아무것도 보여 주지 않습니다. 모두 통과했고 확인되었습니다. + +| 스위트 | 시각(UTC) | 결과 | CS-RG의 후보 `ca306ee`에서 | +| --- | --- | --- | --- | +| `candidate` | 02:22 | 통과. 기록은 고정한 기록과 기록한 시각만 다름 | 통과 | +| `matrix` | 02:22–02:23 | 통과: 시나리오 45개 가운데 45개(`in-process` 22개, `uds` 23개), 검사 210개와 213개 | 통과: 45개 가운데 45개 | +| `faults` | 02:23–02:26 | 통과: 사례 27개 가운데 27개(13개와 14개), 검사 83개와 89개 | 통과: 27개 가운데 27개 | +| `authority` | 02:27–02:31 | 통과: 단계 10개 가운데 10개, MCP 연결이 끊긴 표본 없음 | 통과: 10개 가운데 10개 | + +그 뒤 자원을 차지한 것도 활성 `codespace` 인스턴스도 없었고, 서비스는 프로세스 ID 23023으로 그 release를 계속 실행했습니다. + +후보 commit의 CI는 두 번 실행되었고 두 번 모두 모든 작업이 통과했습니다. [#115](https://github.com/novelKR/CodeSpace/pull/115)를 병합한 뒤의 push 실행([실행 39114783991](https://github.com/novelKR/CodeSpace/actions/runs/39114783991), 2026-10-18 09:18–09:31 UTC)과 전체 프로필의 예약 실행([실행 39169645390](https://github.com/novelKR/CodeSpace/actions/runs/39169645390), 21:51–22:02 UTC)입니다. 각 실행은 형식과 pin 검사, 정책 검사, Clippy 빌드 세 개와 단위 단계 일곱 개, Ubuntu의 `integration`과 `linux-isolation`, 호스팅된 macOS runner의 `macos-core`, `macos-fork-race`, `dependencies`를 실행했습니다. 이것이 독립 Runner 시험을 포함한 Linux와 macOS의 회귀 단계이며, Linux에서는 위에서 [주장](#recovery-report-claims)한 `off`의 기능 확인입니다. + + + +## 수정 기록 + +후보는 네 번 고정했습니다. 처음 세 번은 각각 그 후보에서 검사 스위트 다섯 개가 통과한 뒤 고정했고, 각각 나중에 하네스의 결함으로 실패했으며 CodeSpace나 DevGuard의 결함은 없었습니다. 고칠 때마다 절차와 함께 고정한 하네스가 바뀌었으므로 새 후보가 되었고, 실행은 검사부터 다시 했습니다. 이전 후보의 실행은 모두 보존했으며 [증거](#recovery-report-evidence)에 있습니다. + +- **열린 파일 한도, `b2cb042`(정정).** 첫 후보는 [#112](https://github.com/novelKR/CodeSpace/pull/112)의 병합이었습니다. 검사 스위트 다섯 개는 열린 파일 한도가 높은 세션에서 통과했지만, 기본 한도가 256인 터미널에서 실행한 예행은 `harness_failed`로 실패했습니다. 하네스가 gateway마다 pipe와 transcript를 열어 둔 채였기 때문에, 27번째 기능 시나리오부터는 하네스 자체에서 `Too many open files`로 모두 실패했고, 부하 구간의 drain은 journal을 열지 못했습니다. [#113](https://github.com/novelKR/CodeSpace/pull/113)이 더는 읽지 않는 것을 하네스가 닫게 해, 예행 전체에서 열린 파일이 많아야 43개가 되었고, 모든 보고서가 실행한 한도를 기록합니다. 다음 후보 `41f83b6`은 한도 256에서 검사를 통과했습니다. +- **재시작 직후의 경쟁, `41f83b6`(정정).** 전체 실행이 gateway의 처음 몇 밀리초에 생기는 하네스 경쟁 세 가지로 실패했습니다. 그동안 gateway는 제품 문서대로 새 작업을 거부하고 있었습니다. gateway의 Claim 5 ms 뒤, 표본의 workspace를 대조하기 전에 보낸 종료 표본이 `RECOVERY_PENDING`으로 거부되어 오류로 셌습니다. `required`의 대조 `approval`에서는 두 round가 새 gateway가 workspace를 대조하기 전에 실행을 잡아 두었습니다. `required` pipe의 `kill-during-terminate`에서는 `process_status`가 앞 프로세스가 끝났다고 보고한 지 1 ms도 안 되어 새 작업이 `WORKSPACE_BUSY`로 거부되었습니다. 두 번 실행된 명령은 없었습니다. [#114](https://github.com/novelKR/CodeSpace/pull/114)가 이런 거부를 만나는 동안 하네스가 그 작업을 다시 보내게 했고(지금 절차의 [기능 스위트](recovery-qualification.md#recovery-functional)와 [구간](recovery-qualification.md#recovery-intervals)에 적혀 있음), 표본과 round마다 기다린 시간을 기록하게 했습니다. +- **바쁜 디스크에서의 gateway 로그, `b5ddd59`(정정).** 전체 실행이 둘째 부하 구간의 종료 표본 84개 가운데 하나를 하네스가 지켜보지 못해 실패했습니다. 하네스가 gateway 로그를 읽으면서 각 줄을 분석하기 전에 증거에 먼저 썼는데, DG-1의 cold 빌드가 증거 디스크를 바쁘게 하는 동안 attempt ID가 표본의 5초 대기를 넘겨 도착했습니다. DevGuard는 그 attempt를 `scope_terminated`로 해제했습니다. [#115](https://github.com/novelKR/CodeSpace/pull/115)가 각 줄을 먼저 분석하고 로그는 별도 스레드에서 쓰게 했고, 지금 [절차](recovery-qualification.md#recovery-running)에 그렇게 적혀 있습니다. 로그를 쓰지 못하면 그 구간은 판정 불가입니다. `1c0eadd`에서도 부하 아래 writer는 최대 6.7초 늦었지만 놓친 launch 줄은 없었습니다. +- **검사와 고정을 실행한 사람(보완).** `41f83b6`에서 qualification 호스트를 다루는 세션은 `candidate`를 실행하지 못했습니다. 그 안전 검사가 명령을 거부했습니다. 그 뒤로는 소유자가 검사 스위트, `candidate`, 예행, 측정을 터미널에서 검토한 명령 하나로 실행했습니다. 이 명령은 앞 단계가 깨끗할 때만 다음 단계를 시작했고, 예행은 기록이 검사한 조합과 일치할 때만 시작했습니다. 대조한 것은 commit, code tree, 바이너리 다섯 개, release, 정책 fingerprint, DG-1의 정책, 메모리 pressure입니다. 절차의 순서는 바뀌지 않았습니다. +- **`WORKSPACE_BUSY`가 이어지는 시간(참고).** 이 모드에서 gateway의 안내문은 실행 중으로 확인된 프로세스가 workspace를 다시 잡으므로, 그 프로세스가 "끝나거나 종료될 때까지" 그 workspace의 새 작업은 `WORKSPACE_BUSY`를 돌려준다고 말합니다. 실제로 workspace는 프로세스의 출력이 닫힌 뒤 Runner의 종료 이벤트가 올 때 풀리며, 이는 `process_status`가 처음 프로세스가 끝났다고 보고한 때보다 늦을 수 있습니다. 이 거부 뒤에 새 작업을 다시 보내는 클라이언트는, 지금 하네스처럼, 영향을 받지 않습니다. 이 보고서는 문서만 바꾸므로 안내문은 여기서 바꾸지 않았고, 정확하게 고치는 일은 별도 변경입니다. + + + +## 증거 + +증거 디렉터리는 qualification 호스트의 DevGuard checkout 아래 `evidence/` 디렉터리에 있습니다. 각 디렉터리는 모든 파일의 SHA-256을 담은 `MANIFEST.json`으로 봉인했고, 파일은 읽기 전용입니다. 어떤 디렉터리에도 자격 증명, 자격 증명의 digest, 자식 프로세스의 환경은 없습니다. 디렉터리는 그 `MANIFEST.json`의 SHA-256이 아래와 같고 `qualify-devguard.py verify`가 받아들이면 확인된 것입니다. 모든 실행의 콘솔 출력과 `verify` 출력은 그 디렉터리 옆에 보관했습니다. + +| 디렉터리 | 실행 | `MANIFEST.json` SHA-256 | +| --- | --- | --- | +| `p1r-c06-check-recovery-lifecycle-2026-10-18-1c0eadd` | `recovery-lifecycle` | `ea8f534f434080e9eb33909a8b4aebb98bd6c3791996fb8ca424c0307d0b1da3` | +| `p1r-c06-check-recovery-store-2026-10-18-1c0eadd` | `recovery-store` | `45123bc349de4341202cfdbcfce19ee182584ad4f1272b8d131d3b276b506197` | +| `p1r-c06-check-recovery-fence-2026-10-18-1c0eadd` | `recovery-fence` | `299e646f77497f7ae8a2d7c58f92b89ea59499f562d86ca648d64f3d9675e6fb` | +| `p1r-c06-check-recovery-reconcile-2026-10-18-1c0eadd` | `recovery-reconcile` | `a176626961017483061f4d4a9334ebf4811037782170084697c43bbf66d4164c` | +| `p1r-c06-check-recovery-observation-2026-10-18-1c0eadd` | `recovery-observation` | `37fcf791b8bfca09bf1c805141ee7108a44448a790551611e2c62699a6ffa407` | +| `p1r-c06-candidate-2026-10-18-1c0eadd` | `candidate` | `c7b26e3dc118e4b07b27255e038dcb02d2e1b411ef073d6027754f60fb4622ad` | +| `p1r-c06-recovery-rehearsal-2026-10-18-1c0eadd` | `recovery --rehearsal` | `ef5ab907a9fbe03fa737b2b23d0f2023e64b36685b466248465a32a5a0880894` | +| `p1r-c06-recovery-2026-10-18-1c0eadd` | `recovery` | `1f6f7ba8e4694d4aba6aa371db2edaeeb6fc0b0d7812dd17847444d1e06f790b` | +| `p1r-c06-qual-candidate-2026-10-19-1c0eadd` | CS-RG의 `candidate` | `caff08ef246a57f5e673e7b54a41fd3d99376b3c14d12d463270911c2750898f` | +| `p1r-c06-qual-matrix-2026-10-19-1c0eadd` | `matrix` | `33443fce5b6d0261e74cfe77a317a7de3bcbd019836331c3a63cefca40e2011f` | +| `p1r-c06-qual-faults-2026-10-19-1c0eadd` | `faults` | `bb29b613eff8df0c8df8108f0d1ba953b5cc9298a2c79cafbee6f4a280b55936` | +| `p1r-c06-qual-authority-2026-10-19-1c0eadd` | `authority` | `7cebeacfb92cb39b6956607b9cc2e80663102ce307152cf438a7bb4229bfbb65` | + +이전 후보의 실행도 보존했습니다. 각각 실행할 때 자신의 하네스로 확인했고, 2026-10-19에 후보의 하네스로 실행한 `verify`도 모두 다시 받아들였습니다. `harness_failed`로 기록된 `b2cb042`의 예행은 다시 계산하면 실패입니다. + +| 디렉터리 | 결과 | `MANIFEST.json` SHA-256 | +| --- | --- | --- | +| `p1r-c06-check-recovery-lifecycle-2026-10-17-b2cb042` | 통과 | `b401d3c878d95148f2aa3f42d0b6013fa0a5e0a723c2f0acf352809e5d5b9ab6` | +| `p1r-c06-check-recovery-store-2026-10-17-b2cb042` | 통과 | `c0aaca7ed5f5c917ec5711c4f8b435a8fb49b616c284ddef8298c990974c7b0a` | +| `p1r-c06-check-recovery-fence-2026-10-17-b2cb042` | 통과 | `5efb6bf4d9830fbbe50cbce31a0ac0f570ec3a67787eb9b481a18a42063dc778` | +| `p1r-c06-check-recovery-reconcile-2026-10-17-b2cb042` | 통과 | `d70cd660821dd558c75d0fa5a8f9c5a97029c8147b28cd446335112077a59193` | +| `p1r-c06-check-recovery-observation-2026-10-17-b2cb042` | 통과 | `971fc90242a11cbd4d9db405d26f6e79ecf12f41ddf6ad3b415f3655383f52d9` | +| `p1r-c06-candidate-2026-10-17-b2cb042` | 통과 | `15c6735be844292b3f84ad74ce20a534d1ec6b9d9bdc38ab7988f98d971f87c7` | +| `p1r-c06-recovery-rehearsal-2026-10-17-b2cb042` | `harness_failed`: 열린 파일 한도 | `5f58673c69f35c993852953daf4d5a09ff0dbaf2c857a7fca343aeb507fac631` | +| `p1r-c06-check-recovery-lifecycle-2026-10-17-41f83b6` | 통과 | `0614d740f7eef0a96941e6c36ec974666b22a301f3f958e0642f29d05a6efb62` | +| `p1r-c06-check-recovery-store-2026-10-17-41f83b6` | 통과 | `395b4c051b483c3e9609bf3aaee4ecfbb19df82ad3ae213156fa652fc3cddfe8` | +| `p1r-c06-check-recovery-fence-2026-10-17-41f83b6` | 통과 | `f81578c3398efefda24a9f2758b53499d5ebb969d93b80800825ba5218824271` | +| `p1r-c06-check-recovery-reconcile-2026-10-17-41f83b6` | 통과 | `9ad23449bb1bc765f99b937ee9b7ad8716b8864f6ee72a97e3b2d53e0d54dfa1` | +| `p1r-c06-check-recovery-observation-2026-10-17-41f83b6` | 통과 | `66434755ffe5ffb3aa0c60fb28c4397f188d0ff6bd0fdc91bacd92813c3a8921` | +| `p1r-c06-candidate-2026-10-17-41f83b6` | 통과 | `37ed6aa3967f25602dc00f7ce7c40bb8b773ecea102329e2c50ccdb5c94b4fdc` | +| `p1r-c06-recovery-rehearsal-2026-10-17-41f83b6` | 판정 불가(설계대로) | `aad4c41e36b6d6ddcffe76e24de32fd013f765d781c3de7e70ae5c1e28cfefd6` | +| `p1r-c06-recovery-2026-10-17-41f83b6` | 실패: 재시작 직후의 경쟁 | `1b2b57fbea66ae3a08b78dcd76d86f992dc4709a21e22a51343b97cf0f0b22a2` | +| `p1r-c06-check-recovery-lifecycle-2026-10-18-b5ddd59` | 통과 | `221a31cd238d47ec57190b1355994f473c7fa2592185752bcc81ae647c9ff501` | +| `p1r-c06-check-recovery-store-2026-10-18-b5ddd59` | 통과 | `b8533e38c2a663dac17dd23e9789d0e30aec9c02cb65870d1bd1cf4800c0991b` | +| `p1r-c06-check-recovery-fence-2026-10-18-b5ddd59` | 통과 | `9109c738e3f1e4e0279368a37fcefd6b4ef9a86414dbe7b0921ea6b16ebe4048` | +| `p1r-c06-check-recovery-reconcile-2026-10-18-b5ddd59` | 통과 | `2f07f807876c13f1b7cf63120bb6e92763e19de5a9d5cebeaa3d5e753c60dfb8` | +| `p1r-c06-check-recovery-observation-2026-10-18-b5ddd59` | 통과 | `bec8ab11383407e43fa041ffdfe163c606ecbf8ad996d1738ac0a93b48b37f50` | +| `p1r-c06-candidate-2026-10-18-b5ddd59` | 통과 | `1fe4928c3666310f83da9914985627dcc9fb72ff8202d1b7ae5d95b5b7d2abf9` | +| `p1r-c06-recovery-rehearsal-2026-10-18-b5ddd59` | 판정 불가(설계대로) | `5b2237d8c70037cd6ef1a1fa8e346063a4cf43363a3b62a99c4ca603756f1226` | +| `p1r-c06-recovery-2026-10-18-b5ddd59` | 실패: 바쁜 디스크에서의 gateway 로그 | `dbdee96032d7d715a56a729cb1b6b97ace2901bc93f834e9fdf85e2dc7945f02` | + + + +## P1-RECOVERY 밖에 남는 것 + +P1-RECOVERY는 여기서 끝납니다. 다음은 그 밖에 남으며, 이 문서는 어느 것도 주장하지 않습니다. + +- **Runner 자신이 재시작한 뒤 프로세스 복원.** gateway는 잃은 Runner 자리에 새 Runner를 받아들이지 않으며, 잃은 Runner가 실행하던 것은 운영자가 정리합니다([Runner 손실](operations.md#runner-loss)). +- **독립 모드를 기본값으로 바꾸는 일.** DevGuard ADR-002에 따른 별도 결정입니다. +- **DG-LINUX**, Linux에서의 강제. 이것이 qualification을 통과하기 전까지 Linux에서 `required`는 거부되고, `off`는 Linux에서 기능 확인만 합니다. +- **DevGuard release나 정책의 변경.** 예를 들어 `codespace` 소비자에 인스턴스를 둘 이상 두는 것(결정 D12)이며, 그러면 DG-1을 다시 측정해야 합니다. +- **다른 조합.** 다른 DevGuard client pin, release, 정책, 호스트, 코드 변경, 다른 도구는 새 후보이며 자기 실행이 필요합니다. +- **실행이 보이지 않은 것:** 실제 호스트 재시작, DG-1의 warm 부하, runner wire에서의 메시지 재전송, 이 후보에서의 CS-RG 제어 경로 측정. diff --git a/docs/ko/recovery-qualification.md b/docs/ko/recovery-qualification.md index 34d5539..e793b3a 100644 --- a/docs/ko/recovery-qualification.md +++ b/docs/ko/recovery-qualification.md @@ -6,7 +6,7 @@ 이 문서는 Gateway 복구(P1-RECOVERY)의 opt-in 모드인 [독립 Runner](runner-isolation.md#independent-runner)를 qualification하는 절차입니다. 어떤 조합을 지원한다고 주장하는지, 각 주장을 어떻게 시험하고 측정하는지, 무엇이 통과인지, 실행이 어떤 증거를 남기는지를 정합니다. [CS-RG qualification 절차](cs-rg-qualification.md)를 따르며, 그 후보 조합, 증거, 확인 방식을 그대로 쓰고, 하네스도 같은 [`scripts/qualify-devguard.py`](../../scripts/qualify-devguard.py)입니다. 이 절차는 첫 qualification 실행 전에 하네스와 함께 고정합니다. 첫 실행 뒤에 둘 중 하나라도 바꾸면 새 절차가 되며, 다른 변경처럼 검토하고 영향받는 실행은 다시 합니다. 각 단위가 무엇을 만들었는지는 [결합 로드맵](devguard-integration.md#resource-gateway-recovery)에 있고, 이 문서는 그 결과가 qualification을 통과하는지를 판단합니다. -**현재 상태:** 첫 qualification 실행 전에 하네스와 함께 고정했습니다. 아직 통과한 조합이 없으므로, 독립 Runner는 어디에서도 지원하는 모드가 아닙니다. +**현재 상태:** 첫 qualification 실행 전에 하네스와 함께 고정했습니다([#112](https://github.com/novelKR/CodeSpace/pull/112)). 통과한 실행이 나오기 전에 하네스 결함 세 개를 고쳤고([#113](https://github.com/novelKR/CodeSpace/pull/113), [#114](https://github.com/novelKR/CodeSpace/pull/114), [#115](https://github.com/novelKR/CodeSpace/pull/115)), 고칠 때마다 새 절차와 새 후보가 되어 실행을 다시 했습니다. 후보 `1c0eadd`의 실행이 통과해, 독립 Runner는 그 조합으로 qualification을 마쳤습니다. 결과와 증거는 [qualification 보고서](recovery-qualification-report.md)에 있고, 수정 내용은 그 [수정 기록](recovery-qualification-report.md#recovery-report-amendments)에 있습니다. diff --git a/docs/ko/runner-isolation.md b/docs/ko/runner-isolation.md index af6690a..2cf6e44 100644 --- a/docs/ko/runner-isolation.md +++ b/docs/ko/runner-isolation.md @@ -23,7 +23,7 @@ ## 독립 Runner -> **qualification을 통과한 곳에서만 지원합니다.** 독립 Runner와 그 실행의 기록, 한 게이트웨이에서 다음 게이트웨이로 제어권을 넘기는 Claim, 그리고 Claim마다 뒤따르는 대조가 게이트웨이 복구를 이룹니다. 게이트웨이는 연결을 잃으면 Runner를 다시 Claim하고, 이전 controller가 작업을 시작한 곳에서는 그 controller가 남긴 것을 대조한 뒤에야 새 작업을 시작합니다. Runner 자체를 잃으면 게이트웨이는 그 실행들에 대해 기록으로 답하고, 운영자가 풀 때까지 그 workspace를 점유한 채로 둡니다([Runner 손실](#runner-loss)). 이 모드는 [qualification](recovery-qualification.md)이 통과시킨 조합에서만 지원하며, 아직 통과한 조합은 없습니다. 도입하고 되돌리는 방법은 [운영](operations.md#independent-runner-adoption)에 있습니다. +> **qualification을 통과한 곳에서만 지원합니다.** 독립 Runner와 그 실행의 기록, 한 게이트웨이에서 다음 게이트웨이로 제어권을 넘기는 Claim, 그리고 Claim마다 뒤따르는 대조가 게이트웨이 복구를 이룹니다. 게이트웨이는 연결을 잃으면 Runner를 다시 Claim하고, 이전 controller가 작업을 시작한 곳에서는 그 controller가 남긴 것을 대조한 뒤에야 새 작업을 시작합니다. Runner 자체를 잃으면 게이트웨이는 그 실행들에 대해 기록으로 답하고, 운영자가 풀 때까지 그 workspace를 점유한 채로 둡니다([Runner 손실](#runner-loss)). 이 모드는 [qualification](recovery-qualification.md)이 통과시킨 조합에서만 지원합니다. 그 조합은 macOS에서 [qualification 보고서](recovery-qualification-report.md#recovery-report-combination)가 기록한 하나뿐입니다. 도입하고 되돌리는 방법은 [운영](operations.md#independent-runner-adoption)에 있습니다. 독립 Runner는 자신에게 연결하는 게이트웨이보다 오래 삽니다. 게이트웨이가 종료되거나 재시작되거나 강제로 종료되어도 프로세스와 그 출력, timeout은 계속됩니다. 운영자나 하네스가 독립 Runner를 시작하고, 게이트웨이는 연결만 합니다. diff --git a/docs/operations.md b/docs/operations.md index 6d0396d..0b17110 100644 --- a/docs/operations.md +++ b/docs/operations.md @@ -137,7 +137,7 @@ After losing a patch response, query `operation_status` with exactly one of `ope ## Independent Runner adoption and rollback -The [independent Runner](runner-isolation.md#independent-runner) is supported only on the combinations that its [qualification](recovery-qualification.md) qualifies, and none is qualified yet. It is opt-in, and the default stays `in-process`. Adopt it only on a qualified combination, with the CodeSpace build, the DevGuard release and the policy that were qualified: anything else is a new candidate. +The [independent Runner](runner-isolation.md#independent-runner) is supported only on the combinations that its [qualification](recovery-qualification.md) qualifies: on macOS, the one combination that the [qualification report](recovery-qualification-report.md#recovery-report-combination) records. It is opt-in, and the default stays `in-process`. Adopt it only on a qualified combination, with the CodeSpace build, the DevGuard release and the policy that were qualified: anything else is a new candidate. 1. **Prepare** a state directory outside every workspace, with an operations database that no CodeSpace version from before the process records will open ([schema version](#operations-database)), and a socket directory of mode 0700 for the Runner. With DevGuard, the Runner is the execution owner and registers as the `codespace` consumer's one instance, so stop any other gateway that registers as that consumer first. 2. **Start the Runner** in its own session, as the qualification starts it, so that neither your terminal nor any gateway's end reaches it. On Linux `setsid` does this; the Python line below does it on macOS and Linux alike. With DevGuard, give it the consumer settings of the [status-only connection](devguard-integration.md#devguard-status-connection) and the release's `devguard-launch`; without DevGuard, leave out the `--devguard-*` settings, and `required` workspaces are refused. diff --git a/docs/recovery-qualification-report.md b/docs/recovery-qualification-report.md new file mode 100644 index 0000000..1ffb298 --- /dev/null +++ b/docs/recovery-qualification-report.md @@ -0,0 +1,253 @@ + + +# Gateway recovery qualification report + +[English](recovery-qualification-report.md) | [한국어](ko/recovery-qualification-report.md) + +This page reports the qualification of Gateway recovery (P1-RECOVERY) by unit P1R-C06, under the [Gateway recovery qualification protocol](recovery-qualification.md). The protocol says what a run must show; this page records what the runs on the frozen candidate showed and where their evidence is. Counts, durations and percentiles are what these runs observed on one host. They are not failure probabilities. + +**Result:** the [independent Runner](runner-isolation.md#independent-runner) is qualified on macOS for the [combination](#recovery-report-combination) below: `uds` with `--runner-lifecycle independent` and a Runner the operator starts, with pipe and PTY executions, participations `off` and `required`, and both in one gateway. Linux `off` is CI functional verification only, Linux `required` stays unsupported until DG-LINUX, and restoring processes after the Runner itself restarts is unsupported, as [claimed](#recovery-report-claims) below. On the same candidate, the modes that CS-RG qualified passed CS-RG's suites again. The independent mode stays opt-in, and making it the default is a separate decision. How an operator adopts it and rolls it back is in [adoption and rollback](operations.md#independent-runner-adoption). + + + +## What is claimed + +The levels are those of the [CS-RG report](cs-rg-qualification-report.md#report-claims): **implemented**, merged in CodeSpace's main branch; **feature-verified**, shown by tests with fakes, scripted workers or DevGuard's fixture authority, in CodeSpace's tests or CI; **platform-verified**, shown on macOS against DevGuard's own components by a suite of the protocol; **qualified**, the exact combination passed every suite that the protocol requires for the claim, and its sealed evidence was verified and reviewed. + +| Mode or behavior | Level | Shown by | +| --- | --- | --- | +| macOS, `uds` with `--runner-lifecycle independent`, pipe and PTY, `required` | qualified | the [functional suites](#recovery-report-functional) with `required`, the [restart measurement](#recovery-report-measurement), the [regressions](#recovery-report-regressions) | +| macOS, the same, `off` | qualified | the functional suites with `off`; the measurement's ungoverned workspaces | +| macOS, `off` and `required` workspaces in one gateway | qualified | the measurement, whose every gateway serves governed and ungoverned processes | +| Linux, the same, `off` | feature-verified: CI functional tests only, never a Linux qualification (decision D7) | CodeSpace's tests of the independent Runner in Linux CI on the candidate's commit; the harness's 34 `off` scenarios also passed on Linux with the candidate's harness and code ([#115](https://github.com/novelKR/CodeSpace/pull/115)) | +| Linux, the same, `required` | unsupported until DG-LINUX | refused before anything runs, as every Linux `required` execution is: DevGuard has no native host evidence on Linux, so no execution owner registers there, as CS-RG's [Linux CI](cs-rg-qualification-report.md#report-regressions) shows | +| Restoring processes after the Runner itself restarts | unsupported, outside P1-RECOVERY; the refusal is platform-verified | observation `runner-replaced` | +| A host restart | feature-verified; a real restart is not qualified | CodeSpace's tests give a record another boot ID, which is reported `host_rebooted` | +| Another process of the same user claiming the Runner while no gateway controls it | possible by design (decision D6); that it is logged and fenced is platform-verified | fence `stale-epoch` | +| Output beyond the Runner's 256 KiB ring while no gateway reads | lost by design; that the loss is declared is platform-verified | observation `ring-overflow`; the measurement's bursts | +| A PTY process after the Runner's loss | its root is not recorded, so the operator ends it before releasing its record; platform-verified | observation `runner-loss` on a PTY | +| A replayed preparation or launch message on the runner wire | not tested, a known gap | epoch fencing refuses requests from an earlier controller (fence suite) | +| Recovery under DG-1's warm load | not measured | the measurement uses the cold load | +| The modes CS-RG qualified | passed CS-RG's suites again on this candidate; CS-RG's own qualified combination stays [`ca306ee`](cs-rg-qualification-report.md#report-combination) | [regressions](#recovery-report-regressions) | + + + +## The qualified combination + +The `candidate` suite recorded it on 2026-10-18 at 16:31 UTC, after the five check suites had passed on the same commit and binaries. The rehearsal and the measurement started only because the record matched the combination that the checks had run on, and the code-tree hash was also recomputed from the commit independently. + +| Part | Value | +| --- | --- | +| CodeSpace | commit `1c0eaddd81fdd17d35da673b0c8fbe750fe4ac60`, the merge of [#115](https://github.com/novelKR/CodeSpace/pull/115), with no uncommitted change; code-tree SHA-256 `cb18534dedbd001a1de2f42c930ee303d41791dee279a36a7e5fe1b7de808d38`. The commit that adds this report changes documentation only and keeps that code tree | +| DevGuard client | `devguard-client` 0.1.0 and `devguard-contract` 0.1.0 from `novelKR/DevGuard` at `6e7e065a0dd5b3876dc7f4546ab76069489937d1` ([client pin](upstream-lock.md#devguard-client-pin)), as for CS-RG | +| Codex | gitlink `6b9826e3aa83b1a5947db50f4332cb9c65f1b340` (`rust-v0.154.0`) with the [recorded patch](upstream-lock.md#codex-downstream-patch), patched tree `1999937194e3a68d982be85679557f0e50fe5408`; `codex_patch.py check` passed | +| Wire and protocol | CodeSpace's runner wire 11; DevGuard protocol 1; MCP `2025-11-25`, as the harness speaks it | +| Binaries | `codespace-mcp` 0.6.0 (`-p codespace-server --features devguard`, release) `18e6f6da6becd3be35f4a3591c7ec047d999eade561d346b2ea6ea4bb6065027`, 40,183,376 bytes; `codespace-codex-runtime` (`--features devguard`, release), which is both the `uds` worker and the independent Runner, `fa3959b430fa93f7a874970dd0e4c77b274f97720d9849ba7bc6e548eeb80237`, 27,718,336 bytes. For the fixture scenarios, `fixture_authority` `ea234acf2a3ddf645d4e5a3ad8ab163cfa81df003d323070ac754c035b3ff124` and `devguard-launch` `85c2165e95e19ca52e8a6c213b3efa4f48184de335a828b5409b737dce0e5bf0`, both built from the client pin; for the load, `devguard-qualify` `694c838621b14762dc2ad4af5139a48041764fe44fd62bed005a501d6eb68cc1`. These three are CS-RG's | +| DevGuard release | `0.1.0-6e7e065-94d85365`, built from `6e7e065`; `MANIFEST.json` SHA-256 `4535b52cc6466e892ebee2a2ad52f396e6b7520d7560e7e25c20ffa3647ed0df`; the installed `devguardd`, `devguard` and `devguard-launch` match it, and the service is healthy and runs it with process ID 23023, as it did for CS-RG | +| Policy | unchanged since CS-RG: configuration fingerprint `8cbb019bbb58e92e2a0d65f797cd5eb5f1696ce9e254bfdb1af97395382efc6f`; `devguard doctor --require admission,registration,macos-cooperative` satisfied; capabilities `durable_admission`, `fenced_launch`, `per_resource_evidence`, `static_control_reservations` and `macos_cooperative`. The `codespace` consumer has the role `control_service`, generation `codespace-g1`, one instance and a control reservation of 150 mCPU, 536,870,912 bytes and 40 tasks. The SHA-256 of `host.toml` is in the sealed record | +| DG-1 qualification | DevGuard's record of the release, SHA-256 `fd4fed318e0fbc640d9e6174a8dd731db80f9607d2235bdfa773c4101f6b17a4`: cold and warm loads qualified under the same `host.toml`, fingerprint and capabilities | +| Toolchain | `rustc 1.95.0 (59807616e 2026-04-14)`, `cargo 1.95.0 (f2d3ce0bd 2026-03-21)`, Python 3.9.6, Xcode 27.0 (27A266a) | +| Host | a Mac mini (Macmini9,1, Apple M1) with 8 logical CPUs and 16 GiB, on AC power, macOS 27.0 (26A428); work capacity after the consumer's reservation 5,350 mCPU, 12,079,595,520 bytes and 104 tasks | + + + +## Runs + +Every run on the qualification host used the candidate's binaries in the minimal environment, with Rust 1.95.0 first in `PATH`, and started after checking that nothing was charged, no `codespace` instance was active and memory pressure was normal. Each run was sealed, and `verify` recomputed its verdicts from the raw files and found them unchanged. The check suites, the freeze, the rehearsal and the measurement ran from one reviewed command in the owner's Terminal, each only after the one before it was clean, under Terminal's limit of 256 open files; the CS-RG suites ran afterwards in the session that had built the binaries, as CS-RG ran them. + +| Run | When (UTC) | Result | +| --- | --- | --- | +| The five check suites, `recovery-lifecycle`, `recovery-store`, `recovery-fence`, `recovery-reconcile` and `recovery-observation` | 2026-10-18 16:26–16:31 | passed: 55 scenarios with 650 checks, none failed; the two gaps recorded as not run, with their reasons | +| `candidate` | 2026-10-18 16:31 | passed | +| `recovery --rehearsal` | 2026-10-18 16:31–16:42 | inconclusive only because a rehearsal is shorter than the protocol, as designed; its functional scenarios and both intervals passed | +| `recovery` | 2026-10-18 16:42–18:52 | qualified: the functional scenarios passed, and all six intervals of the three repetitions | +| CS-RG's `candidate`, `matrix`, `faults` and `authority` | 2026-10-19 02:22–02:31 | passed, as in [regressions](#recovery-report-regressions) | +| `verify` of every P1R-C06 directory | 2026-10-19 02:31 | all 35 verified, those of the earlier candidates included | +| CI on the candidate's commit | 2026-10-18 09:18–09:31 and 21:51–22:02 | passed: every job of the push run after the merge and of the scheduled full run | + + + +## Functional suites + +Each functional scenario ran three times on the candidate: in the five check suites before the freeze, and as the first part of the `recovery` rehearsal and of the `recovery` run. Each time all 55 scenarios that run on macOS passed, 34 with `off` and 21 with `required`, with the same 650 checks, none of which failed, and the two that the protocol lists as [gaps](recovery-qualification.md#recovery-gaps), a host restart and restoring processes after the Runner restarts, were recorded as not run, with their reasons. + +| Suite | With `off` | With `required` | Checks, `off` / `required` | +| --- | --- | --- | ---: | +| `recovery-lifecycle` | exit-kinds and original-timeout, each on a pipe and on a PTY; runner-sigterm; invalid-settings | exit-kinds and original-timeout, each on a pipe and on a PTY; runner-sigterm | 98 / 95 | +| `recovery-store` | records on a pipe and on a PTY; writer-lock; schema; records-command | records on a pipe and on a PTY | 49 / 28 | +| `recovery-fence` | foreign-connection on a pipe and on a PTY; stale-epoch; stopped-gateway; concurrent-claims; other-database; concurrent-gateways | foreign-connection on a pipe and on a PTY | 58 / 30 | +| `recovery-reconcile` | running, terminal, kill-during-exec and kill-during-terminate, each on a pipe and on a PTY; approval; missing-record | running, terminal, kill-during-exec and kill-during-terminate, each on a pipe and on a PTY; approval; authority-outage | 89 / 110 | +| `recovery-observation` | ring-overflow and runner-loss, each on a pipe and on a PTY; retention; runner-replaced | runner-loss on a pipe and on a PTY | 55 / 38 | + +The protocol's [functional suites](recovery-qualification.md#recovery-functional) say what each scenario shows. Every `required` scenario ran against a fresh fixture authority with its own `codespace` consumer of one instance. The waits that [#114](https://github.com/novelKR/CodeSpace/pull/114) added are recorded. In `kill-during-terminate` with `required`, 6 of the 16 rounds of the `recovery-reconcile` check, of the rehearsal and of the run each met one refusal when it sent new work to the next gateway, while that gateway reconciled the workspace or still held it for the earlier process, and started on its second try, 85 to 102 ms after its first. No round with `off` met one. + + + +## Gateway restart measurement + +`recovery` ran the full protocol from 16:42 to 18:52 UTC on 2026-10-18, right after its rehearsal (16:31–16:42), which was inconclusive only because a rehearsal is shorter than the protocol. After its functional scenarios (16:42–16:46), the measurement ran three repetitions of a 10-minute idle interval and a load interval of at least 30 minutes, governed, under DG-1's cold load against the installed release. The idle intervals lasted 606 to 607 seconds and the load intervals 1,837 to 1,850 seconds, because an interval completes its last cycle, and the load its runs, after the planned end. All six intervals and all three repetitions passed, so the combination qualified, and so did the run. Every interval began with nothing charged and no `codespace` instance active, and the service ran the release with process ID 23023 before and after every repetition. + +Each idle interval restarted the gateway 26 times and each load interval 76 or 77 times, against a plan of one restart every 25 seconds, of which the protocol needs 80%. The gateways were ended in turn with their input closed, SIGTERM, SIGUSR1 and SIGKILL, each as asked, and each next gateway claimed the Runner under the next epoch and found every long-running process `running`. Served is the time a gateway served, from its answer to MCP `initialize`, which the samples follow; the rest is mostly the 4-second pauses that make a deadline fall while no gateway runs. Times are in milliseconds. + +| Repetition | Interval | Seconds | Served | Restarts (planned) | Input closed, SIGTERM, SIGUSR1, SIGKILL | Start to serving p50 / max | Verdict | +| ---: | --- | ---: | ---: | ---: | ---: | ---: | --- | +| 1 | idle | 607 | 558 | 26 (24) | 6, 7, 7, 6 | 28.9 / 131.8 | pass | +| 1 | load | 1,850 | 1,654 | 76 (72) | 19, 19, 19, 19 | 14.5 / 4,062.6 | pass | +| 2 | idle | 607 | 558 | 26 (24) | 6, 7, 7, 6 | 31.2 / 111.4 | pass | +| 2 | load | 1,842 | 1,656 | 77 (72) | 19, 20, 19, 19 | 15.2 / 4,117.7 | pass | +| 3 | idle | 606 | 557 | 26 (24) | 6, 7, 7, 6 | 29.3 / 88.8 | pass | +| 3 | load | 1,837 | 1,658 | 77 (72) | 19, 20, 19, 19 | 15.1 / 1,659.5 | pass | + +A new gateway served within 132 ms of its start in the idle intervals. Under load the median stayed at 15 ms, but the slowest start took 4.1 seconds in the first two repetitions and 1.7 seconds in the third. The protocol has no target for this time, so it is reported, not judged. + +| Repetition | Interval | Fence requests (rivals) | Timeout targets (deadline in a gap) | Latest end after the deadline | Approvals (resume in flight at SIGKILL) | Longest resume | Tagged commands started, refused | +| ---: | --- | ---: | ---: | ---: | ---: | ---: | ---: | +| 1 | idle | 405 (27) | 25 (12) | 103.5 | 26 (6) | 269.8 | 56, 20 | +| 1 | load | 1,155 (77) | 75 (35) | 104.1 | 76 (19) | 273.5 | 161, 58 | +| 2 | idle | 405 (27) | 25 (12) | 101.6 | 26 (6) | 279.6 | 56, 18 | +| 2 | load | 1,170 (78) | 76 (38) | 113.0 | 77 (19) | 2,014.1 | 164, 57 | +| 3 | idle | 405 (27) | 25 (12) | 119.4 | 26 (6) | 261.3 | 55, 18 | +| 3 | load | 1,170 (78) | 76 (37) | 103.7 | 77 (19) | 331.1 | 162, 62 | + +After every restart the other process's Claim was refused, and its 15 requests under the previous, the current and no epoch were each refused `STALE_CONTROLLER` and changed nothing; every rival gateway waited for the writer and gave up without reaching the Runner. Every timeout target was reported `timeout` and ended at most 120 ms after its original deadline, against the protocol's 2 seconds, whether its deadline fell while a gateway ran or while none did; none ended early or had its deadline moved. Every approval was resumed by the next gateway, a second resume returned the same process, and its command ran once, the 75 whose resume was in flight when a SIGKILL ended the gateway included. A resume took at most 331 ms from its first try to its answer, waits for the workspace's reconciliation included, except in the second repetition's load interval, where the longest took 2,014 ms. No tagged command ran twice, and none that was refused ran. + +The control path's targets are a `process_status` p99 of at most 500 ms and a termination acknowledgement p99 of at most 1 second, over the time a gateway served; the other times have no established target and are reported. With fewer than 100 samples, as for terminations, the nearest-rank p99 is the largest value. The schedule follows the time a gateway served, so a count can be a little above or below it. + +| Repetition | Interval | Status p99 / max | Status samples / schedule | Acknowledgement p99 | Terminations / schedule | Scope ended p99 | Released p99 | I/O p99 | Refusal p99 | +| ---: | --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | +| 1 | idle | 3.283 / 7.786 | 1,120 / 1,116 | 0.540 | 27 / 27 | 79.1 | 188.6 | 22.942 | 49.405 | +| 1 | load | 1.882 / 41.516 | 3,297 / 3,306 | 0.487 | 82 / 82 | 92.6 | 195.3 | 2.390 | 48.449 | +| 2 | idle | 3.137 / 9.269 | 1,116 / 1,114 | 0.739 | 27 / 27 | 77.5 | 185.7 | 6.239 | 46.620 | +| 2 | load | 1.475 / missing | 3,312 / 3,312 | 0.508 | 84 / 82 | 66.7 | 182.2 | 17.081 | 47.541 | +| 3 | idle | 2.651 / 8.858 | 1,116 / 1,114 | 0.543 | 26 / 27 | 65.6 | 161.2 | 5.523 | 41.619 | +| 3 | load | 1.497 / 15.581 | 3,316 / 3,314 | 0.486 | 82 / 82 | 76.8 | 184.3 | 2.640 | 53.347 | + +In the second repetition's load interval one status slot of each target was missed: its moment passed while the sample before it still ran, so it counts as a missing sample, as DevGuard's protocol counts it. A missing sample ranks above every answer, so that interval's maximum shows as missing; its p99 was 1.475 ms. No status sample was an error or an answer about a target that no longer ran. Every acknowledged termination ended its target and was released, and each status target's final termination was effective. Every I/O line came back, the slowest after 506 ms. Every refusal was `RESOURCE_UNAVAILABLE`, and no request that had to be refused was admitted. Two termination targets and two refusals met one `RECOVERY_PENDING` from a gateway that had just started, one of each in the first repetition's idle interval and in the third's load interval, and were sent again, as the protocol allows. No sample lost the MCP connection. + +Both output streams were read through every gateway. The PTY's numbered lines came through with none skipped or repeated. Each 40,000-line burst of the pipe was written while no gateway read it, and each time the Runner declared a gap (`ring_overflow`) for the 22,524 lines beyond its 256 KiB ring, after which the other 17,476 came through. No line was lost or repeated outside a declared gap. + +| Repetition | Interval | PTY lines read | Pipe lines read | Declared gaps | Lines in declared gaps | +| ---: | --- | ---: | ---: | ---: | ---: | +| 1 | idle | 2,825 | 52,428 | 3 | 67,572 | +| 1 | load | 8,795 | 157,284 | 9 | 202,716 | +| 2 | idle | 2,825 | 52,428 | 3 | 67,572 | +| 2 | load | 8,756 | 174,760 | 10 | 225,240 | +| 3 | idle | 2,822 | 52,428 | 3 | 67,572 | +| 3 | load | 8,765 | 174,760 | 10 | 225,240 | + +**Launches.** All 967 attempts that the run's gateways logged or its samples named were found in DevGuard's journal; no attempt was launched twice, no uncertain attempt was admitted again, and nothing stayed charged. The journal recorded 4 more launches than the gateways' logs in each idle interval and 13 or 14 more in each load interval: a launch that the Runner made for a gateway killed before it logged it is in the journal and in no gateway's log. These are reported, not counted as duplicates; the marker files, one line for each run of each tagged command, show that no command ran twice. + +**Load.** DG-1's six consumers made 583 runs in the three load intervals, 193 to 196 in each, and every run succeeded: none exited with a nonzero status, failed to start or was uncertain, and admitted load covered 99.99% of each load interval. Every consumer ran in every interval: `cargo` 19 to 21 times, `cpu` and `memory` 15 times each, `io` 58, `output` 58 or 59 and `slow-input` 28. + +**Host and footprint.** Every host sample of every interval was at memory pressure level 1 (normal); swap peaked at 1,378 MiB, and the load average at 7.92, in the second repetition's load interval. The Runner, which owns every execution in this mode and registers as the `codespace` consumer's instance, stayed within the consumer's reservation of 150 mCPU, 512 MiB and 40 tasks in every interval: its peaks were 15.25 MiB of RSS and 15 threads, with a mean CPU of at most 2.9 mCPU. Each gateway peaked at 21.3 MiB and 12 threads, with a mean CPU of at most 4.9 mCPU. This comparison is reported, not judged. + +**Evidence writer.** The harness's writer of raw evidence fell behind by at most 33 ms in the idle intervals and by up to 6.7 seconds in the load intervals, while DG-1's cold builds shared the evidence disk. Since [#115](https://github.com/novelKR/CodeSpace/pull/115) each gateway's log is parsed before the writer stores it, and every termination sample was watched until DevGuard released it. + +**Rehearsal and afterwards.** The rehearsal's two intervals, 127 seconds idle and 216 seconds of load with 5 and 7 restarts, passed with the same zero counts, a status p99 of at most 6.329 ms and an acknowledgement p99 of at most 0.916 ms. After the run the last interval's Runner had been drained: nothing was charged, no lease was held, every `codespace` instance in DevGuard's journal was retired, and the service still ran the release with process ID 23023, so it did not restart during the rehearsal or the run. + + + +## Completion evidence + +The protocol's [completion evidence](recovery-qualification.md#recovery-completion), with what the runs on the candidate showed: + +| What P1-RECOVERY must show | Where it is shown | Result | +| --- | --- | --- | +| The same execution is reached again after a restart | the measurement's 308 restarts, after each of which the next gateway found every long-running process `running` and kept reading both output streams; lifecycle `exit-kinds`; fence `stale-epoch` | shown: no long-running process was lost, and no line was lost or repeated outside a gap the Runner declared | +| No execution runs twice | the measurement's 308 approvals, its 654 tagged commands that started and 233 that were refused, and DevGuard's journal against the gateways' logs; reconcile `approval`, `kill-during-exec` and `kill-during-terminate` | shown: each command ran once, none that was refused ran, and no attempt was launched twice | +| No stale mutation | each cycle's 15 requests of the other process, 4,710 in all, and its rival gateway, 314 in all; the fence suite | shown: every request was refused as stale and changed nothing, and no rival reached the Runner | +| No timeout extension | the measurement's 302 timeout targets, 146 of them with the deadline while no gateway ran; lifecycle `original-timeout` | shown: none was extended, ended early or misreported, and the latest ended 119 ms after its deadline | +| Explicit gaps | the protocol's [gaps](recovery-qualification.md#recovery-gaps) | recorded as not run, with their reasons, in the `recovery-observation` check, the rehearsal and the run | +| Existing modes still pass | [regressions](#recovery-report-regressions) | shown: CS-RG's four suites on the candidate, and CI on its commit | +| Supported modes | the [claims](#recovery-report-claims) above | macOS `off` and `required` qualified; Linux `off` CI functional verification only; Linux `required` unsupported | + + + +## Regressions + +After the run its last Runner had been drained, and DevGuard showed no active `codespace` instance and nothing charged, so CS-RG's suites could register their own gateways. They ran on the same candidate on the qualification host, from 02:22 to 02:31 UTC on 2026-10-19, in the session that had built the binaries, under that session's limit of 1,048,576 open files; they say nothing about Terminal's limit of 256. Each passed and was verified: + +| Suite | When (UTC) | Result | On CS-RG's candidate `ca306ee` | +| --- | --- | --- | --- | +| `candidate` | 02:22 | passed; the record differs from the frozen one only in the time it was taken | passed | +| `matrix` | 02:22–02:23 | passed: 45 of 45 scenarios, 22 in `in-process` and 23 in `uds`, with 210 and 213 checks | passed: 45 of 45 | +| `faults` | 02:23–02:26 | passed: 27 of 27 cases, 13 and 14, with 83 and 89 checks | passed: 27 of 27 | +| `authority` | 02:27–02:31 | passed: 10 of 10 phases; no sample lost the MCP connection | passed: 10 of 10 | + +Afterwards nothing was charged, no `codespace` instance was active, and the service still ran the release with process ID 23023. + +CI ran on the candidate's commit twice, and every job passed both times: the push run after the merge of [#115](https://github.com/novelKR/CodeSpace/pull/115) ([run 39114783991](https://github.com/novelKR/CodeSpace/actions/runs/39114783991), 09:18 to 09:31 UTC on 2026-10-18) and the scheduled run with the full profile ([run 39169645390](https://github.com/novelKR/CodeSpace/actions/runs/39169645390), 21:51 to 22:02 UTC). Each ran the format and pin check, the policy scan, the three Clippy builds and the seven unit stages, `integration` and `linux-isolation` on Ubuntu, and `macos-core`, `macos-fork-race` and `dependencies` on a hosted macOS runner. These are the regression stages on Linux and macOS, the independent Runner's tests included; on Linux they are the functional verification of `off` [claimed](#recovery-report-claims) above. + + + +## Amendments and corrections + +The candidate was frozen four times. Each of the first three froze after the five check suites had passed on it, and each failed later on a defect of the harness, never of CodeSpace or DevGuard. Each fix changed the harness, which is frozen with the protocol, so each made a new candidate, and the runs were repeated from the checks. Every run on the earlier candidates is kept and listed under [evidence](#recovery-report-evidence). + +- **The open-file limit, `b2cb042` (correction).** The first candidate was the merge of [#112](https://github.com/novelKR/CodeSpace/pull/112). Its five check suites passed in a session with a high limit of open files, and its rehearsal, run from Terminal, whose default limit is 256, failed as `harness_failed`: the harness kept every gateway's pipes and transcripts open, so from the 27th functional scenario on, each failed with `Too many open files` in the harness itself, and the load interval's drain could not open the journal. [#113](https://github.com/novelKR/CodeSpace/pull/113) made the harness close what it no longer reads, which keeps at most 43 files open through a whole rehearsal, and every report now records the limit it ran under. The next candidate, `41f83b6`, passed its checks under a limit of 256. +- **Races after a restart, `41f83b6` (correction).** Its full run failed on three harness races in a gateway's first milliseconds, while the gateway refused new work as the product documents: a termination sample sent 5 ms after a gateway's Claim and before it had reconciled the sample's workspace was refused `RECOVERY_PENDING` and counted as an error; in reconcile `approval` with `required`, two rounds held their execution before the new gateway had reconciled the workspace; and in `kill-during-terminate` with `required` on a pipe, new work was refused `WORKSPACE_BUSY` less than a millisecond after `process_status` reported the earlier process exited. No command ran twice. [#114](https://github.com/novelKR/CodeSpace/pull/114) made the harness send such work again while it meets one of these refusals, as the protocol now says ([functional suites](recovery-qualification.md#recovery-functional), [intervals](recovery-qualification.md#recovery-intervals)), and record how long each sample and round waited. +- **The gateway's log on a busy disk, `b5ddd59` (correction).** Its full run failed on one termination sample of 84 in the second load interval, which the harness could not watch: its reader of the gateway's log wrote each line to the evidence before parsing it, and while DG-1's cold builds kept the evidence disk busy, the attempt ID arrived after the sample's 5-second wait. DevGuard released that attempt `scope_terminated`. [#115](https://github.com/novelKR/CodeSpace/pull/115) made the reader parse each line first and write the log on a thread of its own, as the [protocol](recovery-qualification.md#recovery-running) now says; a log it cannot write makes the interval inconclusive. On `1c0eadd` the writer still lagged up to 6.7 seconds under load, and no launch line was missed. +- **Who ran the checks and the freeze (amendment).** On `41f83b6` the session that drives the qualification host could not run `candidate`: its safety check refused the command. From then on the owner ran the check suites, `candidate`, the rehearsal and the measurement as one reviewed command in Terminal, and that command started each step only once the one before it was clean, and the rehearsal only once the record matched the checked combination: commit, code tree, the five binaries, release, policy fingerprint, DG-1's policy and memory pressure. The protocol's order is unchanged. +- **How long `WORKSPACE_BUSY` lasts (note).** The gateway's guidance for this mode says that a process found running holds its workspace, so that new work there returns `WORKSPACE_BUSY` "until the process exits or is terminated". The workspace is freed when the Runner's exit event arrives, once the process's output has closed, which can follow the moment `process_status` first reports the process exited. A client that sends new work again after this refusal, as the harness now does, is unaffected. The text is unchanged here, because this report changes documentation only; making it exact is a separate change. + + + +## Evidence + +The evidence directories are on the qualification host, under the DevGuard checkout's `evidence/` directory. Each is sealed with a `MANIFEST.json` of every file's SHA-256, and its files are read-only. None holds a credential, a credential digest or a child's environment. A directory checks out when its `MANIFEST.json` has the SHA-256 below and `qualify-devguard.py verify` accepts it. The console and `verify` output of every run are kept beside its directory. + +| Directory | Run | `MANIFEST.json` SHA-256 | +| --- | --- | --- | +| `p1r-c06-check-recovery-lifecycle-2026-10-18-1c0eadd` | `recovery-lifecycle` | `ea8f534f434080e9eb33909a8b4aebb98bd6c3791996fb8ca424c0307d0b1da3` | +| `p1r-c06-check-recovery-store-2026-10-18-1c0eadd` | `recovery-store` | `45123bc349de4341202cfdbcfce19ee182584ad4f1272b8d131d3b276b506197` | +| `p1r-c06-check-recovery-fence-2026-10-18-1c0eadd` | `recovery-fence` | `299e646f77497f7ae8a2d7c58f92b89ea59499f562d86ca648d64f3d9675e6fb` | +| `p1r-c06-check-recovery-reconcile-2026-10-18-1c0eadd` | `recovery-reconcile` | `a176626961017483061f4d4a9334ebf4811037782170084697c43bbf66d4164c` | +| `p1r-c06-check-recovery-observation-2026-10-18-1c0eadd` | `recovery-observation` | `37fcf791b8bfca09bf1c805141ee7108a44448a790551611e2c62699a6ffa407` | +| `p1r-c06-candidate-2026-10-18-1c0eadd` | `candidate` | `c7b26e3dc118e4b07b27255e038dcb02d2e1b411ef073d6027754f60fb4622ad` | +| `p1r-c06-recovery-rehearsal-2026-10-18-1c0eadd` | `recovery --rehearsal` | `ef5ab907a9fbe03fa737b2b23d0f2023e64b36685b466248465a32a5a0880894` | +| `p1r-c06-recovery-2026-10-18-1c0eadd` | `recovery` | `1f6f7ba8e4694d4aba6aa371db2edaeeb6fc0b0d7812dd17847444d1e06f790b` | +| `p1r-c06-qual-candidate-2026-10-19-1c0eadd` | CS-RG's `candidate` | `caff08ef246a57f5e673e7b54a41fd3d99376b3c14d12d463270911c2750898f` | +| `p1r-c06-qual-matrix-2026-10-19-1c0eadd` | `matrix` | `33443fce5b6d0261e74cfe77a317a7de3bcbd019836331c3a63cefca40e2011f` | +| `p1r-c06-qual-faults-2026-10-19-1c0eadd` | `faults` | `bb29b613eff8df0c8df8108f0d1ba953b5cc9298a2c79cafbee6f4a280b55936` | +| `p1r-c06-qual-authority-2026-10-19-1c0eadd` | `authority` | `7cebeacfb92cb39b6956607b9cc2e80663102ce307152cf438a7bb4229bfbb65` | + +The runs on the earlier candidates are kept too. Each was verified with its own harness when it ran, and `verify` on the candidate's harness accepted every one again on 2026-10-19; the rehearsal on `b2cb042`, recorded `harness_failed`, recomputes as failed: + +| Directory | Result | `MANIFEST.json` SHA-256 | +| --- | --- | --- | +| `p1r-c06-check-recovery-lifecycle-2026-10-17-b2cb042` | passed | `b401d3c878d95148f2aa3f42d0b6013fa0a5e0a723c2f0acf352809e5d5b9ab6` | +| `p1r-c06-check-recovery-store-2026-10-17-b2cb042` | passed | `c0aaca7ed5f5c917ec5711c4f8b435a8fb49b616c284ddef8298c990974c7b0a` | +| `p1r-c06-check-recovery-fence-2026-10-17-b2cb042` | passed | `5efb6bf4d9830fbbe50cbce31a0ac0f570ec3a67787eb9b481a18a42063dc778` | +| `p1r-c06-check-recovery-reconcile-2026-10-17-b2cb042` | passed | `d70cd660821dd558c75d0fa5a8f9c5a97029c8147b28cd446335112077a59193` | +| `p1r-c06-check-recovery-observation-2026-10-17-b2cb042` | passed | `971fc90242a11cbd4d9db405d26f6e79ecf12f41ddf6ad3b415f3655383f52d9` | +| `p1r-c06-candidate-2026-10-17-b2cb042` | passed | `15c6735be844292b3f84ad74ce20a534d1ec6b9d9bdc38ab7988f98d971f87c7` | +| `p1r-c06-recovery-rehearsal-2026-10-17-b2cb042` | `harness_failed`: the open-file limit | `5f58673c69f35c993852953daf4d5a09ff0dbaf2c857a7fca343aeb507fac631` | +| `p1r-c06-check-recovery-lifecycle-2026-10-17-41f83b6` | passed | `0614d740f7eef0a96941e6c36ec974666b22a301f3f958e0642f29d05a6efb62` | +| `p1r-c06-check-recovery-store-2026-10-17-41f83b6` | passed | `395b4c051b483c3e9609bf3aaee4ecfbb19df82ad3ae213156fa652fc3cddfe8` | +| `p1r-c06-check-recovery-fence-2026-10-17-41f83b6` | passed | `f81578c3398efefda24a9f2758b53499d5ebb969d93b80800825ba5218824271` | +| `p1r-c06-check-recovery-reconcile-2026-10-17-41f83b6` | passed | `9ad23449bb1bc765f99b937ee9b7ad8716b8864f6ee72a97e3b2d53e0d54dfa1` | +| `p1r-c06-check-recovery-observation-2026-10-17-41f83b6` | passed | `66434755ffe5ffb3aa0c60fb28c4397f188d0ff6bd0fdc91bacd92813c3a8921` | +| `p1r-c06-candidate-2026-10-17-41f83b6` | passed | `37ed6aa3967f25602dc00f7ce7c40bb8b773ecea102329e2c50ccdb5c94b4fdc` | +| `p1r-c06-recovery-rehearsal-2026-10-17-41f83b6` | inconclusive, as designed | `aad4c41e36b6d6ddcffe76e24de32fd013f765d781c3de7e70ae5c1e28cfefd6` | +| `p1r-c06-recovery-2026-10-17-41f83b6` | failed: races after a restart | `1b2b57fbea66ae3a08b78dcd76d86f992dc4709a21e22a51343b97cf0f0b22a2` | +| `p1r-c06-check-recovery-lifecycle-2026-10-18-b5ddd59` | passed | `221a31cd238d47ec57190b1355994f473c7fa2592185752bcc81ae647c9ff501` | +| `p1r-c06-check-recovery-store-2026-10-18-b5ddd59` | passed | `b8533e38c2a663dac17dd23e9789d0e30aec9c02cb65870d1bd1cf4800c0991b` | +| `p1r-c06-check-recovery-fence-2026-10-18-b5ddd59` | passed | `9109c738e3f1e4e0279368a37fcefd6b4ef9a86414dbe7b0921ea6b16ebe4048` | +| `p1r-c06-check-recovery-reconcile-2026-10-18-b5ddd59` | passed | `2f07f807876c13f1b7cf63120bb6e92763e19de5a9d5cebeaa3d5e753c60dfb8` | +| `p1r-c06-check-recovery-observation-2026-10-18-b5ddd59` | passed | `bec8ab11383407e43fa041ffdfe163c606ecbf8ad996d1738ac0a93b48b37f50` | +| `p1r-c06-candidate-2026-10-18-b5ddd59` | passed | `1fe4928c3666310f83da9914985627dcc9fb72ff8202d1b7ae5d95b5b7d2abf9` | +| `p1r-c06-recovery-rehearsal-2026-10-18-b5ddd59` | inconclusive, as designed | `5b2237d8c70037cd6ef1a1fa8e346063a4cf43363a3b62a99c4ca603756f1226` | +| `p1r-c06-recovery-2026-10-18-b5ddd59` | failed: the gateway's log on a busy disk | `dbdee96032d7d715a56a729cb1b6b97ace2901bc93f834e9fdf85e2dc7945f02` | + + + +## Outside P1-RECOVERY + +P1-RECOVERY ends here. These stay outside it, and nothing here claims them: + +- **Restoring processes after the Runner itself restarts.** A gateway never takes a new Runner for the one it lost; an operator settles what the lost Runner ran ([Runner loss](operations.md#runner-loss)). +- **Making the independent mode the default**, a separate decision under DevGuard's ADR-002. +- **DG-LINUX**, enforcement on Linux. Until it qualifies, `required` is refused on Linux, and `off` is functionally verified there only. +- **A change to DevGuard's release or policy**, such as more than one instance for the `codespace` consumer (decision D12), which would need DG-1 to be measured again. +- **Another combination.** A different DevGuard client pin, release, policy or host, a code change, or another toolchain is a new candidate that needs its own runs. +- **What the runs did not show:** a real host restart, DG-1's warm load, a replayed message on the runner wire, and CS-RG's control-path measurement on this candidate. diff --git a/docs/recovery-qualification.md b/docs/recovery-qualification.md index cf81671..74640b3 100644 --- a/docs/recovery-qualification.md +++ b/docs/recovery-qualification.md @@ -6,7 +6,7 @@ This page is the protocol for qualifying the [independent Runner](runner-isolation.md#independent-runner), the opt-in mode of Gateway recovery (P1-RECOVERY): which combinations are claimed, how each claim is tested and measured, what makes a run pass, and what evidence a run leaves. It follows the [CS-RG qualification protocol](cs-rg-qualification.md), whose candidate, evidence and verification it reuses, and its harness is the same [`scripts/qualify-devguard.py`](../scripts/qualify-devguard.py). It is frozen together with the harness before any qualification run; a change to either after the first run is a new protocol, reviewed like any other change, and the runs it affects are repeated. The [integration roadmap](devguard-integration.md#resource-gateway-recovery) describes what each unit built; this page decides whether the result qualifies. -**Status:** frozen with its harness before the first qualification run. No combination is qualified yet, so the independent Runner is not a supported mode anywhere. +**Status:** frozen with its harness ([#112](https://github.com/novelKR/CodeSpace/pull/112)) before the first qualification run. Before any run qualified, three harness defects were corrected ([#113](https://github.com/novelKR/CodeSpace/pull/113), [#114](https://github.com/novelKR/CodeSpace/pull/114), [#115](https://github.com/novelKR/CodeSpace/pull/115)); each correction made a new protocol and a new candidate, and the runs were repeated on it. The runs on the candidate `1c0eadd` passed, and the independent Runner is qualified for that combination; the [qualification report](recovery-qualification-report.md) has the results and the evidence, and its [amendments](recovery-qualification-report.md#recovery-report-amendments) record the corrections. diff --git a/docs/runner-isolation.md b/docs/runner-isolation.md index cb7c7d5..9a302a0 100644 --- a/docs/runner-isolation.md +++ b/docs/runner-isolation.md @@ -20,7 +20,7 @@ Managed process lifetime is owned by the runner instance, not the MCP connection ## Independent Runner -> **Supported only where qualified.** The independent Runner, the records of its executions, the Claim that hands its control from one gateway to the next and the reconciliation after each Claim make up Gateway recovery. A gateway claims the Runner again when its connection is lost, and starts new work where an earlier controller started work only once it has reconciled what that controller left. When the Runner itself is lost, the gateway answers for its executions from their records and keeps their workspaces until an operator releases them ([Runner loss](#runner-loss)). The mode is supported only on the combinations that its [qualification](recovery-qualification.md) qualifies, and none is qualified yet. [Operations](operations.md#independent-runner-adoption) describes how to adopt it and roll it back. +> **Supported only where qualified.** The independent Runner, the records of its executions, the Claim that hands its control from one gateway to the next and the reconciliation after each Claim make up Gateway recovery. A gateway claims the Runner again when its connection is lost, and starts new work where an earlier controller started work only once it has reconciled what that controller left. When the Runner itself is lost, the gateway answers for its executions from their records and keeps their workspaces until an operator releases them ([Runner loss](#runner-loss)). The mode is supported only on the combinations that its [qualification](recovery-qualification.md) qualifies: on macOS, the one combination that the [qualification report](recovery-qualification-report.md#recovery-report-combination) records. [Operations](operations.md#independent-runner-adoption) describes how to adopt it and roll it back. An independent Runner outlives the gateways that connect to it: its processes, their output and their timeouts carry on when a gateway exits, restarts or is killed. An operator or a harness starts it, and a gateway only connects to it: diff --git a/docs/translations.json b/docs/translations.json index 861ccdc..82cadb4 100644 --- a/docs/translations.json +++ b/docs/translations.json @@ -78,8 +78,8 @@ "최소-도입-조건", "최초-gateway-복구-범위" ], - "source_sha256": "030dcad13f96bb7f9d9ae48e93cf9381b3774e61322218770fb353277ad3c1c6", - "translation_sha256": "e924382d4b0571900d5ef8b1c562b4445031d2d785ca25eeae3224b1a03569a5" + "source_sha256": "78a4f64e46a076a0420a0c702da64d83dacc8f23cabab166b1ce8b0d3a62a579", + "translation_sha256": "7c9f1669bd46318c3cccb62051919e7b24a5c6447d400edce19f739290c582cc" }, { "id": "cs-rg-qualification", @@ -249,8 +249,53 @@ "회귀", "후보-조합" ], - "source_sha256": "8adac9dd635d082db99988c2213538346a2944578f243b7b93addd025c7fc00e", - "translation_sha256": "40136e6153849dfa854f655621ac3c09ba8d73af35a9b6ada8afad4dcc5c3253" + "source_sha256": "a85f4cb3fef6228e77ab7613e796bfd211732b66a88a639dc986c2a10292fad5", + "translation_sha256": "a249e22d93e288e16c88f4605c63b9faa2b722819049f3a619edac8508eaf415" + }, + { + "id": "recovery-qualification-report", + "section": "project", + "order": 5, + "route": "/guide/recovery-qualification-report", + "source": "docs/recovery-qualification-report.md", + "translation": "docs/ko/recovery-qualification-report.md", + "anchors": [ + "amendments-and-corrections", + "completion-evidence", + "evidence", + "functional-suites", + "gateway-recovery-qualification-report", + "gateway-restart-measurement", + "gateway-복구-qualification-보고서", + "gateway-재시작-측정", + "outside-p1-recovery", + "p1-recovery-밖에-남는-것", + "qualification한-조합", + "recovery-qualification-report", + "recovery-report-amendments", + "recovery-report-claims", + "recovery-report-combination", + "recovery-report-completion", + "recovery-report-evidence", + "recovery-report-functional", + "recovery-report-measurement", + "recovery-report-outside", + "recovery-report-regressions", + "recovery-report-runs", + "regressions", + "runs", + "the-qualified-combination", + "what-is-claimed", + "기능-스위트", + "수정-기록", + "실행", + "완료-증거", + "주장하는-것", + "증거", + "회귀" + ], + "source_sha256": "c4ca357c5e3cbf442293356d61255a6ca85841128c8db81c6e077365d549b0b0", + "translation_sha256": "2d82f676da2ec4e25d81cf65a874755111c1cac4cff306c346751afb24247147" }, { "id": "overview", @@ -381,8 +426,8 @@ "작업-공간-등록", "첫-연결-확인" ], - "source_sha256": "da04d1227b65a31be67e5b841d42934ed410a9ab775033e1eed7d4c87264ddb5", - "translation_sha256": "dbd583fe19271b9db950726cbc1363d789c186405988539b3cdd1aa48b8d7871" + "source_sha256": "acb28d2cb1086f51e86612e3fab462cd80542224c32a581bcea0889390005603", + "translation_sha256": "69161270d53d48ed97deb72a5e94b59feb41ee965469c2e11a7700f45b9ecf80" }, { "id": "chatgpt-connector", @@ -685,8 +730,8 @@ "컨테이너-실험-구성과-검증", "호스트와-worker-실행" ], - "source_sha256": "9f8118e96dc863babaae5e629fa924ab4ce7f6942eb968d96dd766f24f8851c7", - "translation_sha256": "a99a0c9ee574e71a8eb8035c7541f3e1019cc2226e58a39a0080e1cc05113768" + "source_sha256": "d3e6eef123f6f141c921e35d38d95cb6a160ee8cde031f674b397f8f132bd6c6", + "translation_sha256": "584fc9708b41e85bbb437656d027844223c79ff8fa954a25abcd86c88a49fc4c" }, { "id": "error-codes", From 30165c9653ffc5be0194faa6069b0cc200e9b834 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 19 Oct 2026 03:25:11 +0000 Subject: [PATCH 2/2] docs: state the recovery report's secret scan (P1R-C06) A scan of the 35 P1R-C06 evidence directories and the console and `verify` logs beside them, 13,454 files, found no private key, authorization header, token, credential digest, session variable or environment dump. Its only matches were eight lines of the CS-RG `faults` run's MANIFEST.json, which pair the files of the `credential` fault case, whose names contain "credential", with their own SHA-256. The evidence section of the report now says so, as CS-RG's report says of its evidence, in English and Korean. Co-Authored-By: Claude Opus 5.5 (1M context) --- docs/ko/recovery-qualification-report.md | 2 +- docs/recovery-qualification-report.md | 2 +- docs/translations.json | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/ko/recovery-qualification-report.md b/docs/ko/recovery-qualification-report.md index a401164..c24ecd5 100644 --- a/docs/ko/recovery-qualification-report.md +++ b/docs/ko/recovery-qualification-report.md @@ -194,7 +194,7 @@ qualification 호스트의 모든 실행은 최소 환경에서 후보의 바이 ## 증거 -증거 디렉터리는 qualification 호스트의 DevGuard checkout 아래 `evidence/` 디렉터리에 있습니다. 각 디렉터리는 모든 파일의 SHA-256을 담은 `MANIFEST.json`으로 봉인했고, 파일은 읽기 전용입니다. 어떤 디렉터리에도 자격 증명, 자격 증명의 digest, 자식 프로세스의 환경은 없습니다. 디렉터리는 그 `MANIFEST.json`의 SHA-256이 아래와 같고 `qualify-devguard.py verify`가 받아들이면 확인된 것입니다. 모든 실행의 콘솔 출력과 `verify` 출력은 그 디렉터리 옆에 보관했습니다. +증거 디렉터리는 qualification 호스트의 DevGuard checkout 아래 `evidence/` 디렉터리에 있고, 모든 실행의 콘솔 출력과 `verify` 출력은 그 디렉터리 옆에 보관했습니다. 각 디렉터리는 모든 파일의 SHA-256을 담은 `MANIFEST.json`으로 봉인했고, 파일은 읽기 전용입니다. 디렉터리에도, 그 옆의 로그에도 자격 증명, 자격 증명의 digest, 자식 프로세스의 환경은 없습니다. 2026-10-19에 파일 13,454개를 모두 검사해 하나도 찾지 못했습니다. 디렉터리는 그 `MANIFEST.json`의 SHA-256이 아래와 같고 `qualify-devguard.py verify`가 받아들이면 확인된 것입니다. | 디렉터리 | 실행 | `MANIFEST.json` SHA-256 | | --- | --- | --- | diff --git a/docs/recovery-qualification-report.md b/docs/recovery-qualification-report.md index 1ffb298..1335590 100644 --- a/docs/recovery-qualification-report.md +++ b/docs/recovery-qualification-report.md @@ -194,7 +194,7 @@ The candidate was frozen four times. Each of the first three froze after the fiv ## Evidence -The evidence directories are on the qualification host, under the DevGuard checkout's `evidence/` directory. Each is sealed with a `MANIFEST.json` of every file's SHA-256, and its files are read-only. None holds a credential, a credential digest or a child's environment. A directory checks out when its `MANIFEST.json` has the SHA-256 below and `qualify-devguard.py verify` accepts it. The console and `verify` output of every run are kept beside its directory. +The evidence directories are on the qualification host, under the DevGuard checkout's `evidence/` directory, and the console and `verify` output of every run are kept beside its directory. Each directory is sealed with a `MANIFEST.json` of every file's SHA-256, and its files are read-only. Nothing in the directories or in the logs beside them holds a credential, a credential digest or a child's environment: a scan of all 13,454 files on 2026-10-19 found none. A directory checks out when its `MANIFEST.json` has the SHA-256 below and `qualify-devguard.py verify` accepts it. | Directory | Run | `MANIFEST.json` SHA-256 | | --- | --- | --- | diff --git a/docs/translations.json b/docs/translations.json index 82cadb4..cb6e531 100644 --- a/docs/translations.json +++ b/docs/translations.json @@ -294,8 +294,8 @@ "증거", "회귀" ], - "source_sha256": "c4ca357c5e3cbf442293356d61255a6ca85841128c8db81c6e077365d549b0b0", - "translation_sha256": "2d82f676da2ec4e25d81cf65a874755111c1cac4cff306c346751afb24247147" + "source_sha256": "8ad13813fdddb7535a85af23690a585c500940ef3201e1a9f29b3035b99f3398", + "translation_sha256": "1221ad61cb871f1ba71d320aadc69d2f9a3864ffdf1f8bd50800a9580c141028" }, { "id": "overview",