Context
iterate-policy.ts's own doc comment defines rejectionSignaled as true when "the target repo (or this contributor's history with it) has signaled it does not want automated contributions -- an explicit AI-usage-policy ban, OR a prior submission from this same miner was closed/rejected on this exact repo." rejection-signal.js's resolveRejectionSignaled only resolves the FIRST trigger (the live AI-USAGE.md/CONTRIBUTING.md ban). The file's own header explicitly documents the second trigger as a known, deliberate gap — "not fabricated as 'no rejection history'" — and names exactly what's needed to close it: governor-state.js's listRecentOwnSubmissions (already tracks this miner's own recorded submissions, filterable by repo) and rejection-state-machine.js's resolveRejection (already classifies a fetched PR payload's terminal outcome into a rejection reason). Both already exist, are already tested, and are simply not wired together for this use.
Dependencies
None — independently shippable. Consumes two already-shipped modules (listRecentOwnSubmissions #5134, resolveRejection #4278) without modifying either.
Requirements
- Add a function (e.g.
resolveOwnRejectionHistory(repoFullName, options)) that: calls listRecentOwnSubmissions({ repoFullName }) to get this miner's own prior submissions on the target repo; for each with a real pullRequestNumber, fetches its current GET /pulls/{n} state (reusing the existing GitHub-fetch conventions this package already uses elsewhere, e.g. live-issue-snapshot.js's auth/timeout/user-agent handling); runs the payload through resolveRejection (no signal — gate/duplicate context isn't available or needed here, just "was it closed without merge"); returns true if ANY prior submission on this repo was rejected (closed without merge).
- Bound the number of PR-status fetches per call (e.g. the N most recent submissions on that repo) so a miner with a long history on one repo doesn't trigger an unbounded fan-out of API calls on every single attempt.
- Fail-open on any fetch/parse failure for an individual PR (consistent with
resolveRejectionSignaled's own existing fail-open behavior for the policy-doc trigger) — one unreachable PR must never block resolution of the others, and a wholesale fetch failure resolves to false (not fabricated as a rejection, and not silently treated as "definitely no rejection" either — log/surface the degraded check).
resolveRejectionSignaled (or its caller in attempt-cli.js) combines both triggers: true if EITHER the live policy-doc ban OR the new own-rejection-history check fires.
- Out of scope: any change to
rejection-state-machine.js's classification logic itself, or to governor-state.js's own submission-recording — this issue only wires the two together for a new caller.
Deliverables / Acceptance Criteria
Test Coverage Requirements
This PR must ship with full test coverage for every changed line and branch — the repo's Codecov patch gate requires 99%+ coverage and the house standard is to aim for 100%, including both sides of every conditional/nullish-coalescing branch introduced. Required: (1) a real-rejection-detected test (a prior submission on the repo that resolves to a closed-without-merge PR flips the signal true); (2) a no-prior-submissions-on-this-repo test (signal stays false, no fetch attempted); (3) a bounded-fetch-count test (more prior submissions than the cap exist; only the cap's worth are fetched); (4) a fail-open test for both an individual PR fetch failure (other submissions still checked) and a wholesale failure (resolves false, not thrown); (5) a combined-trigger test proving either trigger alone is sufficient (policy-doc ban with clean history, OR clean policy doc with a real rejection, both resolve true).
Expected Outcome
rejectionSignaled finally means what its own doc comment has said since it was written — a miner won't keep re-attempting a repo that has already rejected its work, closing a real behavioral gap before Wave 5 puts this on a multi-tenant, reputation-sensitive footing.
Links & Resources
packages/gittensory-miner/lib/rejection-signal.js (the file's own documented gap)
packages/gittensory-miner/lib/governor-state.js's listRecentOwnSubmissions
packages/gittensory-miner/lib/rejection-state-machine.js's resolveRejection
packages/gittensory-engine/src/portfolio/iterate-policy.ts (the doc comment defining both triggers)
- Theme: AMS hardening / Wave 5 prerequisite
Context
iterate-policy.ts's own doc comment definesrejectionSignaledas true when "the target repo (or this contributor's history with it) has signaled it does not want automated contributions -- an explicit AI-usage-policy ban, OR a prior submission from this same miner was closed/rejected on this exact repo."rejection-signal.js'sresolveRejectionSignaledonly resolves the FIRST trigger (the live AI-USAGE.md/CONTRIBUTING.md ban). The file's own header explicitly documents the second trigger as a known, deliberate gap — "not fabricated as 'no rejection history'" — and names exactly what's needed to close it:governor-state.js'slistRecentOwnSubmissions(already tracks this miner's own recorded submissions, filterable by repo) andrejection-state-machine.js'sresolveRejection(already classifies a fetched PR payload's terminal outcome into a rejection reason). Both already exist, are already tested, and are simply not wired together for this use.Dependencies
None — independently shippable. Consumes two already-shipped modules (
listRecentOwnSubmissions#5134,resolveRejection#4278) without modifying either.Requirements
resolveOwnRejectionHistory(repoFullName, options)) that: callslistRecentOwnSubmissions({ repoFullName })to get this miner's own prior submissions on the target repo; for each with a realpullRequestNumber, fetches its currentGET /pulls/{n}state (reusing the existing GitHub-fetch conventions this package already uses elsewhere, e.g.live-issue-snapshot.js's auth/timeout/user-agent handling); runs the payload throughresolveRejection(nosignal— gate/duplicate context isn't available or needed here, just "was it closed without merge"); returns true if ANY prior submission on this repo was rejected (closed without merge).resolveRejectionSignaled's own existing fail-open behavior for the policy-doc trigger) — one unreachable PR must never block resolution of the others, and a wholesale fetch failure resolves tofalse(not fabricated as a rejection, and not silently treated as "definitely no rejection" either — log/surface the degraded check).resolveRejectionSignaled(or its caller inattempt-cli.js) combines both triggers:trueif EITHER the live policy-doc ban OR the new own-rejection-history check fires.rejection-state-machine.js's classification logic itself, or togovernor-state.js's own submission-recording — this issue only wires the two together for a new caller.Deliverables / Acceptance Criteria
rejectionSignalednow reflects BOTH documented triggers, matchingiterate-policy.ts's own doc comment for the first time since it was writtenrejection-state-machine.jsorgovernor-state.js's own logicTest Coverage Requirements
This PR must ship with full test coverage for every changed line and branch — the repo's Codecov patch gate requires 99%+ coverage and the house standard is to aim for 100%, including both sides of every conditional/nullish-coalescing branch introduced. Required: (1) a real-rejection-detected test (a prior submission on the repo that resolves to a closed-without-merge PR flips the signal true); (2) a no-prior-submissions-on-this-repo test (signal stays false, no fetch attempted); (3) a bounded-fetch-count test (more prior submissions than the cap exist; only the cap's worth are fetched); (4) a fail-open test for both an individual PR fetch failure (other submissions still checked) and a wholesale failure (resolves false, not thrown); (5) a combined-trigger test proving either trigger alone is sufficient (policy-doc ban with clean history, OR clean policy doc with a real rejection, both resolve true).
Expected Outcome
rejectionSignaledfinally means what its own doc comment has said since it was written — a miner won't keep re-attempting a repo that has already rejected its work, closing a real behavioral gap before Wave 5 puts this on a multi-tenant, reputation-sensitive footing.Links & Resources
packages/gittensory-miner/lib/rejection-signal.js(the file's own documented gap)packages/gittensory-miner/lib/governor-state.js'slistRecentOwnSubmissionspackages/gittensory-miner/lib/rejection-state-machine.js'sresolveRejectionpackages/gittensory-engine/src/portfolio/iterate-policy.ts(the doc comment defining both triggers)