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 (complete, merged 696f5aa -- 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 the oVirt path left one on the console row for two hours. The direct-qemu lane proved both that a connected client survives set_password and expire_password and that eight concurrent channels mint once under the row lock on MariaDB. Two review rounds ran 1 fix then 0 fix; round one added a per-console guard so an abandoned mint cannot replace a newer ticket within one daemon, and serialising mints across daemons is recorded as future work), phase 3b (complete, merged c435ea3 -- a Tunnel sub-message on Target, the proxy's backend leg dialling it TLS-only through an HTTP CONNECT proxy with no plaintext attempt, and the session token row carrying the tunnel beside its ticket. The survey found the ryll pin already contains phase 2, and that the master plan's migration was aimed at the wrong table: the Console row already names the node in hypervisor, while the proxy URL, the signed CONNECT host and the TLS port all arrive with each mint, so they belong with the session's ticket. The CONNECT host is itself a credential. The decision most likely to be argued with is three named columns rather than a transport blob. Kerbside's first Proxmox lane moves to phase 5; the direct-qemu lane proved the tunnel against a CONNECT proxy in three modes, with no connect host in any artifact or the step log. CI found a test that passed only after another had installed the rustls crypto provider. One review round, with 0 fix items, made a minted tunnel missing a field a clean denial rather than a gRPC INTERNAL error), phase 4 (complete, merged 6519c3b -- kerbside/sources/proxmox.py, discovering running SPICE VMs over /cluster/resources and minting over spiceproxy, plus the direct .vv handler refusing a proxied-only source before it mints, the role's source checker and the configuration reference. The survey read PVE's source rather than its docs. It found that spiceproxy takes a proxy parameter, so the broker can CONNECT to the API host and the node's FQDN need not resolve for it. It found that the host subject can be read from certificates/info without minting, which matters because every mint replaces the VM's SPICE password. And it found that VM.Audit and VM.Console on /vms cover every call, answering open question 4 pending phase 5's proof. The decision most likely to be argued with is a console uuid of <vmid>-<smbios uuid>, because PVE reuses freed vmids. Review round 1 found that uuid could not fit: the initial schema made every console uuid column String(36), which SQLite-backed unit tests never noticed, so migration dabe2f66e369 widens them, proven on MariaDB in the direct-qemu lane. Round 2, with 0 fix items, made any failed read error the source for that scrape rather than finish without the consoles it could not read, which would have deleted them and the tokens of their running sessions), phase 5 (in progress -- kerbside's first lane against a real PVE node, on shakenfist/actions' deploy-proxmox-on-shakenfist. It deploys kerbside on the runner and relays a proxied session through spiceproxy. The survey found that the action's token is not the documented one: it is privsep=0 with PVEVMUser and PVEAuditor, and its own comments call the least privilege unproven. So the lane creates the source's token by running console-sources.md's pveum block, and proves each of the two privileges necessary with single-privilege tokens. It counts mints in PVE's own access log rather than trusting kerbside's account. The decision most likely to be argued with is the tier: nightly and advisory on Proxmox-path pull requests, as ryll's lane is, rather than a node booked on every push), 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 (complete -- transcoding spike. Steps 1a and 1b landed #477's caps fix. Re-surveyed 2026-10-28 once ryll's RDP phase 1 unblocked step 3b: step 3a turned out already delivered by andris's wire-type and encoder phases and ryll's slim renderer, and andris's paced display output becomes 3b's client half, copied for the spike and extracted into a shared crate only if the answer is go. Steps 2 and 3b landed in #616: the ACK window bounds the backlog at 2W+1 but does not cap throughput, and the transcoder exists behind a non-default feature. Steps 4 to 6 measured the transcoder, tested switching mid-session and wrote up the verdict: no-go. Transcoding wins clearly only on incompressible activity with streaming off on links of 10 Mbit or less; relay with streaming-video=all is level or faster elsewhere at a fraction of the CPU, and switching works only against a relay without GLZ or streams. The transcoder stays an experiment behind its feature, and the effort moves to making streaming-video=all safe to recommend), phase 4 (in progress -- Son of SPICE spike, planned 2026-10-29. The survey found that the helper is a qemu platform backend for andris rather than a new program, that qemu hands virtio-gpu 2D flush rectangles to a D-Bus listener with no timer or column splitting, and that Nova and oVirt cannot configure a D-Bus display. Step 1 built the picture half and passed its gate: qemu delivers virtio-gpu flushes to the listener in 0.2 ms. The platform landed in andris as andris#56 (9a997a3), and the rig measured it against in-qemu SPICE. Step 5's verdict: no-go, as measured. Cost passes (at most 1.27 times spice-server's CPU) and Shaken Fist could attach a helper, but on slow links the helper was worse than streaming off in four cells, because andris's stream heuristic counts frames sent and never fires once its ACK window spaces them. That is andris's, not the D-Bus display's, so a re-measure after an andris fix could turn the verdict), 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. Superseded by ryll's PLAN-rdp-frontend.md, which builds it as ryll --rdp first and moves it here once proven |
Superseded | phase 1 to phase 7 (not yet planned) |
| 2026-10-10 | Deployment options | A docs/deployment/ section, one page per deployment mechanism (by hand, Kolla-Ansible, containers, the ansible role), with a table in docs/index.md of which sources each mechanism configures and what CI proves, so that how Kerbside is deployed stops being indexed by which cloud it fronts (#546). Written as Kolla-Ansible moves from actively upstreamed to maintained by rebase, and on the view that a front-door Kerbside deployed independently of any one cloud is the default worth documenting. Documentation only: the image, the role move and upstream Kolla work are filed or recorded against the plans that own them |
In progress | phase 1 (complete, merged 200d6b3 -- survey, matrix and scaffolding. The survey corrected three master-plan claims at source: the Kolla merge lane passed in all 32 of the last 40 merge runs that started it, with no failures, rather than being flaky, so it stays in the merge queue; the Shaken Fist role is about 2000 lines, not 1000; and the role move, now accepted as shakenfist PLAN-kerbside-deployer phase 5, needs a Kerbside release after v0.6.0. It also found that a new docs directory needs an order.yml for site navigation, and that the TLS claim guard's scope literal is copied into its mutation tool, so the two have to change together. One review item was deferred because #555 was already queued, rewriting the by-hand and Ansible rows' CI cells; #587 overtook it, rewriting both when sf-e2e moved onto the role), phase 2 (complete, merged b10f2ac -- the by-hand page, docs/deployment/manual.md, and example systemd units in etc/systemd/. The survey found the master plan's premise for question 3 overtaken: the role moved into this repository with #580, so the units already live here as Jinja templates, and the static copies are held to them by a unit test rather than left to drift. It also found that there are no state or log directories to create, that etc/kerbside.conf.example and the role disagree about who owns kerbside.ini, and that nothing installs the client libraries the oVirt and OpenStack sources and the Keystone login import, by hand or through the role, although the index says the role configures any source. The decision most likely to be argued with is binding the example API unit to loopback where the role binds every interface), phase 3 (complete, merged d159d17 -- docs/deployment/kolla-ansible.md, and the front-door path for Kerbside in front of any OpenStack, which goes in use-cases/openstack.md rather than on the Kolla-Ansible page because it is the same however Kerbside is deployed. The survey found that #605 moved the OpenStack lane to nightly, overtaking question 2 and leaving two stale merge-queue sentences in openstack.md; that the enablement change 967801 is stacked on the deployment change 976889, so question 1's split has not taken effect; that openstack.md has no Deployment section, only a paragraph; and that a Kerbside in front of a stock Kolla-Ansible cloud needs both Nova settings by configuration override), phase 4 (complete, merged 46a03a6 -- docs/deployment/containers.md, one short page saying there is no first-party image (#553), no production compose file and nothing for Kubernetes. The survey found that Kolla publishes kerbside-api and kerbside-proxy on quay.io, built from Kerbside's develop rather than a release, which the master plan's inventory and #553 did not say, and that kolla-ansible.md wrongly says Kerbside's images have to be built. The decision most likely to be argued with is shipping no production compose file, even as an example), phase 5 (complete, merged c1f5938 -- docs/deployment/ansible.md, a short page that routes to the collection's README for the role's variables and to Shaken Fist's operator guide for placement beside a cluster, both of which the survey found already complete. The survey also found that ansible/README.md says the role works with any source type, which #602 makes untrue, and that the role writes fifteen fixed kerbside.ini settings with no pass-through, so a role-deployed Kerbside cannot have a Keystone login even once #602 is fixed; this phase filed #629 for that. Review found that the Proxmox source (#618) had landed after the branch was cut, so the page, the README and the index's sources cell name Proxmox as a source the role can deploy. The decision most likely to be argued with is linking both references rather than restating either), phase 6 (complete, merged eebb1b2 -- installation.md's "Deploying for real" becomes a pointer to docs/deployment/ and the index, keeping its heading. The survey found the section still opens "Kerbside is deployed as a component of the cloud it serves", the opposite of the front-door default, with no Proxmox row and an OpenStack row that skips kolla-ansible.md. It also found the same cloud-keyed framing in the index, the README and ARCHITECTURE.md, and that neither AGENTS.md nor ARCHITECTURE.md mentions docs/deployment/, nor ARCHITECTURE.md's tree ansible/, so the master plan's "probably not" for those two was corrected at source. The Use Cases table's Proxmox row is stale too, but PLAN-proxmox-source.md phase 6 owns it. The decision most likely to be argued with is leaving the README's Kolla-Ansible pitch paragraph alone, which Mikal kept at the back brief. The automated review raised no fix items), phase 7 (in progress -- the mandatory push audit over seven merges: the six phases and #589, which changed only the master plan and was in no phase's row. The survey found that tools/audit/plan-range.sh refuses the range from a phase worktree, because it prefers a stale local develop over origin/develop, and that #491 is still open, so a single range would audit the Proxmox plan's changes to the same files. So each merge is audited against its own first parent. The plan's executable surface is small but real: two systemd units and the test that holds them to the role, and changes to the TLS claim guard. Findings are fixed in a follow-up pull request. The decision most likely to be argued with is running the documentation review on opus, above the runbook's sonnet. Audited 2026-10-30: both waves passed on all seven merges, with no critical or high finding. Ten findings go to a follow-up pull request, two of them blocking: containers.md says two mechanisms are documented, leaving out Ansible, and ansible.md says the role test renders every source type, contradicting its own Proxmox row. Filed #638 to #644 for problems that predate the plan) |
| 2026-10-19 | Kerbside deployer in the ansible role (Shaken Fist's plan) | Cross-repo plan (master in shakenfist's docs/plans/): move the kerbside ansible role into this repository as the shakenfist.kerbside collection, released with Kerbside and made generic over source types; Shaken Fist then consumes it as an external, optional collection. Kerbside side: ansible/, the release job and the collection's tests |
In progress | Tracked in shakenfist's plan |
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.