bxctl: deny, another name for refuse
Implemented-By: OpenCode session (model recorded in docs/implementer-log.md)
This commit is contained in:
@@ -21,6 +21,9 @@ usage: bxctl <command> [options]
|
|||||||
bxctl refuse <id> [--reason TEXT] [--admin-socket PATH]
|
bxctl refuse <id> [--reason TEXT] [--admin-socket PATH]
|
||||||
Refuse approval <id>, recording a reason.
|
Refuse approval <id>, recording a reason.
|
||||||
|
|
||||||
|
bxctl deny <id> [--reason TEXT] [--admin-socket PATH]
|
||||||
|
Another name for refuse.
|
||||||
|
|
||||||
bxctl grants check [--admin-socket PATH]
|
bxctl grants check [--admin-socket PATH]
|
||||||
Check that the grants load.
|
Check that the grants load.
|
||||||
|
|
||||||
@@ -98,7 +101,7 @@ pub fn parse(args: &[String], home: &Path) -> Result<Command, UsageError> {
|
|||||||
"chat" => parse_chat(rest, home),
|
"chat" => parse_chat(rest, home),
|
||||||
"approvals" => parse_approvals(rest, home),
|
"approvals" => parse_approvals(rest, home),
|
||||||
"approve" => parse_approve(rest, home),
|
"approve" => parse_approve(rest, home),
|
||||||
"refuse" => parse_refuse(rest, home),
|
"refuse" | "deny" => parse_refuse(rest, home),
|
||||||
"grants" => parse_grants(rest, home),
|
"grants" => parse_grants(rest, home),
|
||||||
"audit" => parse_audit(rest, home),
|
"audit" => parse_audit(rest, home),
|
||||||
// A flag before the command, or any unknown command, is not valid.
|
// A flag before the command, or any unknown command, is not valid.
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
//! `bxctl deny` is another name for `bxctl refuse`: every argument list means the same under both
|
||||||
|
//! (M4b spec, section 2). Do not edit.
|
||||||
|
|
||||||
|
use bxctl::cli::{USAGE, parse};
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
fn under(command: &str, rest: &[&str]) -> Vec<String> {
|
||||||
|
std::iter::once(command)
|
||||||
|
.chain(rest.iter().copied())
|
||||||
|
.map(str::to_string)
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn deny_parses_as_refuse() {
|
||||||
|
let cases: &[&[&str]] = &[
|
||||||
|
&["41"],
|
||||||
|
&["41", "--reason", "not on a Friday"],
|
||||||
|
&["--reason", "no", "41"],
|
||||||
|
&["41", "--admin-socket", "/tmp/a.sock"],
|
||||||
|
&["41", "--reason", "x", "--admin-socket", "/tmp/a.sock"],
|
||||||
|
&[],
|
||||||
|
&["x41"],
|
||||||
|
&["41", "--reason"],
|
||||||
|
&["41", "42"],
|
||||||
|
&["41", "--bogus"],
|
||||||
|
];
|
||||||
|
for rest in cases {
|
||||||
|
let home = Path::new("/srv/bx");
|
||||||
|
assert_eq!(
|
||||||
|
parse(&under("deny", rest), home),
|
||||||
|
parse(&under("refuse", rest), home),
|
||||||
|
"{rest:?}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn usage_names_deny() {
|
||||||
|
assert!(USAGE.contains("bxctl deny <id>"), "{USAGE}");
|
||||||
|
assert!(USAGE.contains("bxctl refuse <id>"), "{USAGE}");
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@ reviewer adds findings under "Reviews" once per milestone.
|
|||||||
|
|
||||||
| Task | Date | Status | Gate runs | First gate | Deviations | Notes | Model |
|
| Task | Date | Status | Gate runs | First gate | Deviations | Notes | Model |
|
||||||
|---|---|---|---|---|---|---|---|
|
|---|---|---|---|---|---|---|---|
|
||||||
|
| M4b/03-bxctl-deny | 2026-09-24 | done | 1 | pass | none | Copied `docs/plans/M4b/files/crates/bxctl/tests/cli_deny.rs` into `crates/bxctl/tests/`. In `parse`, `"refuse" => parse_refuse(rest, home)` became `"refuse" | "deny" => parse_refuse(rest, home)`, so `deny` produces the same `Command::Refuse` as `refuse` for every argument list including the error cases. Added a blank line and the two-line `bxctl deny <id> [--reason TEXT] [--admin-socket PATH]` / `Another name for refuse.` block to `USAGE` after the `refuse` block, keeping the two-space and six-space indents. The copied test's `deny_parses_as_refuse` and `usage_names_deny` both failed before the changes and pass after; `cargo test -p bxctl` is green and `make gate` prints `gate: ok` on the first run. | ? |
|
||||||
| M4b/02-proto-gateway-wire | 2026-09-24 | done | 2 | fail | none | Copied `gateway.rs`, `gateway_wire.rs` and the eight `gateway_*.json` fixtures into place; added `pub mod gateway;` after `pub mod frame;` and the re-export to `lib.rs`, the four `Message` variants plus the `use` to `wire.rs`, and the four `kind` arms (changing "fourteen" to "eighteen") in `broker.rs`. Filled `is_mattermost_id` (`id.len() == 26 && every byte a-z0-9`) and `ApprovalReply::check` in the given order: `by`, `post`, then a reason over `MAX_REASON` chars by `chars().count()`. All 3 `gateway_wire` tests pass; the first `make gate` failed on clippy `collapsible_if`, fixed by collapsing the nested `if let` into a let-chain guard, and the second prints `gate: ok`. | ? |
|
| M4b/02-proto-gateway-wire | 2026-09-24 | done | 2 | fail | none | Copied `gateway.rs`, `gateway_wire.rs` and the eight `gateway_*.json` fixtures into place; added `pub mod gateway;` after `pub mod frame;` and the re-export to `lib.rs`, the four `Message` variants plus the `use` to `wire.rs`, and the four `kind` arms (changing "fourteen" to "eighteen") in `broker.rs`. Filled `is_mattermost_id` (`id.len() == 26 && every byte a-z0-9`) and `ApprovalReply::check` in the given order: `by`, `post`, then a reason over `MAX_REASON` chars by `chars().count()`. All 3 `gateway_wire` tests pass; the first `make gate` failed on clippy `collapsible_if`, fixed by collapsing the nested `if let` into a let-chain guard, and the second prints `gate: ok`. | ? |
|
||||||
| M4b/01-proto-escape | 2026-09-24 | done | 1 | pass | none | Copied `docs/plans/M4b/files/crates/proto/src/escape.rs`, `bxctl/src/escape.rs` and `proto/tests/escape.rs` into place, and added `pub mod escape;` to `proto/src/lib.rs` after `pub mod class;`. The bxctl file is now the one-line re-export `pub use proto::escape::{escape_json_text, escape_model_text};`, so no bxctl source or test changed and behaviour is identical. `cargo test -p proto --test escape` reports 8 passed; every bxctl suite passes unchanged (the moved escape test now runs against `proto::escape`); `cargo fmt --all` then `make gate` prints `gate: ok` on the first run. | ? |
|
| M4b/01-proto-escape | 2026-09-24 | done | 1 | pass | none | Copied `docs/plans/M4b/files/crates/proto/src/escape.rs`, `bxctl/src/escape.rs` and `proto/tests/escape.rs` into place, and added `pub mod escape;` to `proto/src/lib.rs` after `pub mod class;`. The bxctl file is now the one-line re-export `pub use proto::escape::{escape_json_text, escape_model_text};`, so no bxctl source or test changed and behaviour is identical. `cargo test -p proto --test escape` reports 8 passed; every bxctl suite passes unchanged (the moved escape test now runs against `proto::escape`); `cargo fmt --all` then `make gate` prints `gate: ok` on the first run. | ? |
|
||||||
| M4a/16-gatewayd-secret-file-race | 2026-09-24 | done | 2 | pass | Reworded the template comment in `read_checked` to drop the literal `check_file` token, so step 5's grep prints only the credential branch's `std::fs::read(&path)` | Replaced `check_file` with `pub fn read_checked(path, between) -> Result<Zeroizing<Vec<u8>>, String>`: check the path (absolute; `symlink_metadata` as `named`; not a symlink; a regular file), call `between()`, open the path, take `opened` metadata, refuse if `(opened.dev(), opened.ino()) != (named.dev(), named.ino())`, then the owner-uid and mode checks on `opened` (not `named`), then read the whole file into a `Zeroizing<Vec<u8>>` from that same handle. Added `use std::io::Read;`. In `load`, the File branch now calls `read_checked(path, &|| {})` and `value(std::mem::take(&mut *bytes))` so nothing unwiped remains. The three suites pass (5, 8, 5); the only remaining `std::fs::read(` is the credential branch and `check_file` is gone. `make gate` prints `gate: ok`. | ? |
|
| M4a/16-gatewayd-secret-file-race | 2026-09-24 | done | 2 | pass | Reworded the template comment in `read_checked` to drop the literal `check_file` token, so step 5's grep prints only the credential branch's `std::fs::read(&path)` | Replaced `check_file` with `pub fn read_checked(path, between) -> Result<Zeroizing<Vec<u8>>, String>`: check the path (absolute; `symlink_metadata` as `named`; not a symlink; a regular file), call `between()`, open the path, take `opened` metadata, refuse if `(opened.dev(), opened.ino()) != (named.dev(), named.ino())`, then the owner-uid and mode checks on `opened` (not `named`), then read the whole file into a `Zeroizing<Vec<u8>>` from that same handle. Added `use std::io::Read;`. In `load`, the File branch now calls `read_checked(path, &|| {})` and `value(std::mem::take(&mut *bytes))` so nothing unwiped remains. The three suites pass (5, 8, 5); the only remaining `std::fs::read(` is the credential branch and `check_file` is gone. `make gate` prints `gate: ok`. | ? |
|
||||||
|
|||||||
Reference in New Issue
Block a user