Skip to content

fix(registry): flip 5 buying-vs-sales inversions missed by #3540 sweep — closes #3774#3776

Merged
EmmaLouise2018 merged 1 commit into
mainfrom
EmmaLouise2018/sweep-3540-leftovers
May 1, 2026
Merged

fix(registry): flip 5 buying-vs-sales inversions missed by #3540 sweep — closes #3774#3776
EmmaLouise2018 merged 1 commit into
mainfrom
EmmaLouise2018/sweep-3540-leftovers

Conversation

@EmmaLouise2018

Copy link
Copy Markdown
Contributor

Closes #3774. Surfaced by Brian-style red-team of #3766.

Same inversion class as PR #3540 swept across filter sites — but 5 sites slipped through because they had a different shape (function-call arguments and local-inference re-implementations rather than equality comparisons against stored type). Two were visible bugs in the public discovery diagnostic endpoints; three were silently aligned with the registry's defensive re-filter and would become live bugs if the defensive filter ever changed.

Sites flipped

File:line Shape Effect today Fix
http.ts:2007 listAgents("buying") filter No-op (crawler re-filters) flip → "sales"
http.ts:8729 listAgents("buying") filter + buyingAgents var No-op (crawler re-filters) flip + rename to salesAgents
mcp-tools.ts:831 listAgents("buying") filter No-op flip → "sales"
http.ts:8442 Local-inference branch Visible bug: type: 'buying' returned for sales agents consolidate + flip
registry-api.ts:5802 Local-inference branch (duplicate of above) Visible bug (same) consolidate + flip
mcp-tools.ts:2058 Resource-handler type allow-list missing 'sales' agents://sales returns "Unknown resource type" add 'sales'

What changed structurally

The two duplicated local-inference blocks at http.ts:8442 and registry-api.ts:5802 are now consolidated into a single helper:

server/src/lib/diagnostic-agent-type-inference.ts
  inferDiagnosticAgentType(toolNames: string[]): 'sales' | 'creative' | 'signals' | 'unknown'

Both diagnostic endpoints call it. Polarity is now in one place — future polarity changes need exactly one fix, not two. Loose substring matching preserved as-is (separate concern from polarity; the canonical strict-match inference at CapabilityDiscovery.inferAgentType is unchanged).

Why this wasn't caught in #3540

#3540's sweep used grep -E 'type [!=]==? \"buying\"' for filter-site inversions on stored agent.type. These 5 sites are different:

  • listAgents("buying") — function-call argument, not a comparison
  • Local-inference branches — assign 'buying' based on tool surface, not compare against stored type

A wider sweep at the time (or a smell-test for "any string literal 'buying'") would have caught them.

Test plan

  • New: server/tests/unit/diagnostic-agent-type-inference.test.ts12/12 pass. Pins the polarity matrix including an exhaustive NEVER returns 'buying' invariant assertion. If anyone reverts polarity, cases marked // pinning #3774 fail loudly.
  • npx tsc --noEmit -p server/tsconfig.json — clean.
  • Pre-commit hooks green.

Backwards compatibility

Non-breaking on the wire. Only behavior change: the public discovery diagnostic endpoints now return type: 'sales' for sales-tool-exposing agents instead of type: 'buying'. Any consumer that was relying on the broken 'buying' response was already wrong'sales' is the correct AdCP polarity per #3540 and #3495.

Pairs with

This sweep makes the registry's agent.type polarity consistent across the filter sites, the inference paths, the resource handler, and the diagnostic endpoints. No remaining 'buying' references except the deliberate by_type stat tallies (which correctly count buying-typed agents).

Out of scope

  • Tightening the diagnostic helper's loose substring matching to align with CapabilityDiscovery.inferAgentType's strict matching. Loose matching preserved here intentionally — diagnostic endpoints serve pre-onboarding probes where tool-name conformance is incomplete. Worth a follow-up if false-positive rate becomes a concern.
  • Replacing the diagnostic helper with the canonical inference helper (different match semantics — strict vs loose). Same follow-up.

Closes #3774. Surfaced by Brian-style red-team of #3766. Same inversion
class as #3540 swept across filter sites, but 5 sites slipped through
because they had a different shape — function-call arguments and local-
inference re-implementations rather than equality comparisons against
stored type.

## listAgents("buying") -> listAgents("sales") at 3 call sites

- server/src/http.ts:2007 — POST /api/crawler/run
- server/src/http.ts:8729 — periodic property crawler boot (variable
  also renamed buyingAgents -> salesAgents and the comment fixed)
- server/src/mcp-tools.ts:831 — find_agents_for_property MCP tool

These pass the agent set into property-crawl logic that semantically
operates on sales agents (the ones with publisher authorizations and
list_authorized_properties responses). Pre-fix the filter was a no-op
because the crawler internally re-filters on agent.type !== "sales" at
crawler.ts:420 — but the call sites were misleading and load-bearing
on the defensive re-filter.

## Diagnostic-endpoint local inference consolidated + polarity flipped

server/src/http.ts:8442 and server/src/routes/registry-api.ts:5802 had
duplicated inline inference that returned 'buying' for agents exposing
get_products / create_media_buy / create_media — exactly inverted vs the
canonical CapabilityDiscovery.inferAgentType. Visible bug for any consumer
of the public discovery diagnostic endpoints: every sales agent reported
as type='buying'.

Extracted into a single helper:

  server/src/lib/diagnostic-agent-type-inference.ts

Both endpoints now call inferDiagnosticAgentType. Loose substring matching
preserved (separate concern from polarity); canonical strict-match path
in CapabilityDiscovery is unchanged.

## Missing 'sales' in resource handler

server/src/mcp-tools.ts:2058 hard-coded ["creative", "signals", "buying"]
in its agent-resource type filter — pre-#3540 'buying' was the
(incorrectly) only sell-side type, so 'sales' was never added. Post-#3540
this means agents://sales returns "Unknown resource type". Added 'sales'
to the list. 'buying' kept because per #3766 the type is now meaningful.

## Tests

New: server/tests/unit/diagnostic-agent-type-inference.test.ts pins the
12-case polarity matrix, including an exhaustive "NEVER returns 'buying'"
invariant assertion. If anyone reverts polarity back to 'buying', the
cases marked `// pinning #3774` fail loudly.

12/12 pass. Typecheck clean.

## Out of scope

- Tightening the diagnostic helper's loose substring matching to align
  with CapabilityDiscovery.inferAgentType's strict matching. Loose
  matching is preserved here intentionally — diagnostic endpoints serve
  pre-onboarding probes where tool-name conformance is incomplete.
  Worth a follow-up if the diagnostic endpoints' false-positive rate
  becomes a concern.
- Replacing the diagnostic helper with the canonical CapabilityDiscovery
  inference helper (different match semantics — strict vs loose). Same
  follow-up.
@EmmaLouise2018 EmmaLouise2018 requested a review from bokelley as a code owner May 1, 2026 18:10
@EmmaLouise2018 EmmaLouise2018 merged commit 274c59f into main May 1, 2026
11 checks passed
@EmmaLouise2018 EmmaLouise2018 deleted the EmmaLouise2018/sweep-3540-leftovers branch May 1, 2026 20: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

1 participant