Skip to content

feat(campaign): panic-attack campaign poll — GitHub PR transitions (issue #33 S2b)#60

Merged
hyperpolymath merged 1 commit into
mainfrom
feat/issue-33-s2b-campaign-poll
May 27, 2026
Merged

feat(campaign): panic-attack campaign poll — GitHub PR transitions (issue #33 S2b)#60
hyperpolymath merged 1 commit into
mainfrom
feat/issue-33-s2b-campaign-poll

Conversation

@hyperpolymath
Copy link
Copy Markdown
Owner

Summary

Closes the S2 follow-up called out in #56. Adds a new subcommand under `panic-attack campaign`:

```
panic-attack campaign poll [--verisimdb-dir DIR]
```

For each finding whose latest campaign state is `pr-filed`, the poller:

  1. Parses the stored PR URL into `(owner, repo, number)`.
  2. Calls `GET https://api.github.com/repos///pulls/`.
  3. Maps the response to `RemotePrState::{Open, Merged, Closed}`.
  4. If the new state differs from `pr-filed`, writes a new campaign hexad stamping `last_polled` to the current timestamp.

Auth: reads `GH_TOKEN` or `GITHUB_TOKEN` from the environment. Falls back to unauthenticated (GitHub 60/hour cap). Output: one line per transition.

Stacked on #56 — diff against `main` includes the S2 changes until S2 lands; this PR rebases cleanly.

New surface

Item Purpose
`transition(finding_id, new_state, pr_url, reason, dir)` Lower-level than `register_pr`/`dismiss`; stamps `last_polled`.
`parse_pr_url(url) -> ParsedPrUrl` Pure parser; accepts canonical PR URL + trailing slash + fragment.
`RemotePrState` + `should_transition` Pure helpers — decide whether a fetched state warrants a new hexad.
`poll(base_dir) -> Vec` Orchestrator.
`fetch_remote_pr_state` Single ureq GET with correct headers (`Accept`, `User-Agent`, `X-GitHub-Api-Version`).

Feature gating

The whole new section is `#[cfg(feature = "http")]`. Default `cargo build` is unaffected. `cargo build --features http` picks up the new subcommand.

Test plan

  • `cargo test --lib` — 222 green (default).
  • `cargo test --lib --features http` — 260 green (11 new poll tests).
  • `cargo clippy --all-targets --features http -- -D warnings` — clean.
  • `cargo fmt --all` — clean.

11 new tests cover:

  • 6 `parse_pr_url` cases (canonical, trailing slash, fragment, non-github, issue-url, missing-number).
  • 4 `should_transition` cases (open→filed no-op, filed→merged, filed→closed, merged→merged no-op).
  • 1 end-to-end `transition()` + latest-state fold.

Refs #33. Stacked on #56 (S2).

🤖 Generated with Claude Code

@hyperpolymath hyperpolymath enabled auto-merge (squash) May 26, 2026 12:30
@github-actions
Copy link
Copy Markdown

🔍 Hypatia Security Scan

Findings: 48 issues detected

Severity Count
🔴 Critical 4
🟠 High 16
🟡 Medium 28

⚠️ Action Required: Critical security issues found!

View findings
[
  {
    "reason": "Action hyperpolymath/standards/.github/workflows/governance-reusable.yml@main needs attention",
    "type": "unpinned_action",
    "file": "governance.yml",
    "action": "pin_sha",
    "rule_module": "workflow_audit",
    "severity": "high"
  },
  {
    "reason": "Nickel file missing SPDX-License-Identifier header (1 occurrences, CWE-1104)",
    "type": "ncl_missing_spdx",
    "file": "/home/runner/work/panic-attack/panic-attack/reports/panic-attack-20260211180017.ncl",
    "action": "flag",
    "rule_module": "code_safety",
    "severity": "medium"
  },
  {
    "reason": "expect() in hot path (2 occurrences, CWE-754)",
    "type": "expect_in_hot_path",
    "file": "/home/runner/work/panic-attack/panic-attack/src/attestation/chain.rs",
    "action": "flag",
    "rule_module": "code_safety",
    "severity": "medium"
  },
  {
    "reason": "unwrap_or(0) with dangerous default (1 occurrences, CWE-754)",
    "type": "unwrap_dangerous_default",
    "file": "/home/runner/work/panic-attack/panic-attack/src/attestation/evidence.rs",
    "action": "flag",
    "rule_module": "code_safety",
    "severity": "critical"
  },
  {
    "reason": "unwrap_or(0) with dangerous default (1 occurrences, CWE-754)",
    "type": "unwrap_dangerous_default",
    "file": "/home/runner/work/panic-attack/panic-attack/src/ambush/mod.rs",
    "action": "flag",
    "rule_module": "code_safety",
    "severity": "critical"
  },
  {
    "reason": "unwrap_or(0) with dangerous default (3 occurrences, CWE-754)",
    "type": "unwrap_dangerous_default",
    "file": "/home/runner/work/panic-attack/panic-attack/src/kanren/strategy.rs",
    "action": "flag",
    "rule_module": "code_safety",
    "severity": "critical"
  },
  {
    "reason": "unwrap_or(0) with dangerous default (3 occurrences, CWE-754)",
    "type": "unwrap_dangerous_default",
    "file": "/home/runner/work/panic-attack/panic-attack/src/axial/mod.rs",
    "action": "flag",
    "rule_module": "code_safety",
    "severity": "critical"
  },
  {
    "reason": "expect() in hot path (4 occurrences, CWE-754)",
    "type": "expect_in_hot_path",
    "file": "/home/runner/work/panic-attack/panic-attack/src/assail/analyzer.rs",
    "action": "flag",
    "rule_module": "code_safety",
    "severity": "medium"
  },
  {
    "reason": "unwrap() without prior check -- DoS via panic (4 occurrences, CWE-754)",
    "type": "unwrap_without_check",
    "file": "/home/runner/work/panic-attack/panic-attack/benches/scan_bench.rs",
    "action": "flag",
    "rule_module": "code_safety",
    "severity": "high"
  },
  {
    "reason": "expect() in hot path (2 occurrences, CWE-754)",
    "type": "expect_in_hot_path",
    "file": "/home/runner/work/panic-attack/panic-attack/benches/scan_bench.rs",
    "action": "flag",
    "rule_module": "code_safety",
    "severity": "medium"
  }
]

Powered by Hypatia Neurosymbolic CI/CD Intelligence

@github-actions
Copy link
Copy Markdown

🔍 Hypatia Security Scan

Findings: 45 issues detected

Severity Count
🔴 Critical 4
🟠 High 16
🟡 Medium 25

⚠️ Action Required: Critical security issues found!

View findings
[
  {
    "reason": "Action hyperpolymath/standards/.github/workflows/governance-reusable.yml@main needs attention",
    "type": "unpinned_action",
    "file": "governance.yml",
    "action": "pin_sha",
    "rule_module": "workflow_audit",
    "severity": "high"
  },
  {
    "reason": "Nickel file missing SPDX-License-Identifier header (1 occurrences, CWE-1104)",
    "type": "ncl_missing_spdx",
    "file": "/home/runner/work/panic-attack/panic-attack/reports/panic-attack-20260211180017.ncl",
    "action": "flag",
    "rule_module": "code_safety",
    "severity": "medium"
  },
  {
    "reason": "expect() in hot path (2 occurrences, CWE-754)",
    "type": "expect_in_hot_path",
    "file": "/home/runner/work/panic-attack/panic-attack/src/attestation/chain.rs",
    "action": "flag",
    "rule_module": "code_safety",
    "severity": "medium"
  },
  {
    "reason": "unwrap_or(0) with dangerous default (1 occurrences, CWE-754)",
    "type": "unwrap_dangerous_default",
    "file": "/home/runner/work/panic-attack/panic-attack/src/attestation/evidence.rs",
    "action": "flag",
    "rule_module": "code_safety",
    "severity": "critical"
  },
  {
    "reason": "unwrap_or(0) with dangerous default (1 occurrences, CWE-754)",
    "type": "unwrap_dangerous_default",
    "file": "/home/runner/work/panic-attack/panic-attack/src/ambush/mod.rs",
    "action": "flag",
    "rule_module": "code_safety",
    "severity": "critical"
  },
  {
    "reason": "unwrap_or(0) with dangerous default (3 occurrences, CWE-754)",
    "type": "unwrap_dangerous_default",
    "file": "/home/runner/work/panic-attack/panic-attack/src/kanren/strategy.rs",
    "action": "flag",
    "rule_module": "code_safety",
    "severity": "critical"
  },
  {
    "reason": "unwrap_or(0) with dangerous default (3 occurrences, CWE-754)",
    "type": "unwrap_dangerous_default",
    "file": "/home/runner/work/panic-attack/panic-attack/src/axial/mod.rs",
    "action": "flag",
    "rule_module": "code_safety",
    "severity": "critical"
  },
  {
    "reason": "expect() in hot path (4 occurrences, CWE-754)",
    "type": "expect_in_hot_path",
    "file": "/home/runner/work/panic-attack/panic-attack/src/assail/analyzer.rs",
    "action": "flag",
    "rule_module": "code_safety",
    "severity": "medium"
  },
  {
    "reason": "unwrap() without prior check -- DoS via panic (4 occurrences, CWE-754)",
    "type": "unwrap_without_check",
    "file": "/home/runner/work/panic-attack/panic-attack/benches/scan_bench.rs",
    "action": "flag",
    "rule_module": "code_safety",
    "severity": "high"
  },
  {
    "reason": "expect() in hot path (2 occurrences, CWE-754)",
    "type": "expect_in_hot_path",
    "file": "/home/runner/work/panic-attack/panic-attack/benches/scan_bench.rs",
    "action": "flag",
    "rule_module": "code_safety",
    "severity": "medium"
  }
]

Powered by Hypatia Neurosymbolic CI/CD Intelligence

…ssue #33 S2b)

Closes the S2 follow-up called out in #56. Adds a new subcommand:

  panic-attack campaign poll

For each finding whose latest campaign state is `pr-filed`, the
poller:

1. Parses the stored PR URL into (owner, repo, number).
2. Calls GET https://api.github.com/repos/<o>/<r>/pulls/<n>.
3. Maps the response to RemotePrState::{Open, Merged, Closed}.
4. If the new state differs from `pr-filed`, writes a new campaign
   hexad stamping `last_polled` to the current timestamp.

Auth: reads `GH_TOKEN` or `GITHUB_TOKEN` from the environment. Falls
back to unauthenticated requests (60/hour). Output: one line per
transition.

Implementation:

- New campaign primitives: `transition(finding_id, new_state, pr_url,
  reason, base_dir)` — lower-level than `register_pr`/`dismiss`,
  stamps `last_polled`.
- `parse_pr_url(url) -> ParsedPrUrl` — pure, accepts canonical
  `https://github.com/<o>/<r>/pull/<n>` plus trailing slash and
  fragment.
- `RemotePrState` + `should_transition` — pure mapping helpers that
  decide whether a fetched state warrants a new hexad.
- `poll(base_dir) -> Vec<PollOutcome>` — orchestrator.
- `fetch_remote_pr_state` — single ureq GET with correct headers
  (Accept, User-Agent, X-GitHub-Api-Version).

The whole new section is gated on `#[cfg(feature = "http")]`; the
default `cargo build` is unaffected, and `cargo build --features http`
picks up the new subcommand.

Tests (added under http feature): 11 new — 6 parse_pr_url cases
(canonical, trailing slash, fragment, non-github reject, issue-url
reject, missing-number reject); 4 should_transition cases (open→filed
no-op, filed→merged, filed→closed, merged→merged no-op); 1
transition() writes a new hexad and the latest-state fold sees it.

Full lib suite: 222 green default, 260 green with --features http.
Clippy clean with --features http -D warnings.

Refs #33. Stacked on #56 (S2). Diff against main includes the S2
changes until S2 lands; this PR rebases cleanly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@hyperpolymath hyperpolymath force-pushed the feat/issue-33-s2b-campaign-poll branch from 4d373ba to ae05611 Compare May 27, 2026 13:27
@hyperpolymath hyperpolymath merged commit acd329f into main May 27, 2026
@hyperpolymath hyperpolymath deleted the feat/issue-33-s2b-campaign-poll branch May 27, 2026 13:27
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.

1 participant