pmacs/docs/repository-audit-2026-07-03.md

492 lines
20 KiB
Markdown

# Repository Audit - 2026-07-03
Scope: repository-wide audit of the current `pmacs` worktree at
`/home/jeans/Repos/active/pmacs`.
This audit covers Rust crates, Lua built-ins and fixtures, package-manager code,
GPU frontend code, documentation, repository hygiene, and verification commands.
It audits the files currently on disk.
## Worktree Context
Excluding this newly added report, `git status --short` reported these
untracked worktree files at the end of the audit:
```text
?? #run.sh#
?? docs/pmacs-gpu-editing-perf-handoff.md
?? docs/semantic-frontend-protocol.md.local-bak
?? docs/session-5-stale-styling-handover.md
?? pmacs
?? python_experiment.md
```
The audit did not revert or normalize this state.
## Verification Summary
Commands run:
| Command | Result | Notes |
|---|---:|---|
| `cargo fmt --all --check` | Pass | Formatting is clean. |
| `cargo clippy --all-targets -- -D warnings` | Pass | This only checked the root default package from this workspace location. |
| `cargo clippy --workspace --all-targets -- -D warnings` | Fail | `pmacs-gpu` has 3 denied clippy warnings. See F-001. |
| `cargo test -p pmacs-gpu --bin pmacs-gpu` | Pass | 39 tests passed on the current worktree. |
| `cargo test -p pmacs-protocol` | Pass | 12 tests passed; 0 doctests. |
| `cargo test -p pmacs --no-default-features --features lua54 --lib` | Pass | 1431 passed, 0 failed, 3 ignored. |
| `cargo test -p pmacs --features crdt --lib` | Pass | 1601 passed, 0 failed, 3 ignored. |
| `cargo test --workspace --all-targets --all-features` | Fail | `mlua-sys` rejects enabling both `luajit` and `lua54`. See F-002. |
| `cargo run --quiet --bin pmacs-audit -- --pretty builtin tests/fixtures` | Pass with warnings | 0 errors, 3 warnings, 10 infos. |
Sandbox caveat: an initial `cargo test --workspace --all-targets` run failed in
three attach/socket tests with `PermissionDenied: Operation not permitted`.
Rerunning the attach lib-test group with normal socket permissions passed
(`44 passed`). I did not treat that sandbox failure as a product defect.
## Findings
### F-001 - High - Workspace clippy fails, while the documented clippy command misses workspace members
Evidence:
- `README.md:37-43` documents:
- `cargo test`
- `cargo fmt --check`
- `cargo clippy --all-targets -- -D warnings`
- `cargo metadata --no-deps --format-version 1` reports `workspace_default_members`
as only the root `pmacs` package.
- `cargo clippy --all-targets -- -D warnings` passed, but
`cargo clippy --workspace --all-targets -- -D warnings` failed.
- The workspace clippy failures are in `pmacs-gpu/src/main.rs`:
- `pmacs-gpu/src/main.rs:2712`: two `clippy::cast_possible_wrap` errors from
`centered as i64 - self.scroll_top as i64`.
- `pmacs-gpu/src/main.rs:2948`: `clippy::format_push_string` from
`readout.push_str(&format!(...))`.
Impact:
The documented release/developer clippy command can give a false green while
`pmacs-gpu` is not clippy-clean under the workspace's lint policy. This makes CI
and local release validation vulnerable to missing frontend regressions.
Recommendation:
Fix the three `pmacs-gpu` lints, then update README and CI to use workspace-aware
commands, e.g. `cargo clippy --workspace --all-targets -- -D warnings` and
`cargo test --workspace --all-targets`. Alternatively set explicit workspace
`default-members` if the intent is for plain `cargo test` and `cargo clippy` to
cover all first-party crates.
Resolution (PR #75, then extended): the three `pmacs-gpu` lints were fixed and
CI now runs `cargo clippy -p pmacs-gpu --all-targets` (README uses `--workspace`).
That closed the *clippy* blind spot but not the symmetric *test* one — the
default-member scope also means `cargo test` skips `pmacs-protocol`. F-014's
GPU-render job later ran `pmacs-gpu`'s tests; this change adds
`cargo test -p pmacs-protocol --all-targets` to the test job so the shared wire
format (its ~12 encode/decode + transport-framing tests) is finally exercised in
CI. All three first-party crates now run in CI.
### F-002 - Medium - `--all-features` is not buildable because Lua features are mutually exclusive
Evidence:
- `Cargo.toml:63-75` defines `default = ["luajit"]`, `luajit`, and `lua54`.
- `cargo test --workspace --all-targets --all-features` fails in `mlua-sys` with:
```text
You can enable only one of the features: lua54, lua53, lua52, lua51, luajit, luajit52, luau
```
Impact:
Generic CI, packaging automation, `cargo hack`, and distro tooling often starts
with `--all-features`. For this workspace, that produces a hard build failure
even though both Lua flavors pass independently.
Recommendation:
Document the supported feature matrix explicitly and avoid `--all-features` in
CI. Prefer a matrix such as default LuaJIT, `--no-default-features --features
lua54`, and `--features crdt`. If possible, add a clearer crate-local compile
error for simultaneously enabled Lua flavors so users see a pmacs-specific
message before the `mlua-sys` failure.
Resolution (PR TBD): documented the feature matrix explicitly — a table in
`README.md` §Build (the two Lua flavors + orthogonal `crdt`, the supported
build lines, and an explicit "don't use `--all-features`"), a `# Lua flavor
features` section in `src/lib.rs`'s crate docs, and an expanded `Cargo.toml`
`[features]` comment. CI already avoids `--all-features` (it iterates the
flavors explicitly), so no CI change was needed.
The suggested crate-local `compile_error!` was **investigated and rejected as
unreachable**: the flavor check lives in the `mlua-sys` *build script*, which
cargo compiles before the `pmacs` crate, so any mis-set flavor (both, or
neither) fails there first and `pmacs`'s own `compile_error!` never evaluates
— confirmed empirically for both cases. A dependent crate cannot preempt a
dependency's build failure, so the honest mitigation is the documented matrix
rather than a guard that can never fire. The docs state that the actual error
surface is the `mlua-sys` message.
### F-003 - Medium - `pmacs-gpu` can appear hung when attached to a non-CRDT daemon
Evidence:
- `pmacs-gpu/src/attach.rs:119-128` documents the failure mode directly: without
daemon `crdt`, negotiation succeeds, no `BufferSnapshot` arrives, and the GPU
window sits on `(connecting...)` forever.
- `pmacs-gpu/src/attach.rs:129-148` sends an `AttachRequest` requesting
`multi_frontend`, `crdt_replica`, and `semantic_render`, but there is no
explicit capability rejection path in the GPU client after the request.
Impact:
This is a confusing operational failure. A user can start the daemon without the
right feature and get an inert GPU window instead of a clear compatibility error.
Recommendation:
Make capability negotiation explicit. The daemon should reject required
capability mismatches with a structured `Goodbye` or handshake error, and the GPU
frontend should render a fatal status explaining that the daemon must be started
with `--features crdt`.
### F-004 - Medium - Current GPU command-chord forwarding can break AltGr/international text input
Evidence:
- `pmacs-gpu/src/main.rs:4674-4688` reduces winit modifiers to Shift, Ctrl, Alt,
and Super/Meta. There is no AltGraph/AltGr distinction in the protocol
modifier set (`pmacs-protocol/src/message.rs:92-115`).
- `pmacs-gpu/src/main.rs:4775-4779` treats any `Char`, `Enter`, or `Tab` with
Ctrl or Alt held as a command chord.
- The current tests assert that this generalized command-chord path forwards
`Char` plus Ctrl/Alt combinations (`pmacs-gpu/src/main.rs:5822-5853`).
Impact:
On many keyboard layouts, printable characters are entered through AltGr, which
toolkits may expose as Ctrl+Alt or as an Alt-related modifier. Those characters
can be misclassified as command chords and forwarded to the daemon keymap instead
of inserted as text. This affects non-US layouts and users typing characters
such as `@`, `[]`, `{}`, `\`, `|`, or currency symbols depending on layout.
Recommendation:
Use winit's text/IME path or an explicit AltGraph signal to distinguish printable
text input from command chords. Add tests for AltGr-style printable input. If the
wire protocol needs it, add an AltGraph modifier bit rather than folding it into
Ctrl/Alt.
Resolution (PR #76, then hardened): a keypress that produced printable
`key.text` while a command modifier was held is reclassified as text input and
the modifiers are stripped (`is_layout_text`). The first cut gated on *any*
command modifier, which over-reached: on macOS the `Option` key is reported as
`Alt` and emits printable text for most letters, so `Option+x` was stripped to a
plain insert — swallowing every GUI Meta binding (`M-x`, `M-f`, …) on that
platform. The gate is now the true AltGr signature — **both `Ctrl` and `Alt`**
(the LCtrl+RAlt the OS synthesizes for AltGr on Windows) — so `Alt`-alone forwards
as a Meta chord again while Windows AltGr still inserts. A protocol AltGraph bit
was *not* added; the text-presence heuristic is sufficient and layout-agnostic.
Not locally validatable on macOS (no box) — a narrowing of when we strip, so
low-risk on Linux/Windows.
### F-005 - Medium - Package install paths collide for namespaced packages with the same basename
Evidence:
- `src/packages/installer.rs:44-49` states that install directories are named by
package basename and accepts collisions for v0.1.
- `src/packages/installer.rs:1040-1051` strips a namespace prefix with
`package_basename`.
- `src/packages/loader.rs:43-48` and `src/packages/loader.rs:164-176` route
require lookup by basename, with most-recent install winning on collisions.
Impact:
Two distinct packages such as `owner/magit` and `other/magit` can target the same
install directory and require namespace. Depending on order, one can shadow or
block the other. A resolver can reason about canonical names while the installer
and loader collapse them to the same basename.
Recommendation:
Reject duplicate basenames during resolution/install until a namespace-aware
install layout exists, or install under a namespace-preserving path. If basename
requires remain a compatibility goal, add an explicit aliasing rule rather than
silently collapsing distinct package names.
### F-006 - Medium - Atomic save has file-metadata and crash-durability gaps
Evidence:
- `src/file_io.rs:139-148` creates a new sibling temp file, writes bytes,
`sync_all`s the temp, then renames it over the target.
- The save path does not copy the existing file mode/permissions onto the temp
before rename.
- The save path does not fsync the parent directory after rename.
- `src/file_io.rs:156-174` uses PID plus subsecond nanoseconds for the temp name
and relies on `create_new` failing if a collision occurs; it does not retry.
Impact:
Saving an existing executable or otherwise specially-permissioned file can replace
it with default temp-file permissions. On POSIX filesystems, a crash after rename
but before the parent directory entry is synced can lose the rename despite the
file bytes having been synced. The temp-name collision path is unlikely, but a
same-process collision would surface as a spurious save failure.
Recommendation:
Before writing, read existing metadata and apply the target's mode to the temp
file where supported. After a successful rename, fsync the parent directory on
Unix. Add a bounded retry loop for temp-name collisions.
### F-007 - Medium - Minibuffer dropdown height is unbounded and can render off-screen
Evidence:
- `pmacs-gpu/src/main.rs:3160-3164` computes dropdown `top_y` as
`band_top - n as f32 * MB_DROP_ROW_HEIGHT`.
- `pmacs-gpu/src/main.rs:3176-3193` draws a background rect and selected row for
all candidates without clipping/windowing the candidate list.
Impact:
Large completion lists can extend above the top of the window. The selected row
can be outside the visible area, and hit testing/rendering can become inconsistent
with what the user can see.
Recommendation:
Cap visible rows based on available height, keep a scroll/window offset around
the selected candidate, clamp `top_y`, and make hit testing use the same visible
window.
### F-008 - Medium - GPU attach outbound queue is unbounded
Evidence:
- `pmacs-gpu/src/attach.rs:156` uses `std::sync::mpsc::channel`.
- `pmacs-gpu/src/attach.rs:186-199` writes queued events on a separate writer
thread to avoid blocking the UI thread.
- `pmacs-gpu/src/attach.rs:326-333` sends every `FrontendEvent` into that
unbounded queue.
Impact:
If the daemon or socket stalls, the UI thread can continue enqueueing key,
pointer, paste, viewport, and CRDT events without backpressure. This can grow
memory and can also deliver stale pointer/viewport traffic after the daemon
recovers.
Recommendation:
Use a bounded channel with a clear policy. Coalesce superseded events such as
viewport and pointer motion. Consider failing fast or entering a degraded state
when the writer cannot keep up.
### F-009 - Low - Package fetch cache key uses non-cryptographic 64-bit FNV-1a
Evidence:
- `src/packages/fetcher.rs:161-165` derives the bare-repo cache path from a hash
of the normalized URL.
- `src/packages/fetcher.rs:510-518` implements 64-bit FNV-1a and describes it as
"collision-resistant enough for a cache key."
Impact:
The package cache is keyed by potentially attacker-controlled repository URLs.
A deliberate or accidental hash collision could make two URLs share one bare
mirror path and lock file, mixing refs or producing confusing installs. This is
not a content-integrity bypass by itself, but it is avoidable risk in
security-adjacent package infrastructure.
Recommendation:
Use SHA-256 or another cryptographic digest of the normalized URL for cache
directory names. Store the normalized URL in a sidecar file for diagnostics.
### F-010 - Low - Timed-out git subprocesses return before stdout/stderr drain threads are joined
Evidence:
- `src/packages/fetcher.rs:599-604` spawns stdout/stderr drain threads.
- `src/packages/fetcher.rs:611-617` kills and waits for the child on timeout,
then returns `FetchError::Timeout` immediately.
- `src/packages/fetcher.rs:630-631` only joins the drain threads on the normal
completion path.
Impact:
Repeated fetch timeouts can leave short-lived detached reader threads behind
until their pipe reads finish. It is unlikely to leak permanently after the child
is killed, but it makes timeout behavior less deterministic and harder to test.
Recommendation:
After killing and waiting for the child, join the drain threads before returning
the timeout error, or restructure process execution around a timeout-aware
`wait_with_output` helper.
### F-011 - Low - `ResolvedPackage::commit` is documented as a SHA but can hold a tag string
Evidence:
- `src/packages/resolver.rs:143-144` documents `ResolvedPackage.commit` as a
"Full 40-character commit hash."
- `src/packages/resolver.rs:1036-1044` returns the tag string from
`TagCandidate::commit_for_tag`.
- `src/packages/lockfile.rs:573-610` compensates by resolving the commit-ish to a
SHA before writing the lockfile.
Impact:
The lockfile path is protected, but the internal type contract is misleading.
Downstream code that trusts `ResolvedPackage.commit` as a SHA can accidentally
re-resolve a moving commit-ish or use the field in error messages and markers as
if it were immutable.
Recommendation:
Either resolve tags to SHA inside the resolver before constructing
`ResolvedPackage`, or rename the field to `revision`/`commitish` and update docs
and call sites to reflect the actual contract.
### F-012 - Low - Resolver topological sort contains dead/shadowed indegree work
Evidence:
- `src/packages/resolver.rs:705-715` builds and mutates an `indegree` map.
- `src/packages/resolver.rs:735-738` immediately shadows it with the actual
`indegree` map used by the algorithm.
Impact:
This is a small inefficiency and a readability hazard in dependency-ordering
code. The surrounding comments already show the algorithm was corrected in place,
but the abandoned block remains.
Recommendation:
Delete the first `indegree` construction block and keep only the final outgoing
dependency-count implementation.
### F-013 - Low - GPU documentation and crate metadata are stale relative to implementation
Evidence:
- `pmacs-gpu/Cargo.toml:6` still describes the crate as "session 2:
hello-world."
- `docs/pmacs-gpu-design.md:3-5` says the GPU work is pre-implementation with
sessions queued.
- The current GPU code has attach, CRDT, rendering, minimap, diagnostics,
minibuffer/menu, mouse, and editing code.
Impact:
New contributors and future audit passes start from incorrect project state.
This also makes it harder to decide which TODOs are live versus historical.
Recommendation:
Update `pmacs-gpu` metadata and the top of the design note to point at the
current status and the active per-phase framing/audit docs.
### F-014 - Low - GPU visual behavior lacks a committed screenshot/pixel regression harness
Evidence:
- `pmacs-gpu/src/main.rs:5670-6750` contains 39 unit tests focused on helper
logic and vertex construction.
- The design note explicitly deferred acceptance-test shape to golden-frame or
screenshot comparison (`docs/pmacs-gpu-design.md:356-360`).
- I did not find an automated headless GPU screenshot/pixel test in `tests/` or
`pmacs-gpu/src`.
Impact:
Layout and rendering regressions can pass the current unit suite. This matters
because the frontend has many pixel-local responsibilities: hit testing, dropdown
layout, minimap projection, diagnostic underlines, selection/current-line
backgrounds, and status/minibuffer composition.
Recommendation:
Add at least one deterministic headless render smoke test or recorded-frame
pixel/golden harness. Start with narrow cases: nonblank frame, text visible,
dropdown visible and clipped, minimap visible, diagnostic underline visible, and
resize behavior.
### F-015 - Low - Repository contains backup/build artifacts and untriaged untracked files
Evidence:
- `find` located backup/editor artifacts:
- `#run.sh#`
- `docs/semantic-frontend-protocol.md.local-bak`
- `git status --short` also showed an untracked root-level `pmacs` file, which
appears likely to be a local build/run artifact.
- Several untracked docs are present and may be intentional, but they need
explicit triage before commit.
Impact:
Backup and binary artifacts increase review noise and can be committed by
accident. Untracked docs make it unclear which design notes are canonical.
Recommendation:
Remove or `.gitignore` editor backups and local binaries. Triage untracked docs
as either real project documents or local handoff notes.
### F-016 - Low - Very large source files are maintenance hotspots
Evidence from line counts:
```text
15202 src/lua_bindings.rs
7093 src/editor.rs
6761 pmacs-gpu/src/main.rs
3729 src/lsp.rs
3495 src/semantic_render.rs
3178 src/buffer.rs
2988 src/editor_core.rs
```
Impact:
These files concentrate unrelated behavior, make review difficult, and increase
the chance of hidden coupling. `src/lua_bindings.rs` in particular combines many
Lua API domains, package management glue, LSP stores, theme bindings, attachment
bindings, and tests in one file.
Recommendation:
Split only along stable boundaries, not as a drive-by refactor. Good candidates:
Lua package APIs, Lua LSP APIs, Lua theme APIs, GPU input handling, GPU render
pipelines, GPU layout/dropdown logic, and editor command groups.
## Lua Audit Notes
The Lua audit command reported no errors. Warnings were:
- `builtin/packages/repl/init.lua:274`: `pmacs.process.spawn(spec)`.
- `tests/fixtures/pmacs-magit/gestures.lua:357`: `io.open(tmpfile, "w")`.
- `tests/fixtures/pmacs-magit/status.lua:55`: `pmacs.process.spawn { ... }`.
These are not automatically defects. The REPL package needs process access, and
fixture warnings are expected if those packages deliberately exercise filesystem
or process APIs. They should still be checked against package manifests whenever
fixtures graduate into real packages.
## Suggested Priority Order
1. Fix F-001 and update workspace verification commands.
2. Fix F-003 and F-004 before treating `pmacs-gpu` as user-ready.
3. Fix F-006 before relying on pmacs for editing permission-sensitive files.
4. Decide the package basename-collision policy in F-005 before wider package
ecosystem growth.
5. Add the GPU visual regression harness in F-014 before large layout changes.