5.4 KiB
5.4 KiB
phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, must_haves
| phase | plan | type | wave | depends_on | files_modified | autonomous | requirements | must_haves | ||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 09-osint-infrastructure | 02 | execute | 1 |
|
true |
|
|
Purpose: Satisfies RECON-INFRA-05 and guarantees the "every source holds its own limiter — no centralized limiter" success criterion from the roadmap. Output: pkg/recon/limiter.go, pkg/recon/limiter_test.go
<execution_context> @$HOME/.claude/get-shit-done/workflows/execute-plan.md @$HOME/.claude/get-shit-done/templates/summary.md </execution_context>
@.planning/phases/09-osint-infrastructure/09-CONTEXT.md @go.mod Task 1: LimiterRegistry with per-source rate.Limiter and jitter pkg/recon/limiter.go, pkg/recon/limiter_test.go - LimiterRegistry.For(name string, r rate.Limit, burst int) *rate.Limiter returns the existing limiter for name or creates a new one. Subsequent calls with the same name return the SAME pointer (idempotent). - Wait(ctx, name, r, burst, stealth bool) error calls limiter.Wait(ctx), then if stealth==true sleeps a random duration between 100ms and 1s (respecting ctx). - Per-source isolation: two different names produce two distinct *rate.Limiter instances. - Ctx cancellation during Wait returns ctx.Err() promptly. - Tests: - TestLimiterPerSourceIsolation: registry.For("a", 10, 1) != registry.For("b", 10, 1) - TestLimiterIdempotent: registry.For("a", 10, 1) == registry.For("a", 10, 1) (same pointer) - TestWaitRespectsContext: cancelled ctx returns error - TestJitterRange: with stealth=true, Wait duration is >= 100ms. Use a high rate (1000/s, burst 100) so only jitter contributes. Create pkg/recon/limiter.go:```go
package recon
import (
"context"
"math/rand"
"sync"
"time"
"golang.org/x/time/rate"
)
// LimiterRegistry holds one *rate.Limiter per source name.
// RECON-INFRA-05: each source owns its own limiter — no centralization.
type LimiterRegistry struct {
mu sync.Mutex
limiters map[string]*rate.Limiter
}
func NewLimiterRegistry() *LimiterRegistry {
return &LimiterRegistry{limiters: make(map[string]*rate.Limiter)}
}
// For returns the limiter for name, creating it with (r, burst) on first call.
// Repeat calls with the same name return the same *rate.Limiter pointer.
func (lr *LimiterRegistry) For(name string, r rate.Limit, burst int) *rate.Limiter {
lr.mu.Lock()
defer lr.mu.Unlock()
if l, ok := lr.limiters[name]; ok {
return l
}
l := rate.NewLimiter(r, burst)
lr.limiters[name] = l
return l
}
// Wait blocks until the source's token is available. If stealth is true,
// an additional random jitter between 100ms and 1s is applied to evade
// fingerprint detection (RECON-INFRA-06 partial — fully wired in 09-03).
func (lr *LimiterRegistry) Wait(ctx context.Context, name string, r rate.Limit, burst int, stealth bool) error {
l := lr.For(name, r, burst)
if err := l.Wait(ctx); err != nil {
return err
}
if stealth {
jitter := time.Duration(100+rand.Intn(900)) * time.Millisecond
select {
case <-time.After(jitter):
case <-ctx.Done():
return ctx.Err()
}
}
return nil
}
```
Create pkg/recon/limiter_test.go with the four tests above. Use testify require. For TestJitterRange, call Wait with rate=1000, burst=100, stealth=true, measure elapsed, assert >= 90ms (10ms slack) and <= 1100ms.
cd /home/salva/Documents/apikey && go test ./pkg/recon/ -run 'TestLimiter|TestWait|TestJitter' -count=1
All limiter tests pass; per-source isolation verified; jitter bounded; ctx cancellation honored.
- `go test ./pkg/recon/ -run Limiter -count=1` passes
- `go vet ./pkg/recon/...` clean
<success_criteria>
- LimiterRegistry exported with For and Wait
- Each source receives its own *rate.Limiter
- Stealth jitter range 100ms-1s enforced </success_criteria>