Tool runner: the cap's reason fails the call; the test proves the ending; the refused-answer limit keys on the caller's prefix and forgets old entries (issue 1966, review round 1)
Claude-Session: https://claude.ai/code/session_01NcbgfFfQ4Z9UE5jK32PmE5
This commit is contained in:
@@ -1229,15 +1229,25 @@ func (c *Conn) answerRefused(err error) bool {
|
||||
if len(m) < 2 || !strings.HasPrefix(strings.Trim(m[1], `"`), "_INBOX.") {
|
||||
return false
|
||||
}
|
||||
// Keyed without the reply's last token (a new one per request), so a caller's burst is one line.
|
||||
subject := strings.Trim(m[1], `"`)
|
||||
if dot := strings.LastIndex(subject, "."); dot > len("_INBOX.") {
|
||||
subject = subject[:dot]
|
||||
}
|
||||
c.mu.Lock()
|
||||
if c.inboxSaid == nil {
|
||||
c.inboxSaid = map[string]time.Time{}
|
||||
}
|
||||
last, seen := c.inboxSaid[subject]
|
||||
say := !seen || time.Since(last) >= time.Minute
|
||||
now := time.Now()
|
||||
for k, at := range c.inboxSaid {
|
||||
if now.Sub(at) >= time.Minute {
|
||||
delete(c.inboxSaid, k)
|
||||
}
|
||||
}
|
||||
_, seen := c.inboxSaid[subject]
|
||||
say := !seen
|
||||
if say {
|
||||
c.inboxSaid[subject] = time.Now()
|
||||
c.inboxSaid[subject] = now
|
||||
}
|
||||
c.mu.Unlock()
|
||||
if say && c.Logf != nil {
|
||||
|
||||
@@ -94,17 +94,22 @@ func TestARefusedAnswerIsSaidOncePerMinute(t *testing.T) {
|
||||
refuse := func(subject string) bool {
|
||||
return c.answerRefused(fmt.Errorf("%w: Permissions Violation for Publish to %q", nats.ErrPermissionViolation, subject))
|
||||
}
|
||||
if !refuse("_INBOX.user.abc") || !refuse("_INBOX.user.abc") || !refuse("_INBOX.user.def") {
|
||||
// A reply subject ends in a token new to every request: a burst under one prefix is one line.
|
||||
if !refuse("_INBOX.user.abc.t1") || !refuse("_INBOX.user.abc.t2") || !refuse("_INBOX.user.def.t1") {
|
||||
t.Fatal("a refused answer was not taken for one")
|
||||
}
|
||||
if len(said) != 2 {
|
||||
t.Fatalf("want one line per subject, got %v", said)
|
||||
t.Fatalf("want one line per caller, got %v", said)
|
||||
}
|
||||
c.inboxSaid["_INBOX.user.abc"] = time.Now().Add(-2 * time.Minute)
|
||||
refuse("_INBOX.user.abc")
|
||||
c.inboxSaid["_INBOX.user.old"] = time.Now().Add(-2 * time.Minute)
|
||||
refuse("_INBOX.user.abc.t3")
|
||||
if len(said) != 3 || !strings.Contains(said[2], "_INBOX.user.abc") {
|
||||
t.Fatalf("a minute later it is said again, got %v", said)
|
||||
}
|
||||
if _, kept := c.inboxSaid["_INBOX.user.old"]; kept || len(c.inboxSaid) != 2 {
|
||||
t.Fatalf("entries older than a minute are dropped on write, kept %v", c.inboxSaid)
|
||||
}
|
||||
if refuse("mesh.mod.x.tool.y") {
|
||||
t.Fatal("a refused publish that is no answer was taken for one")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user