What is built, what is coming, and roughly when. Generated from ROADMAP.md.
The same information as ROADMAP.md, which is where it is written and where the reasoning behind each one lives.
144 of 154 are built, tested, documented and in main. Area is not progress. Every square is the same size and one item is not one day: the first is the whole signal engine, and another is a page of questions. The estimates below are the closest thing to an answer, and they are estimates.
Everything that is finished, in one go
A little under half a minute. It scrolls what is done, waits four seconds with a countdown in the corner, and starts again.
An animation rather than a video file, and that is a decision rather than a shortcut. This project ships no codec and does not bundle ffmpeg, and the rule it already follows for video is to render here and always produce something that needs nothing else installed. An encoded file would also be a committed binary whose bytes depend on which build of which encoder made it, so it could not be regenerated and compared the way every other picture here is. If you want a file, ffmpeg -i roadmap-film.svg roadmap.mp4 will make you one from this.
What is left, and roughly what it takes
Working days, one person, no interruptions, so the calendar will be longer than the sum. In the order it is expected to be done.
Memory that reads as ciphertext from outside the processthe app-lock key holds the secrets and the veiling state encrypted in RAM with the same post-quantum sealing used at rest, each value decrypted only for the moment it is used and re-sealed straight after, so a scan of the process's memory finds no passphrase and no voiceprint
A layout no two copies sharethe in-memory shape of that protected state varied per build and per run, so a scanner tuned to one copy of VeilVoice does not recognise the next, and no fixed offset survives from one binary to another. No junk and no decoy RAM: the footprint is unchanged, the protection is in the arrangement rather than in bulk
Tamper noticed, and the source namedan attempt to read or write VeilVoice's memory from another process detected where each operating system allows it, the app locked and the person told, and the reaching process identified as far as the platform permits, on Windows, macOS, Linux and the BSDs. Where a platform cannot say, it says that rather than guessing
Told when anything touches VeilVoice at allevery attempt to open, read, write or attach to one of VeilVoice's processes surfaced to the person, not only the ones that succeed, with what reached in named as far as the platform will say. Built on roadmap items 113 to 115 rather than beside them: the memory is already sealed, this is the part that says somebody tried
A verdict, and then a choicesomething reaching into VeilVoice is reported with what it did and what VeilVoice can prove about it, and the person decides: remove it, quarantine it, or mark it a false positive that is remembered. The decision is the person's, always, because a program that silently uninstalls another program on a heuristic is a worse problem than the one it set out to solve
The allowlist that cannot be worn as a disguisea signed antivirus reading VeilVoice's memory is what antivirus does, and flagging Defender as a rootkit would train somebody to ignore the warnings. So known-good is recognised by a verified code signature checked at the moment of the access, never by a process name, a path or a hash somebody can copy. An allowlist entry permits the read and still records it, still shows it in the log, and never widens to the file storage or the app lock: nothing on it can turn into a way through the protection roadmap items 113 to 115 provide. Stated limit, as everywhere: an attacker who already holds the kernel can forge what the kernel is asked, and this says so rather than promising otherwise
The Studio release145 with the Browser, the decoys, the setup and the BSD reproducibility, tagged after its own audit round rather than on the strength of an earlier one. An audit covers the tree it was run on and no other. v0.1.20 carried the vault, both tabs, the render settings, the corrections, the player and the BSD double build, and was audited in its own round; the decoys, the machine card, the portable arrangement, the acceleration switch and the choice of which side to keep landed after it. So this describes the release after 0.1.20, and says so rather than keeping a number that has gone
A signature check that needs only a signature checkveilvoice verify and the Verify tab check an RSA-4096 OpenPGP signature over SHA256SUMS, and do it through pgp, which brings an entire OpenPGP implementation, rsa, aes, aes-gcm, aes-kw and the whole RustCrypto generation it was written against. That last part is why every cryptographic crate in this tree is held a generation back: moving them while pgp stays would compile two copies of each primitive into both binaries. The job is a reader for exactly what a detached signature over a text file is, the packet framing, the hashed subpackets, the RSA-PKCS1-v1.5 verify over SHA-256 and SHA-512, with nothing else in it, tested against the published signatures of every release and against the fixtures the fuzz targets already hold. When it lands, pgp and rsa leave the graph, RUSTSEC-2023-0071 leaves the exceptions with them, and the ten held majors move in one commit
Companion software installed for you, wherever that can honestly be donetoday VeilVoice detects ffmpeg, Audacity, GnuPG and the audio routing drivers, and offers a package-manager line where the platform has one and its own page where it does not. That leaves the Windows reader, who has no package manager by default, doing the most work on the platform where the routing driver matters most. This finishes the job per operating system and per architecture: the release that matches the machine, fetched and checked against its published hash before anything is run, then installed. Where the software has an installer of its own, that installer is run and the install path is its question to ask, because a program that answers it on the installer's behalf is a program guessing at somebody else's layout. Where there is no installer, the default location for the platform is used unless the person names another, and where a package manager already governs the machine it stays in charge rather than being worked around. Nothing proprietary is ever installed silently: its licence is still accepted by the person it binds. The same routes from veilvoice companions and from the About tab, one list, one implementation, with progress, cancel and resume as the existing installs already have
VeilVoice on a phonean Android package a person installs without developer tools, signed, published beside the desktop archives and verifiable the same way. Roadmap item 52 established that the code compiles for Android; what is missing is the NDK in the release build, a capture path that uses the platform's own audio rather than ALSA, a window that works at a phone's size, and a signing key that does not require a legal identity, all four set out in VeilVoice on a phone above along with why iOS is a separate question. It sits last because it is worth more once there is a Studio to put on the phone, and because none of the desktop work is waiting on it
planned
10
Blocked, and why that is not the same as late
Nothing is blocked. Five items were, some of them for months, each waiting on a decision rather than on effort, and they have been taken off rather than left to make the list look busy. The roadmap says what each one was, what it was waiting for, and why the reasoning is kept even though the item is gone.
Everything that is finished
In the order the roadmap lists it, under the heading it was asked for. Every item links to itself, so one can be sent to somebody on its own.
18 Documentation generator: a page, flowchart and banner for every crate and every.rs file, mirrored to the website and the GitHub wiki
20 Repository panel no longer shows a README's own markup as text
21 Write the missing module documentation for the 14 files that had almost none
22 Website split into a page per section, every published link still working
23 Motion and polish: smooth loading and scrolling, hover, CSS-first tooltips
24 Demonstration animation: a voice going in, the mark lighting up, an unidentifiable wave coming out
25 Cycling line of project facts, slow enough to read: CSS rather than an image, so it follows the reader's theme and needs no script
26 Every website theme in the app, plus user-defined palettes with contrast computed rather than assumed
27 Interactive workflow diagrams that open the relevant source, highlighted, in the site's palette
28 Randomised, user-configurable ratchet interval, with invalid input refused rather than clamped
30 Installer with a window: Tokyo Night, animated, and portable described as the normal case rather than as something missing
31 Optional companion setup: VB-CABLE on Windows, PipeWire on Linux, BlackHole on macOS, and Audacity everywhere, detected if present and installed only if confirmed
32 The site's search presented as an index, and animated
Each of these is a crate of its own, so that another project can depend on one without taking all of them. Every one is opt-in, and every one states what it cannot do as plainly as what it can.
33 Screen-capture detection: which recorders are running, muted per program by an allowlist
35 Keyboard and mouse activity monitoring, reported as the heuristic it is
36 Ransomware canaries and mass-change rate detection, now veilvoice_guard::sentry
37 Learn what runs, then allowlist it, with time-limited grants and a log, now veilvoice_watch::appctl
38veilvoice-policy: settings sealed with the existing post-quantum cryptography, and shaped so they can only be tightened
39 Privileged mode: an opt-in service, and an elevated no-service mode, with the difference visible to the user
40 Alert on driver and kernel-module installation; cross-view checks
65Failsafeon by default: notice the moment another program picks up a real microphone while you are being veiled, warn, and close it
41 Notification overlay: rounded, translucent, contrast computed, or an alert, or off
Asked for after v0.1.12. One recording, several speakers, each given a different voice and each voiceprint destroyed just as thoroughly; names and subtitles; and an optional video of the result.
46Conversation modetell the engine a recording holds more than one speaker, and give each a distinct voice while destroying every voiceprint
47 Up to ten speakers, each with a name, carried into the audio and into subtitles
48 A rolling seed per speaker, at a randomised interval inside a range the user sets, with no interval hardcoded and a fresh one at every launch
49Video outputthe waveform, a circle per speaker in their palette colour or their own picture inside a coloured ring, a title, and a black or image background with padding
50 A preview of the video and of the voices before anything is generated
51 An asynchronous pipeline, every speaker rendering at once rather than in sequence
52 Every crate and every .rs file explained: the technical workflow in a paragraph, then the same thing in plain words
53 The website on mobile, and on every engine: not only the one it was written in
54Seventh audit roundacross the whole tree, then the production deploy
Building it yourself, and proving the download matches #
Asked for after the conversation work. Today veilvoice-verify answers one question, is this download the one that was published, and answers it without GnuPG, without a network client of its own, and without ever holding a private key. The request is to make the same program answer the harder question: is the published build the one this source produces, and to have it set your machine up so you can find out.
The program is renamed veilvoice-setup-tools, because checking a signature is then the smallest thing it does.
55Build the whole repository from source, from the tool itself: find or install a toolchain, pin it to rust-toolchain.toml, and run the same build the release does
56Reproducibility checkbuild here, hash what came out, and compare it against the published SHA256SUMS entry for this platform, saying which files matched and which did not
57The hashes are trusted only after the signature isverify the detached signature over SHA256SUMS against the project key before any hash from it is compared, and refuse rather than warn if it does not verify
58Set the machine up per platformthe build dependencies each operating system actually needs, detected, named with who ships them, and installed only on an explicit yes
59Custom installCLI, desktop app, or both, from a build you just made or from a download you just verified
60Four verbosity levelsnothing, minimal, normal (the default) and everything, applied to every one of the above, with the exit status carrying the answer when the output carries nothing
The engine has handled several speakers since roadmap item 46. The desktop app has never shown it. These are about making the thing visible and usable rather than about the signal, which is already done.
61Group mode in the desktop app, shown as a mode rather than hidden in a flag: off by default, a toggle that does not persist, and a separate tick for "always start in group mode"
62A name and a colour per speaker in the appthe colour chosen automatically to be as distinct as the number of speakers allows, overridable per speaker, and drawn from every palette the website offers
63Live levels while a recording is running, in the app and in the terminal
Asked for after v0.1.14. Everything here is about the same problem from two sides: somebody deciding whether to trust this, and somebody using it and not being sure it is working. Neither is answered by more features.
66The live monitorwhat is going in and what is coming out, on every tab, on by default, and a preview that lets you hear yourself veiled before anybody else does
67An interactive demonstration on the websitethe inside of the application and of the command line, laid out in the site's own colours, that a reader can click through before downloading anything
68A frequently asked questions page, answering what gets asked rather than what is convenient to answer
69A drawn graphic for every workflow chartcoloured arrows, an explanation inside the picture, and every word wrapped rather than running off the edge
70This roadmap, published as a page, with a picture of what is done and what is not, generated from this file so the two cannot disagree
71A video of the roadmap, scrolling what is finished, with a short pause and a countdown before it repeats
72The front page animation, in more depththe same picture, saying what the engine actually does to the signal rather than one word
73A full security and functionality audit, and an optimisation pass, before the next deploythe whole tree, both halves, and the last thing that happens
The lock, the guard, and a window that does not stutter #
Asked for after the tenth audit round. Four of these are one subject seen from different sides: the app lock is the weakest control this project ships, it is described as such in several places, and the request is to make it as strong as it can honestly be made rather than to keep apologising for it.
74The lock screen tells an attacker nothingno explanation of what the lock is or is not worth while it is locked, the account of that moved to the documentation and to the unlocked application, and a small animation in its place
75veilvoice-guard inside the desktop applicationthe integrity record taken at the first launch and checked at every one after, sealed under the app-lock passphrase where there is one
76The app lock, hardened as far as it honestly goesan authentication tag under the passphrase, two copies with the spare administrator-owned where the platform allows it, restoration when one goes, randomised names and masked contents, and a report that only the passphrase can clear
77What the lock is worth, written down properlyone account, in the documentation, separating the parts that are real from the parts that are only obscurity
78Every website palette in the application, chosen from the interface
79A window that does not stutterthe interface measured rather than described, every task off the drawing thread, and the smallest amount of code that does it
44 Fifth audit round: every vulnerability class across the tree, twelve findings written up individually (F-48 to F-59)
45v0.1.10 releasedten platforms, signed, and verified by hand after publication
80Ready for the release auditthe RPM built, lintian run and its findings fixed, manual pages generated from the binaries, 32-bit re-run over the new code, and the parser campaign run over all six targets with a seed corpus kept
Encrypted volumes: Cryptomator, VeraCrypt, and the disk underneath #
81Find the encrypted volumes this machine already hasdetect an installed Cryptomator or VeraCrypt, and the vaults and mounted volumes each is offering, without asking either to do anything
82Write veiled output into a chosen volumea destination that is a Cryptomator vault or a mounted VeraCrypt volume, remembered, and used for every export
83The hidden-volume question, asked properlyasked before the first write, three answers, and a job that will not start until one is given
84The guided path, for when detection failsplain instructions, a folder chosen by hand, and the same confirmation a detected one gets
85What full-disk encryption is for, said once and said properlyBitLocker, FileVault, LUKS and LUKS2, and the OpenBSD and FreeBSD equivalents, single-sourced and shown in both
86The app lock as a key, not only a verifierthe app-lock passphrase seals everything VeilVoice veils, automatically, as an option that says what it costs
87A video of a veiled recordinga black frame and the audio, so a recording can be posted where only video is accepted
88Import from every format OBS writesbring in a recording made elsewhere, video or audio, and take the sound out of it
89Veil the other person afterwardsthe interviewee given their own voice in post, through the group plan that already exists
90GnuPG verification inside the windowin the verify tab, beside the hash check, using the GnuPG somebody already has
91veilvoice-verify finds the release itselfGnuPG arguments where wanted, and an auto that looks in Downloads, checks the archive, and checks what came out of it
92An autolock timeouton at half an hour, from five minutes to forty eight hours, chosen from a list or typed, with the range itself adjustable, and offered during first-run setup
93Group mode explained where it is usedhow to build a plan, what each field does, and what happens without one
94Release notes people can actually readevery release listed newest first, its notes opening in place, and every file one click away
95One version per release, in order, enforcedthe tag, the workspace and every package definition checked against each other before a release can go out
96v0.1.15 releasedthe audit run over everything since v0.1.14, CI green, and the release published
97A verifier anybody can use, checking everythingone press or one command checks the signature, the archive, every file you extracted, and then asks your own GnuPG the same question
The window, and the things that were marked done and were not #
Two rows above were marked done before they were, and this section exists partly to say so. Roadmap item 79 declared a window that does not stutter, twice: once when the drawing thread was cleared, and again when a repaint timer was removed and the improvement measured. Both changes were real and neither reached the cause, which was the animated logo asking for another frame thirty times a second whether or not anybody could see it. Roadmap item 95 declared one version per release enforced, and the enforcement covered the package definitions and not the twelve other places, the README's install block among them, that repeat the version by hand.
Neither is being un-marked. What was done was done. The correction is that a roadmap item means the work described happened, not that the symptom is gone, and this list is more useful if the difference is visible.
98A window that is genuinely idlethe cause of the frames found by asking the toolkit rather than by reasoning, the animation stopped when the window is unfocused or being dragged, and the result measured to nothing
99The window toolkit brought forwardeframe and egui from 0.29 to 0.32, which is what made the frames answerable, and the runtime dependency it added declared everywhere a package can declare it. Brought forward again to 0.36 with roadmap item 148, which took ttf-parser out of the dependency graph and one advisory out of the exception list with it
100What drew the window, reportedthe graphics choices named in the source with their reasoning, and the driver actually obtained shown on the About tab, read from the driver
101Crash reports about the failure they are aboutthe closing note chosen from the panic rather than the same guess every time, and a missing system library named along with the package that carries it
102One version, in one fileevery other copy derived from Cargo.toml or checked against it, with the files that keep a history added to rather than rewritten
103v0.1.17 releasedthe idle window fixed at the cause, the toolkit upgraded, and the release published
Everything here came from one message and they belong together: somebody arriving at this project for the first time, deciding whether to trust it, installing it, and finding their way around it without reading a manual.
104A demonstration that is the programfive recorded sessions of the real binaries replayed on the website, re-run and compared on every build, sitting with the screenshots
105A crash report offered rather than buriedthe report already written to disk surfaced above whatever tab you land on, with what it contains listed and readable in full before anything leaves your machine, and the filing left to the person
106A first run that explains itselfone card per tab saying what it is for, skippable, and after an upgrade only the tabs that are new, with portable and installed said plainly on the last card
109An obfuscated program folderVeilVoice's own files encrypted under names derived from the app-lock passphrase, with decoys among them, so the lock protects data rather than only a window
110A first-run setup and tourthe app lock, the recording passphrase and the autolock explained and offered once, skipping whichever is already set
111Host the website locallya script and an nginx config that serve website/ exactly as GitHub Pages does, so the site survives the repo or Pages going down, and is the audit surface during development
112A self-signed code certificatebeside the OpenPGP key: a detached, signed APPMANIFEST.json describing each binary, verify scripts for Unix and Windows, and an import tutorial, for organisations that want a known publisher without breaking reproducible builds
The app lock already turns the app-lock passphrase into a key that seals everything VeilVoice writes (roadmap item 86). This asks for the same protection one layer in: the secrets and the veiling state while they are in memory, so that a program which can read another process's RAM, a rootkit or a debugger, reads ciphertext rather than a passphrase or a voiceprint.
The honest limit is stated with the feature rather than after it. Data the CPU is actively working on has to be plaintext for the instant it is used, and an attacker already running as the kernel can wait for that instant. This raises the cost of an external read and narrows the window to nearly nothing; it does not claim to beat an adversary who already owns the machine.
116One author in the history, and every commit verifiedthe commit history rewritten so the whole of it matches the attribution rule this project already states, with the assistance credit staying exactly where a reader looks for it (the README, and the footer of every page) rather than in a trailer that makes a second contributor of it, and every commit in the history signed rather than only the recent ones. Trees are unchanged by this, so reproducible builds still verify
117A demonstration you navigate rather than watchthe website's demo driven by the reader, a header per part of the program, and the screenshots for whichever they pick shown under it. Not an imitation of the interface: the real captures, the ones the build already regenerates and compares, so the demo cannot drift from the program. The command line gets the same treatment, a worked case per thing somebody actually wants to do, explained rather than listed, and the site's own Demo link lands on the section and opens it
121A studio vault that needs both keysevery recording the studio makes, and every preview it holds, sealed into one vault whose key exists only when the app lock and the at-rest passphrase have both been given. Neither alone derives it, so a stolen laptop with the app unlocked opens nothing and a known recording passphrase without the app opens nothing. The same post-quantum sealing used everywhere else, held in the same page-locked, zeroizing memory, with the same honest report of how much the operating system actually agreed to lock
122Recording Studiothe place recording happens, with the vault already open because both locks were answered on the way in. Capture, monitor, preview and re-take without a plaintext file existing at any point, and a session that locks itself back up when the app does rather than staying open behind a screensaver
123Recording Browsereverything in the vault, listed with what it is and when it was made, played back by decrypting into page-locked memory rather than writing a temporary file somebody would have to remember to shred, whole rather than in blocks because the container is sealed and authenticated as one piece. Export is a deliberate act with its own warning, because leaving the vault is the moment the protection ends
124Neither lock alone, proven rather than assertedtests that open the vault with each secret in turn and get nothing, and a stated account of what the pair does and does not buy. It raises the cost of a stolen machine and of a guessed passphrase; it does not defeat somebody watching the process while both are entered, and that is written beside the feature rather than left implied
125A pass for size and speed, changing no behaviourthe tree read for the things that accumulate rather than the things that break. Work repeated that could be done once, allocations in paths that run per frame or per sample, generic code instantiated more times than it needs to be, dependencies pulled in for one function, and anything the compiler is doing twice. Measured before and after, with the binary size and the timings recorded, and not one behavioural change: every test that passed before passes after, or the change is reverted rather than argued for
126Written so the next pass finds nothingthe practices that pass established are now the standing way this project is written, in CLAUDE.md, and three of the four are enforced by a build rather than by somebody remembering. No audio callback may allocate, block or print, checked by a test that reads the callbacks themselves rather than by the comments that said so. Every dependency says what it is for on the line that declares it, checked in CI, which found three on its first run that no line of code referred to. Work that can be done once is done once, and the comment says what made it constant. The fourth is a habit and is written as one: the reading happens before the push rather than to the tree once a year
128The BSDs built twice, like everywhere elseFreeBSD, OpenBSD and NetBSD were the three platforms whose archives said not-verified (built once, in a VM), which was honest and was the only gap in the reproducibility claim. The second build now happens in the same VM, with the same path remapping and the same SOURCE_DATE_EPOCH the other ten platforms use, and the verdict is published in the release notes beside theirs. At v0.1.19 all three reported reproducible, so every one of the eleven targets is now verified rather than eight of them. A BSD that stops reproducing says so in those words rather than quietly dropping the line
129A BSD reader can check their own copy either wayall three routes written up per system rather than left as a Linux instruction somebody has to translate. veilvoice verify on its own is the same command everywhere and needs no GnuPG, no network and none of the system's own tools, which is why it is offered first and why a BSD reader has nothing to translate. The second opinion is where systems differ, and the script now has a spelling for the BSDs: it had two, Linux and macOS, and a BSD reader fell through to the Linux one and was told to run a command this project's other script says they do not have. That is F-167. The guide's table of which system runs what is checked against the program rather than typed beside it, and the one command this project has not run on a BSD, installing GnuPG on OpenBSD and NetBSD, is left unsaid rather than guessed
130The live scramble moved into the Studioveiling as it runs has stopped being a separate tab and is what the Studio does. It was already the same engine, the same ratchet and the same virtual-cable routing on both screens, which is what made two of them wrong rather than merely redundant: the Studio recorded with whichever devices the other tab happened to be set to, and each tab started a session of its own, so veiling on one and recording on the other opened the same microphone twice. There is one starter now. The tab is the voice above and the take below, the voice half works with the vault shut because veiling a call has never needed a vault, and ending a take leaves the veiling running
131Both sides of the glass, kept or discardedthe Studio asks which of the two to keep before the button rather than after it, because a recording of somebody's real voice is not a thing to discover having made. The veiled voice is selected, both is offered, and the microphone on its own is offered last. Anything that keeps the microphone says so in the same words the plaintext path uses: it is sealed in the vault as strongly as anything else, and it is still a recording anybody who opens the vault can hear who was speaking in. Never remembered between runs and reset when the window locks, for the reason group mode is not remembered. Both kept means two takes, the unveiled one named for it. The command line's record keeps the veiled voice only and is unchanged: this is a Studio decision, made where the vault that receives it is
132Audio that says when it was interfered witha device swapped or unplugged mid-session, and another program taking the microphone while a take is running, are noticed and shown rather than recorded silently. This row used to open by asking for the samples reaching the recorder to be checked against what the engine produced. They cannot differ: the recorder is fed from inside the output callback, from the same slice the engine has just written into, so that check is a buffer compared with itself. It is a property to keep rather than one to measure, and a test reads the source for it. What was actually missing was the report: the platform announces a stream error on a callback of its own, and both of them were eprintln! and nothing else, so on Windows, where the window has no console, a microphone unplugged mid-call was silent. Stated limit up front, beside the report rather than after it: this notices interference with VeilVoice's own path and cannot vouch for a microphone that was already lying
133Every guest, veiled and plain, side by sidetwo bars per speaker in group mode, what went into each of their turns and what the engine produced from it, drawn as the render walks the file. One bar answers "is something being written" and not "is this person being veiled", which is the question somebody rendering an interview is asking; two that move differently are the only thing on screen showing the engine is between them. Under them, in the same words the live meters use, what they cannot show. The row asked for this live, and that half is roadmap item 147: group mode works on a recording that already exists and never opens a device, the live path opens one input, and there is no per-guest live signal in this tree to draw. The one-microphone live case already has its two bars, in the Studio
134Decoy vaults, as many as you choosethe Browser asks whether to make dummy vaults and how many, sizes them from the real one so they are indistinguishable by size, and fills them with encrypted nonsense under keys that are thrown away. Cracked, one yields bytes that will not parse as anything: a decoy is not a vault with weak contents, it is a vault whose contents never existed. Asked there rather than at first run, because a decoy is sized from the real vault and at first run there is not one yet. The real vault is found by trying each directory rather than by name, so a decoy is not told apart by reading the folder either. The count defaults from the free space actually detected rather than from a number picked here, and the honest limit is stated: this raises the cost of a search, it does not make the real vault unfindable to somebody who watches you open it
135Setup inside the app, with defaults it worked outfirst run ends on a card that reads this machine rather than asserting anything about it. Where recordings will go and how much room is free there, read from the operating system and said as an hour of veiled audio rather than as a number of bytes. How many devices there are to record from and play to. What the window will ask the graphics driver for, with the tick that turns it off. Every one says what it costs, and where the machine will not answer the card says that instead of printing a figure nobody measured. Nothing on it has to be answered and nothing is a gate
136Portable first, and it tells you where it isa folder called veilvoice-data beside the program makes the settings, the vaults and the lock live in it, so a copy on a stick stays a copy on a stick. Opted into rather than detected, because "beside the program if that is writable" would move an ordinary installation's state the day somebody unpacked it somewhere writable, and the symptom would be an empty vault. The About tab prints the exact paths it is using and says which of the two arrangements is in force, rather than leaving somebody to guess. Installing later can carry those settings over or leave them, and that is a question the install button waits for rather than a default, because the answer differs for a shared machine and a private one. Carried means copied, never moved, and nothing already at the destination is replaced
137Acceleration on unless you turned it offthe window asks the platform for a hardware context and accepts a software one, so a virtual machine, a remote desktop or a server with no card still opens. One tick in Settings turns the asking off, for the machines where a driver accepts and then draws badly: a hybrid-graphics laptop handing over the wrong adapter, or a black window on a broken OpenGL path. That case cannot be detected, because from inside the process it looks like success, so it is a switch rather than a measurement and the honest limit is said where the tick is. The About tab shows what was asked for beside what the driver actually gave, which is the pair that answers "why is this slow"
139A video of who said what, from the Studiodone. The preview page and the video file are now two drawings of one recording rather than a picture and a black rectangle. A circle per speaker in their own colour, whoever is talking lit, a level under each name that moves with the sound, the waveform and a playhead. The frames are drawn here, as pixels: raster is a canvas with rectangles, antialiased circles and a PNG writer, and font is a five-by-seven monospace face of ninety-five glyphs written out in the file. Neither borrows anything, because the drawing is SVG and no ffmpeg can be assumed to read SVG, and pulling in a rasteriser to convert it would have undone the argument the ffmpeg module already makes about large C libraries. The one dependency is miniz_oxide for the deflate PNG needs, which was already in this tree under flate2. A picture is written when the picture changes, not thirty times a second: the playhead moves a pixel at a time rather than a frame at a time, so ten minutes at thirty writes hundreds of files rather than eighteen thousand, and the saving is reported rather than claimed. A short recording holds nothing and should not, because the playhead crosses the whole waveform however long the recording is. That is why the ffmpeg command is a concat list with a duration per picture rather than a numbered sequence, which would have played an hour of conversation in a few seconds. Names outside printable ASCII cannot be drawn by a face this size and come out as boxes; the render names them rather than letting somebody find out by watching. Single-person recordings take the same path as a group of eight
140Resolutions somebody would actually pick1080p as the default, with 1440p, 2160p and a custom size offered, rather than the 1280x720 the renderer starts at today. The size the display is actually running at is offered as a preset where the platform will say, and where it will not the list is simply the fixed ones: a guess about somebody's monitor is worse than a menu
141How much code this actually is, counted one way and said oncea functional line count in the README, and the same count per crate in each crate's own documentation, with the definition stated where it is used: a line holding code, not a blank line and not a comment. Deliberately a new number rather than a redefinition of an existing one. The per-file line counts already printed on the generated pages, in the artwork and in the reference links are a different measure, counted differently, and are left exactly as they are: changing what an existing number means, everywhere it appears, to match a new definition would silently alter every page and link that carries one
138v0.1.19, the audited releasethe full audit of roadmap item 127 run over the whole repository, everything it finds fixed, and the tag cut on what came out. Not the code alone: the documentation, the website, the packaging, the scripts and the reproducibility, and the optimisation pass, because a release audited in part is a release described inaccurately. The Studio is deliberately not in it. A release named after a feature that is still being written is the thing this roadmap exists to prevent, so 0.1.19 is the release that says the tree is sound and 0.1.20 is the one that says what was built on it
127The full final audit, and what counts as completeno part of this repository is signed off until every part of it has been. Security, memory safety and the post-quantum surface; correctness and QA over every crate; reproducibility of the build and of every generated artefact; the documentation, the website, the packaging and the scripts; and the optimisation pass above, because bloat that nobody measured is a claim nobody checked. A round that covers the code and skips the documentation, or covers both and skips whether the thing still builds byte for byte, is not a final audit and is not to be described as one
142The demonstration on the page, and the drawing of it gonethe recorded terminal sessions and the photographs of the window both on the front page in the order somebody meets them, neither behind a button. The hand-drawn CSS model of the application is removed rather than relabelled: it sat beside photographs of the same interface and asked a reader to work out which to believe, on a site whose argument is that they should not have to take anybody's word for anything
143veilvoice verify, said that way everywherenine places still described a veilvoice-verify executable that stopped being published at 0.1.18, three of them lists the program itself reads. The lists are now taken from the job that publishes the binaries, and a test reads the repository the way a reader does and fails on anything telling somebody to run a program that is not there
144Staleness as an invariant rather than a habitCLAUDE.md now states that a change is not finished until everything describing it has changed with it, that a fact appearing twice is derived or checked rather than repeated, and that the only exception is a record of the past. Written down because the two roadmap items above were the same failure found twice, in different files, months apart
145The Recording Studio, in fulleverything roadmap items 121 to 137 specify, built and working together rather than as parts. Recording in an environment that never lets a plaintext sample reach the disk, sealed post-quantum into a vault that needs both the app lock and the at-rest passphrase, held in page-locked zeroizing memory. It shows, at all times and both live and on replay, whether the voice being recorded is veiled or not: done, and the part that was missing was the running take, which said the clock and not which voice it was keeping, so a take of somebody's real voice looked exactly like one that was not for the whole of the recording. It has its own failsafe, separate from the application's, so a fault in the Studio stops the Studio rather than the recording: done. The device a take is being recorded from going away is the fault it catches, and it stores what was captured and stops, rather than discarding it, retrying, or moving the recording onto a microphone the person did not choose. Group mode records every guest with the same guarantees: done, and it is roadmap item 147, because group mode never opens a device and the live path opened one input. A room in the Studio opens one per guest, veils each into a voice of their own, and stores a take per guest beside the mix, in the same vault, under the same lock and with the same choice of which side to keep. The output is rendered inside the vault, viewed and listened to inside the vault, and leaving it is an export: a deliberate act, warned about, because that is the moment the protection ends. Something like a recording studio somebody already knows how to use, that happens to be secure, rather than a security tool somebody has to learn to record with. No longer carries a version number: it was aimed at v0.1.20, that release shipped with the parts named in their own entries, and a target date a release has already passed is worse than none
147Several microphones at once, veiled and metered eachone input per guest, each veiled by its own engine with its own seed and destination voice, mixed into the one output a call or a recorder hears. The capture path is veilvoice_audio::room: a recorder per guest and one for the mix, one sample rate agreed across every device before anything opens, a ring per guest so one microphone's clock drifting is that guest's dropped samples rather than everybody's, and the honest account the row asked for as a number rather than a promise, since the output callback runs every engine before it returns and the load is the sum of their realtime factors against the deadline they share. The mix is summed and clipped and never limited, because a limiter is a dynamics processor and this program's whole claim is about what changes a voice. The Studio drives it: a guest list with a name and a microphone each, two bars per guest with the load and the clipping count beside them, and a take that stores the mix and every guest separately, veiled or unveiled on the same choice the single microphone has. The Studio holds one session or the other and never both, which is one field rather than two so it cannot be got wrong. Two guests on one microphone is refused by name before anything opens, the default device included, because one microphone carrying two people is one signal and veiling it gives both of them the same voice
148The window draws at the display's rate, and says sothe animations ran at twenty frames a second by design, from a constant in the mark and a fifty-millisecond cadence in the busy path, and a reader with a 144 Hz display saw that as judder. The sixteen-millisecond veiling path was worse than it looks: a display at sixty shows a frame every 16.67 ms, so asking for one "within sixteen" misses the frame it wanted and lands on the next, which is thirty asked for as sixty. Done, and not by choosing a bigger number: while anything is moving the window asks for the next frame now and vsync spaces it, so it draws once per refresh at whatever the display runs at. That is also what makes the display measurable, since neither egui nor eframe exposes a refresh rate: frames paced that way are the display's own, and the median of the last thirty-two is a figure one slow frame cannot move, clamped between 30 and 240. Settings offers 30, 60, 90, 120, 144, 165 and 240 for somebody who wants fewer frames on a battery, and a live readout in the header. The About tab carries what it is aiming at, what the display measured, the rate as drawn and how many frames arrived late; a frame more than half again late is late, and two seconds of that says so once, naming what the window is drawing with. Idle still draws nothing. Nothing in the measurement allocates, locks or prints per frame
150The meters above a callthe live monitor had two places to sit and both were inside the VeilVoice window, which on a call or while streaming is behind the thing being talked into, so the one picture of what a microphone is doing was covered exactly when it mattered. A third choice now: a small window of its own, kept above other windows, off the task bar, draggable and resizable, drawing the same two levels and the same sentence about what a level cannot tell you. Not the default, because a window that puts itself above everything is a thing to ask for. Closing it brings the strip back rather than turning the meters off, since the close button belongs to the window manager and pressing it means "not in my way". Where a platform will not give a second window it falls back to the floating card, which is the same thing inside the window
151Mutation testing, as a check rather than as an afternoonchanging the code a line at a time and asking whether any test objects is the only thing this project runs that asks whether an assertion exists for what came back, as opposed to whether an input was explored. Its first run over four cryptographic files found fifteen changes the whole suite accepted, including one that replaced the randomness adapter with a constant. All three parts are in. mutants.yml runs the campaign weekly over the eight cryptography files and dispatches before a release, and its verdict is not a number: tools/mutants/check.py compares the run against tools/mutants/survivors.txt, a committed list where every entry carries the argument for why that mutant cannot be killed, so a new survivor is a diff somebody reads and a survivor that has been killed fails too, because the list would then claim something untrue. The campaign was extended to the app lock, the studio vault, the obfuscated store and the reversible encodings: 674 mutants, 81 survivors, and four rounds of writing tests and re-measuring brought that to fifteen, every one of them argued. And tools/audit/build_output.py fails the build on any build output outside the root target/, which is F-185, because the fifteen gigabytes the first attempt died on turned out to be written by this project's own tooling on every machine that is not Windows
152The numbers the README states, written by the tool that measures themthe functional line count and the test count appear in README.md, on the front page and in one row of docs/AUDIT.md, and all four are typed by hand and compared against the tree by the website suite. The comparison is right and the typing is the problem: every change to any Rust file moves the line count, so a commit that touches code and does not touch those four places fails a check that has nothing to do with what the commit was for. Four times in one round was the measurement. tools/measured/generate.py now writes all ten of them from the numbers it already takes, using the same anchors the suite checks, so the tool that writes a claim and the suite that checks it cannot disagree about where the claim is. The comparison stays exactly as it was: it is the guard, and it no longer doubles as the thing that catches a person on every push
153A link into a page lands on what it namesthe site's header is sticky, so a fragment jump puts the heading underneath it unless the target is pushed down clear of the bar. The amount it was pushed by was one number, 90px, and the header is 133px at desktop widths, 171px where the navigation wraps to three rows, 115px on a phone, 143px at 320px and 81px on the reference pages. The rule also covered sections and the two larger headings only, so every entry on the releases page and every roadmap entry got nothing at all. Driven in a browser, 425 of 430 fragment links on this site landed wrong. The offset is measured from the header now and re-measured when the window changes shape or a font arrives late, the landing is held for a second afterwards because pictures below the fold can still move the page and it lets go the moment the reader scrolls, and where somebody landed is outlined briefly, which under reduced motion is an outline that sits there rather than fading. Navigation itself is untouched: no click is prevented and no history entry written, because the full-size screenshot viewers open through :target. F-191
154What this project says it is made of, checked against what it isthe front page renders the README, the README lists the crates twice and the library guide lists them a third time, and the three had drifted to thirteen, twelve and thirteen rows against a workspace of twenty-seven. The missing fifteen included both halves of the download verifier, the failsafe, the video renderer and the workspace store. tools/audit/crate_tables.py reads the workspace and all three tables on every build and fails naming each crate a table has missed, so the next crate cannot be added without them. The sentences stay hand-written, because one assembled from a crate name is padding. F-192
156Every page says where it lives, and a crawler is told it may read the siteog:image was assets/banner.png on every page, a relative address, and the crawlers that read that tag do not resolve one. Every link to this site posted anywhere had always shown no picture, and nothing about the page looked wrong. There was no canonical address either, so one page reachable two ways was two pages to an index, and two pages carried no preview tags at all. tools/site/seo.py builds all of it from what each page already says, every generator that writes a page calls it, and robots.txt and a sitemap covering all 398 pages exist where there were none. Checked twice on purpose: once that each page is what the generator would write, once that what the generator writes is correct. F-194
157The banner moves on the no-JavaScript page toothat edition showed the still while the main site animated the same artwork, both drawn by assets/generate.py from the same source. It shows the animation now, with the still still served to anybody who has asked their system for less movement, chosen by markup rather than by a script because that page runs none
158Fewer crates, chosen by what somebody would actually taketwenty-seven crates for one application, seven of them under seven hundred lines and four used by exactly one caller. Full modularity is not free: every split is a published surface, a Cargo.toml, a README, a banner, a page of the reference and a row in every table that lists them, and a reader deciding what to depend on had to read twenty-seven descriptions to find the two they wanted. Thirteen now, drawn at what somebody would realistically take on its own. The six observers became veilvoice-watch, the two alarms and the safety catch became veilvoice-guard, the verifier absorbed both of its backends, the renderer absorbed the graphics probe that answers one question for it, the decoy passphrase joined the cryptography it is part of, the update check joined the installer, and the saved profiles joined the settings. Nothing was deleted and no behaviour changed: every module kept its name, its tests and its page, and the whole suite passed before and after
159The wiki becomes the documentation, and publishes itselfthe wiki held the reference, a page for each of the thirteen crates and each source file in them, and three guides, and nothing else. No Home, so a reader arrived at whichever page GitHub picked; no _Sidebar, so there was no list of the rest; and none of the prose that answers what somebody actually arrives asking, how to build it, how to check a download reproduces, how to contribute, what the website is made of. Those are pages now, converted from the documents rather than written again, so --check fails on a difference and there is no second copy to go stale. Two documents that did not exist were written to be converted: docs/CONTRIBUTING.md and docs/WEBSITE.md, the second covering both editions of the site and all twelve scripts. A guard walks every [[link]] in all 202 pages, because a wiki link to a missing page renders as an invitation to create it rather than as an error, so nothing else would ever notice. And a workflow pushes the lot to the wiki repository, which is where a reader looks and where none of it was
160The lock and the unlock are one controlthe button that locks the window and the button that unlocks it were written where each was needed and sized by whatever its own text happened to measure, so one was 46 points wide beside a 132-point dropdown and the other was the word unlock padded with literal spaces, eight points taller than the password field beside it and four points off its middle. Roadmap item 123 fixed the height of the first and said the width was a different complaint; it was the same complaint. One width for the picker and both buttons, each button's height taken from whatever it stands beside, and the field grown to the button rather than the button squashed to the field. Measured in a headless frame rather than read off a photograph, on the rows that ship rather than on replicas of them, with a test that fails if controls left to themselves happen to agree anyway. F-196
Generated from ROADMAP.md at commit time by tools/site/roadmap.py. 154 items across 14 sections.