Skip to content

scripts: index a gateway's log before its file is written (P1R-C06) - #115

Merged
novelKR merged 1 commit into
mainfrom
claude/p1r-c06-qualification-report-k7gur1
Oct 18, 2026
Merged

novelKR merged 1 commit into
mainfrom
claude/p1r-c06-qualification-report-k7gur1

Conversation

@novelKR

@novelKR novelKR commented Oct 18, 2026 •

Copy link
Copy Markdown
Owner

Issue

Part of #101: P1R-C06. The full recovery run on the qualification host failed again, this time on one termination sample that the harness could not watch. This fixes the harness and its protocol text before the candidate is frozen again. It follows #114 and does not close an issue.

Item Value
Base main at b5ddd59 (the merge of #114)
Failed run p1r-c06-recovery-2026-10-18-b5ddd59 on the qualification host, MANIFEST.json SHA-256 dbdee96032d7d715a56a729cb1b6b97ace2901bc93f834e9fdf85e2dc7945f02; verify accepts it (recorded and recomputed failed). The five check suites passed, the candidate (1fe4928c…) passed, and the rehearsal (5b2237d8…) was inconclusive as designed
Pins Codex gitlink with its recorded patch, and DevGuard 6e7e065; both unchanged

Summary

Before: the harness took a governed launch's attempt ID from the gateway's log only after it had written that line to the evidence file. When the disk under the evidence stalled, the attempt ID arrived late, and a termination sample could go unwatched. In the full run everything else passed: 55 functional scenarios (2 gaps not run), repetitions 1 and 3, repetition 2's idle interval, and every host sample at pressure level 1. Repetition 2's load interval failed on termination alone:

  • Slot 67 (05:44:59.951Z) started its target through the 59th gateway. That gateway logged the managed launch, with its attempt ID, just before it answered exec_command.
  • The harness's log reader wrote and flushed each line to the evidence before parsing it. DG-1's cold load builds on the same volume as the evidence, and that interval's evidence writer lagged by up to 5.6 seconds (the 41f83b6 run's load intervals lagged 5.2 to 10.4 seconds).
  • The harness waits 5 seconds for the attempt ID. It ran out before the reader got to the line, so the termination was sent 5.015 s after exec_command answered, with no attempt to watch. The sample was effective (the scope ended in 34 ms), but with one of 84 samples unreleased the release p99 is missing, which fails the interval.
  • DevGuard released that attempt with scope_terminated, charging nothing. The product did what it should.

None of the three failures of the 41f83b6 run came back, and #114's waits did their job. In the five checks, the rehearsal and the full run, no termination or refusal sample was refused with RECOVERY_PENDING, and each kill-during-terminate round refused at its start was refused once and waited 86 to 105 ms.

After: a gateway's log is indexed as the gateway writes it, whatever its file's disk does. A disk the load keeps busy delays only the log file, and a log that cannot be written is counted as raw evidence that could not be written.

How.

  • Gateway (scripts/qualify_host.py): the log's reader decodes, parses and indexes each line, keeps it in memory, and only then hands it to a writer thread of the gateway's own, which opens the log file and appends lines as fast as the disk takes them. The file stays a byte-exact copy of what the gateway wrote. join_readers, and with it close and end_gateway, also waits for that writer, so a gateway's log file is complete once it has ended, as before.
  • Gateway.log() returns what the gateway has logged so far, whether or not the file has it yet, and all of it once the gateway has exited. The two scenarios that read a gateway's log (store writer-lock and fence concurrent-gateways) use it instead of the file.
  • Errors: a log that cannot be written keeps its error and reports it to the bench's evidence writer (Writer.failed). That writer counts it as one of its own errors, so it makes a measurement interval inconclusive as "raw evidence could not be written", and in the functional suites it is recorded in the writer's report. Before, such an error ended the reader, and with it the index.
  • Protocol (docs/recovery-qualification.md and the Korean page): the harness section states where the attempt ID comes from and how the log is written. The registry hashes are recorded.

The protocol freezes the harness with it, and a change after the first run is a new protocol, reviewed like any other change, whose affected runs are repeated. On this merge the candidate is frozen again and the whole recovery run is repeated. Gateway is shared with the CS-RG suites, which C06 reruns on the same candidate anyway.

