From 00653a4099ee610cc79a82423db593ca96ca1684 Mon Sep 17 00:00:00 2001 From: HackingGate Date: Fri, 21 Aug 2026 20:49:49 +0900 Subject: [PATCH] uphold 1.8.0: one command, more than one flag vocabulary A `[[shim]]` named one `text_flags` for a whole command, and a command's flags do not all mean one thing. On `gh`, `-c` is a boolean on `pr review` and takes a value on `issue close`; naming it once for the table reads one of the two wrong, and either way the mistake is a false negative in the seam that exists to prevent one. `[[shim.verbs]]` lets the verbs whose grammar differs say so. The entry's lists replace the table's for those verbs rather than adding to them -- the rule `allowed_scripts` already follows, because a union would mean a vocabulary nobody wrote. `target_flags` stays table-wide: a per-verb answer to which repository something is going to would be a way to publish somewhere the table did not expect. Also in this release, from #76 and #78: a baseline entry may be signed `path | owner | reason` and required to be, and a baseline line that does not parse is refused rather than skipped -- the skip removed a ratchet silently, and the staleness check could not see it because a dropped entry was never listed. #79 refuses two shim tables naming one command, which used to drop one of them. TAKING IT. Nothing existing changes shape: a policy written for 1.7.0 resolves to exactly the rules it resolved to before, and no default changed. But a policy that USES `[[shim.verbs]]` cannot be read by an older binary, and the installed binary is the shim -- so a tree whose policy adopts the field before its shims are reinstalled has every git command failing closed. Bump the pin, reinstall, then adopt. Documented pins move to v1.8.0 in README.md and hooks/lefthook.yml. --- Cargo.lock | 2 +- Cargo.toml | 2 +- README.md | 6 +++--- docs/REFERENCE.md | 47 ++++++++++++++++++++++++++++++++++++++++++++++ hooks/lefthook.yml | 4 ++-- 5 files changed, 54 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9ee2059..987b486 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -593,7 +593,7 @@ checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" [[package]] name = "uphold" -version = "1.7.0" +version = "1.8.0" dependencies = [ "encoding_rs", "globset", diff --git a/Cargo.toml b/Cargo.toml index 625e830..6246b49 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uphold" -version = "1.7.0" +version = "1.8.0" edition = "2021" # 1.88, not 1.85: the ripgrep stack this crate embeds -- globset 0.4.20 and # ignore 0.4.33 in Cargo.lock -- refuses anything older. The 1.85 that stood here diff --git a/README.md b/README.md index 1022ae5..036e8f7 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ bootstraps). default_install_hook_types: [pre-commit, commit-msg, pre-merge-commit, pre-push] repos: - repo: https://github.com/HackingGate/uphold - rev: v1.7.0 + rev: v1.8.0 hooks: - id: uphold-check # the claims still hold - id: uphold-scan # the content policy @@ -48,13 +48,13 @@ the binary must be on PATH. # lefthook.yml remotes: - git_url: https://github.com/HackingGate/uphold - ref: v1.7.0 + ref: v1.8.0 configs: - hooks/lefthook.yml ``` ```sh -cargo install --git https://github.com/HackingGate/uphold --tag v1.7.0 +cargo install --git https://github.com/HackingGate/uphold --tag v1.8.0 ``` That `ref:` is the one version a lefthook consumer pins, and **Dependabot does diff --git a/docs/REFERENCE.md b/docs/REFERENCE.md index c7e90af..921da11 100644 --- a/docs/REFERENCE.md +++ b/docs/REFERENCE.md @@ -72,6 +72,53 @@ every git hook, and a repository whose own prose cites its issues would have every one of those citations refused — so the seam it belongs at is the command that publishes text to a forge, and only that one. +### One command, more than one flag vocabulary + +A `[[shim]]` names one `text_flags` for a whole command, and a command's flags do +not all mean one thing. On `gh`, `-c` is a **boolean** on `pr review` -- "Comment +on a pull request" -- and **takes a value** on `issue close` -- "Leave a closing +comment". Name it once for the table and one of the two is read wrong: + +* named -- `gh pr review -c -b "body"` reads `-c` as consuming `-b`, and the body + being published goes unread; +* not named -- `gh issue close --comment "text"` publishes with nothing in front + of it. + +Both are false negatives in the seam that exists to prevent one. A second +`[[shim]]` for the same command is not the answer either: it is refused at load, +because two vocabularies for one command have to be merged and merging is the +same guess in a different place. + +So a table may carry entries for the verbs whose grammar differs: + +```toml +[[shim]] +command = "gh" +match = ["pr:review", "issue:close"] +text_flags = ["-t", "--title", "-b", "--body"] + + [[shim.verbs]] + match = ["issue:close"] + text_flags = ["-c", "--comment"] +``` + +`match` takes the same `verb:noun` and `verb:*` spellings the table's own does, +and every entry must name a verb the table matches -- a vocabulary for an +invocation the shim does not stand in front of classifies nothing, and is refused +at load. + +**The entry's lists replace the table's** for the verbs it names, rather than +adding to them. Same rule as `allowed_scripts`, for the same reason: what is +declared beside the narrower thing is the whole truth for it. A union would mean +a vocabulary nobody wrote -- here `issue close --body`, a flag the real command +does not accept -- and reading a flag a command will not take is the shim +claiming to have checked a subject that was never published. + +`text_flags`, `file_flags`, `path_flags`, `skip_flags` and `web_flags` may be +overridden. `target_flags` may not: `-R`/`--repo` means the same thing on every +verb, and a per-verb answer to "which repository is this going to" would be a way +to publish somewhere the table did not expect. + ### A baseline entry may be asked to say who excused it and why `files.baseline` names a file of repository-relative paths a rule excuses, and diff --git a/hooks/lefthook.yml b/hooks/lefthook.yml index ad6de32..2f30025 100644 --- a/hooks/lefthook.yml +++ b/hooks/lefthook.yml @@ -10,7 +10,7 @@ # # lefthook.yml, in the consuming repository # remotes: # - git_url: https://github.com/HackingGate/uphold -# ref: v1.7.0 +# ref: v1.8.0 # configs: # - hooks/lefthook.yml # @@ -21,7 +21,7 @@ # their manifest contract; lefthook runs commands and has no such contract, so # `uphold` must be on PATH: # -# cargo install --git https://github.com/HackingGate/uphold --tag v1.7.0 +# cargo install --git https://github.com/HackingGate/uphold --tag v1.8.0 # # The names below are prefixed `uphold-` on purpose. A remote config is # merged into the consumer's own, and two commands sharing a name under one hook