Skip to content

fix(BUY-65693): sentinel-price guard for find_similar (flat price:number shape) - #310

Merged
BuyWhere merged 2 commits into
mainfrom
fix/BUY-65693-find-similar-sentinel-guard
Aug 15, 2026
Merged

fix(BUY-65693): sentinel-price guard for find_similar (flat price:number shape)#310
BuyWhere merged 2 commits into
mainfrom
fix/BUY-65693-find-similar-sentinel-guard

Conversation

@BuyWhere

Copy link
Copy Markdown
Owner

Summary

Follow-up to #309 (BUY-65685) — extends the sentinel-price guard to handleFindSimilar (the 6th JSON-RPC tool, not covered by the parent PR).

The other 5 tools (search_products, get_product, compare_products, get_deals, find_best_price) return a nested {price: {amount, currency}} shape and were rewrapped by formatPriceField in PR #309. find_similar returns a flat shape with sibling price: number + currency: string fields, which is why it could not reuse formatPriceField 1:1.

What lands

+32 / -3 across 2 files in mcp-railway/:

  • src/lib/response.ts (+18 / -0) — new formatSimilarPriceField helper that returns ProductPrice | string (delegates to formatPriceField but documents the flat-shape contract).
  • src/routes/mcp.ts (+14 / -3) — handleFindSimilar parses the raw DB price into a number, applies the sentinel guard, and:
    • returns {amount, currency} in the price slot (and drops the now-redundant sibling currency) when amount ≥ 10
    • returns the sentinel hint string in the price slot (and keeps currency for context) when amount < 10 / null / non-finite

Why

Catalog rows where price.amount < 10 (typically written as 1 by the BuyWhere ingest pipeline when the merchant page had no parseable price — see BUY-52807) surface as .00 in AI-agent responses like Price: $1.00 SGD. The fix surfaces a "see merchant" hint instead so MCP clients (AI agents) do not quote a fake price.

Behavior

Input Before After
price=1500, currency=SGD {price: "1500", currency: "SGD"} {price: {amount: 1500, currency: "SGD"}} (currency sibling dropped)
price=1, currency=SGD {price: "1", currency: "SGD"} {price: "see merchant …", currency: "SGD"}
price=null, currency=USD {price: null, currency: "USD"} {price: "see merchant …", currency: "USD"}

Blocker / verification

Blocked by #309 merge + Railway redeploy for live verification on mcp.buywhere.ai/mcp. The smoke-test pattern from BUY-65685 applies here — pick a sentinel reference product (e.g. id=871873063695734596, Gigabyte A16, sentinel amount=1) and confirm find_similar rows surface the sentinel string instead of .00.

Branching

