You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
--help and docs/cli.md both say --dangerously-skip-approval implies three flags. The binary wires none of them.
apps/sysknife-cli/src/cli.rs:63-68:
/// Execute HIGH-risk steps with no human confirmation.
///
/// Requires `SYSKNIFE_I_ACCEPT_UNATTENDED_ROOT=1` in the environment as
/// well; the flag on its own refuses to run. Implies `--yes`,
/// `--max-risk high` and `--non-interactive` unless you set a lower
/// `--max-risk`, which still wins.
apps/sysknife-cli/src/main.rs:26 parses and never mutates the result. build_run_opts at :183 copies the flags across one for one:
and ApprovalPolicy::effective_auto_ceiling at apps/sysknife-cli/src/approval.rs:133 leaves before the ceiling is computed:
if !self.yes {
return None;
}
No default_value_if on yes, max_risk or non_interactive, and no assignment to any of them anywhere in the binary.
The unit test at apps/sysknife-cli/src/approval.rs:251 assumes the wiring lives somewhere it does not:
fn the_override_is_inert_without_yes() {
// The flag implies --yes at the CLI layer. If that wiring is ever
// dropped, the policy must not auto-approve on the override alone.
Measured
Two throwaway tests appended to apps/sysknife-cli/src/main.rs in a scratch clone of a3d17ad, run in a container, then removed. git status --porcelain | wc -l was 0 afterwards.
The first parses ["sysknife", "--dangerously-skip-approval", "check disk usage"] and asserts cli.yes is false, cli.max_risk is None, cli.non_interactive is false. The second sets SYSKNIFE_I_ACCEPT_UNATTENDED_ROOT=1, builds the real RunOpts through build_run_opts, and asserts effective_auto_ceiling() is None and decide_step(&PlanRiskLevel::Low) is RequiresPrompt.
The podman line below carried an absolute path to a cargo target cache on my own
machine when I filed this. I have replaced it with $CARGO_TARGET_CACHE; point that
at any writable directory, or drop both -v .../ct and CARGO_TARGET_DIR and let
cargo build into the checkout. Nothing else about the run changed.
$ podman run --rm --network=none -v "$PWD:/repo:z" -v "$CARGO_TARGET_CACHE:/ct:z" -v "$HOME/.cargo:/cargo:O" -w /repo -e CARGO_HOME=/cargo -e CARGO_TARGET_DIR=/ct/rv436 -e CARGO_NET_OFFLINE=true -e HOME=/tmp docker.io/library/rust:1-slim cargo test -p sysknife-cli --bins --offline maintainer_probe_336
running 2 tests
test maintainer_probe_336::probe_parsed_flags ... ok
test maintainer_probe_336::probe_decision_with_both_keys ... ok
test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 268 filtered out; finished in 0.00s
So SYSKNIFE_I_ACCEPT_UNATTENDED_ROOT=1 sysknife --dangerously-skip-approval "check disk usage" prompts for every step, LOW included. An operator following the Unattended mode section into a cron job gets a run that blocks on a prompt instead of one that executes, or gets nothing at all if stdin is closed.
Which side moves
The documentation, not the binary.
A flag that silently switches on three others is the opposite of the deliberateness the rest of this design insists on: the two-key rule exists so that neither a flag left in a script nor a variable left in a profile is enough on its own, and --yes typed explicitly is a third act of intent worth keeping. The current behaviour is also the safe one, which is why this survived unnoticed.
So --dangerously-skip-approval raises the --yes auto-approval ceiling from MEDIUM to HIGH and does nothing else, and every place that describes it says that.
Scope
apps/sysknife-cli/src/cli.rs:63-68, the doc comment that becomes --help.
apps/sysknife-cli/src/approval.rs:251-253, the comment inside the_override_is_inert_without_yes. The assertion is correct and describes the binary; only its comment claims otherwise. Rename it if a better name suggests itself, and keep the test.
A test that pins the decision rather than the parse: with both keys and no --yes, decide_step on a LOW step is RequiresPrompt. the_override_is_inert_without_yes already proves this at the policy layer; what is missing is the same assertion built from parsed CLI flags through build_run_opts, so a future default_value_if cannot change the contract without turning a test red.
Both prose figures and the test baseline move with any test you add: UPDATE_TEST_BASELINE=1 scripts/test_baseline.sh writes tests/evidence/workspace-tests.json, and the {count} Rust tests literal in README.md, docs/introduction.md and docs/distro-support.md moves in the same commit.
Break what the guard protects. Build RunOpts through build_run_opts from
parsed CLI flags with --dangerously-skip-approval and no --yes, then assert decide_step(&PlanRiskLevel::Low) is RequiresPrompt. That pins the contract at
the layer the documentation describes, which is the layer nothing currently
tests. the_override_is_inert_without_yes proves the same thing one level down,
at the policy, so a future default_value_if on yes could change the user-
visible behaviour with every test still green.
Break the guard's own input. Assert the parse selected the flag. A test that
builds RunOpts from a Cli whose flag never parsed passes for the wrong
reason, and renaming the long flag would not turn it red. Assert cli.dangerously_skip_approval is true in the same test that asserts the
decision, so the two cannot drift apart.
Fix the comment inside the_override_is_inert_without_yes while you are there.
The assertion is right; only its comment claims wiring that does not exist.
Difficulty
easy. Three files carrying prose and one new test. No VM, no daemon, no
provider credentials. The reading is the work: confirm for yourself that the
binary wires none of the three before you rewrite the sentence that says it does.
Getting started
CONTRIBUTING.md
has the build and test commands. cargo test -p sysknife-cli --bins is the
suite for this one.
Both prose figures and the test baseline move with any test you add: UPDATE_TEST_BASELINE=1 scripts/test_baseline.sh writes tests/evidence/workspace-tests.json, and the {count} Rust tests literal in README.md, docs/introduction.md and docs/distro-support.md moves in the
same commit. It is 1,861 today.
No CLA and no copyright waiver. The project is MIT.
--helpanddocs/cli.mdboth say--dangerously-skip-approvalimplies three flags. The binary wires none of them.apps/sysknife-cli/src/cli.rs:63-68:apps/sysknife-cli/src/main.rs:26parses and never mutates the result.build_run_optsat:183copies the flags across one for one:and
ApprovalPolicy::effective_auto_ceilingatapps/sysknife-cli/src/approval.rs:133leaves before the ceiling is computed:No
default_value_ifonyes,max_riskornon_interactive, and no assignment to any of them anywhere in the binary.The unit test at
apps/sysknife-cli/src/approval.rs:251assumes the wiring lives somewhere it does not:Measured
Two throwaway tests appended to
apps/sysknife-cli/src/main.rsin a scratch clone ofa3d17ad, run in a container, then removed.git status --porcelain | wc -lwas 0 afterwards.The first parses
["sysknife", "--dangerously-skip-approval", "check disk usage"]and assertscli.yesis false,cli.max_riskisNone,cli.non_interactiveis false. The second setsSYSKNIFE_I_ACCEPT_UNATTENDED_ROOT=1, builds the realRunOptsthroughbuild_run_opts, and assertseffective_auto_ceiling()isNoneanddecide_step(&PlanRiskLevel::Low)isRequiresPrompt.The
podmanline below carried an absolute path to a cargo target cache on my ownmachine when I filed this. I have replaced it with
$CARGO_TARGET_CACHE; point thatat any writable directory, or drop both
-v .../ctandCARGO_TARGET_DIRand letcargo build into the checkout. Nothing else about the run changed.
So
SYSKNIFE_I_ACCEPT_UNATTENDED_ROOT=1 sysknife --dangerously-skip-approval "check disk usage"prompts for every step, LOW included. An operator following the Unattended mode section into a cron job gets a run that blocks on a prompt instead of one that executes, or gets nothing at all if stdin is closed.Which side moves
The documentation, not the binary.
A flag that silently switches on three others is the opposite of the deliberateness the rest of this design insists on: the two-key rule exists so that neither a flag left in a script nor a variable left in a profile is enough on its own, and
--yestyped explicitly is a third act of intent worth keeping. The current behaviour is also the safe one, which is why this survived unnoticed.So
--dangerously-skip-approvalraises the--yesauto-approval ceiling from MEDIUM to HIGH and does nothing else, and every place that describes it says that.Scope
apps/sysknife-cli/src/cli.rs:63-68, the doc comment that becomes--help.apps/sysknife-cli/src/approval.rs:251-253, the comment insidethe_override_is_inert_without_yes. The assertion is correct and describes the binary; only its comment claims otherwise. Rename it if a better name suggests itself, and keep the test.docs/cli.md, the Global flags row and the "What it turns off" bullet. docs: align CLI unattended mode contract #436 is open against that file and will carry these two.--yes,decide_stepon a LOW step isRequiresPrompt.the_override_is_inert_without_yesalready proves this at the policy layer; what is missing is the same assertion built from parsed CLI flags throughbuild_run_opts, so a futuredefault_value_ifcannot change the contract without turning a test red.Both prose figures and the test baseline move with any test you add:
UPDATE_TEST_BASELINE=1 scripts/test_baseline.shwritestests/evidence/workspace-tests.json, and the{count} Rust testsliteral inREADME.md,docs/introduction.mdanddocs/distro-support.mdmoves in the same commit.Found while reviewing #436 against #336.
Tests first
Break what the guard protects. Build
RunOptsthroughbuild_run_optsfromparsed CLI flags with
--dangerously-skip-approvaland no--yes, then assertdecide_step(&PlanRiskLevel::Low)isRequiresPrompt. That pins the contract atthe layer the documentation describes, which is the layer nothing currently
tests.
the_override_is_inert_without_yesproves the same thing one level down,at the policy, so a future
default_value_ifonyescould change the user-visible behaviour with every test still green.
Break the guard's own input. Assert the parse selected the flag. A test that
builds
RunOptsfrom aCliwhose flag never parsed passes for the wrongreason, and renaming the long flag would not turn it red. Assert
cli.dangerously_skip_approvalis true in the same test that asserts thedecision, so the two cannot drift apart.
Fix the comment inside
the_override_is_inert_without_yeswhile you are there.The assertion is right; only its comment claims wiring that does not exist.
Difficulty
easy. Three files carrying prose and one new test. No VM, no daemon, noprovider credentials. The reading is the work: confirm for yourself that the
binary wires none of the three before you rewrite the sentence that says it does.
Getting started
CONTRIBUTING.md
has the build and test commands.
cargo test -p sysknife-cli --binsis thesuite for this one.
Both prose figures and the test baseline move with any test you add:
UPDATE_TEST_BASELINE=1 scripts/test_baseline.shwritestests/evidence/workspace-tests.json, and the{count} Rust testsliteral inREADME.md,docs/introduction.mdanddocs/distro-support.mdmoves in thesame commit. It is 1,861 today.
No CLA and no copyright waiver. The project is MIT.