Skip to content

The published-text set carries the destination guard, so a gh invocation bound for a repository the workspace does not own is refused by a rule the fleet inherits rather than one it transcribes - #203

Merged
HackingGate merged 1 commit into
mainfrom
published-text-carries-the-unowned-forge-target-checker
Sep 20, 2026

Conversation

@HackingGate

Copy link
Copy Markdown
Owner

Closes #197.

What the set now carries

[rule.unowned-forge-target]
builtin = "prevent-unowned-target"
owner_required = true
command.before = ["gh"]
command.scope = "always"

Under a header giving the two justifications the 84 hand copies shared: owner_required because the only other owner is the one read off origin, which is the remote a destination guard exists to catch being wrong; always because whether a repository is yours is a fact about the destination and not its visibility, and a forge that does not answer is the invocation that most needs asking about. gh alone: the set's commands ceiling admits gh and git push, and a repository whose tables also stand in front of glab shadows the id with the wider list, which is the ADR 0006 override.

What else changed

  • The shim names a destination refusal with its set. The loader admitted the rule under the existing ceiling (stands_in_front_of_a_command already counts TARGET_GUARDS, and validate_shims already names the set in its cure). What was missing was the label: a destination refusal at the shim seam printed the report alone, where a text refusal goes through refused_by. With the rule arriving from a set, a reader who greps their policy for unowned-forge-target finds nothing, so the shim now prints unowned-forge-target [set: published-text]: ... the way a hook does.
  • uphold's own policy keeps the same-id shadow for command.before = ["gh", "glab"] (the glab table is declared there), and its comment now records that the promotion happened and that the copy remains only to widen the command list. The two counts of shadowed rules in the file are updated.
  • policy/base/sets.lock.json regenerated.
  • docs/REFERENCE.md: the published-text row and the prevent-unowned-target row say the set carries it; the checker passage shows the bundled declaration and the glab shadow instead of a hand copy.

Tests

tests/shim_cli.rs, three cases over a fixture that declares owner, inherits published-text, and writes only its own gh (public-target) and git tables, with a stub gh that answers private / not-the-owner / not-admin:

  • gh pr create --repo other-owner/their-repo -b ... is refused, exit 1, naming unowned-forge-target [set: published-text] and the pin; the same verb under example-user/widget runs.
  • the fixture without owner is exit 2 naming owner_required and the line to write.
  • the fixture without the gh table is refused at load naming published-text, [[shim]] and command = "gh".

src/config.rs: the load test for inheriting the set now asserts the four ids that arrive.

Not a corpus case: the corpus covers pattern rules and exempts built-ins by design.

Gates: cargo fmt, clippy -D warnings, cargo test (897 passed), uphold check, uphold scan, validate.py, build_reference.py --check, unittest (89) all green; lefthook pre-commit, commit-msg and pre-push ran.

…ion bound for a repository the workspace does not own is refused by a rule the fleet inherits rather than one it transcribes

84 consumer policies declared unowned-forge-target by hand, 74 of them
byte-identical, each under the same comment and each justified by the
published-text header's line that a set ships checkers and never shims.
The rule is a checker. ADR 0006 puts the shim on the repository's side
of the seam and the rules a shim consults on the engine's, and the set's
commands ceiling already named gh, so the rule now ships in the set with
owner_required = true and command.scope = "always" in the bundled
declaration: without the first the only owner is the one read off
origin, and without the second a public-target table stands the rule
down for a private destination and for a forge that did not answer,
which are the two invocations the rule exists for.

The loader admitted the rule under the existing ceiling. What it did not
do was name it: a destination refusal at the shim seam printed the
report alone, where a text refusal goes through refused_by and carries
the set. A reader who greps their policy for unowned-forge-target now
finds nothing, so the shim labels a destination refusal the way a hook
does, id and set together.

uphold's own policy keeps a same-id shadow for the one thing the set
cannot say, command.before widened to glab, and its comment now records
that the promotion happened rather than deferring it. The reference
names the set as the rule's carrier, the lock is regenerated, and three
CLI cases drive a fixture that inherits the set with only its own shim
tables: the refusal names the rule with its set, a fixture that has not
declared owner is exit 2 naming the line to write, and one without the
gh table is refused at load with the cure the ADR describes.

Closes #197
@coderabbitai

coderabbitai Bot commented Sep 20, 2026

Copy link
Copy Markdown

Warning

Review limit reached

Next included review available in 13 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: 56503cad-1ad9-49d4-849e-795cec4f80d8

📥 Commits

Reviewing files that changed from the base of the PR and between 310ebab and 1a6bd77.

📒 Files selected for processing (7)
  • docs/REFERENCE.md
  • policy/base/published-text.toml
  • policy/base/sets.lock.json
  • policy/principles.toml
  • src/config.rs
  • src/shim.rs
  • tests/shim_cli.rs

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@codecov-commenter

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 93.65%. Comparing base (310ebab) to head (1a6bd77).

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #203      +/-   ##
==========================================
+ Coverage   93.63%   93.65%   +0.02%     
==========================================
  Files          44       44              
  Lines       17057    17066       +9     
==========================================
+ Hits        15971    15983      +12     
+ Misses       1086     1083       -3     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@HackingGate
HackingGate merged commit a36dcd2 into main Sep 20, 2026
11 checks passed
@HackingGate
HackingGate deleted the published-text-carries-the-unowned-forge-target-checker branch September 20, 2026 07:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

unowned-forge-target is a checker rule declared by hand in 84 consumer policy files, and published-text may carry it

2 participants