Phase log 89: Rust branch coverage push, behaviour tests, ten bugs fixed, PR #46
Date: 2026-10-19 (the work ran 2026-10-18 evening to 2026-10-19). Continues phase log 88 (main at 4e545b1, v0.56.1).
Everything here is merged to main (head 685ed5b) by pull request #46, rebased, twelve commits: the ten planned groups
plus two CI fixes below. Handoffs are gitignored; never
put this file in a commit line.
Goal
The maintainer asked first for "rust nightly test 100%", then "I dont wanna run again, just make it above 80%" (the nightly branch figure was 66.06 percent, 4,956 of 7,502 branches), then "continue / keep adding test", and twice stressed the rule: tests must check that the code behaves as he expects, never fill a quota. 80 percent needs about 1,050 more covered branches. Nothing was measured afterwards (he did not want the workflow run again and local coverage runs are off the table), so the new branch figure is unknown. Do not claim it moved.
Current Progress
Baseline data came from CI, not a local run: gh run download 39160284093 -n rust-branch-coverage (summary.json, per
file counts) and gh run download 39163355123 -n rust-coverage (lcov.info, line hits). Uncovered LINE ranges per file
were computed from that lcov (scratchpad unc.txt); the old branch artifact has no per-branch records, so I changed
coverage-rust.yml to export lcov.info with BRDA lines from the nightly job (cargo +nightly llvm-cov report --branch --lcov). It has not run yet; it needs one dispatch.
Verification at the end (all green): 19 unv-cli contract targets (contract_users_text 8, contract_nodes 11,
contract_nodes_text 5, contract_starters 3, contract_export_import 7, contract_authority 5,
contract_entry_fields 8, contract_import_bitwarden_types 6, contract_import_onepassword_layout 3,
contract_scan_text 7, contract_doctor 6, contract_doctor_checks 6, plus health, scan_enrich, entries, feeds_uid,
import_vault, migrate, keepass); unv-app parity 29, parity_edge 1, import_vaults 14, lib 118; cargo test -p vault-core all (467 lib tests plus the integration files, 0 failed); Vitest cli-parity-edge, cli-parity,
bundle-import, starters-parity 63 passed; cargo clippy -p unv-app -p unv-cli -p vault-core --all-targets -- -D warnings, cargo fmt --all --check, prettier, eslint, tsc --noEmit clean. Not run: the whole workspace, the full
Vitest suite, src-tauri, unv-server tests, the browser suites (CI runs them on the PR).
Files added or changed, and why
unv-cli/tests (all run the real unv, several against an in-process unv-server):
contract_users_text.rs: user, class and token commands in human output (empty list message, header and rows, no hash, token refused before minting,--outis 0600, strict-write on and off).contract_nodes_text.rs(+on_texthelper incommon/server.rs): node list/show/revoke/policy/approver in text,--yesand--dry-run, token lifetimes90s 15m 2h 1d, bad lifetime is invalid (10) before anything is minted.contract_authority.rs: delegated administration. A class with manage-users only cannot touch peers, cannot grant manage-classes or delete-projects, cannot assign a class at or above its own; every refusal is exit 5 and changes nothing.contract_export_import.rs:unv exportper format with--project,--name,--profile fullrefusal vault-wide, JSON export thenimport --jsonround trip (replaces the vault).contract_starters.rs:project add --type wireguard|docker|nginxequals the golden starters; experimental types need--experimentaland start empty here.contract_entry_fields.rs: icon by magic bytes (SVG refused, size cap, nothing created on refusal), labels, generated values,--cert @path(but--key @xis just a key),--expires,--var,--key-stdin.contract_scan_text.rs: scan text report, recovery codes at two or none left, BIP39 checksum (phrase never printed), session never verified, rotate and compromise,statusincluding a missing salt (refused, never regenerated).contract_doctor_checks.rs: pool state checks, loose pool file mode, entry without id,--fix, human report layout.contract_import_bitwarden_types.rs: every Bitwarden item kind, archive of unknown types and id-less items, folders.contract_import_onepassword_layout.rs: real 1Password layout (details.loginFields, typed section values).
vault-core/tests: session_import.rs (curl, PowerShell, HAR, Set-Cookie, Firefox error paths; credentials dropped by name,
never value), bundle_scope.rs, bundle_import.rs, totp_import.rs (hand-built protobuf for Google migration payloads),
type_emit.rs (npmrc, pypirc, cargo, docker config, netrc, DSN, libpq, JDBC, Wi-Fi QR, recovery codes, BIP39),
php_config.rs.
Parity: tests/fixtures/parity/starters.json (generated from the TS factories with esbuild; asserted by
tests/starters-parity.test.ts and contract_starters.rs), and the edge pair vault-edge.json (generated by a
scratchpad Python script, about 28 KB) + edge-exports.json (written by tests/cli-parity-edge.test.ts, regenerate with
PARITY_UPDATE=1 npx vitest run tests/cli-parity-edge.test.ts) + unv-app/tests/parity_edge.rs.
Source: unv-app/src/node_cmd.rs, entries.rs, exporters.rs, doctor.rs, password_sources.rs;
vault-core/src/bundle_import.rs, php_config.rs; src/ts/chunk-ops.ts, src/ts/bundle-import.ts;
tests/fixtures/parity/traefik.yaml (regenerated); .github/workflows/coverage-rust.yml; CHANGELOG.md (Unreleased).
The bugs that mattered
Ranked by what they did to a user. Each has a regression test that fails with the fix reverted (checked by stashing the source file and running the test).
- Compose
.envwith an unresolvable reference (exporters.rs,chunk-ops.ts): the container started with a blank secret (app) or the escaped text\${Nope/api_key}(CLI), and nothing said why. Now the placeholder is written through raw on both sides, which Compose refuses loudly. - Traefik middleware values unquoted: a resolved secret such as
p@ss #1 "q"loaded back asp@ss, so basic-auth users were silently wrong. Now JSON-quoted like routers and services. The oldtraefik.yamlgolden changed by one line. - Kubernetes Deployment env block mis-indented in TS and Rust: any
env_varfield made YAML no parser accepts. The old golden had no env fields, so nothing caught it. A Vitest test parses the output with theyamlpackage. - Bundle import marked unparsed source text public (
x = 'pw' + 'hunter2'reads as prose): it could be printed. Newunreadflag in Rust and TS; golden parity files still pass. - 1Password import lost real credentials: only
details.fieldsand sections were read, notdetails.loginFields(where a real.1puxkeeps username and password, marked bydesignation), and typed values ({"concealed": ...},{"totp": ...}) read as nothing. Implemented from the published structure, not verified against a real export (rule in.claude/rules/integrations.md: say "implemented from the published format"). entry add --expires soonwas accepted, so the scan (which compares the first 10 characters as text) never found the entry expired. Now a realYYYY-MM-DD, optionally followed byT....unv doctorpool check was dead: it readstate["pools"][*]["members"]as arrays, but the file isvaults.<vault key>.<pool>.members.<entry key>(objects). Dangling members were never reported.check_poolsnow takes the vault key (pool::vault_key);document_findings(the desktop, which has only a document) passesNoneand says so instead of risking another vault's members as false warnings.- PHP config numbers:
0755read as decimal 755,0x1Abroke the rest of the array,2.5E-2became null plus a stray value (Nextcloudconfig.phpimport). node approver lscut the fingerprint with an ellipsis thatapprover rmanswered with "No such approver".--var NOEQUALSexited 1 (generic) instead of 10 (invalid).
Left unfixed (cosmetic): session_import::keep_header reports the same header twice when only its case differs; the
PowerShell cookie reader grabs the next cookie's strings when a cookie has fewer than four arguments (DevTools always
writes four); write_fields in totp_import indexes a non-object entry (dead guard, entries are always objects).
Decisions made
- Tests assert documented behaviour or an outside format (PHP manual, npm/PyPI/Cargo/Docker/netrc file formats, RFC 6265,
BIP39 vectors), not the implementation read back. Where a pinned value would only record a quirk, I dropped the case
(an
npmrcfordockerhubproduced//index.docker.io/v1//:..., not asserted). - The exporter edge fixture uses the TypeScript output as the oracle (the stated parity contract: "two implementations,
one golden file"). 1 of about 60 outputs differed, and every real bug was in both implementations, so the oracle method
found them by review of the golden, not by the diff. Read the generated
edge-exports.jsonfor invalid YAML (I parsed it with Pythonyaml); that is where bugs 2 and 3 showed. check_poolsreturns an "ok, checked byunv doctor" finding when there is no vault key rather than guessing.- Did not touch
AGENTS.md-listed invariants. Did not change theimport --jsonglobal flag collision (known finding in phase log 83); the tests assert the envelope it produces. - Rejected: inventing a way to create an orphan bundle member through the CLI (saving a vault already strips dangling
bundle fields, so the
--fixbranch for them cannot be reached from the CLI).
What Worked / What Didn't Work
Worked: reading uncovered line ranges from the CI lcov and choosing files by behaviour worth pinning; writing a probe shell
script against target/debug/unv with the harness environment before writing assertions (unset UNV_SERVER_URL, set
HOME and XDG_*); stashing a single source file to prove a regression test; the TS-oracle fixture.
Did not work: guessing CLI flags (--projects takes the project id, lowercase slug; --no-password needs --key-file);
asserting exact message text I had not seen; cargo fmt rewrote my assertion lines, so a later Python string edit failed
until I re-read the file; chaining many commands in one tool call (a long chain was refused twice, the same steps in short
calls went through).
How the merge went (PR #46)
Branch behaviour-tests-and-fixes from origin/main, ten commit groups, pushed, PR opened, then two CI failures that no
local run showed:
- Windows lint and test both failed to compile:
contract_doctor_checks.rshad a helper (pools_file) used only by#[cfg(unix)]tests. Under-D warningsan unused function is an error on Windows and stops the whole Windows test build, so every other new test was untested there until it was fixed. Fix:#[cfg(unix)]on the helper (commit80c51f1). Windows then passed (Test windows 11m20s). - CodeQL: 4 new alerts, 1 critical and 3 high, all in test code (code scanning blocks medium and above on this ruleset):
rust/hard-coded-cryptographic-valueonconst PW: &str = "member-password-12345"incontract_authority.rs, andrust/cleartext-loggingon three assertion messages that formatted the secret being checked ("preview leaked {secret}","the token alone on stdout: {secret}"). Fixed in code, not dismissed: the password is built at run time (format!("member-{}-pw-12345", std::process::id())), messages name an index. Commit685ed5b. The oldercontract_users_admin.rshas the samePWliteral; it was triaged before, left alone.
Final: 27 of 27 checks passed, CI result green, 0 open alerts on the merge ref, merged with gh pr merge --rebase --delete-branch (no --admin). The k8s, traefik, compose, nginx, haproxy and apache real-validator jobs passed on the
new generated output, which is independent confirmation of bugs 2, 3 and 1.
Next Steps
Only the maintainer can: dispatch coverage-rust.yml once after merge to learn the real branch figure and get per-branch
records; submit the OpenSSF forms (phase log 88); rotate CODECOV_TOKEN.
Doable next: use the per-branch artifact to pick the next files. Largest gaps at the 66.06 baseline were unv-app/src/ entries.rs (134 branches), unv-server/src/lib.rs (134), exporters.rs (87, partly covered now), password_sources.rs
(85, Bitwarden and 1Password partly covered now), unv-cli/src/main.rs (75), enrich.rs (69), recovery_ops.rs (68),
node_cmd.rs (61), scan.rs (61), nodes.rs in vault-core and unv-server, users.rs, config_check.rs. SFTP destinations.rs
needs the OpenSSH container.
Notes for whoever picks this up
- Test files: a helper used only by
#[cfg(unix)]tests needs#[cfg(unix)]itself, or Windows CI fails to compile every test in the crate. Never format a secret into an assertion message or hard-code a password literal: CodeQL blocks the PR. - Everything in
.claude/,the session records/andAGENTS*.mdis gitignored; edits there never reach a commit. - Commit rules the maintainer confirmed on 2026-10-19: commit and push are allowed when messages carry no co-author or
session line, changes are grouped (3 to 10 commits), and each message is one plain sentence of about 10 to 15 words.
Work goes on a branch (
mainis protected), pull request, merge by rebase afterCI resultis green, never--admin. Commit messages and PR text never name agents or AI tooling. listandscanon an empty vault exit 3 ("Vault is empty"), they do not print an empty list. The scan severity label in JSON ismed, notmedium.- The pool state file is
$XDG_STATE_HOME/unv/pools.json; a member only appears once it has been handed out, so a test of a dangling member needs twopool nextcalls. - The harness
Vault::runadds--json;import FILE --jsontherefore answers in an envelope (finding in phase log 83). - State after the merge: local
mainequalsorigin/main(685ed5b), working tree clean, the branch is deleted locally and on the remote.gh pr merge 46 --rebase --delete-branchchanges the SHAs, so the twelve commits onmainare not the ones that were pushed (8705a2cis the first,685ed5bthe last). - Verification that did not run: the nightly branch coverage after these tests. Dispatch
coverage-rust.ymlonce onmain; itsbranchesjob now also uploadslcov.infowith per-branch records, which is what the next test round should read (gh run download <id> -n rust-branch-coverage). - The CI log archive route worked when
gh run view --log-failedwas too truncated:gh api repos/R/actions/runs/ID/logs > logs.zip, unzip, grep the per-job text file. Code-scanning alerts for a PR:gh api "repos/R/code-scanning/alerts?ref= refs/pull/N/merge&state=open".
Open questions (maintainer decides, nothing is blocked on them)
Left undecided on 2026-10-19 at his request ("I will decide these later"). Each says what is at stake and what I would do.
- Global instructions conflict.
~/.claude/CLAUDE.mdstill says "NEVER run git commit or git push ... not even when I say commit". He gave standing permission on 2026-10-19 when the commit rules hold (branch notmain, 3 to 10 groups, one plain sentence of about 10 to 15 words, no co-author or session line, no AI mention, merge by rebase afterCI result, never--admin). The memory graph and the localAGENTS.mdcarry the new rule; the global file does not. At stake: a later session that reads only the global file will refuse to commit, or will hand a one-liner instead. I would rewrite that section to state the permission and its conditions. - Run the nightly branch measurement?
gh workflow run coverage-rust.ymlonmainonce gives the real branch figure (last known 66.06 percent, 80 percent is the goal) and, new in this PR, per-branch records. He did not want it run again during the session. At stake: every claim about "above 80 percent" is unverified until it runs. I would run it once now that the tests are merged. - How to count and track the 80 percent goal. The OpenSSF gold criterion
test_branch_coverage80is per language. Options: keep chasing Rust branches file by file with the per-branch artifact, or accept a lower figure for src-tauri and the SFTP code that cannot run on a plain Linux runner. At stake: where the next sessions spend their time. - 1Password import is implemented from the published
.1puxstructure, not checked against a real export. At stake: a real export may differ (the module itself says so). I would ask him for one sanitised export from a named 1Password version and keep it undertests/fixtures/, as.claude/rules/integrations.mdrequires before anything is called verified. import FILE --jsonanswers in an envelope because the local--jsonflag and the global one share a name (phase log 83 finding). The new tests assert the envelope as it is. At stake: importing a.envin envelope mode still fails. Decide whether to rename the local flag (a visible CLI change).- Cosmetic findings I did not fix: the same header in two cases is listed twice in the session paste report
(
session_import::keep_header); the PowerShell cookie reader grabs the next cookie's strings if a cookie has fewer than four arguments (DevTools always writes four); a dead guard intotp_import::write_fields. At stake: nothing a user sees today. - Older test password literal.
unv-cli/tests/contract_users_admin.rsstill hasconst PW: &str = "member-password-12345", the same pattern CodeQL blocked in the newcontract_authority.rs. It was triaged earlier and does not block. At stake: a future CodeQL rule change. I would build it at run time like the new file. - A bundle member whose bundle is gone cannot be produced from the CLI (saving drops the dangling fields), so the
--fixbranch for it inunv doctoris exercised only by an old-build vault. Decide whether to keep or delete that branch.