This branch is stacked on top of fix/BUY-65685-mcp-railway-sentinel-guard (PR #309). If #309 merges first, this PR rebases onto main cleanly (the only BUY-65685-specific change is formatPriceField, which is still imported and used). If reviewers prefer a single combined PR, this branch can be merged into the BUY-65685 branch instead of main.

🤖 Generated with Claude Code

@BuyWhere

Copy link
Copy Markdown
Owner Author

Regression coverage added

Pushed 01cb4b217test(BUY-65693): add regression coverage for sentinel-price guard in mcp-railway.

File: mcp-railway/tests/response-sentinel.test.mjs (141 lines, 21 cases)

Coverage pins:

  • isSentinelPrice: classifies amount<10 / null / non-finite / string-coercion as sentinel; amount=10 (PRICE_SENTINEL_MIN boundary) is not sentinel
  • formatPriceField: returns sentinel string when sentinel, {amount, currency} object otherwise
  • formatSimilarPriceField: same shape contract; preserves currency in structured branch
  • PRICE_UNAVAILABLE_TEXT exact wording (catches future copy edits)

Test run on this branch:

1..1
# tests 21
# pass 21
# fail 0

Reviewer tip: node --test --test-force-exit mcp-railway/tests/response-sentinel.test.mjs runs the suite in ~270ms against mcp-railway/dist/lib/response.js. No DB / network needed.

Merge-order note: this branch is stacked on top of PR #309 (acb4fea5f8). Once #309 merges to main, this branch will need a rebase onto fresh main to drop the now-merged PR #309 commit (acb4fea5f8) before merging. I can do that rebase when #309 merges.

Cc @Reviewer — please ack and merge order: #309 first, then rebase #310, then #310. 🎯

@BuyWhere

BuyWhere commented Aug 3, 2026

Copy link
Copy Markdown
Owner Author

Heartbeat 2026-08-03T20:02:47Z

PR #310 still open, 0 reviews, 0 PR-level comments, awaiting human review. BUY-65693 stays in_review per the PR review gate pattern. No new live signal since last heartbeat (regression test commit 01cb4b2 still HEAD).

@BuyWhere

BuyWhere commented Aug 4, 2026

Copy link
Copy Markdown
Owner Author

Heartbeat 2026-08-04T00:01Z

PR #310 still open, 0 reviews since 2026-07-31. Awaiting human review per [[buywhere-pr-review-gate-pattern]].

New finding (relevant to this PR): the canonical sentinel product (871873063695734596, BUY-52807) now returns price.amount: null instead of amount: 1 at the v1 REST endpoint. The PR #310 find_similar flat-shape guard was written against the price.amount=1 payload shape — recent nullable-payload shift means the guard should now ALSO tolerate price.amount=null. Flagged for reviewer consideration.

Live state: https://buywhere.ai/, https://mcp.buywhere.ai/, https://api.buywhere.ai/ all HTTP 200. Sentinel query amount=null, metadata issue_tag=BUY-52807 + consumed_by_issue=BUY-52807 retained.

Status: still in_review, gated by human review.

@BuyWhere

BuyWhere commented Aug 7, 2026

Copy link
Copy Markdown
Owner Author

Heartbeat 2026-08-07T20:45Z

PR #310 still open, 0 reviews, 0 PR-level comments since 2026-07-31 — awaiting human review per [[buywhere-pr-review-gate-pattern]].

Discovered this run:

  • 21/21 regression tests in mcp-railway/tests/response-sentinel.test.mjs pass clean against PR head dist (# tests 21, # pass 21, # fail 0, duration_ms 143).
  • isSentinelPrice correctly handles null, undefined, NaN, Infinity, strings, 0, and 9.99 as sentinel; 10.01 and PRICE_SENTINEL_MIN (boundary) as not sentinel.
  • formatSimilarPriceField correctly returns the sentinel string OR {amount, currency} object — the find_similar flat-shape contract is honored.
  • formatPriceField (PR fix(BUY-65685): sentinel-price guard for mcp-railway JSON-RPC tools #309 territory) is the actual sentinel workhorse; formatSimilarPriceField is a typed wrapper that delegates to it.

Merge-stack status: PR #310 ref 01cb4b21 is stacked on PR #309 head acb4fea5 (still open). Both PRs are 88 commits behind main HEAD (115e8e93). main HEAD does NOT contain formatPriceField / isSentinelPrice yet — the only path is merge order #309 → rebase #310 → #310. Rebase post-#309 needs to drop the BUY-65685 commit before #310 merge.

Discovered blocker: finding a null payload consideration raised in the 2026-08-04 heartbeat (canonical sentinel product 871873063695734596 now returns price.amount: null at v1 REST) — the PR #310 find_similar flat-shape guard handles null already via isSentinelPrice(null)==true per the boundary test. So the guard is robust to the nullable payload shift. No patch needed on the PR.

Action items:

  • Reviewer: this PR is small (+244 / -19 across 5 files, mostly in mcp-railway/src/lib/response.ts and mcp-railway/src/routes/mcp.ts) and ships a complete 21-case regression test. Recommend ack + merge after PR fix(BUY-65685): sentinel-price guard for mcp-railway JSON-RPC tools #309 lands.
  • Agent (Reed): cannot drive PR past human review gate. Marked BUY-67118 blocked, filed child issue BUY-67119 owning the PR-review+merge action.

Cc @Reviewer

CI and others added 2 commits August 15, 2026 19:01
…ber shape)

Mirrors BUY-65685 / PR #309 for handleFindSimilar in src/routes/mcp.ts.

Adds formatSimilarPriceField helper in src/lib/response.ts that returns
either the sentinel hint string (when amount < 10 / null / non-finite)
or the structured {amount, currency} object. Cannot reuse formatPriceField
1:1 because find_similar returns a flat shape with sibling price+currency
fields (not a wrapper).

When the structured form is returned, the sibling currency field is
omitted from the JSON-RPC output to avoid redundancy. When the sentinel
form is returned, currency is kept so AI agents still see the currency
context if they parse the flat shape.

Blocked by PR #309 (BUY-65685) merge + Railway redeploy for live
verification on mcp.buywhere.ai/mcp.

Co-Authored-By: Claude <noreply@anthropic.com>
…mcp-railway

Pin the contract of:
  - isSentinelPrice: classifies amount<10 / null / non-finite as sentinel
  - formatPriceField: returns string when sentinel, {amount,currency} otherwise
  - formatSimilarPriceField: same shape; intended for find_similar's flat shape

21 cases, including boundary checks (amount=PRICE_SENTINEL_MIN is NOT sentinel),
defensive string-coercion checks, and the exact PRICE_UNAVAILABLE_TEXT wording
so future copy edits trigger a test failure.

These tests give reviewers and the post-merge Railway smoke probe a deterministic
way to confirm find_similar's sentinel guard works before it ships.
@BuyWhere
BuyWhere force-pushed the fix/BUY-65693-find-similar-sentinel-guard branch from 01cb4b2 to 53db470 Compare August 15, 2026 19:05
@BuyWhere
BuyWhere merged commit e06b21f into main Aug 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant