Skip to content

Plans index

This page summarises every planning document in chronological order. Master plans decompose work into numbered phases, each with its own detailed plan file. Standalone plans track issues, follow-ups, or design decisions that do not require phased execution.

New plans should follow the structure in PLAN-TEMPLATE.md at the repo root. For pre-push audits of our own work see PUSH-AUDIT.md.

Master plans

Date Plan Intent Status Phases
2026-06-02 Automated SPICE test harness End-to-end SPICE test harness driving Uncalibrated Sextant via Ryll's control socket, with assertions against the visual digest and serial drain; replaces the OpenStack-dependent integration tests with a direct qemu/KVM lane Complete phase 1 (done), phase 2 (done), phase 3 (done), phase 4 (done), phase 5 (done), phase 6 (done), phase 7 (done), phase 8 (done)
2026-07-04 Rust SPICE proxy (kerbside-proxy) Replace the Python SPICE proxy with a Rust kerbside-proxy that talks tonic/gRPC over a UDS to the Python daemon, reuses ryll's shakenfist-spice-protocol crate, enforces L0+L1 firewall policy from day one, and ships inside the kerbside pip install via a maturin bin wheel Complete phase 1 (done), phase 2 (done), phase 3 (done), phase 4 (done), phase 5 (done), phase 6 (done), phase 7 (done), phase 8 (done)
2026-07-16 Consistency audit compliance Clear the backlog of open consistency issues filed by the daily shakenfist/development audit: four missing shared blocks in PUSH-AUDIT.md and PLAN-TEMPLATE.md, a vendored sfui copy two commits behind canonical, the retired comment addresser still deployed with contents: write on the pull request branch, and 70 files needing human review against a threshold of 5. The reading of those files is deliberately not tracked here -- decision 5 draws the boundary at the scaffolding, leaving #227 as the sole tracker, so this plan can complete while review-coverage is still failing. Surveyed 2026-08-29: two of the six findings did not survive contact with the tree -- skillsaw does run in CI and the audit's own checker is what is wrong, and the review backlog is 70 files rather than the 152 the issue claims. Promoted from a standalone plan that tracked only two GitHub settings checkboxes In progress phase 1 (complete, merged cbca9b1 -- the four shared blocks, the sfui stamp, and the settings closeout. audit-check.py against the branch goes from 6 failures to 3, leaving only phases 2-4. Two corrections landed during implementation: the plan had two block placements backwards, caught because the brief told the agent to read each block before placing it, and the master plan's own Execution table put Merged in the middle when the block it was adopting requires it last. The research step also killed the plan's assumption that the github-security audit covers the three settings ticked by hand in July: it does not check Dependabot at all, and returns pass when its API call fails), phase 2 (complete, merged 5f3c80c -- retire the comment addresser. The survey widened the blast radius twice over the master plan's sketch: pr-re-review.yml carries three references rather than one, and tools/shellcheck-wrap.sh carries a fourth nobody had noticed. One of the three is the stated reason for a live bot-comment guard, so the phase deletes the justification and keeps the guard, re-attributing it to pr-auto-review.yml, which still posts the comments that make it necessary. Every other measured criterion of the ci-review-automation audit already passes), phase 3 (complete, merged 16e6173 -- skillsaw CI detection. The survey found the sketch accurate in every particular, unusually. The change landed upstream as shakenfist/development PR 73 and nothing in kerbside moved; review there caught a false-positive the original regex would have introduced, where a word boundary matched the wrapped continuation line of a pip install, so four repositories would have passed on a mention rather than an invocation), phase 4 (complete, merged ade2788 -- review scope and session scaffolding. The survey corrected three of the sketch's claims: the backlog is 77 files not 70, the bulk is not docs/spice/, and the sketch was wrong to treat the review marks as freshly scoped work. A fourth survey finding, that no review mark had ever been signed, was itself wrong and was corrected on 2026-09-02: %G? reports N for a valid gitsign x509 signature in a clone without gpg.format set, and testing the commit objects directly finds 29 signed mark-adding commits since 2026-08-14 (30 until 2026-09-03, when the extra turned out to be a merge carrying GitHub's web-flow PGP signature rather than a gitsign attestation). The phase also absorbs review-scope-completeness, a check that arrived on 2026-08-30 and fails with 44 orphaned files, because settling scope after the grind would mean redoing part of it. Fixing scope took the in-scope count from 192 to 227 and the backlog from 77 to 104 -- worse-looking, correct, and eight better than predicted because weAudit had banked stamps on files that were out of scope at the time. The phase completes on the scaffolding: the reading itself is out of scope for this plan and is tracked by #227 alone, per decision 5), phase 5 (complete, merged 54e8d08 -- diagram discipline and mermaid linting, for #370 and #381. The survey corrected the sketch's dates -- #370 was filed 2026-08-26 and is the pre-push audit issue phase 1 already worked on, refiled against a criterion that has since grown a block, and #381 was filed 2026-08-30 -- and confirmed that #370's body is stale, still describing phase 1's already-fixed finding when its only live one is the missing diagram-discipline block. The load-bearing result is that all nine diagram-bearing files already render, measured by running the upstream linter against develop, so this is a pure adoption with no remediation hiding behind the new lane. The lane needs a docker daemon and kerbside's only lint job runs on a static runner without one, so the shipped path-filtered workflow is taken rather than folded into the gate, and stays advisory: a path-filtered workflow that a ruleset requires never reports on a pull request it skips, and blocks it forever. The review raised no actionable item and filed no issues, and the merge closed both #370 and #381. The gap it did name is that the lane does not cover its own inputs: the path filter does not match tools/mermaid-lint.sh or the workflow itself, so a pinned image bump merges unexercised), phase 6 (not started -- push audit)
2026-07-17 Backend host_subject enforcement Restore hypervisor certificate subject pinning on the proxy's backend TLS leg, lost in the Rust proxy cutover: enforce spice-common host-subject matching semantics in ryll's shakenfist-spice-protocol verifier, adopt it in kerbside, and prove both accept and refuse paths in the direct-qemu CI lane Complete phase 1 (done, ryll PR #166), phase 2 (done, kerbside PR #114)
2026-07-19 Shaken Fist VDI console tokens Cross-repo plan (master in shakenfist's docs/plans/): per-instance console authorisation via short-lived Ed25519-signed tokens minted by Shaken Fist and validated offline by kerbside; kerbside side adds an /sf-console.vv exchange endpoint mirroring the Nova flow, a jti replay table, cluster-wide scraping, and host_subject for SF consoles Complete phase 5 (done), phase 6 (done), phase 7 (done, SF mint-path test; kerbside exchange lane moved to phase 9), phase 8 (done), phase 9 (done, lane green 2026-07-31, run 30608314535) — post-merge cross-repo e2e + kerbside exchange lane
2026-08-02 Two-tier CI Adopt the shakenfist/shakenfist two-tier CI flow: smoke gates (sanity, direct-qemu, Rust, single-node sf-e2e) on PRs; oVirt and Kolla/OpenStack move to a merge-queue tier; fix the oVirt lane to actually deploy kerbside; record the oVirt front-door architecture decision Complete phase 1 (done), phase 2 (done, promoted on four green nightlies), phase 3 (done, queue live 2026-08-09; first entry caught a real failure), phase 4 (done)
2026-08-02 Use case documentation One docs page per deployment permutation, each covering value proposition, how it works, and setup. The oVirt page landed 2026-08-10 as two-tier CI phase 4 and settles the format, the docs/use-cases/ directory and the index placement; all six writable pages now exist and Proxmox stays deferred until a source driver exists. Promoted from a standalone plan on 2026-09-18 when phase 1 was planned, which brings the mandatory push-audit phase with it Complete phase 1 (complete, merged 2f0e526 -- the Shaken Fist page, chosen ahead of OpenStack so the format's second instance has the least unsettled ground under it: the VDI tokens plan is complete and sf-e2e gates it on every pull request, where the OpenStack deployment story is still moving upstream on Gerrit. The survey found the master plan's "remaining six pages are unblocked" wrong by its own table, the static-source row's precondition since met by the install rewrite, and the multi-cloud row's "stated nowhere" now one sentence and one bullet wide; all three corrected at source. It also found the index scaffolding already built -- docs/index.md carries a seven-row Use Cases table, so each page phase links an existing row rather than adding one. The load-bearing finding is not a staleness: docs/console-sources.md:67-146 already documents the Shaken Fist flow in eighty accurate lines, so the phase's central decision is what the page does not say, and six specifics are named in the definition of done as forbidden to duplicate. Review caught two factual errors that were not auto-filed as issues, so phase 2 carries them: the page asserted the backend leg is unconditionally TLS-pinned, where the proxy dials the insecure port first and escalates only on NEED_SECURED, and it claimed OpenStack has a portal to write, where Nova embeds the broker exactly as Shaken Fist does), phase 2 (complete, merged a7df5e5 -- the OpenStack page. The survey corrected nothing at source, unusually: the master plan's row and this description both hold. Its load-bearing findings are structural instead. There is no kerbside/sources/openstack.py at all -- the scrape loop skips type: openstack at main.py:175-178 and the whole implementation is class NovaToken inline in api.py:571-700 -- so this is the first page that cannot borrow either existing page's scrape-then-exchange shape. And OpenStack consoles are never subject-pinned on the backend leg, because Nova's token validation response carries no certificate subject and api.py:665 therefore leaves host_subject at its default; the decision most likely to be argued with is putting that in the value proposition rather than only in the limitations table. The Kolla-Ansible deployment change 976889 is still open upstream, rechecked 2026-09-20, so the index's claim about it survives. Review found one defect the page had introduced about TLS: it said that configuring only Kerbside's own certificate leaves the backend leg unverified, where ryll's create_tls_connector falls back to the public web trust store with hostname verification, so an internal hypervisor certificate fails the handshake instead. Corrected before landing; the lesson for later pages is that a claim about the backend TLS leg is only checkable in ryll, not in this repository. Three issues were filed from the survey and the review rounds: #451, #454 and #456), phase 3 (complete, merged 28efa6c -- the standalone page, for the static driver. The survey corrected nothing in the master plan's row, which held in every particular, but found the hazard phases 1 and 2 met in its sharpest form: docs/console-sources.md:217-298 already carries the framing -- an intended use-cases list and a "not intended for production use" paragraph -- that PLAN-demo-install.md decision 2 assigns to this page, so the phase has to move material rather than merely decline to duplicate it. The load-bearing finding is that the framing is wrong: the static console list is re-read every sixty seconds in both directions, with audit events either way, where console-sources.md and static.py's own header both say a restart is required. Two other files, demo/sources.yaml and a comment in main.py, already had it right. The decision most likely to be argued with is correcting a code comment inside a documentation phase. Three review rounds ran to completion and every actionable item was taken; the round-2 finding is the one with a life beyond the page, because it was the third phase running to state a backend TLS claim more strongly than rust/kerbside-proxy/src/backend.rs supports. Filed #459, #463, #464 and #465), phase 4 (complete, merged 8c5c042 -- the two topology pages, multi-cloud aggregation and placement. The first phase whose pages describe how Kerbside is deployed rather than what it talks to, so they add no driver and no configuration key. The survey corrected two of the master plan's own rows: aggregation is no longer stated in one sentence but on three cloud pages, one of them substantively, so the phase moves material rather than writing fresh; and the placement row claimed the WAN leg is TLS'd with host-subject pinning, where rust/kerbside-proxy/src/backend.rs:92 escalates to TLS only when the hypervisor rejects plaintext and :198-211 pins only when the source supplied a subject, which OpenStack never does. That is the fourth phase running to catch an overstated backend TLS claim, which is why the decision most likely to be argued with is sweeping the claim across all six use-case pages here rather than leaving it to phase 5. Placement is otherwise the only genuinely greenfield page in the plan: grepping every tracked markdown file found it stated nowhere but its own index row. The sweep found a sixth instance the survey had missed, in docs/proxy-architecture.md, filed as #472 rather than fixed here. What the sweep cost is the durable part: the claim is now guarded by tools/check-backend-tls-claims.py, which review moved out of sanity_checks into an ungated docs_checks job after finding that check_paths excludes docs/**, so the guard could never have run on a documentation-only pull request -- the entire class it exists for. The guard has a unit test and a committed mutation tool, tools/mutate-backend-tls-claims.py, which found four rules with no test coverage at all. Three review rounds ran 3 fix, 1 fix, 0 fix and every actionable item was taken; round two fixed a sentence round one had introduced, which is the stop signal the loop was watched for. Also renamed shaken-fist.md to shakenfist.md, and filed #468 -- console rows are keyed on identifier alone, so two sources publishing one identifier share a row and either can delete the other's), phase 5 (complete, merged 073603b -- the index slim-down and closeout. The survey found the master plan's phase 5 description held in every particular, unusually, and the load-bearing finding is not a staleness but a surviving error: docs/index.md:52-54 still says the OpenStack broker role "is likely performed by Horizon or Skyline, although this is not yet implemented", where Nova performs it itself -- the same claim phase 1's review caught and corrected on the Shaken Fist page, which nobody looked for in the index because every phase so far had put the introduction out of scope. The two sections the plan set out to move split: ### Implementation in OpenStack is deleted outright, because openstack.md was written in phase 2 to supersede it and states all six of its claims more precisely, but ### What About Bumblebee? is the tree's only copy and moves to openstack.md's broker enumeration. The decision most likely to be argued with is that destination rather than the index's own ## Related Projects section. Also collapses README's six use-case bullets to one, which phase 1's risk table recorded for the closeout, and corrects ARCHITECTURE.md and .claude/CLAUDE.md where they still describe docs/use-cases/ as holding the oVirt page. The automated review raised zero fix items, the first round in this plan to do so, and the pull request then merged directly rather than through the merge queue after its merge group hit the recurring oVirt host-provisioning flake tracked in #309, so the oVirt lane never ran green for this change), phase 6 (in progress -- the mandatory push audit over the accumulated diff of phases 1 to 5. The survey corrected two claims in the master plan at source: that the oVirt page is "audited by that plan rather than this one", where PLAN-two-tier-ci.md predates the push-audit shared block and carries neither the phase nor a Merged column, so the page's creation is audited nowhere -- permitted by the block, since a completed plan without the phase is not reopened; and that the plan carried no description of phase 6 at all, where both sibling plans holding the same obligation sketch it. The load-bearing finding is that the audit is not vacuous despite this being a documentation plan: tools/audit/plan-range.sh derives 2f0e526^1..073603b over 31 paths, and the plan landed 758 lines of Python that did not exist before -- the backend-TLS claim guard, its mutation tester and their tests -- plus the docs_checks CI job and a change to kerbside/sources/static.py. The decision most likely to be argued with is running the 2d security agent at all on a docs plan; it is there because the plan shipped two new executables, one of which rewrites tracked files in place. That judgement paid: 2d produced the phase's one substantive code finding. Completed 2026-09-26 with no blocking findings and nothing above LOW. Wave 1 failed once, on a real defect -- phase 4 shipped two reporting CLIs without the audit-allow-print marker that tools/check-pypi-storage.py already establishes for that shape. Five items were fixed in the audit's own pull request, one was declined in writing, and three were out of range and filed: #488, the wave 1 flake8 gate ignoring AUDIT_RANGE and reporting "No python files in change" as a pass over a six-file range; #490, nothing in CI validating the docs/index.md#use-cases anchor that README.md and ARCHITECTURE.md both depend on, which escapes test_docs_links.py twice over -- line 82 skips the absolute link and line 89 strips the anchor off the relative one; and #491, the durable one, plan-range.sh unioning files touched by the phase merges and then diffing one contiguous range across them, so the unrelated issue #132 commits added 188 lines of test_db.py that three judgment agents reviewed as this plan's work. The plan is complete; Proxmox was never a phase of it and stays with PLAN-proxmox-source.md)
2026-08-08 Convert the admin UI to sfui Make kerbside the second sfui consumer: vendor sfui into the static assets, rebuild the six templates on tokens and sfui components (theme toggle, tab-strip nav, brand chrome, details disclosures, morphdom polling), drop Bootstrap/jQuery/axios, fix the accumulated markup defects, and add template smoke tests; resolves #244 and #133 Complete phase 1 (done), phase 2 (done), phase 3 (done), phase 4 (done), phase 5 (done — consoles page: disclosures, flattened connect actions, two-step terminate, inline theme-aware icons; sfui gained the .sf-btn underline suppression), phase 6 (done — sessions/sources/audit pages: disclosure-panel accordion, CA cert disclosure, total_events footnote, shared two-step terminate include; all five pages now on base-sfui.html, template-only phase), phase 7 (done — meta refresh replaced by a 30s fetch-and-morph poll with an onBeforeElUpdated hook preserving open disclosures and the armed terminate button; failed polls pin a stale-since stamp instead of reloading), phase 8 (done — both terminate routes are POST behind the X-CSRF-TOKEN double-submit header, with a SameSite=Lax cookie covering the one destructive GET that cannot convert; the survey found two CI callers the master plan missed, cleared two it named in error, and split that remaining GET out as #319), phase 9 (done — teardown: 47 files and 8.4 MB of Bootstrap, jQuery, axios and logo.svg deleted along with the base.html nothing extended, base-sfui.html renamed into place, the pre-commit and review-scope exclusions pruned, and a StaticAssetReferenceTestCase added so a dangling /static/ reference fails the unit tests. The deletion and the rename were split into two commits, against the plan, because doing both to one path in one commit made git record a delete plus a modify rather than a rename; the survey also missed tools/preview-templates.py as a live reference. #244 is closed by hand, with a comment drafted in the phase plan)
2026-08-14 A working installation path: the compose demo docs/installation.md stops at acquisition, so a reader who follows it has software and no running system: it never mentions the two processes, the database, the TLS material, or a console source. Package the migrations and add kerbside db upgrade (a wheel install currently cannot create its own schema), write the missing etc/kerbside.conf.example, build a docker compose demo with a CI lane, and rewrite the page around it; resolves #3 Complete phase 1 (done 2026-08-14 — migrations moved into the package, kerbside db upgrade/downgrade and kerbside demo token added; verified from an installed wheel run outside any checkout. Fixed two logging defects found on the failure path, including alembic's env.py disabling every kerbside logger), phase 2 (done 2026-08-15 — etc/kerbside.conf.example covers all 34 Config fields, 8 live and 26 commented at their defaults, with no value that would work if pasted; tests pin coverage in both directions and the environment-beats-INI precedence that nothing previously held, both demonstrated to fail before being trusted. Verified from the real /etc/kerbside/kerbside.ini path in a container. Found and filed #313: a malformed INI exits zero, and percent-encoded database passwords reach it through configparser interpolation), phase 3 (done 2026-08-16 — demo/ brings up MariaDB, a disk-less qemu SPICE target and kerbside under one docker compose up; verified end to end with remote-viewer over TLS, every socket on 5900 and none on 5901, with the CA travelling in the .vv. The survey caught a qemu invocation that fails on QEMU 10, which the image then confirmed at 10.0.11; implementation found three more: the then-current release predated kerbside db upgrade so the install source defaulted to the checkout until v0.5.0 shipped it, Debian trixie keeps SPICE in qemu-system-modules-spice, and docker compose exec inherits nothing the entrypoint exported), phase 4 (planned 2026-08-17 — an advisory, path-filtered lane bringing the compose stack up against the PR and asserting a proxied SPICE session. The survey rewrote the draft: every one of its five line citations now points at unrelated content, its ryll feature-flag claim is backwards, its four-step proxy-wheel recipe is one step calling a script that did not exist when it was written, and #314 removed the reason for the cargo build entirely — the dev-inclusive floor in pyproject.toml resolves kerbside-proxy 0.5.1.dev1 from PyPI on its own. Also found that no kerbside workflow uses Docker at all, so this would be the first container build in CI and the phase now leads with a runner probe; and that renovate had just bumped the demo database a major version untested, which the survey verified by hand. Done 2026-08-18 — the lane brings the stack up against the pull request in 4.5 minutes and asserts 11 things about it, including that the session crossed the TLS port and not the plaintext one (5900=12, 5901=0, matching phase 3's manual count). The probe earned its place: the runner image had no docker at all, and Debian 12 supplies neither a new enough engine nor a docker compose v2 plugin, so the lane installs from Docker's own repository in 12 seconds. Review then found three real defects, every one an assertion that reported the wrong thing — set -e had made the hang branch dead code and silently skipped everything after it, the TLS-port check was a startup-log tautology that passed whenever the proxy merely started, and the docker short-circuit skipped the proxy configuration it existed to apply; testing those fixes found three more of the same family. Also caught and fixed a MariaDB root password reaching a downloadable CI artifact, from the redaction step that ran before log collection and so could never have scrubbed it. One item was deferred to phase 5 and is now settled: PR #351 demonstrated both directions of the path filter -- no run while it held only the planning commit, a run within seconds of docs/installation.md joining the diff), phase 5 (done 2026-08-22 — docs/installation.md now runs from pip install to a proxied console, via the two-process model, kerbside db upgrade, the TLS material and the eight settings with no useful default, then the compose walkthrough, then a handoff to docs/use-cases/. Running every command from a clean clone found four defects the prose had wrong, two of them mine: docker compose ps does not print the columns claimed, ss without -H counts its own header so the socket split read 13/1 rather than 12/0, pip install does not give you demo/ so the walkthrough now starts with the clone, and remote-viewer was never installed anywhere. Review then caught a fifth, and it was the load-bearing one: the page put pip install before the OS packages it depends on, which a container test confirmed cannot work — mysqlclient ships no wheel, so a clean pip install kerbside dies in pkg-config. Verifying the fix found that bindep.txt was itself incomplete, missing python3-dev/python3-devel, so tox -e bindep would have reported the dependency list complete while an install from it still failed in gcc; confirmed on both debian:trixie and rockylinux:10 and fixed here. Also closed phase 3's inbound-link gap in README.md, docs/index.md and ARCHITECTURE.md. Planned 2026-08-16, re-surveyed 2026-08-22 before implementation because the draft predated phase 4's completion. Phases 1-3 hold and nothing links inward to demo/ yet, as phase 3 review said; but the page had grown 16 lines from another plan's docs pass that the rewrite must preserve, four of the draft's line citations had rotted, the docs/index.md ordering question was already settled, and phase 4's deferred checkbox — proving the demo-compose path filter does not fire for docs outside installation.md — was missing from the draft entirely and is now step 5e, takeable only while the pull request holds the planning commit alone. The conf-example rename and the .claude/CLAUDE.md row are both decided against rather than left open)
2026-08-14 Rolling kerbside-proxy dev releases Publish PEP 440 dev releases of the kerbside-proxy wheel to PyPI when the binary's inputs change (path-filtered: rust/**, kerbside.proto), commit a dev-inclusive version specifier so unreleased git installs (notably upstream Kolla image builds) resolve a working binary with no upstream kolla changes, and add a proto-hash contract handshake between daemon and binary as the skew backstop Complete phase 1 (done, PR #314), phase 2 (done, PR #314), phase 3 (done, PR #314 — committed proto-hash constant, --contract-hash flag, startup refusal with KERBSIDE_SKIP_CONTRACT_CHECK escape hatch), phase 4 (docs done in PR #314; bootstrap wheel 0.4.1.dev184 published 2026-08-17 once the operator registered the trusted publisher, and the committed floor was verified against the live index to resolve it with no pre-release opt-in. Tail: 4b (patch175 simplification) withdrawn 2026-08-18 after measurement showed the fallback it would delete is inert — kolla's install_pip always passes --upgrade, which does not downgrade the newer dev wheel the committed floor already installed; the Gerrit recheck (4c) completed 2026-08-29 with every Gerrit review passing Zuul CI), phase 5 (done, PR #328 — survey found publishing denser than the master plan assumed, ~365 dev releases/year with 76% of triggers being Renovate dependency bumps. PyPI has no delete/yank API for any credential (Warehouse #12810 open and blocked), so this automates the watching rather than the deleting: a weekly credential-free storage monitor that files a tracking issue, Cargo.lock dropped from the publish trigger, and a manual pruning runbook), phase 6 (done, PR #375, 2026-08-29 — no critical, high or blocking findings. PUSH-AUDIT.md over the accumulated diff of phases 1-3, 4a and 5: 14b54f3^1..2e1fd43 scoped to the 40 paths those phases touched, 4,040 insertions. Survey found both audit scripts hard-code DIFF_BASE=develop, not just wave1.sh as the master plan said, so an unmodified run is a vacuous pass; that the Rust half of the range has no mechanical coverage at all; and one known wave-1 false positive, the two print() calls in tools/check-pypi-storage.py. The tooling step also caught wave 1's two fatal style checks never having inspected a line: a BRE grep -v '^\+\+\+' matched every added line rather than the diff headers, so $ADDED was empty for any diff at all. The audit produced five fixes — one correctness bug (get_binary_contract_hash() raised UnicodeDecodeError on a binary printing non-UTF-8, escaping its documented every-failure-returns-a-reason contract), two hardening fixes (an unanchored stamped-tree grep that could disagree with its two siblings, and an issue title interpolated into a jq program), and two documentation honesty fixes (the contract handshake is a compatibility check and not an integrity control; a git install resolves an unreviewed pre-release wheel and nothing on any install path verifies the build provenance attestations that are produced). Everything else is declined in writing in the master plan, and the finding that unattended OIDC publishes share a persistent runner pool with pull-request code is tracked as #374. The security review also refuted this phase plan's own claim that phases 3 and 4 describe the contract hash both ways)
2026-09-20 Proxmox console source A Proxmox VE source driver and the transport work it needs: PVE reaches qemu's SPICE listener only through an HTTP CONNECT to its spiceproxy, which the backend dialer cannot do, and mints both credentials per request, which the authorize path has no way to ask for. Carries the feasibility research promoted out of PLAN-two-tier-ci.md's future-work section on 2026-09-20, now measured against an ephemeral PVE 9.2.20 deployment built by private prototype Ansible. Promoted to a master plan on 2026-09-24 when phase 2 was planned, which brings the mandatory push-audit phase (phase 7) with it In progress phase 1 (complete -- ticket semantics, measured against an ephemeral deployment on 2026-09-20 before the plan was scheduled; no code and no plan file, and its results are the master plan's What the measurements settled), phase 1b (complete, merged actions 5399c4f (#96), its ryll half inside phase 2's merge -- a Proxmox deployment action in shakenfist/actions and a ryll lane on it, replacing phase 2's operator-assisted step 2f. The survey found there was never a standing node, that the measurements never minted with the API token or resolved the node's FQDN off-node, and that ryll's VM lanes carry no fork guard. The decision most likely to be argued with is landing the lane's ryll commits inside phase 2's pull request. The node deploys in about seven minutes; the lane went red at the expired check alone on a deliberate-failure run, after review found it could not fail at all without pipefail), phase 2 (complete, merged ryll ea4bf67 (#402) -- HTTP CONNECT in ryll's shakenfist-spice-protocol, plus .vv proxy= support in the ryll binary, so ryll opens a Proxmox .vv without kerbside. The survey found open question 2 is a correctness requirement rather than hardening: ServerName::try_from rejects a pseudo-hostname outright, so every tunnelled handshake would fail. It also found the pseudo-hostname, which carries a signed ticket, written into ryll's logs, capture metadata and bug reports; an IPv6-literal dial bug the rewrite fixes in passing; and that kerbside's build_config escalates to TLS only on NEED_SECURED, which phase 3b must change for tunnels. Both settled open questions are recorded at source. The decision most likely to be argued with is redacting the whole tunnel target from ryll's output rather than parsing Proxmox's format to show the vmid and node. Its push audit ran after merge and raised only low and advisory findings, fixed in ryll #411), phase 3a (in progress -- minting moved into the authorize path through a source-driver hook, with ovirt.py requesting an explicit expiry and no longer storing its ticket on the Console row. The survey overturned the master plan's in-memory ticket store: a load balancer may send one session's channels to different proxy nodes, so the ticket lives on the session's token row in the shared database, under a row lock so one channel mints. It also found that no channel can arrive after the session's one-minute token expires, so an oVirt ticket covering the token never needs replacing, and settled supersession from the oVirt, libvirt, qemu and Proxmox sources: a new SPICE password keeps connected clients. The decision most likely to be argued with is that database store, which puts a live password in the database the master plan meant to keep it out of -- for about a minute and a half, where today's oVirt path leaves one on the console row for two hours), phase 3b (not started), phase 4 (not started), phase 5 (not started), phase 6 (not started), phase 7 (not started -- push audit)
2026-09-23 SPICE session performance Make SPICE sessions through Kerbside measurably faster in three horizons: proxy socket backpressure now (the relay hides congestion from spice-server), a qemu ui/spice-display.c damage-path patch series soon (unioned damage cut into 32px columns defeats stream detection on virtio-gpu), and a feasibility spike for a Rust SPICE server on qemu's D-Bus display. Carries the ranked upstream wishlist from the 2026-09-23 research. Same-day measurement overturned the proxy finding (spice-server's ACK window, not proxy buffering, bounds the backlog) and found a sticky ignore_damage_clips bug making virtio-gpu guests report full-plane damage In progress phase 1 (complete), phase 2 (in progress -- import merged, kernel fix to submit), phase 3 (in progress -- transcoding spike planned), phase 4 (not started), phase 5 (not started -- spice-server patches), phase 6 (not started -- push audit of phases 1-5), phase 7 (not started -- QUIC client leg experiments), phase 8 (not started -- QUIC implementation, if go), phase 9 (not started -- push audit of phases 7-8)
2026-09-27 RDP out: a transcoding gateway A statement of direction, not a schedule: let users on networks that allow only outbound RDP reach Kerbside consoles with a stock RDP client. A per-session gateway terminates the SPICE session with Ryll and serves RDP with IronRDP, as an opt-in fallback that keeps SPICE relay the default. Depends on SPICE session performance phase 3 (the display transcoding spike) Proposed phase 1 to phase 7 (not yet planned)

Standalone plans

Date Plan Intent Status
2026-10-08 Restrict direct console access to admins Gate /console/direct on a Keystone admin group claim and audit it (issue #134) Complete

The previous standalone plan, Use case documentation, was promoted to a master plan on 2026-09-18.

📝 Report an issue with this page