fix(spec16): close the review findings for the Void port #34

Merged
xavierk merged 24 commits from fix/spec16-review into main 2026-11-06 14:13:50 +00:00
24 Commits
Author SHA1 Message Date
xavierk c457b1d343 chore(ci): ignore generated requirements exports in jscpd
CI / security (pull_request) Successful in 1m12s
CI / lint (pull_request) Successful in 22s
CI / ai-review (pull_request) Successful in 8s
requirements/*.txt are hash-pinned exports written by `just lock` and
checked byte-for-byte by scripts/check_exports.py. Duplication between
them is the lock's output, not hand-written code, so it is excluded
from the duplication gate. The threshold and minLines/minTokens are
unchanged.
2026-11-06 19:29:21 +05:30
xavierk 4dddbf12fb refactor(vosk): share the medium and large language table
The medium and large entries carried the same 22-language table
verbatim. Hoist it into _BEST_AVAILABLE_LANGUAGES and give each entry a
dict() copy, so the two sizes keep independent mappings. Values are
unchanged (the old literals were asserted equal before the move).
2026-11-06 19:29:21 +05:30
xavierk d5c000e58e refactor(utils): drop importlib.resources fallback below the 3.11 floor
requires-python is >=3.11, so the ImportError branch that guarded
importlib.resources.files is dead code. Import it at module scope and
keep only the failures files() can really raise.

The python37-compatibility rule still matches the import itself even
though 3.11 has files(). That is a false positive for this floor, so
the import carries a one-line nosemgrep.
2026-11-06 19:29:21 +05:30
xavierk a79f979090 fix(security): enforce https on model downloads and record reviewed findings
scripts/generate-model-checksums.py downloads the URLs that come from
remote model listings (the VOSK model-list.json and Hugging Face
responses), and urlopen would follow a file: URL there. _https() now
refuses anything but https before each request. The md5 digest is only
compared against the published listing, not used for security, so it is
marked usedforsecurity=False.

The remaining findings are false positives for this codebase, and each
gets a narrow nosemgrep on its own line with the reason:
- text_injector.py: argv lists without a shell; dictated text sits
  after "--" so it cannot be parsed as an option.
- layout_key_map.py: the KDE layout_memory.xml in the user's own session
  directory, not remote input.
- check_runtime.py: module names come from the literal tuples above.
- sync_flatpak_deps.py, verify_release.py: fixed https://pypi.org
  endpoint.
- web tests: walks this repo's own src/app tree; loopback dev-server
  probe in a test.
2026-11-06 19:29:21 +05:30
xavierk a9a1900ee4 fix(gateway): require http(s) URLs before urlopen
urllib.request.urlopen also opens file: and ftp: URLs, and the gateway
base URL and pairing targets are not guaranteed to be http(s). Add
require_http_url() to gateway_embed/urls.py and call it before each
urlopen. Health probes turn a rejected scheme into the existing soft
error; pairing raises ValueError.

The urlopen calls themselves stay flagged by the dynamic-urllib rule,
so each carries a one-line nosemgrep for that rule that points at the
check.
2026-11-06 19:29:21 +05:30
xavierk 73d33a7228 chore(ci): add dependency cooldowns to dependabot and uv
Semgrep's missing-cooldown rules flag Dependabot and uv configs that
apply newly published releases immediately. Dependabot waits seven
days before proposing updates, and [tool.uv] sets exclude-newer to seven
days.

uv.lock was regenerated with uv 0.13.3 (the pinned version). The only
change is the [options] block that records the cutoff: the same 165
packages resolve, and all eight requirements/*.txt exports are
byte-identical to the committed files.
2026-11-06 19:29:21 +05:30
xavierk 09690e5c77 fix(ci): pass release tag and repo to shell through env
Semgrep's run-shell-injection rule flags ${{ github.ref_name }} and
${{ github.repository }} interpolated into run: blocks. The tag name is
attacker-influenced input (git allows $, ( and ; in ref names), so the
release upload steps now read both through step env and quote them.
2026-11-06 19:29:21 +05:30
xavierk 25c5132b03 docs(models): mark ladder thresholds provisional until ticket 19 is recorded
CI / security (pull_request) Failing after 1m19s
CI / lint (pull_request) Failing after 22s
CI / ai-review (pull_request) Successful in 8s
The rung cutoffs cite ticket 19's transcribe peaks, but ticket 19 is
still open and its measurement is not in this repository. The code and
test docstrings now say the numbers are provisional calibration to
replace, rather than evidence. Also formats the niri guard tests to the
project line length.
2026-11-06 18:29:15 +05:30
xavierk d3a3f54204 docs(release): describe the Gitea xbps release and the template bump pull request
Cover the one-time setup (the XBPS_SIGNING_KEY secret, the committed public
key and its fingerprint, the runner and token needs), the tag-push gates and
what the workflow publishes to the tag release and to latest, and the
checklist items for verifying the run. The template version and checksum
bump stays a normal pull request, never a bot commit to main, and now says
the checksum is the sha256 of the fork's own release asset.

The cherry-pick policy from ticket #30 was already in this file and is
enforced by tests/test_upstream_intake_policy.py. Correct what it names as
the retained internal module: it is vocalinux (src/vocalinux/), not
voculinux.
2026-11-06 18:28:11 +05:30
xavierk 3c33aa62b8 feat(void): pin the repository key and register the rolling latest release
Commit the public half of the repository signing key as
packaging/void/repository-key.plist and show its fingerprint beside the
registration lines in the root README and the Void packaging README. The
test derives the fingerprint from the committed key with the xbps
definition (MD5 of the ssh-rsa blob) instead of trusting typed text, checks
the key is 4096-bit public material only, and ties the README, the
registration URL and the release workflow to it.

Clients now register the fork's rolling latest release on Gitea instead of
a per-tag upstream GitHub URL, and the first sync no longer passes -y, so an
unknown key is never accepted silently. The Void README also documents key
generation, the XBPS_SIGNING_KEY secret, the tag workflow and the pending
app-template checksum.
2026-11-06 18:27:34 +05:30
xavierk ba6462ccf8 refactor(ci): move the xbps job out of the GitHub release workflow
The signed repository is now published by the tag-triggered Gitea workflow,
so release.yml drops the build-xbps job, its place in the checksum job's
needs list, the xbps-repo artifact pattern, and the checksum lines for
files that no longer arrive. The release notes point at the Gitea release
instead of promising attached repository files.

The policy tests now assert the new shape: release.yml carries no xbps
job, and the Gitea workflow is the one place that runs repo.sh.
2026-11-06 18:26:14 +05:30
xavierk 227c3cc1a3 docs(niri): state the first-run experience and refresh the layout map
AGENTS.md layout now lists app_identity, legacy_import, model_ladder,
packaging/void, the perf protocol script, the Gitea workflows and the
niri doc, as its own maintenance rule requires.

USER_GUIDE orders Wayland autodetection wtype before ydotool, matching
the decided ladder, and says the portal is never tried on niri.
docs/NIRI.md states the niri first dictation (no prompt; typing goes
direct through wtype; IBus excluded) and points to the GNOME and KDE
section.
2026-11-06 18:26:02 +05:30
xavierk 34d33d73bd fix(void): package the fork's sources and runtime helpers, not upstream's
The app template fetched upstream's 0.18.5 archive, carried upstream's
homepage and maintainer, and renamed the launchers and desktop entry in
post_install. The fork's sdist already names them voculinux, so the
template now sources the fork's release asset and installs the names as
they are. The checksum is an explicit pending marker until the fork's
v0.18.5 release exists, which fails closed in xbps-src and never falls
back to the upstream digest.

depends now pulls in pipewire (capture), wtype (direct typing on niri),
wl-clipboard (clipboard), xdotool (X11 and XWayland) and pulseaudio-utils
(pactl), and drops ibus, which cannot inject on niri. Every name is
checked against the Void index.

INSTALL.msg states the niri first run plainly (no permission prompt;
typing goes direct), never points at the portal on niri, and drops the
IBus claim. The container smoke queries the fork's distribution name.

Tests assert the fork sources, the pending-or-pinned checksum, the
runtime helpers, the absence of ibus, and the niri wording.
2026-11-06 18:25:22 +05:30
xavierk e071328b5f test(rename): tighten the naming-sweep allowlist to retained surfaces
The allowlist accepted any line that mentioned upstream, legacy, cmdline,
a dotted name or a path segment, so a user-facing old name could pass
by sharing a line with one of those words. Each rule is now scoped to a
retained surface: upstream repository slugs and labelled upstream prose,
dotted references to modules that exist under src/vocalinux, import and
-m forms, retained class names, legacy import sources in the files that
implement or test the import, quoted package names in tooling and test
paths only, and the pre-rename guard in the Void container harness.

The sweep skips the .git worktree pointer file, and the allowlist itself
has behaviour tests: user-facing names are denied and retained surfaces
are kept.
2026-11-06 18:25:22 +05:30
xavierk bf8a6b7b56 refactor(identity): derive identity strings from one slug; tidy legacy import
Every lowercase identity constant is now derived from a single _SLUG, so
a rename is one edit. The values are unchanged (checked against
origin/main for all 33 constants and the user agent).

legacy_import: drop the unused subdirectory field from the payload
tuples, merge the four near-identical directory helpers, and use the
identity constants directly instead of aliases.

Docstrings: the retained package is vocalinux, not voculinux; the
one-time import is described as a legacy Vocalinux install.
2026-11-06 18:25:17 +05:30
xavierk e6fdb3be9b fix(perf): hand host binaries the host environment in the baseline script
nvidia-smi (vram_mib) and wtype (inject probe) ran without env=host_env(),
so under an AppImage bundle they inherit the bundle's library paths. The
AST guard now covers the measurement script as well as src/vocalinux, and
the script gains the module logger the Style rules require.
2026-11-06 18:25:17 +05:30
xavierk 7978ba7d76 fix(models): recommend the ladder rung from the installed build and free memory
get_recommended_model() now keys to the backend the installed whisper.cpp
build actually has (detect_pywhispercpp_gpu_backend) and to free memory
measured at recommendation time: free VRAM on a discrete GPU for a
Vulkan or CUDA build, free system RAM otherwise. The result is a rung of
the model ladder, so a Vulkan-capable Nvidia card reaches the ladder
instead of the total-memory tiers, and unquantized turbo, the large
family and medium are never a default.

Manual selections are untouched. The informational hardware probe no
longer decides the model. Dead VRAM-total parsing is removed.

free_memory_for_backend() holds the source choice; the floor-rung map is
shared by rung selection and step-down.
2026-11-06 18:25:17 +05:30
xavierk 6609cfa6d9 feat(ci): add the signed xbps release workflow on Gitea
A version tag push builds every Void template through packaging/void/repo.sh
(podman, pinned Void glibc container), signs the repository, and publishes
the complete glibc x86_64 set to the tag's release and to the rolling latest
release that the client registration URL serves.

Gates, each failing the job before anything publishes: the tag must equal
v plus __version__ exactly, XBPS_SIGNING_KEY must be set, the staged
repository must hold every class of file and nothing but glibc x86_64
data, every package must carry its signature, and the signing key must have
the pinned fingerprint. The key lives in a 0600 temp file that an
always-run step removes. Nothing commits, pushes, or bumps templates.

The offline tests run the workflow's own gate, verify, build and publish
scripts against a stateful stand-in for the Gitea release API.
2026-11-06 18:23:03 +05:30
xavierk df34293f27 test(web): cover fork source links, AUR honesty and privacy wording
Guards the star-count removal, the fork source host constant, the Arch AUR
wording, the unlinked unpublished Snap and AUR cards, the privacy claims and
the install-page rule that the old name only appears with an upstream label.
2026-11-06 18:22:17 +05:30
xavierk 26aa5a0d54 fix(web): align privacy and capability wording with PRODUCT.md
PRODUCT.md promises no usage telemetry in the installed app, a guaranteed
whisper.cpp default, best-effort extras and audio leaving the machine only
through a user-configured remote API. Copy that went further is corrected:
"Zero Data Collection", "Truly Private" and the maintainer-cannot-see claim
are gone, the hero and ribbon say "no app telemetry" because the site itself
loads analytics, and the engine lists name whisper.cpp as the default with
the rest as extras. The privacy, FAQ, offline and home pages now say the app
uses the network for model downloads and the update check, without audio or
transcripts.
2026-11-06 18:22:17 +05:30
xavierk 37262ced0e fix(web): the AUR packages are not published; stop saying Arch users can install them
The Arch guide said Arch users can install Voculinux from the AUR while the
install and home pages say the fork AUR packages are not published yet.
The Arch description, intro, prerequisites and notes now say there is no
AUR install path today. The Snap and AUR cards on the install and home pages
no longer link to package pages that do not exist, and the nixos and flatpak
pages stop listing the AUR as an option.
2026-11-06 18:22:17 +05:30
xavierk c9be45d3c3 fix(web): link the fork's own source host and drop the upstream star count
The header badge fetched the star count of VocaHQ/vocalinux at build time
and labelled it as Voculinux's. The fetch and the count are gone; the
header, footer and social "source" links now go to the fork repository at
git.bongbetic.com/xavierk/voculinux, as do the issue links, the docs
anchors, the home page JSON-LD and the xbps release card.

Upstream GitHub stays only where it is the real artefact or provenance and
is labelled as such: the install script, the AppImage release card, the
upstream card and the changelog release links. The changelog now says its
entries are upstream history, the open-source page no longer claims
VocaHQ membership or an active GitHub community, and the organization
JSON-LD drops the upstream author as founder. DESIGN.md and PRODUCT.md
follow.
2026-11-06 18:22:17 +05:30
xavierk a2736ee6e4 fix(web): label the old launcher name as upstream on the install page
The install page spelled the old binary name on a line with no upstream
label, which fails the offline naming sweep. The launch note now leads
with voculinux and marks the old name as upstream on the same line.
2026-11-06 18:22:17 +05:30
xavierk cf14c5b27c fix(inject): add explicit niri guard and wtype-first key tool order
Autodetection on niri now never probes or selects the RemoteDesktop
portal, and the ibus-wayland bridge exception no longer reopens IBus
there. Key events after startup resolve wtype before ydotool, matching
_check_dependencies and the decided ladder.

conftest scrubs NIRI_SOCKET so tests modelling other desktops are not
affected when run inside a niri session.
2026-11-06 18:17:17 +05:30