Skip to content

Guard: ignore stale maintainer reactions when content is edited after endorsement#4228

Merged
lpcox merged 3 commits intomainfrom
copilot/guard-detect-stale-endorsement-reactions
Apr 21, 2026
Merged

Guard: ignore stale maintainer reactions when content is edited after endorsement#4228
lpcox merged 3 commits intomainfrom
copilot/guard-detect-stale-endorsement-reactions

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Apr 21, 2026

Maintainer reactions were treated as evergreen endorsements, even if an issue/PR/comment was edited after the reaction was added. This allowed post-endorsement content mutation to retain approved integrity.

  • Stale endorsement detection in reaction evaluation

    • Updated has_maintainer_reaction_with_callback() to compare item and reaction timestamps.
    • If item.updatedAt/updated_at is newer than reaction createdAt/created_at, that reaction is ignored for integrity promotion/demotion.
    • Timestamp extraction supports both camelCase and snake_case payload variants already seen in guard inputs.
  • Observability for skipped reactions

    • Added debug logging when a reaction is skipped as stale, including reactor, reaction type, and compared timestamps.
  • Coverage for timestamp edge cases

    • Added focused unit tests for:
      • unmodified item + endorsement (counts)
      • item edited after endorsement (ignored)
      • endorsement after latest edit (counts)
      • mixed stale and fresh reactions (fresh still counts)
      • missing timestamps (preserves prior behavior)
