diff --git a/TODO.md b/TODO.md index 15c6b5e..b26af90 100644 --- a/TODO.md +++ b/TODO.md @@ -1933,6 +1933,16 @@ and borrows `pr`'s conventions rather than inventing new ones beside them. if wanted; nothing asks for them yet ## misc +- [x] "N scope(s) granted since this login" said the opposite of what it + meant, in all three places it was printed: `doctor local`'s scopes row, + `auth status`'s per-account lines, and `scope_gap`'s own doc comment. + Nothing was granted — these are the scopes *this build asks for* that + the session does not carry, which is why the row is a failure rather + than a note. The agent notes made it worse by telling an agent to + ignore the message "which is just advisory", when a missing scope fails + `doctor local` and refuses the command it names; they now separate it + from the expired access token, which really is advisory + - [x] Output channels — one rule, on every flag: stdout is the answer, everything else is on stderr. Warnings, notes and progress lines all go through `crate::term::say` at a level (`warn`/`note`/`step`/`debug`) and diff --git a/src/auth.rs b/src/auth.rs index c2f5324..3aedf81 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -1747,7 +1747,8 @@ pub async fn status(json: bool) -> Result<()> { let gap = scope_gap(&account.did); if !gap.is_empty() { println!( - " {} scope(s) granted since this login: log in again to add them:", + " {} scope(s) atgc asks for that this login does not carry: \ + log in again to add them:", gap.len() ); for line in &gap { diff --git a/src/clients/tangled/scope.rs b/src/clients/tangled/scope.rs index 76794a2..b1f99a9 100644 --- a/src/clients/tangled/scope.rs +++ b/src/clients/tangled/scope.rs @@ -263,8 +263,14 @@ pub fn writer_of(scope: &str) -> Option<&'static str> { .map(|(_, command)| *command) } -/// One line per scope this account's session was granted before atgc asked -/// for it, and nothing at all when there is nothing to say. +/// One line per scope this build asks for that this account's session does +/// not carry, and nothing at all when there is nothing to say. +/// +/// The direction is worth stating plainly, because every caller of this used +/// to phrase it backwards: nothing here was *granted*. These are the scopes +/// atgc has started asking for since the session was created, so a login +/// made before a feature shipped is missing the scope that feature needs and +/// the PDS will refuse the write with a bare 403 that names nothing. /// /// Silent when the session records no scope string: "we do not know what was /// granted" is not the same as "something is missing", and inventing a diff --git a/src/cmd/agent_notes.txt b/src/cmd/agent_notes.txt index 74564af..34fd84b 100644 --- a/src/cmd/agent_notes.txt +++ b/src/cmd/agent_notes.txt @@ -9,8 +9,9 @@ The model Identity atgc supports multiple simultaneous logged in accounts. Push access - to the knot is authorized via SSH. Ignore "expired access token" and - "scopes granted since this login" which are just advisory messages. + to the knot is authorized via SSH. An expired access token is + advisory; it refreshes on next use. A missing scope is not: it fails + `doctor local` and the command it names, and only a login adds it. atgc auth status # display current login atgc key add # register an account-wide push SSH key atgc repo configure # write atproto info into .git/config diff --git a/src/cmd/doctor.rs b/src/cmd/doctor.rs index e678f83..63ab1e9 100644 --- a/src/cmd/doctor.rs +++ b/src/cmd/doctor.rs @@ -534,8 +534,11 @@ fn scopes_check(acting: Option<&account::Selection>) -> Check { if gap.is_empty() { return Check::ok("scopes", "no gaps for the commands in this build"); } + // "granted since this login" said the opposite of what it means. Nothing + // was granted: these are scopes this build *asks for* that the session + // does not carry, which is why the row is a failure and not a note. let mut detail = format!( - "{} scope(s) granted since this login; each costs a command:", + "{} scope(s) this build needs that your login does not carry; each costs a command:", gap.len() ); for line in &gap {