Contract changes

None. The product is unchanged; only the harness, its tests and the protocol text change. The evidence's files and rows are unchanged.

Tests

Verified here at the head of this branch, in a Linux cloud container, with debug builds of codespace-mcp and codespace-codex-runtime (the product is unchanged since b2cb042):

  • python3 -B -m unittest discover -s scripts/tests: 126 passed, 2 of them new.
    • A gateway whose log file is a FIFO with no reader, so that opening it blocks as a stalled disk does, still indexes a launch it logs at once: execution returns it and log() has the line while the file's writer still waits. Once the FIFO is read, the file holds exactly what the gateway wrote. Against b5ddd59's harness the same test fails as on the host: execution returns None after its 5-second wait.
    • A gateway whose log cannot be opened still indexes its launches, and its error is the one error in the bench writer's report.
    • DescriptorTests still finds no descriptor left open by an ended gateway.
  • pyflakes on the four scripts; scripts/check_docs.py, and the docs site's test, build and check as the docs workflow runs them.
  • recovery --participation off --repetitions 1 under a soft limit of 256 (ulimit -Sn 256), at the protocol's durations: functional 34 passed, 2 gaps not run; the 10-minute idle interval passed (26 restarts); the 30-minute load interval was inconclusive by design here ("no load ran: there is no DevGuard service to run it") with nothing failing (79 restarts); the run inconclusive (exit 2), verify verified. None of the 326 gateway logs (rivals' included) is empty, no evidence write failed, and the harness had at most 43 files open. MANIFEST.json SHA-256 81c418b01f8990d913dabbc39c9f5c44f348f2b31213a98e0f1e1485bea12f50.

Not verified here: required, which runs only on macOS, and a disk stall under DG-1's load. On the merge, the qualification host builds the merged main, and the owner's paste runs the five checks, freezes the candidate, and repeats the rehearsal and the full run.

Security scenarios

  • Rollback honesty: a gateway log that cannot be written is never silently missing; it counts as raw evidence that could not be written, so a measurement interval is inconclusive, never passed. The log file is still byte-exact and complete once its gateway has ended.
  • Credentials: unchanged; the harness passes only the credential file's path and reads no credential.

Out of scope

  • The repeated qualification run and its report, which follow on the candidate frozen at this merge.
  • Why gateways took longer to start under the load. The restarts' serving_ms (from starting a gateway to its first answer) had a p99 of 4.1 to 4.9 s in the load intervals and about 0.13 s idle. That time includes the harness's own synchronous evidence writes for a new gateway (its files and its row in gateways.jsonl) as well as the gateway's own start; how much of it the busy disk caused was not measured. No verdict rule reads that time, and it is noted for the report.
  • Moving DG-1's load or the evidence to another volume: the disk is the host's, and the harness should not depend on it.

The full recovery run on the qualification host failed on one termination
sample whose release the harness never watched. The product released the
attempt (`scope_terminated`); the harness had no attempt ID to watch.

The gateway logged the launch when it started the target, but the harness's
log reader wrote each line to the evidence before it parsed it. DG-1's cold
load builds on the same volume as the evidence, and that interval's evidence
writer lagged by up to 5.6 seconds. The reader was still waiting on
the disk when the 5-second wait for the attempt ID ran out, so the sample went
unwatched and the interval failed as unreleased.

The reader now parses and indexes each line as the gateway writes it, then
hands it to a thread of its own that writes the log file. A busy disk delays
only the file. `Gateway.log()` returns what the gateway has logged whether or
not the file has it yet, and the two scenarios that read a gateway's log use
it. A log that cannot be written is reported to the bench's evidence writer
as raw evidence that could not be written, so it makes a measurement interval
inconclusive as the writer's own errors do. Ending or closing a gateway still
waits until its log file is complete.

The protocol states this in English and Korean.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@novelKR novelKR self-assigned this Oct 18, 2026
@novelKR
novelKR marked this pull request as ready for review October 18, 2026 08:42
@novelKR
novelKR merged commit 1c0eadd into main Oct 18, 2026
19 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants