Commit 9e91827
authored
feat: Maintainer reaction endorsement for integrity promotion/demotion (#3666)
Adds two new integrity mechanisms to the AllowOnly guard: maintainer
👍/❤️ reactions can promote an item to `approved` integrity, and
maintainer 👎/😕 reactions can cap it at a configured level (default
`none`). Disapproval always overrides endorsement.
## Config
```json
{
"allow-only": {
"min-integrity": "approved",
"endorsement-reactions": ["THUMBS_UP", "HEART"],
"disapproval-reactions": ["THUMBS_DOWN", "CONFUSED"],
"disapproval-integrity": "none",
"endorser-min-integrity": "approved"
}
}
```
- **`endorsement-reactions`** / **`disapproval-reactions`**:
`ReactionContent` enum values. Empty = disabled.
- **`disapproval-integrity`**: integrity cap when disapproval detected.
Default `none`.
- **`endorser-min-integrity`**: minimum collaborator permission for a
reactor to count. Default `approved` (write/maintain/admin).
## Precedence (new steps 3 & 4)
`blocked_users` > author_association > trusted_users > merged >
approval_labels > **endorsement** > **disapproval**
## Rust guard (`helpers.rs`, `lib.rs`)
- 4 new `PolicyContext` fields
- `has_maintainer_reaction_with_callback()`: extracts
`reactions.nodes[]{user.login, content}` (GraphQL proxy shape), checks
up to 20 reactions, resolves reactor integrity via
`get_collaborator_permission`
- `apply_endorsement_promotion()` / `apply_disapproval_demotion()` wired
as steps 3/4 in `issue_integrity()` and `pr_integrity()`
- `cap_integrity()`: min-of-two for demotion (inverse of
`max_integrity`)
- `AllowOnlyPolicy` deserialization extended for the 4 new fields
## Gateway-mode degradation
When `reactions` field is present but has no `nodes` array (MCP server
returns counts only, not per-user data), each reaction kind logs a
warning **once per process lifetime** and skips evaluation entirely — no
promotion or demotion occurs.
## Backend (`backend.rs`)
- Per-request cache for `get_collaborator_permission` keyed
`owner/repo:username` (case-insensitive) to bound API calls when
multiple items share the same reactor
## Go config (`guard_policy.go`, `wasm.go`)
- `AllowOnlyPolicy` + `NormalizedGuardPolicy` structs extended with 4
new fields
- `NormalizeGuardPolicy`: uppercases reaction content values,
deduplicates, validates integrity level strings
- `buildStrictLabelAgentPayload`: allows and validates the 4 new
`allow-only` keys
> [!WARNING]
>
> <details>
> <summary>Firewall rules blocked me from connecting to one or more
addresses (expand for details)</summary>
>
> #### I tried to connect to the following addresses, but was blocked by
firewall rules:
>
> - `example.com`
> - Triggering command: `/tmp/go-build2564705994/b514/launcher.test
/tmp/go-build2564705994/b514/launcher.test
-test.testlogfile=/tmp/go-build2564705994/b514/testlog.txt
-test.paniconexit0 -test.timeout=10m0s
/tmp/go-build2564705994/b422/vet.cfg` (dns block)
> - `invalid-host-that-does-not-exist-12345.com`
> - Triggering command: `/tmp/go-build2193583002/b001/config.test
/tmp/go-build2193583002/b001/config.test
-test.testlogfile=/tmp/go-build2193583002/b001/testlog.txt
-test.paniconexit0 -test.timeout=10m0s aw-m�� go aw-mcpg/guards/g--64
x_amd64/asm
aw-mcpg/guards/g/opt/hostedtoolcache/go/1.25.8/x64/pkg/tool/linux_amd64/vet
aw-mcpg/guards/g-unsafeptr=false aw-mcpg/guards/g-unreachable=false
x_amd64/asm aw-m�� g_.a Lj2wtMmGc x_amd64/compile
k/gh-aw-mcpg/gh-/opt/hostedtoolcache/go/1.25.8/x64/pkg/tool/linux_amd64/vet`
(dns block)
> - Triggering command: `/tmp/go-build2936037720/b001/config.test
/tmp/go-build2936037720/b001/config.test
-test.testlogfile=/tmp/go-build2936037720/b001/testlog.txt
-test.paniconexit0 -test.timeout=10m0s --uid-owner 0 -j ACCEPT
/home/REDACTED/wor/usr/lib/sysstat/sadc /home/REDACTED/wor-F
/home/REDACTED/wor-L x_amd64/vet go_.�� 64/src/net ache/go/1.25.8/x1
64/pkg/tool/linu/var/log/sysstat -p internal/runtime-w -lang=go1.25
64/pkg/tool/linusecurity` (dns block)
> - `nonexistent.local`
> - Triggering command: `/tmp/go-build2564705994/b514/launcher.test
/tmp/go-build2564705994/b514/launcher.test
-test.testlogfile=/tmp/go-build2564705994/b514/testlog.txt
-test.paniconexit0 -test.timeout=10m0s
/tmp/go-build2564705994/b422/vet.cfg` (dns block)
> - `slow.example.com`
> - Triggering command: `/tmp/go-build2564705994/b514/launcher.test
/tmp/go-build2564705994/b514/launcher.test
-test.testlogfile=/tmp/go-build2564705994/b514/testlog.txt
-test.paniconexit0 -test.timeout=10m0s
/tmp/go-build2564705994/b422/vet.cfg` (dns block)
> - `this-host-does-not-exist-12345.com`
> - Triggering command: `/tmp/go-build2564705994/b523/mcp.test
/tmp/go-build2564705994/b523/mcp.test
-test.testlogfile=/tmp/go-build2564705994/b523/testlog.txt
-test.paniconexit0 -test.timeout=10m0s
/tmp/go-build2564705994/b446/vet.cfg g_.a
/home/REDACTED/go/pkg/mod/github.c-nolocalimports x_amd64/vet
64/src/runtime/c/opt/hostedtoolcache/go/1.25.8/x64/pkg/tool/linux_amd64/vet
json x_amd64/asm x_amd64/vet --no�� 1.80.0/grpclog/c-errorsas
1.80.0/grpclog/g-ifaceassert x_amd64/vet
64/src/runtime/c/opt/hostedtoolcache/go/1.25.8/x64/pkg/tool/linux_amd64/vet
k/gh-aw-mcpg/gh--atomic x_amd64/compile x_amd64/vet` (dns block)
>
> If you need me to access, download, or install something from one of
these locations, you can either:
>
> - Configure [Actions setup
steps](https://gh.io/copilot/actions-setup-steps) to set up my
environment, which run before the firewall is enabled
> - Add the appropriate URLs or hosts to the custom allowlist in this
repository's [Copilot coding agent
settings](https://github.com/github/gh-aw-mcpg/settings/copilot/coding_agent)
(admins only)
>
> </details>File tree
8 files changed
+1139
-26
lines changed- guards/github-guard/rust-guard/src
- labels
- internal
- config
- guard
8 files changed
+1139
-26
lines changed| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
24 | 24 | | |
25 | 25 | | |
26 | 26 | | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
27 | 50 | | |
28 | 51 | | |
29 | 52 | | |
| |||
449 | 472 | | |
450 | 473 | | |
451 | 474 | | |
| 475 | + | |
| 476 | + | |
| 477 | + | |
452 | 478 | | |
453 | 479 | | |
454 | 480 | | |
| |||
463 | 489 | | |
464 | 490 | | |
465 | 491 | | |
| 492 | + | |
| 493 | + | |
| 494 | + | |
| 495 | + | |
| 496 | + | |
| 497 | + | |
| 498 | + | |
| 499 | + | |
| 500 | + | |
| 501 | + | |
| 502 | + | |
| 503 | + | |
| 504 | + | |
| 505 | + | |
| 506 | + | |
| 507 | + | |
| 508 | + | |
| 509 | + | |
| 510 | + | |
| 511 | + | |
| 512 | + | |
| 513 | + | |
466 | 514 | | |
467 | 515 | | |
468 | 516 | | |
| |||
484 | 532 | | |
485 | 533 | | |
486 | 534 | | |
| 535 | + | |
487 | 536 | | |
488 | 537 | | |
489 | 538 | | |
| |||
535 | 584 | | |
536 | 585 | | |
537 | 586 | | |
| 587 | + | |
| 588 | + | |
538 | 589 | | |
539 | 590 | | |
540 | 591 | | |
| |||
0 commit comments