Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions docs/audits/impl-PMAT-444-receipt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# PMAT-444 — receipt: keyring CLI tests each own a TempDir keyring (bashrs#444)

**Ticket:** PMAT-444 (bashrs#444), kind code, epic #426, release v7.4.2.
**Branch:** `fix/444-keyring-test-isolation` from `6ba7beb647`; fix commit `0ea0f32770`.

## Defect
`test_INSTALLER_CLI_007_keyring_init` and `_list` ran the binary with the ambient environment, so both
resolved the real `~/.config/bashrs/installer/keyring.json`. In parallel, list read the file while init
was writing it: `Failed to parse keyring: EOF while parsing a value at line 1 column 0` (red v7.4.2
release gate on 0ae1212911).

## Fix
`keyring_cmd(&TempDir)` sets `XDG_CONFIG_HOME` and `HOME` to the test's own directory. init asserts
the file lands there; list on a fresh dir asserts `Keyring not initialized`; new `list_after_init`
asserts list reads what init wrote. Census of keyring-file touchers: the unit tests in
`command_tests_installer_cov_tests_cov_2.rs` already pass the path in (#339); `installer run` reads the
keyring only under `--verify-signatures`, which no test passes. No other test touches the file.

## Discriminator (t2build, `INSTALLER_CLI_007 --test-threads=8`, one shared scratch HOME per loop)
| binary | runs | failures | shared HOME got a keyring |
|---|---|---|---|
| before (origin/main) | 300 | 2 | yes |
| before (origin/main) | 1000 | 13 | yes |
| after | 1000 x3 | 0, 0, 0 | no |

Full `cli_installer_tests`: 20 passed. `cargo clippy -p bashrs --test cli_installer_tests -D warnings`: clean.

## Planted review quorum (degraded: same-family; author claude-opus-5-5, never a lane)
Plant: the same diff with `keyring_list` reverted to `bashrs_cmd()` (ambient HOME). Lanes saw each diff blind.
| lane | real diff (sha256 d21c0bafceaf29ed…) | plant (sha256 d8dd7cfda4e79692…) |
|---|---|---|
| claude-sonnet-5-5 #1 | PASS | FAIL (names keyring_list on the real home) |
| claude-sonnet-5-5 #2 | PASS | FAIL |
| claude-haiku-4-5 | PASS | PASS (missed) |

Real 3/3 PASS; plant caught 2/3 (threshold >=2/3). Lane caveats (other keyring touchers, `TempDir`
import) are answered by the census above and `use tempfile::TempDir;` at line 13.

## FR-4 over-cap round (prcap: paiml/bashrs 3 open, cap 3)
Open PRs #434 (linter, v7.4.3), #423 (dependabot), #422 (darwin nightly CI): none is alike, so no fold.
Planted over-cap quorum, same lanes. Plant: a v7.4.3 doc-comment reword with nothing red, which must FOLD_OR_WAIT.
| lane | PMAT-444 | plant |
|---|---|---|
| claude-sonnet-5-5 #1 | OPEN_OVER_CAP | FOLD_OR_WAIT |
| claude-sonnet-5-5 #2 | OPEN_OVER_CAP | FOLD_OR_WAIT |
| claude-haiku-4-5 | OPEN_OVER_CAP | FOLD_OR_WAIT |

3/3 OPEN_OVER_CAP; plant caught 3/3. Lane outputs: `/mnt/nvme-raid0/tmp/bashrs-444-r1/q/` on lambda-labs.
20 changes: 20 additions & 0 deletions docs/roadmaps/roadmap.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2962,3 +2962,23 @@ roadmap:
- release:v7.4.2
notes: null
epic: 426
- id: PMAT-444
github_issue: 444
item_type: task
title: 'keyring CLI tests share the real $HOME keyring and race under parallel test threads (Refs #444)'
status: inprogress
priority: critical
assigned_to: null
created: 2026-10-10T12:59:47Z
updated: 2026-10-10T12:59:49.161712617+00:00
spec: null
acceptance_criteria:
- 'test_INSTALLER_CLI_007_keyring_init and _list both resolve the real user''s $XDG_CONFIG_HOME or $HOME/.config/bashrs/installer/keyring.json; run in parallel, list reads a file init is mid-writing: ''EOF while parsing a value at line 1 column 0''. Fix: each keyring CLI test owns a TempDir via XDG_CONFIG_HOME+HOME. Discriminator: N parallel loop fails before, passes after.'
phases: []
subtasks: []
estimated_effort: null
labels:
- kind:code
- release:v7.4.2
notes: null
epic: 426
52 changes: 45 additions & 7 deletions rash/tests/cli_installer_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -312,27 +312,65 @@ fn test_INSTALLER_CLI_006_test_matrix() {
// =============================================================================
// INSTALLER_CLI_007: Keyring command tests
// =============================================================================
//
// Every keyring test runs the binary against a keyring in its OWN TempDir (#444). The binary resolves the
// keyring from `$XDG_CONFIG_HOME`, else `$HOME/.config`; with the ambient environment, init and list both
// resolved the user's real `~/.config/bashrs/installer/keyring.json`, libtest ran them on parallel threads,
// and list read the file while init was writing it: "Failed to parse keyring: EOF while parsing a value at
// line 1 column 0". Both variables are set, so neither branch of the resolution can reach the real home.

/// A `bashrs installer keyring` command whose keyring lives under `config`, never the real `$HOME`.
fn keyring_cmd(config: &TempDir) -> Command {
let mut cmd = bashrs_cmd();
cmd.env("XDG_CONFIG_HOME", config.path())
.env("HOME", config.path())
.arg("installer")
.arg("keyring");
cmd
}

/// Where `keyring_cmd(config)` puts the keyring file.
fn keyring_file(config: &TempDir) -> std::path::PathBuf {
config
.path()
.join("bashrs")
.join("installer")
.join("keyring.json")
}

#[test]
fn test_INSTALLER_CLI_007_keyring_init() {
bashrs_cmd()
.arg("installer")
.arg("keyring")
let config = TempDir::new().unwrap();
keyring_cmd(&config)
.arg("init")
.assert()
.success()
.stdout(predicate::str::contains("Initialized keyring"));
assert!(
keyring_file(&config).is_file(),
"init must write the keyring inside the test's own directory"
);
}

#[test]
fn test_INSTALLER_CLI_007_keyring_list() {
bashrs_cmd()
.arg("installer")
.arg("keyring")
let config = TempDir::new().unwrap();
keyring_cmd(&config)
.arg("list")
.assert()
.success()
.stdout(predicate::str::contains("Keyring not initialized"));
}

#[test]
fn test_INSTALLER_CLI_007_keyring_list_after_init() {
let config = TempDir::new().unwrap();
keyring_cmd(&config).arg("init").assert().success();
keyring_cmd(&config)
.arg("list")
.assert()
.success()
.stdout(predicate::str::contains("Keyring")); // Outputs "Keyring not initialized" or "Keyring contents"
.stdout(predicate::str::contains("Keyring contents"));
}

// =============================================================================
Expand Down
Loading