SPICE performance phase 6: push audit of phases 1-5¶
Master plan: PLAN-spice-performance.md.
Planning effort¶
This plan was written in a high-effort session. The master plan gives the phase no effort level, only the push-audit shared block. Review effort: medium. The judgment is in the range, in which repository's rules apply to which landing, and in how the review is split. The steps themselves follow the runbooks.
Scope¶
In scope:
- Running kerbside's
PUSH-AUDIT.md, waves 1 and 2, over everything phases 1-5 landed in this repository: seven merges carrying content outsidedocs/plans/, listed under Audit range. - Running ryll's
PUSH-AUDIT.mdover ryll #406, which phase 3 landed and nothing has audited (survey finding 3). - Applying the shared block's floor checklist, plus questions of its
own, to the two kerbside-patches landings. That repository has no
PUSH-AUDIT.md(survey finding 2). - Reviewing the carried qemu v2 series, the only code in the plan that no upstream reviewer will read (decision 4).
- The floor checklist's items 2 to 5 for kerbside too, as the Proxmox audit did.
- Triaging every finding as blocking or advisory, with the reason, in this plan's Outcome.
- Filing problems that predate the plan as issues.
Out of scope:
- Fixing findings. They land in follow-up pull requests after this one merges, one per repository that has any (decision 7).
- andris #56 (phase 4), whose push audit is recorded in its pull request body (survey finding 4).
- Phase 2's dri-devel submission of the kernel fix. It is not yet sent, and its audit is the upstream review (decision 6).
- The spice-server and Linux series' code. Their disposition is
submit, so the upstream review is their audit (decision 4). - The open audit-tooling defects #488, #491 and #638. This plan works around them (decision 1) and does not fix them.
Audit range¶
Every merge below is a merge commit on its repository's default
branch's first-parent chain. Its diff against its first parent is the
whole of what that pull request landed. "Non-plan content" means
additions outside docs/plans/.
kerbside¶
| Merge | PR | Date | Phase | Non-plan content |
|---|---|---|---|---|
| 5204616 | #478 | 2026-09-25 | 1. Proxy backpressure, re-baseline | about 2,340 lines: socket options in kerbside/config.py, proxy_supervisor.py, etc/kerbside.conf.example and their tests; rust/kerbside-proxy/src/backend.rs, listen.rs, main.rs; the shaped-link rig in tools/shaped-link/ (seven files, including guest/keydraw.c, run-matrix.sh, summarise.py); two docs/performance/ pages |
| 2e19d73 | #489 | 2026-09-26 | 3. Planned | none: docs/plans/ only |
| eab2c57 | #494 | 2026-09-27 | 3, steps 1a-1b. Client caps | about 990 lines: rust/kerbside-proxy/src/caps.rs (441, new), session.rs (+301), backend.rs, rpc.rs, a ryll pin bump; four protocol and architecture pages |
| f51b9d3 | #497 | 2026-09-27 | Phases 7-9 added | none |
| 9d1b64d | #505 | 2026-09-28 | 3. #494's review follow-ups | about 140 lines: session.rs (+119), backend.rs, rpc.rs; four docs pages and one line of AGENTS.md |
| 13e9e2e | #581 | 2026-10-19 | 3. Re-scoped by the RDP plan | none |
| bba7115 | #616 | 2026-10-28 | 3, steps 2 and 3b. ACK window, transcoder | about 8,110 lines: ackwindow.rs (554, new), metrics.rs, relay.rs, backend.rs, main.rs; src/transcode/ (about 5,900 lines, new, behind the transcode feature); Cargo.toml, Cargo.lock (+531) and the Makefile; summarise.py; eight docs pages including ARCHITECTURE.md |
| 7919ead | #630 | 2026-10-28 | 3, steps 4-6. Measure, switch, verdict | about 4,070 lines: transcode/switch.rs (883, new), transcode/mod.rs, tests.rs, backend.rs, main.rs, relay.rs; two steps in .github/workflows/rust.yml; five new or grown shaped-link tools; three docs/performance/ pages and two images |
| a90f17f | #637 | 2026-10-29 | 4. D-Bus display spike | about 1,500 lines: tools/shaped-link/build-andris.sh (new), run-matrix.sh, summarise.py, keydraw.c; four docs/performance/ pages, two images and andris-sessions.txt |
| 46c3e98 | #654 | 2026-10-30 | 5. spice-server patches | about 320 lines, all docs: docs/performance/spice-server-patches.md (264, new) and corrections to five sibling pages |
The phase merges are the master plan's Merged cells. The plan-only
merges were found with git log --merges --first-parent origin/develop
on branch names, and with gh pr list --state merged --search
'spice-performance in:body,title'. They are listed because the shared
block counts a change to the plan's own text as the plan's work. #489,
497 and #581 are not run through the wave scripts, which would pass¶
vacuously; 6e reads the master plan's final text.
Other repositories¶
| Repository | Merge | PR | Phase | Content | Audit |
|---|---|---|---|---|---|
| kerbside-patches | b53aa39c7e | #1748 | 2. Import | about 5,000 lines under upstream/: the qemu v1 and v2 series, the Linux series, their rigs (shell, C and Python) and results; .gitignore, .pre-commit-config.yaml |
this phase, 6h and 6i (survey finding 2) |
| kerbside-patches | 2d7b626422 | #2024 | 5. spice-server series | about 2,400 lines under upstream/spice/: the series, two dropped drafts, rig/build-spice.sh, rig/summarise-spice.py, results; upstream/README.md |
this phase, 6h (survey finding 2) |
| ryll | fdb5ead5b4 | #406 | 3, step 1a. Caller-chosen link caps | about 780 lines in shakenfist-spice-protocol: link.rs (+500), client.rs (+243), reader.rs; docs/spice-protocol.md |
this phase, 6g (survey finding 3) |
| andris | 9a997a3 | #56 | 4. D-Bus display platform | the qemu platform | its pull request body (survey finding 4) |
| linux | not yet | 2. Kernel fix | the one-line core fix | dri-devel review, once sent (decision 6) |
What the survey found¶
-
Phase 3's
Mergedcell recorded a SHA that does not resolve.9d1b64d6is not a prefix of #505's merge commit, which is9d1b64deae.git rev-parse 9d1b64d6fails, so an audit reading the cell would have skipped #505's 140 lines ofsession.rs. The planning commit corrects the cell. -
kerbside-patches has no
PUSH-AUDIT.md, and the audit the master plan says to cite does not exist. The master plan's phase 2 and phase 6 sections both say this phase cites kerbside-patches' own audit. kerbside-patches'docs/plans/upstream-series.mddoes carry a push-audit phase (its phase 5), but its status table readsNot started. Its phase 1, the import, still readsIn progressthough it merged as #1748. #2024 landed under no kerbside-patches plan at all. The template's shared block, now at v4, says what a repository without a runbook does instead: the floor checklist, plus the questions its own blast radius raises, and not another repository's runbook. The master plan still carries v3 of the block, which predates the checklist. The planning commit syncs the block verbatim fromPLAN-TEMPLATE.md, and corrects both sections. The stale status table in kerbside-patches is left for that repository's follow-up (6j). -
ryll #406 was never audited. Phase 3 recorded it in its
Mergedcell. Neither its pull request body nor its comments carry an audit, and no plan in ryll'sdocs/mentions it. ryll's push-audit merges since then (#411, #630, #633, #657) each audit a different plan. Phase 3's re-survey decision 1 says andris and ryll audit the work that step 3a found already delivered. That covers other pull requests, not #406. kerbside's proxy consumes #406 throughcaps.rsto parse a client's link message, which is untrusted input, so this one is worth the opus agent. -
andris #56 was audited, and the record is in its pull request body. Style passed and nothing was blocking. The one medium finding was fixed in the pull request's second commit, and two items were declined with reasons. This phase cites it.
-
The plan changed Rust in five merges, and
PUSH-AUDIT.mdhas no Rust gate. Wave 1 is tox only.rust.ymlrunscargo fmt --check, clippy with-D warnings,cargo testand a release build on every push todevelopthat touchesrust/**. Since #630 it also runs clippy and the tests with--features transcode. #616 added the transcoder before those two steps existed, so its own push run never built the feature. -
Most of the Rust diff is not shipped. The transcoder (about 7,000 lines over #616 and #630) sits behind the
transcodefeature.Cargo.tomlsaystools/build-proxy-wheel.shnever enables it, because it builds openh264 from source. The shipped surface iscaps.rs,ackwindow.rsand the changes tosession.rs,backend.rs,relay.rs,metrics.rs,listen.rsandmain.rs: about 2,400 lines. That is the floor checklist's item 5 here: a build that enabled the feature by mistake would publish an unreviewed decoder in the dev wheels. -
The measurement tools have one test file, and nothing runs it.
tools/shaped-link/grew through four phases:run-matrix.shto 643 lines,summarise.pyto 605. Its only test istest_quality.py, which neithertox.ininor.pre-commit-config.yamlmentions. Every verdict in phases 1, 3, 4 and 5 was computed bysummarise.py. This is a candidate for 6d, not yet a finding: it is a rig, not shipped code. -
Several verdicts were overturned by later phases, and phase 5 corrected five pages for it. Phase 1's backpressure finding was null; phase 3 found the ACK window bounds the backlog but does not cap throughput; phase 5 withdrew the GLZ half of item 9, narrowed the crash claim, and showed the MJPEG codec was Ryll's choice, not a proxy fault. Drift between the ten
docs/performance/pages is the failure 6e looks for first. -
Phase 2 is still
In progress. What remains is sending the kernel fix to dri-devel. It changes nothing in kerbside, and nothing in kerbside-patches unless review asks for a v2 (decision 6).
The master plan's phase 2, phase 3 and phase 6 text and the
docs/plans/index.md row were corrected at source in the planning
commit, for findings 1 to 3. Later steps need not redo that.
Decisions¶
-
Audit merge by merge. For each kerbside merge
Mcarrying Python or shell, the range isM^1..MwithAUDIT_PATHSunset. That is exactly one pull request's diff, and it sidesteps #491 and #638; the Proxmox and deployment-options audits did the same. Wave 1 runs on #478, #616, #630 and #637. #494 and #505 carry only Rust and docs, and #654 only docs, so a tox run there tests a tree the diff did not touch. Their Rust is gated by decision 2 and their docs read by 6e. The judgment agents read all seven diffs together, because what the phases did to each other is the shared block's real point, and each brief names a cross-phase question. -
Rust is gated by CI's own runs. 6a records
rust.yml's push run on each of the five merges that touchedrust/**. Because #616 predates the feature steps, 6a also records the newestdeveloprun, which builds and tests--features transcodeon today's tree. A missing or failed run falls back to the Makefile'slintandtesttargets in Docker, against a detached worktree of that merge. -
kerbside-patches gets the floor checklist, not kerbside's runbook. The v4 shared block says so in terms (survey finding 2). Its blast radius adds questions of its own, which 6h asks:
- Does anything under
upstream/become visible to the OpenStack tooling, which discovers projects as./<project>/config.yaml? - Do the rigs assume a scratchpad or home path?
- Is the rig code imported from elsewhere licensed to be here?
- Does every patch carry the attribution its upstream requires
(
Assisted-by:, and noSigned-off-byuntil Mikal adds his)? - Can
build-spice.shremove anything it did not create?
This audit is also what kerbside-patches' own upstream-series push audit can cite for #1748 and #2024 when it runs.
-
The carried qemu v2 series is reviewed here; the submitted series are not. The spice-server and Linux series go to their upstream maintainers, and that review is their audit. qemu v2 is
disposition: carry. No qemu maintainer will read it, and the master plan says its audit is kerbside-patches' own. It is not in any image today. Whether it reaches the Kolla nova-libvirt image is an open question in upstream-series' Unresolved. If it does, it runs in every hypervisor's display path, parsing guest-controlled damage. So 6i reads its four patches for memory safety and correctness now, while that decision is still open. A reviewer could argue either side: that code nobody ships needs no review, or that the spice series deserves the same read before it is sent. The spice series had phase 5's own review and measurements, and goes to a maintainer who takes AI-assisted patches. The qemu series will only ever be read by us. -
ryll #406 is audited here, under ryll's runbook. The shared block expects a landing in another repository to be audited there and cited. #406 never was (survey finding 3), so 6g runs ryll's
PUSH-AUDIT.mdoverfdb5ead5b4^1..fdb5ead5b4, waves 1 and 2, in a detached ryll worktree. Its findings go to ryll as issues, or as a ryll follow-up pull request if they are fixes worth making. -
The audit runs now, with phase 2 unfinished. Phase 2's remaining work is an email. Its patch text was imported in #1748, which 6h audits. Waiting for dri-devel would hold the QUIC phases for weeks for no benefit to the audit. If review asks for a v2 of the kernel patch, it lands in kerbside-patches as its own pull request, and its upstream review is the audit. The plan stays
In progressuntil phase 2 isComplete, whatever this phase finds. This is the decision a reviewer is most likely to argue with. -
Findings are fixed in follow-up pull requests, one per repository that has any. This phase's pull request is the record: the ranges, every wave's result, and every finding with its triage. Once it merges, the kerbside follow-up records this phase's merge commit and marks phase 6
Complete. The kerbside-patches and ryll follow-ups, if any, are cited there. If nothing needs fixing anywhere, this pull request marks phase 6Completeitself, with noMergedcell. -
One 2d agent for kerbside, reading the transcoder at lower depth. The shipped surface is about 2,400 lines (survey finding 6), which one opus agent can hold. The transcoder is read for one question only: can it run in a shipped build? That means the feature gate, the wheel build script, and what
--transcode-displaydoes without the feature. It is not read line by line for memory growth or decoder bugs. Those would matter only if it ever ships, and the phase 3 verdict was no-go. -
Keep the record public-safe. The Outcome holds verdicts, paths, commands and counts. It quotes no hostname, address, ticket or token, even from a rig script or a captured log. The rigs and the measurement pages were written on a private host, so the floor checklist's item 3 is live here. The definition of done runs a probe over the text this phase adds.
Step plan¶
6a runs first, because kerbside's wave 2 is only worth spending on if wave 1 passes. 6g, 6h and 6i work in other repositories, in their own detached worktrees under their scratch directories, so they start alongside 6a. 6b to 6f only read this worktree, so they spawn in parallel once 6a is green. Each writes only to its own scratch directory. 6j depends on all of them.
Every kerbside agent works from the Audit range table. "The seven
diffs" means git diff M^1 M -- . ':!docs/plans' for each kerbside
merge with non-plan content, oldest first. An agent reads docs/plans/
only for what its brief asks. Mutation runs happen on copies, never on
a worktree, one per run under timeout (300 s for Python, 900 s for a
Rust build in Docker), and a timeout counts as killed. No agent prints
a hostname, address, ticket or token from a rig, fixture or log; each
reports names and counts only.
| Step | Effort | Model | Isolation | Brief for sub-agent |
|---|---|---|---|---|
| 6a | low | sonnet | none | Wave 1, kerbside. For each of 5204616, bba7115, 7919ead and a90f17f, oldest first, run AUDIT_RANGE="$M^1..$M" tools/audit/wave1.sh with AUDIT_PATHS unset, and report each exit code and its advisory output (codes are in PUSH-AUDIT.md under Wave 1). Do not use plan-range.sh (decision 1), and never change a range to make a run pass. If any run exits 6, stop and report. The flake8 gate ignores AUDIT_RANGE (#488), so also run flake8 at 120 columns from the tox environment over the Python files each merge added or changed that still exist, and report per merge. Then Rust (decision 2): with $HOME/bin/ci-status shakenfist/kerbside runs --branch develop --limit 200, find rust.yml's push run on 5204616, eab2c57, 9d1b64d, bba7115 and 7919ead, and the newest develop run of rust.yml; report each conclusion and run ID, and confirm the newest run executed both --features transcode steps. If any is missing or failed, run make lint and make test in rust/kerbside-proxy/ (Docker) against a git worktree add --detach of that merge in your scratch directory, and remove the worktree afterwards. Report in under 350 words. |
| 6b | low | sonnet | none | Style conformance, the judgment portion. Execute PUSH-AUDIT.md's "Style conformance — judgment portion" brief over the seven diffs, in place of git diff develop...HEAD. The conventions are in AGENTS.md. The material is the Python in #478 (config.py, proxy_supervisor.py), the Rust in #478, #494, #505, #616 and #630, and the rig in tools/shaped-link/ (shell, Python, C). Look hardest at config (the socket options in #478: pydantic, defaults, inline documentation, and etc/kerbside.conf.example), logging in the Rust (levels, and whether a client-supplied value reaches a log line), and the field-rename bullet (#494 and #505 changed what rpc.rs carries; did any field change units or meaning without a rename?). Apply the Python rules: single quotes except docstrings, no triple single quotes, 120 columns. Say "no material" for any bullet with none. Report in under 300 words. |
| 6c | medium | sonnet | none | Wave 2 mechanical, plus 2a code quality. Run AUDIT_RANGE="$M^1..$M" tools/audit/wave2-mechanical.sh once for each of the seven merges, and report its output; it never exits non-zero on findings. Then execute the 2a brief in PUSH-AUDIT.md over the seven diffs, with that output as input. Its shared blocks bind: python-version-discipline, comment-proportion, source-file-size and plan-references-in-code. The size block has material: transcode/geometry.rs (1,193), transcode/mod.rs (1,120), transcode/tests.rs (1,242), session.rs (982), switch.rs (883), backend.rs (872), relay.rs (821), run-matrix.sh (643), summarise.py (605). For the Rust, say which file the plan pushed past a mark, against which merge. Cross-phase questions: run-matrix.sh and summarise.py grew in four phases each; has either kept modes or code paths no later phase uses? Phase 3 copied andris's update pipeline into src/transcode/updates/ at a named andris commit; is that commit recorded in the code, so the copy's drift is visible? Floor checklist item 2: did any diff add something that runs unattended (the two rust.yml steps run on push and fail the run; say whether anything else does)? Item 4: #494 and #505 changed rpc.rs and the ryll pin, and #616 bumped Cargo.lock; grep this repository, ../ryll and ../kerbside-patches for any consumer of a removed or renamed item, and state the greps. Report blocking or advisory per finding, with file and line, in under 450 words. |
| 6d | medium | sonnet | none | 2b test review over the seven diffs. The functional-test-coverage shared block binds. Answer directly whether each guard below fails if what it guards regresses, by mutating a copy of the tree in your scratch directory. Python: run only the relevant test file under timeout 300. Rust: make test in the copy's rust/kerbside-proxy/ under timeout 900, at most eight Rust mutants in all, and a timeout counts as killed. The guards: the socket-option config validation and its pass-through to the proxy's command line (config.py, proxy_supervisor.py, test_config.py, test_proxy_supervisor.py); caps.rs rewriting a client's capabilities, including a malformed or oversized link message and the codec-preference passthrough; #505's follow-ups in session.rs; ackwindow.rs holding the backlog at 2W+1, and what a client ACK of zero, or far beyond the window, does; the new metrics' label set staying bounded (metrics.rs); and, under the transcode feature, switch.rs refusing to switch while GLZ or streams are live. Survey finding 7: is tools/shaped-link/test_quality.py run by anything, does it pass today, and which summarise.py paths that produced a published verdict have no test at all? Is that a finding against the plan, or a rig's normal standard? Keep the mutations in a script in your scratch directory and give its path. Report in under 400 words. |
| 6e | medium | opus | none | 2c documentation review over the seven diffs and the plan-only merges. Its four shared blocks bind (readme-discipline, llm-doc-discipline, diagram-discipline, plan-phase-references). Hold the pages together: the ten pages under docs/performance/, including dbus-helper/summary.md, docs/proxy-architecture.md, docs/configuration.md, docs/testing.md, docs/development.md, docs/direct-qemu-harness.md, docs/spice/protocol-overview.md, spice-link-protocol.md and capabilities.md, ARCHITECTURE.md and AGENTS.md. Survey finding 8 is the first question: does any page still state something a later phase overturned? In particular the backpressure finding, whether the ACK window caps throughput, GLZ invalidations as a server defect, which clients trigger the connect crash, and why streaming runs sent MJPEG. Check each against the newest page that owns it, and say which is right. Then the cross-repository check: do docs/performance/spice-server-patches.md and kerbside-patches' upstream/spice/README.md and upstream/README.md (read at origin/develop with git -C ../kerbside-patches show) agree on every number and verdict? Does docs/configuration.md describe #478's options as config.py defines them? Check every Future work entry in the five phase plans and the master plan for an issue, or somewhere live that still tracks it, reading GitHub only with gh issue view, gh issue list and gh pr view. Note that the sysroot bug in the master plan has no issue yet; Mikal is to file it. Grep README.md, AGENTS.md, ARCHITECTURE.md and docs/ outside docs/plans/ for "phase |
| 6f | high | opus | none | 2d security review of kerbside's shipped surface: the diffs of #478, #494, #505, #616 and #630, outside src/transcode/ (decision 8). Its path-traversal-review shared block binds. The material is: caps.rs and its callers in session.rs and backend.rs, which parse and rewrite a client's link message before the backend sees it (bounds on every length and count, what reaches the backend unchanged, and whether a client can make the proxy advertise a capability the operator did not allow); ackwindow.rs and relay.rs, which count client ACKs and server messages (integer overflow, unbounded state per connection, and a client that never ACKs); metrics.rs (label cardinality a client can drive); the socket options in listen.rs, backend.rs, main.rs and config.py (what an out-of-range value does, and whether one reaches setsockopt unchecked); and #505's changes to rpc.rs. For the transcoder, answer only decision 8's question: can it run in a published wheel? Check Cargo.toml's features, tools/build-proxy-wheel.sh, rust.yml, and what --transcode-display does in a build without the feature. That is the floor checklist's item 5. Item 3: did any of the seven diffs, the two PNGs and one JPEG under docs/performance/, or docs/performance/dbus-helper/andris-sessions.txt put a private hostname, an address other than loopback or documentation ranges, a username or a token into this public repository? Check the images by viewing them. Also check the rig in tools/shaped-link/ for commands run as root, tc or namespace setup left behind on failure, and temporary paths an attacker on the host could pre-create. Report severity, file, line, class and fix per finding, in under 550 words. |
| 6g | high | opus | none | Audit ryll #406 under ryll's own PUSH-AUDIT.md (decision 5). First confirm survey finding 3: search ryll's docs/plans/ and the bodies of ryll's push-audit pull requests since 2026-09-26 (#411, #630, #633, #657, with gh pr view) for any audit whose range includes fdb5ead5b4. If one does, report it and stop. Otherwise create git -C ../ryll worktree add --detach <scratch>/ryll-406 fdb5ead5b4, and in it run wave 1 with AUDIT_BASE=fdb5ead5b4^1 AUDIT_HEAD=fdb5ead5b4 tools/audit/wave1.sh, as ryll's runbook describes, in Docker. Then execute ryll's judgment briefs, style, 2a, 2b, 2c and 2d, over that one diff yourself. Weight 2d: link.rs and client.rs parse link messages, and kerbside's proxy uses them to read an untrusted client's link message (rust/kerbside-proxy/src/caps.rs). Check every length, count and offset taken from the wire, and what a truncated or oversized message does. Also check whether ryll's later commits, up to the rev kerbside pins in rust/kerbside-proxy/Cargo.toml, changed the same functions, and if so audit the pinned version's code instead, saying so. Remove the worktree afterwards. Report severity, file, line and fix per finding, and say for each whether it is still present at ryll origin/develop, in under 500 words. |
| 6h | medium | sonnet | none | The floor checklist for kerbside-patches #1748 and #2024 (decision 3). Work in git -C ../kerbside-patches worktree add --detach <scratch>/kp origin/develop, and remove it afterwards. Run pre-commit run --files over every file the two merges added that still exists, and report per hook. Then give a verdict, or a sentence saying why it does not apply, on each floor item in PLAN-TEMPLATE.md's plan-push-audit-phase block: 1, both landings are merge commits read against their first parents; 2, does anything added run unattended?; 3, scan every added file, including results/, patch headers and cover letters, for a secret, a token, a private hostname, an address other than loopback or documentation ranges, or a home or scratchpad path; 4, did either merge remove or rename anything a consumer still reads? (.gitignore and .pre-commit-config.yaml changed; grep for the old paths); 5, does upstream/ publish anything? Then decision 3's own questions: discovery (does find . -maxdepth 2 -name config.yaml, or a */config.yaml glob, now find an upstream/ project? Name the scripts you checked); scratchpad or home paths in the rigs; the licence of the imported rig code (upstream/qemu/rig/, upstream/linux/rig/), and whether each file says where it came from; patch attribution (each series' trailers against its upstream's rule, as upstream/README.md and each project's README state it); and whether upstream/spice/rig/build-spice.sh can remove anything it did not create. Note, for the follow-up, that docs/plans/upstream-series.md's status table is stale (survey finding 2). Report blocking or advisory per finding, in under 450 words. |
| 6i | high | opus | none | Review the carried qemu v2 series (decision 4): the four patches in kerbside-patches' upstream/qemu/series/v2/, read at origin/develop with git -C ../kerbside-patches show, against qemu at the base_sha in upstream/qemu/config.yaml. Clone qemu shallowly at that SHA into an empty directory under your scratch directory if you need the surrounding code, and do not build it. The question is what the series does with guest-controlled input: damage rectangles from the display device, surface dimensions, and the column-gap split. Check every rectangle's arithmetic for overflow and clipping against the surface, the bounded damage list's bound and what happens past it, the lifetime of anything queued to spice-server across a surface resize or display switch, and what max-refresh-rate pacing does at zero or an extreme value. Check that the 12-case unit test in ui/spice-damage.c's patch covers the edges the code handles. Report severity, patch, function and fix per finding, in under 500 words. Say plainly if a finding would matter only once the series is built into an image. |
| 6j | high | (management session) | n/a | Triage and record. Take 6a to 6i's reports. Reproduce every finding against the tree before accepting it. Classify each as blocking or advisory, and decide: fix in that repository's follow-up, decline (with the reason), or file (if it predates the plan, using git log -S or blame against the merges in Audit range). Show Mikal the wording of any issue before filing it. Give a verdict on each floor item for kerbside and for kerbside-patches. Write it all into this plan's Outcome, per decision 9. Commit subject: "Record the SPICE performance push audit." If nothing needs fixing anywhere, also mark phase 6 Complete in the master plan and docs/plans/index.md, with no Merged cell, and leave the plan In progress (decision 6). |
Risks and mitigations¶
| Risk | Mitigation |
|---|---|
An agent audits the wrong content: a contiguous range, develop...HEAD (empty here), a stale develop, or the unresolvable 9d1b64d6. |
Decision 1, the corrected cell, and every brief names its merges. 6j checks each 6a and 6c run names its range, and that there are four and seven runs respectively. |
The kerbside-patches audit borrows kerbside's runbook, and reports tox results for a repository with no tox gate over upstream/. |
Decision 3. 6h's brief names the floor checklist and the repository's own pre-commit. |
| 6g audits #406 as it merged, while kerbside runs a later ryll rev with different code. | 6g's brief compares against the pinned rev and audits that where they differ. |
| An agent copies a hostname or address from a rig, a result table or a screenshot into its report, and from there into the plan. | Every brief forbids printing values, and decision 9's probe runs over the text this phase adds before the commit. |
| A mutation run damages a worktree, or runs forever. | Mutations run on copies, one per run under timeout. 6j checks git status --short is empty in every worktree before committing. |
| Parallel agents trip over each other's builds. | Only 6a runs tox in this worktree, and alone. 6d works in a copy. 6g, 6h and 6i work in detached worktrees of other repositories. Docker builds share only the cargo cache, which is safe to share. |
| A finding is real but predates the plan, and gets fixed here, widening the follow-up. | The push-audit rules: file it as an issue. 6j decides with git log -S or blame against the merges. |
| Phase 2's v2 kernel patch, if review asks for one, lands after this audit and is never audited. | Decision 6: it lands in kerbside-patches as its own pull request, and the upstream review is its audit. Phase 2's section says so. |
Definition of done¶
- 6a ran
wave1.shfour times, once per merge carrying Python or shell. The execution notes give each exit code and the per-merge flake8 result. - 6a recorded
rust.yml's conclusion and run ID for 5204616, eab2c57, 9d1b64d, bba7115 and 7919ead, and for the newestdeveloprun with its transcode steps, or the result of the Docker run that replaced a missing one. - 6c ran
wave2-mechanical.shseven times, likewise. - 6g either cited an existing ryll audit covering
fdb5ead5b4, or ran ryll's wave 1 over it and recorded the exit code. - 6h recorded a verdict on each of the floor checklist's five items
for kerbside-patches, and the
pre-commitresult per hook. - Outcome lists every finding from 6a to 6i, each with a verdict: fixed in a named repository's follow-up, declined (with the reason), or filed (with the issue). If there are none, it says so in one sentence.
- Every finding Outcome accepts was reproduced against the tree by the management session.
- Outcome gives a verdict on each floor item for kerbside (items 2 to 5) and for kerbside-patches (items 1 to 5).
-
git rev-parseresolves every SHA in the master plan's phase 1 to 5Mergedcells, in the repository each names. Checked with:grep -o '[0-9a-f]\{7,\}' <(grep '^| [1-5]\.' docs/plans/PLAN-spice-performance.md | cut -d'|' -f5), each SHA tried against kerbside, kerbside-patches, ryll and andris. - The private-data probe over the text this phase adds prints
nothing else:
git diff origin/develop -- docs/plans/ | grep '^+' | grep -n -E '[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[a-z]{2,}|\b([0-9]{1,3}\.){3}[0-9]{1,3}\b|stillhq|kasm|home\.|/tmp/claude|BEGIN [A-Z ]*PRIVATE KEY|password *[:=] *[^ <]'. The probe's own line in this plan matches itself and is expected. Any other hit is removed, or shown to be a public example address. -
git status --shortis empty in this worktree, and every detached worktree the agents created has been removed (git worktree listin kerbside, kerbside-patches and ryll). - Phase 6's status in the master plan and
docs/plans/index.mdmatches decision 7, and the plan's status staysIn progress. -
pre-commit run --all-filespasses.
Bugs fixed during this work¶
None yet.
Future work¶
None yet.
Execution notes¶
- 2026-10-31 AEDT, back brief. Mikal kept decision 6 (the audit runs with phase 2's kernel submission outstanding), decisions 4 and 5 (6i reviews the carried qemu series, 6g audits ryll #406), and decision 7 (fixes in follow-up pull requests, one per repository). He gave this session permission to push and open the pull request once the definition of done passes, and to file the 6j issues after showing him their wording.
- 6a, 6g, 6h and 6i started together; 6b to 6f started once 6a passed. No agent wrote to this worktree, and every detached worktree an agent created in kerbside, kerbside-patches and ryll was removed.
- 6a ran
wave1.shon 5204616, bba7115, 7919ead and a90f17f: exit 0 on all four. Direct flake8 at 120 columns (.tox/shared/bin/flake8) over each merge's added or changed Python was clean. - 6a could not find
rust.yml's push runs for 5204616, eab2c57, 9d1b64d and bba7115:ci-statusreturns at most the newest 100 runs, which reach back only to 2026-10-28. For those four,make lintandmake testran in Docker against a detached worktree of each merge, and all passed.rust.ymlpassed on 7919ead (run 40754407510), and the newestdeveloprun (40845903771) passed both--features transcodesteps. The Docker runs built the default features only, so bba7115's transcoder is covered by today's run, not its own. - 6c ran
wave2-mechanical.shon all seven merges: exit 0 on each. - 6d ran 14 mutants on a copy of the tree, 6 Python and 8 Rust, and 12 were killed. The survivors are kerbside follow-up items 9 and 10. 6g ran 2 mutants on a copy of ryll at fdb5ead5b4. Both were killed, one only by the 900 s timeout (ryll#672, item 1).
- 6g confirmed survey finding 3: no ryll audit range contains fdb5ead5b4. kerbside pins ryll b33dea44, where two of #406's functions have since changed, so 6g audited the pinned code.
- Departures from the plan:
- 6g's findings went to ryll as issues (#671, #672) rather than a ryll follow-up pull request. Decision 5 allowed either. All are advisory, and one predates #406.
- 6i's high finding is filed (kerbside-patches#2032), not fixed in the follow-up. A fix means a v3 series, a qemu build and a re-run of the rig, and the series is in no image. The kerbside-patches follow-up adds a warning that v2 must not be built into one until it is fixed.
- The master plan's Future work entries owned by kerbside are left
there, not filed. While the plan is
In progressthat section is a live record. The entries owned by ryll, andris and kerbside-patches were filed in those repositories, and the master plan now cites the issues. - 6j filed, with the wording Mikal was shown, on 2026-10-31 AEDT: kerbside#667, kerbside-patches#2032 and #2033, ryll#671, #672 and #673, and andris#62.
Outcome¶
Audited on 2026-10-31. Wave 1 and wave 2's mechanical checks passed on
every kerbside merge, and on ryll #406. The judgment agents raised no
critical or medium finding in kerbside, kerbside-patches or ryll. The
one high finding is in the carried qemu v2 series, which is in no
image. The management session reproduced every accepted finding
against the tree. Fourteen kerbside items and six kerbside-patches
items go to follow-up pull requests. Seven issues are filed. Phase 6
stays In progress until the follow-ups land, and the plan stays In
progress until phase 2 is done.
Wave results¶
| Check | Result |
|---|---|
6a, wave1.sh per merge carrying Python or shell |
exit 0 on 5204616, bba7115, 7919ead and a90f17f; flake8 clean on each |
| 6a, Rust | make lint and make test passed in Docker on 5204616, eab2c57, 9d1b64d and bba7115; rust.yml passed on 7919ead (40754407510) and on today's develop with the transcode steps (40845903771) |
6c, wave2-mechanical.sh per merge |
exit 0 on all seven; one justified noqa: E402 (tools/shaped-link/test_quality.py); no TODO, assert, broad except or new dependency |
| 6d, mutation | 14 mutants, 12 killed; survivors are follow-up items 9 and 10 |
6g, ryll wave1.sh at fdb5ead5b4 |
exit 0: pre-commit, rustfmt, clippy -D warnings, cargo test, wave 1b |
6h, kerbside-patches pre-commit over the two merges' files |
exit 0; shellcheck, skillsaw and the line-reference check passed; nothing lints upstream/**/*.py or *.c (kerbside-patches item 6) |
Floor checklist: kerbside¶
- Item 1, landings: every phase landing in this repository is a merge commit read against its first parent. The plan-only merges are listed in Audit range. andris #56 is cited; ryll #406 and the two kerbside-patches merges were audited here.
- Item 2, unattended work: met. The only addition is two steps in
rust.yml, which run on push and fail the run. Nothing else runs unattended. - Item 3, secrets and addresses: pass. The added lines hold only loopback addresses, an RFC 1918 address in a pre-existing test mock, and RFC 1918 addresses on the rig's namespace-only veth pair. Ticket values are dummy rig constants. All five images were viewed: test patterns, a qemu version in a title bar, and no metadata.
- Item 4, removed or renamed: no consumer is left. #494 and #505
removed or renamed nothing public; the
rpc.rschanges are to the test mock. Greps over this repository, ryll and kerbside-patches atorigin/developforMockService,spawn_mock,caps::,ackwindow,--transcode,notsentandbackend_rcvbuffound no outside consumer. - Item 5, publishing: fails closed. The transcoder cannot reach a
published wheel:
default = [],tools/build-proxy-wheel.shpasses no features, and both wheel workflows build only through it. Without the feature,--transcode-displayis an unknown argument and the proxy refuses to start. Nothing asserts that a wheel was built without it (kerbside item 12).
Floor checklist: kerbside-patches¶
- Item 1: pass. #1748 and #2024 are two-parent merges, read against their first parents.
- Item 2: does not apply. Nothing added runs unattended; the rigs are run by hand.
- Item 3: pass. No secret, private hostname, or address outside loopback and documentation ranges. The emails are public patch authors, mailing lists and attribution trailers.
- Item 4: pass. Both merges only add files, widen two hook
patterns and add
.gitignoreentries. - Item 5: pass.
upstream/publishes nothing, and image tags hash only the directoriesfunctional-tests.ymlnames, which excludeupstream/. - Decision 3's questions: the OpenStack tooling cannot discover an
upstream/project (_build/assemble-source.sh,imagebuild.sh,bump-source-shas.sh,test-patches-for-ci.sh,rebase-with-claude.shandrepush-openstack.shchecked). No rig depends on a scratchpad or home path. The rig code is original apart from one function inbench.c(kerbside-patches item 2).build-spice.shremoves only an empty directory or its own output.
To fix in the kerbside follow-up pull request¶
| # | Source | Severity | Where | Finding | Introduced by |
|---|---|---|---|---|---|
| 1 | 6e | blocking | docs/performance/dbus-helper-measurements.md, the stream-coverage bullet |
Says the earlier pages report H.264 and that why spice-server chose MJPEG "was not established". spice-server-patches.md established it: Ryll's own preference. #654 corrected the sibling pages and missed this one |
a90f17f |
| 2 | 6e | blocking | docs/performance/streaming-rebaseline.md, first caveat |
Says spice-server chose H.264 because Kerbside advertised Ryll's capabilities (#477). The client was Ryll, which asked for H.264 first; the proxy relayed correctly | 5204616 |
| 3 | 6e | advisory | docs/performance/transcoding-spike.md (summary and the crash paragraph), transcoding-measurements.md |
Describe the connect crash as 0.15.2's, triggered by any connect. Only a client that sends its codec preference early triggers it, and 0.16.0 and master crash too. The "every switch" claims hold, because the transcoder's backend dial always sends a preference | 7919ead |
| 4 | 6e | advisory | docs/performance/proxy-backpressure.md, the side finding |
Its bold lead still says the ACK window caps throughput, corrected only further down. ack-window.md is right |
5204616 |
| 5 | 6e | advisory | docs/performance/spice-server-patches.md, the rate-control item |
Says "stream frames" where the measurement and the page's own results say display messages. The master plan's entry is corrected in this phase's record | 46c3e98 |
| 6 | 6f | low | rust/kerbside-proxy/src/caps.rs; backend.rs, the client channel caps |
The client's channel caps reach the backend verbatim, so the reply mask limits nothing: a client can enable spice-server features Kerbside never offered. Say in caps.rs that the reply mask is not a security boundary, and test what the allowlist does with a server message that only an unoffered capability enables. If that message is relayed, mask the forwarded caps to the reply set plus the codec bits |
eab2c57 |
| 7 | 6f | low | tools/shaped-link/quality.sh, QUALITY_VENV |
Defaults to a fixed path under /tmp, and runs that venv's python if it exists, so another local user can plant one. Default under ${XDG_CACHE_HOME:-$HOME/.cache}, or refuse a venv the user does not own |
7919ead |
| 8 | 6f | advisory | kerbside/config.py, the two socket options; the proxy's matching flags in main.rs |
Bounded at 2^32-1, but the kernel takes a signed int, so values above 2^31-1 are silently reinterpreted. Cap both at 2^31-1, in config and in clap | 5204616 |
| 9 | 6d, 6f | advisory | rust/kerbside-proxy/src/ackwindow.rs, the ACK arm |
in_flight -= window is unchecked; use saturating_sub. The documented over-ACK behaviour, and a window of 0, are untested: a mutant clamping in-flight at 0 survived |
bba7115 |
| 10 | 6d | advisory | kerbside/tests/unit/test_config.py, test_negative_values_are_rejected |
Covers only PROXY_BACKEND_RCVBUF_BYTES; a mutant dropping ge=0 on PROXY_CLIENT_NOTSENT_LOWAT_BYTES survived. Test both |
5204616 |
| 11 | 6g | advisory | rust/kerbside-proxy/src/backend.rs, BACKEND_CONNECT_TIMEOUT's comment |
Says the crate's connect has no timeout of its own; at the pinned rev it wraps link and auth in one. Reword, keeping the per-attempt budget's reason | bba7115 (the ryll pin bump) |
| 12 | 6f | advisory | tools/build-proxy-wheel.sh |
Nothing asserts a wheel is built without the transcode feature. Refuse non-empty default features or [tool.maturin] features |
bba7115 |
| 13 | 6c | advisory | relay.rs and transcode/mod.rs, the control-plane termination log |
The same literal, which CI matches on, is written twice. Share one constant | bba7115 |
| 14 | 6b | advisory | kerbside/config.py, PROXY_CLIENT_NOTSENT_LOWAT_BYTES's description |
The string is split mid-phrase ('... in ', 'bytes: ...'). Rejoin it |
5204616 |
To fix in the kerbside-patches follow-up pull request¶
| # | Source | Severity | Where | Finding | Introduced by |
|---|---|---|---|---|---|
| 1 | 6i | high, filed | upstream/qemu/README.md; docs/plans/upstream-series.md Unresolved |
The v2 series' diff helpers can read out of bounds when the guest writes its framebuffer mid-diff (kerbside-patches#2032). Say in both places that v2 must not be built into an image until that issue is fixed | b53aa39c7e |
| 2 | 6h | advisory | upstream/qemu/rig/bench/bench.c; upstream/README.md |
bench.c reproduces qemu's qemu_spice_create_update(), which is GPL, with no notice, and upstream/README.md never states the licence of what upstream/ carries. Add both |
b53aa39c7e |
| 3 | 6h | advisory | upstream/linux/series/v1/0001-*.patch |
Its trailer is Assisted-by: LLM. Use the form the kernel's coding-assistants.rst documents before the patch is sent; check the current text, which this audit did not |
b53aa39c7e |
| 4 | 6e | advisory | upstream/spice/series/v1/0000-cover-letter.patch |
Says "stream frames" where the results say display messages, as kerbside item 5 | 2d7b626422 |
| 5 | 6h | advisory | docs/plans/upstream-series.md, Status |
Phase 1 reads In progress though #1748 merged, and the table has no Merged column. Record b53aa39c7e, and cite this audit for its phase 5 |
b53aa39c7e |
| 6 | 6h | advisory | .pre-commit-config.yaml |
Nothing lints upstream/**/*.py. Widen the flake8 hook to it |
b53aa39c7e |
Filed¶
- kerbside#667:
docs/spice/spice-link-protocol.mdcontradicts itself on whether a token is single-use; the code makes it reusable until expiry. Predates the plan. - kerbside-patches#2032: the qemu v2 diff helpers' unbounded loops (high), a zero-width box, a timer spin above 1000 Hz and deep recursion (6i).
- kerbside-patches#2033: the sysroot
build.shcannot extract what it downloads (found in phase 5). - ryll#671: link errors other than
MissingRequiredCapsare untyped strings, which kerbside string-matches. Predates #406. - ryll#672: #406's follow-ups: an oversized-reply test that hangs instead of failing, a check kerbside has to re-implement, two files past the size mark, and ryll's wave 2 test counting.
- ryll#673: the ryll items in the master plan's Future work, plus
ryll's
streaming-videorecommendation, which the measurements contradict (6e). - andris#62: the five andris items in the master plan's Future work.
Declined, and why¶
- The transcoder's size and comment proportion:
transcode/mod.rsis 1,120 lines, andgeometry.rsandtests.rsare past 800 (6c, advisory). The transcoder is an experiment the phase 3 verdict declined, behind a feature no wheel enables; splitting it is work on code nobody is changing. The same holds for 6c's low-confidence note that the backend task is detached rather than aborted if the outer future is dropped. relay.rs,backend.rsandsession.rspast 800 lines (6c, advisory). Each crossed the mark by a little, in a merge that added coherent code. A split is its own change, along seams the next change to those files will show.- Type hints on
config.py's validator (6c). No function in the file has them; adding them is a whole-file change. tools/shaped-link/test_quality.pyis run by nothing, andsummarise.pyhas no tests (6d, survey finding 7). The rig is not shipped, the measurement phases are over, and each verdict's raw results and summary tables are published, so a wrong exclusion is recoverable from the data. Wiring numpy into CI for a finished rig costs more than it protects.BUFFER_BDPis undocumented outside the plans (6c). It is a rig knob, described inrun-matrix.shwhere it is read.- The client's capability words are logged before authentication (6b, 6f). ryll caps them at 16 words of integers, so the volume is bounded and nothing is injectable.
- The rig's ticket is visible in the process list (6f). It is a constant dummy value for a throwaway guest.
- "scratchpad" in kerbside-patches prose, and
build-spice.sh's forgeable output marker (6h). The first names no path. The second guards a directory the operator chose. switch.rshas no refuse-to-switch code (6d). That is the design: the client leg never carries GLZ, and the first transcoded frame clears streams. The mutant removing that guard was killed.
Back brief¶
Before executing any step of this plan, back brief the operator on your understanding of the plan and how the work you intend to do aligns with it. Four points need Mikal's answer before 6a runs:
- Decision 6: the audit runs now, with phase 2's kernel submission still outstanding.
- Decisions 4 and 5: the widened scope. 6i reviews the carried qemu v2 series, and 6g audits ryll #406 under ryll's runbook. Both are opus at high effort, and the master plan named neither.
- Decision 7: fixes in follow-up pull requests, one per repository with findings.
- Whether this session may push and open the pull request once the definition of done passes, and file the 6j issues once their wording is shown.