Source: PR #1302, triaged at head cdf51d2a5517e3214cf2e60cd8a41bcc95a742c4 (review round 2 residual finding 4).
Severity: MEDIUM (accepted, not fixed in the PR).
Location
hypaware-core/plugins-workspace/github/src/capture.js:745 (hasSavedWork), used at capture.js:114 on the failed-enumeration early return
Observed evidence and behavior
When all_visible enumeration fails, the tick reports pending: hasSavedWork(cursors), which counts any repository cursor holding work. That set is approximate in both directions:
- Over-report: a repository that keeps failing leaves
work behind, and the per-repo catch (capture.js:162) deliberately refuses to call that backlog (LLP 0360#cadence). While enumeration is also failing, nextCaptureDelay (source.js:148) clamps a 24h source to the 15-minute backlog cadence for as long as both faults persist: one extra failing listViewerRepos call per 15 minutes against an already-broken endpoint.
- Under-report: a rotation that exhausted its budget exactly at a repository boundary marks
pending only via visited < repos.length (capture.js:182), which no cursor records. A following failed enumeration then reads false and the saved rotation resumes a full poll interval (up to 24h) later instead of in 15 minutes.
The PR corrected the JSDoc claim (the doc now states the approximation and both directions) instead of the behavior.
Why deferral is safe
Both residual errors are bounded and non-destructive: the worst case is a mis-timed retry or one delayed resume, never lost rows or a corrupted cursor. Both self-heal the moment enumeration succeeds, because the success path recomputes pending from the real signal. The pathological combinations also require enumeration to be persistently broken, which hyp status surfaces via lastError. An exact answer is not on disk today: distinguishing budget residue from failure residue needs a new cursor field, and the alternative (pending: undefined meaning "this tick learned nothing", so source.js keeps its previous flag, matching what the old throw path did) ripples through the shared return type in tick.js, commands.js, and source.js.
Acceptance condition
A failed enumeration leaves the source's backlog cadence exactly as accurate as the durable state allows in both directions: failure residue alone does not pin a daily source to the 15-minute cadence, and a budget-exhausted rotation interrupted by a failed enumeration still resumes on the backlog cadence. Tests cover both: (a) cursors holding only failure-residue work plus a failed enumeration do not yield the backlog cadence, and (b) an exhausted-at-boundary rotation followed by a failed enumeration does. (A pending: undefined "learned nothing" contract or an explicit durable pending marker both satisfy this.)
Source: PR #1302, triaged at head
cdf51d2a5517e3214cf2e60cd8a41bcc95a742c4(review round 2 residual finding 4).Severity: MEDIUM (accepted, not fixed in the PR).
Location
hypaware-core/plugins-workspace/github/src/capture.js:745(hasSavedWork), used atcapture.js:114on the failed-enumeration early returnObserved evidence and behavior
When
all_visibleenumeration fails, the tick reportspending: hasSavedWork(cursors), which counts any repository cursor holdingwork. That set is approximate in both directions:workbehind, and the per-repo catch (capture.js:162) deliberately refuses to call that backlog (LLP 0360#cadence). While enumeration is also failing,nextCaptureDelay(source.js:148) clamps a 24h source to the 15-minute backlog cadence for as long as both faults persist: one extra failinglistViewerReposcall per 15 minutes against an already-broken endpoint.pendingonly viavisited < repos.length(capture.js:182), which no cursor records. A following failed enumeration then readsfalseand the saved rotation resumes a full poll interval (up to 24h) later instead of in 15 minutes.The PR corrected the JSDoc claim (the doc now states the approximation and both directions) instead of the behavior.
Why deferral is safe
Both residual errors are bounded and non-destructive: the worst case is a mis-timed retry or one delayed resume, never lost rows or a corrupted cursor. Both self-heal the moment enumeration succeeds, because the success path recomputes
pendingfrom the real signal. The pathological combinations also require enumeration to be persistently broken, whichhyp statussurfaces vialastError. An exact answer is not on disk today: distinguishing budget residue from failure residue needs a new cursor field, and the alternative (pending: undefinedmeaning "this tick learned nothing", sosource.jskeeps its previous flag, matching what the old throw path did) ripples through the shared return type intick.js,commands.js, andsource.js.Acceptance condition
A failed enumeration leaves the source's backlog cadence exactly as accurate as the durable state allows in both directions: failure residue alone does not pin a daily source to the 15-minute cadence, and a budget-exhausted rotation interrupted by a failed enumeration still resumes on the backlog cadence. Tests cover both: (a) cursors holding only failure-residue
workplus a failed enumeration do not yield the backlog cadence, and (b) an exhausted-at-boundary rotation followed by a failed enumeration does. (Apending: undefined"learned nothing" contract or an explicit durable pending marker both satisfy this.)