if let (Some(item_updated), Some(reaction_created)) = (item_updated_at, reaction_created_at) {
    if item_updated > reaction_created {
        // stale endorsement/disapproval: content changed after reaction
        continue;
    }
}

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • example.com
    • Triggering command: /tmp/go-build4228467833/b509/launcher.test /tmp/go-build4228467833/b509/launcher.test -test.testlogfile=/tmp/go-build4228467833/b509/testlog.txt -test.paniconexit0 -test.timeout=10m0s -test.v=true 1.0.9/bool.go 1.0.9/bool_func.go x_amd64/vet --gdwarf-5 ut-2993393641.c -o x_amd64/vet 1043�� g_.a -trimpath x_amd64/vet -p go-sdk/internal/-atomic -lang=go1.24 x_amd64/vet (dns block)
    • Triggering command: /tmp/go-build3552295474/b513/launcher.test /tmp/go-build3552295474/b513/launcher.test -test.testlogfile=/tmp/go-build3552295474/b513/testlog.txt -test.paniconexit0 -test.timeout=10m0s (dns block)
  • invalid-host-that-does-not-exist-12345.com
    • Triggering command: /tmp/go-build4228467833/b491/config.test /tmp/go-build4228467833/b491/config.test -test.testlogfile=/tmp/go-build4228467833/b491/testlog.txt -test.paniconexit0 -test.timeout=10m0s -test.v=true @v1.1.3/cpu/arm/arm.go 1043343/b166/ x_amd64/vet --gdwarf-5 backoff -o x_amd64/vet 1043�� g_.a 1043343/b166/ x_amd64/vet -p 64 -lang=go1.24 x_amd64/vet (dns block)
    • Triggering command: /tmp/go-build3552295474/b495/config.test /tmp/go-build3552295474/b495/config.test -test.testlogfile=/tmp/go-build3552295474/b495/testlog.txt -test.paniconexit0 -test.timeout=10m0s 1043�� /tmp/go-build212/home/REDACTED/work/gh-aw-mcpg/gh-aw-mcpg/guards/github-guard/rust-guard/target/degit -goversion bin/rustc -c=4 -nolocalimports -importcfg bin/rustc /tmp�� /home/REDACTED/go//home/REDACTED/work/gh-aw-mcpg/gh-aw-mcpg/guards/github-guard/rust-guard/target/de/opt/hostedtoolcache/go/1.25.9/x64/pkg/tool/linux_amd64/vet /home/REDACTED/go//home/REDACTED/work/gh-aw-mcpg/gh-aw-mcpg/guards/github-guard/rust-guard/target/de/tmp/go-build4250267068/b498/vet.cfg .cfg /endpointshardingit 777.build_scriptpush 777.dq1kj865068v-v -guard/target/deorigin (dns block)
  • nonexistent.local
    • Triggering command: /tmp/go-build4228467833/b509/launcher.test /tmp/go-build4228467833/b509/launcher.test -test.testlogfile=/tmp/go-build4228467833/b509/testlog.txt -test.paniconexit0 -test.timeout=10m0s -test.v=true 1.0.9/bool.go 1.0.9/bool_func.go x_amd64/vet --gdwarf-5 ut-2993393641.c -o x_amd64/vet 1043�� g_.a -trimpath x_amd64/vet -p go-sdk/internal/-atomic -lang=go1.24 x_amd64/vet (dns block)
    • Triggering command: /tmp/go-build3552295474/b513/launcher.test /tmp/go-build3552295474/b513/launcher.test -test.testlogfile=/tmp/go-build3552295474/b513/testlog.txt -test.paniconexit0 -test.timeout=10m0s (dns block)
  • slow.example.com
    • Triggering command: /tmp/go-build4228467833/b509/launcher.test /tmp/go-build4228467833/b509/launcher.test -test.testlogfile=/tmp/go-build4228467833/b509/testlog.txt -test.paniconexit0 -test.timeout=10m0s -test.v=true 1.0.9/bool.go 1.0.9/bool_func.go x_amd64/vet --gdwarf-5 ut-2993393641.c -o x_amd64/vet 1043�� g_.a -trimpath x_amd64/vet -p go-sdk/internal/-atomic -lang=go1.24 x_amd64/vet (dns block)
    • Triggering command: /tmp/go-build3552295474/b513/launcher.test /tmp/go-build3552295474/b513/launcher.test -test.testlogfile=/tmp/go-build3552295474/b513/testlog.txt -test.paniconexit0 -test.timeout=10m0s (dns block)
  • this-host-does-not-exist-12345.com
    • Triggering command: /tmp/go-build4228467833/b518/mcp.test /tmp/go-build4228467833/b518/mcp.test -test.testlogfile=/tmp/go-build4228467833/b518/testlog.txt -test.paniconexit0 -test.timeout=10m0s -test.v=true om/tetratelabs/w-errorsas om/tetratelabs/w-ifaceassert x_amd64/vet . --gdwarf2 --64 x_amd64/vet .cfg�� 1043343/b261/_pkg_.a /tmp/go-build2121043343/b298/ x_amd64/vet . telabs/wazero/in/usr/bin/runc --64 x_amd64/vet (dns block)
    • Triggering command: /tmp/go-build3552295474/b522/mcp.test /tmp/go-build3552295474/b522/mcp.test -test.testlogfile=/tmp/go-build3552295474/b522/testlog.txt -test.paniconexit0 -test.timeout=10m0s lib/�� lib/rustlib/x86_64-REDACTED-linux-gnu/lib/libobject-926daa94a00ee327.rlib lib/rustlib/x86_64-REDACTED-linux-gnu/lib/libmemchr-48d5b0db80402653.rlib lib/rustlib/x86_64-REDACTED-linux-gnu/lib/libaddr2line-3367f26bd486b29d.rlib lib/rustlib/x86_bash lib/rustlib/x86_/usr/bin/runc lib/rustlib/x86_--version 05ed-cgu.00.rcgu.o 05ed�� 05ed-cgu.02.rcgu.o 05ed-cgu.03.rcgu.o 05ed-cgu.04.rcgu.o 05ed-cgu.05.rcgubash 05ed-cgu.06.rcgu/usr/bin/runc 05ed-cgu.07.rcgu--version 05ed-cgu.08.rcgu.o (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

Copilot AI changed the title [WIP] Add timestamp comparison for stale endorsement reactions Guard: ignore stale maintainer reactions when content is edited after endorsement Apr 21, 2026
Copilot AI requested a review from lpcox April 21, 2026 01:04
@lpcox lpcox marked this pull request as ready for review April 21, 2026 02:00
Copilot AI review requested due to automatic review settings April 21, 2026 02:00
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates the GitHub DIFC Rust guard’s reaction-based integrity promotion/demotion so maintainer reactions are ignored when the underlying issue/PR/comment has been updated after the reaction was created, preventing post-endorsement content mutation from retaining approved integrity.

Changes:

  • Add “stale reaction” filtering to has_maintainer_reaction_with_callback() by comparing item updatedAt/updated_at with reaction createdAt/created_at.
  • Add debug logging when a reaction is skipped as stale.
  • Add unit tests covering fresh vs stale reactions and missing timestamp behavior.
Show a summary per file
File Description
guards/github-guard/rust-guard/src/labels/helpers.rs Implements timestamp-based stale reaction skipping, adds debug logs, and introduces targeted unit tests for the new behavior.

Copilot's findings

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comments suppressed due to low confidence (1)

guards/github-guard/rust-guard/src/labels/helpers.rs:476

  • nodes.iter().take(MAX_REACTIONS_TO_CHECK) now caps the scan before filtering out stale reactions. With this change, a long list of stale endorsements in the first 20 nodes can cause a fresh endorsement later in the list to be missed (false negative), even though skipping stale reactions doesn't incur backend permission lookups. Consider scanning all reaction nodes but only counting/enriching up to MAX_REACTIONS_TO_CHECK non-stale + matching reactions (or apply the cap after filtering).
    for node in nodes.iter().take(MAX_REACTIONS_TO_CHECK) {
        let content = match node.get("content").and_then(|v| v.as_str()) {
  • Files reviewed: 1/1 changed files
  • Comments generated: 1

Comment thread guards/github-guard/rust-guard/src/labels/helpers.rs Outdated
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@lpcox lpcox merged commit baa83f9 into main Apr 21, 2026
14 checks passed
@lpcox lpcox deleted the copilot/guard-detect-stale-endorsement-reactions branch April 21, 2026 03:25
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.

Guard: detect stale endorsement reactions via timestamp comparison

3 participants